Skip to content

epic #250: Rewrite every component invocation to name a toolkit, behind a build-time gate - #328

Merged
sameera merged 6 commits into
mainfrom
feat/250-invocations-name-a-toolkit
Aug 27, 2026
Merged

epic #250: Rewrite every component invocation to name a toolkit, behind a build-time gate#328
sameera merged 6 commits into
mainfrom
feat/250-invocations-name-a-toolkit

Conversation

@sameera

@sameera sameera commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Implements the story sub-issues of #250, one commit per story in blocked_by order.

Each commit body carries its own Closes #<story> line, so merging this PR
into main closes the stories it implements. The epic itself closes through
/nxs.close, not by merge.

Draft opened by utils/implement-epic.sh; /nxs.analyze runs against it next.

sameera and others added 6 commits August 26, 2026 17:30
The invocation gate lands first, against the sites as they are. One scanner
walks the same shipped bodies the payload-composition boundary walks, extracts
every invocation written in a code span — fenced or inline — and classifies it
against a closed set of addressing forms: the two named-toolkit forms and the
legacy repository-bound ones (transpiler-against-script, interpreter-against-
script, runtime-against-bundle, workspace script alias), plus a repository-bound
artifact named with no command around it.

Each toolkit's surface is read from the surface itself. The executable's
dispatch names are derived from the registry that composes its own usage text,
which now declares each subverb so the dispatcher and the gate read one list —
the full dispatch name is what gets checked, because the commonest realistic
typo lives in the second token. The Python toolkit gains a machine-readable
capability listing beside its human diagnostic, so the gate never scrapes prose.

Migration state is an explicit register of not-yet-migrated bodies. A body
absent from it must be fully migrated: any legacy form found there fails the
build by name. The register only shrinks; reaching empty is this epic's
completion condition.

Closes #301
Every shipped command and skill body now names `nexus` and a verb where it
named a repository-relative script, a vendored bundle, or a workspace script
alias. Each value crosses as its own argument; nothing is interpolated into a
path or an executable name.

The alternative-invocation passages go with the paths that motivated them. A
body that told the reader to run the script here and the bundle there was
offering one capability twice, and unified addressing leaves one command with
no choice to make. The distill stage's single-repo/hub branches keep their
shape for now — both sides name the same verb, and #304 collapses them while
preserving the mode-conditional instructions the hub side carries.

With no body naming them, the seven legacy skill scripts are deleted, which
empties the payload-composition waiver register — the completion condition that
register recorded — and retires the script-versus-verb parity axis that existed
only to compare them. The specs that entered those capabilities through a
script now enter through the maintainer's one from-source command shape, which
is documented in the feature's own README and appears in no shipped body.

Closes #302
Every shipped body that reached a Python capability through a script path now
names `nexus-gh` and the capability. Twenty-three of those sites named a bare
`python`, and nine wrote a working-directory-relative path that resolved only
from inside the skill directory; both are gone, and the capability resolves the
same way from any working directory because the entry point self-locates.

That was the last body carrying a repository-bound form, so the pending register
reached empty — the completion condition it recorded — and is deleted. Tolerance
is not a parameter with nothing in it: the verdict now fails any legacy form
wherever it sits, so a reintroduced script path, bundle path, workspace alias or
bare `python` fails the build immediately.

The acceptance harness, which drove the same two capabilities through paths that
no longer exist, reaches them through the toolkit entry point in the checkout it
was pointed at.

Closes #303
The distill stage described three capabilities twice each — once for a
single-repository drain, once for a hub — and the two copies could diverge with
nothing to catch it. With addressing unified the two sides said the same thing,
so they collapse to one command.

Only the addressing branch goes. Everything the run mode still governs survives,
restated as a condition on arguments rather than on which program to run: a hub
still passes every page path and git ref as its own quoted token, its regenerated
anchor sidecars are still validated alongside the pages, and a member repo still
does not drain. The step that used to select a runner now states those rules and
keeps its number, because eight passages elsewhere cite it by number.

Two other bodies described one capability in two places — the worktree removal in
analyze, and the epic filer's plain form repeated as its own first example. Each
is stated once now.

No new check ships with this: what made the branch possible was a second
addressing form, and the build gate rejects that unconditionally.

Closes #304
An unbalanced inner fence marker inverted codeSpans' fence state and
silently un-gated every invocation after it in that body. Three
component bodies already nest 3-backtick fences inside 4- and
5-backtick ones, so only their correct-by-accident balance held the
gate up.

Follow CommonMark instead: an opening fence records its backtick run
length, and only a marker at least that long with nothing trailing it
closes it. A shorter marker, or one carrying trailing text, is fence
content.

Latent in the live tree — invocation counts are unchanged — so this
changes no verdict today, only what a future body can do to the gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ga8zCem6NqbJ62BKrZURcB
Reconciles the invocation rewrite with #257 (vendored tools retired) and #252
(one published package), both of which landed on main after this branch was
implemented.

- nxs.distill: Phase 5.3 keeps this branch's mode-conditional bullets, with
  main's unconditional quoting rule lifted above them; the derive-entry-diff
  remediation drops the pointer to the retired hub-tooling-install document.
- nxs.epic, nxs.setup, nxs-workspace-status: the "portable CLI instead"
  paragraph goes — one named invocation covers both toolchains.
- parity.spec.ts: main's payload-manifest pin and per-verb parity helpers,
  plus this branch's component-invocation gate; the #274 verb-vs-standalone
  axis retires with the #272/#273 script axes, since #252 left no standalone
  artifact to compare against.
- bundle-fingerprint.json and payload-manifest.json re-pinned; the manifest
  drops the seven deleted skill scripts.

`pnpm nexus:release-gate` — #252's invariant-15 gate, red on main — is now green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011o2Jo7QZ59YpauqUMGmLPz
@sameera
sameera marked this pull request as ready for review August 27, 2026 21:48
@sameera

sameera commented Aug 27, 2026

Copy link
Copy Markdown
Owner Author
Conformance: Rewrite every component invocation to name a toolkit, behind a build-time gate (.nexus/tmp/epic-250)  ·  epic #250
Mode: full (record #325 @ b4c02eedcdc8089d7943145214dd9abc80feb6e2ff52a77245b4b4ed1b4fec2a)
Surface: 41 files changed, 4 stories (0 closed / 4 open — each closed by this PR's merge, per commit `Closes #<story>`)

Per-story AC conformance:
  STORY #301 The build fails when a component names a verb or capability that does not exist: 6/6 met · 0 partial · 0 unmet · 0 contradicted
  STORY #302 The TypeScript invocation sites name the executable: 4/4 met · 0 partial · 0 unmet · 0 contradicted
  STORY #303 The Python invocation sites name the Python toolkit: 5/5 met · 0 partial · 0 unmet · 0 contradicted
  STORY #304 One invocation replaces the duplicated stage prose: 3/3 met · 0 partial · 0 unmet · 0 contradicted

Invariant violations:   none
  1  gate walks `listComponentFiles(liveClaudeDir(SRC_DIR))` — the same set the composition boundary walks — ok
  2  `readToolkitSurfaces` reads `DISPATCH_NAMES` (derived from `REGISTRY`) + `nexus-gh --capabilities`; throws rather than assuming — ok
  3  gate execs only `python3 <repo>/libs/gh-toolkit/bin/nexus-gh --capabilities`, never a string from a body — ok
  4  no shipped body carries a script path, bundle path, `pnpm nexus:` alias or bare `python` (grep over `.claude/**/*.md`: 0 hits) — ok
  5  no interpolation into an executable or dispatch name; every value stays a discrete quoted argument — ok
  6  register emptied and removed; `COMPONENT_COMPOSITION_WAIVERS` is `[]`, `pending` parameter dropped — ok
  7  verb behaviour/exit codes preserved; the subverb guards reuse the existing messages and code 2. `nexus-gh --capabilities` is a new flag, but it is the record's own "dedicated machine-readable capability listing" decision, scoped to #301 — sanctioned, not a violation
  8  from-source path is one shape (`tsx libs/portable-tools/src/nexus-cli.ts <verb>`), documented only in docs/features/component-distribution/README.md, absent from every shipped body — ok
  9  `formatInvocationProblems` names body, line and offending name; parity self-test asserts both appear — ok
  10 distill Phase 5.3 collapse preserves every mode-conditional instruction (hub anchor sidecars, quoted-token discipline, member stop, stale-toolkit escalation) — ok
  11 no `python` at all in shipped bodies; Python capabilities reached as `nexus-gh <capability>` — ok
  12 `nexus` / `nexus-gh` used as single fixed literals throughout — ok

Success metrics:
  No shipped body holds a repo-relative script/executable path  →  measurable (parity gate), met
  Every invocation resolves to a declared name                  →  measurable (parity gate), met
  A migrated body cannot regress                                →  measurable, met — enforcement is unconditional since the register was removed
  Build fails naming the offending body and verb                →  measurable, met — the doctored-surface self-test in parity.spec.ts proves the axis rather than assuming it
  No component invokes a bare `python`                          →  measurable, met

Scope drift:
  - `libs/pr-acceptance` and two portable-tools specs were repointed off the deleted skill scripts onto the from-source shape / the Python entry-point file. Neither is a shipped body, so invariants 4 and 8 do not reach them; this preserves coverage that would otherwise have been deleted with the scripts.
  - `parity.spec.ts` loses ~340 lines: the #272/#273 script-vs-verb and #274 verb-vs-standalone migration axes, all of which lost their second artifact when the scripts and standalone launchers were deleted. Retirement was recorded in the axes' own comments; the durable source-vs-bundle axis survives.
  - Deleting the seven legacy skill scripts (recorded as an in-flight scope growth on #302) closes the composition-waiver register, whose completion condition named this epic.

Findings:
  low  docs/features/README.md:30-31 still instructs the reader to run `python ./.claude/skills/nxs-gh-shared/delivery_config.py backlog-query --form …`. The path does not exist and the interpreter is bare `python` — precisely the two shapes this epic removes. It is a doc, not a shipped body, so no AC, metric or invariant covers it; the named form is `nexus-gh config backlog-query --form …`, already written that way at .claude/commands/nxs.epic.md:469.
  low  Record #325 decides "the gate prints its inventory"; the implementation builds the inventory and asserts over it but never emits it. #301 AC5 ("when its inventory is read") is satisfied by the assertion, and the record's non-printing half — no committed artifact — holds. Nothing surfaces the classified inventory to a maintainer who is not reading a failure.
  note docs/features/pr-driven-delivery/live-acceptance-record.md:248 cites `.claude/skills/nxs-pr-worktree/scripts/pr_worktree.ts:127`, deleted here. It is a historical record, so a stale cite is expected rather than wrong.
  note Both toolkit names must be on the caller's path for any rewritten stage to run. #252 landed on `main` and is merged into this branch at 4aa418c, so the epic's declared blocker is satisfied at the repository level; an existing checkout still needs the install step before `/nxs.*` resolves. The epic puts this out of scope by assumption, and the permission-allowlist entries the new shapes need remain #253's.

Severity: ⛔ critical 0 · ⚠️ high 0 · medium 0 · low 2

Every acceptance criterion across the four stories is met, no decision-record invariant is broken, and all five success metrics are both moved and measurable through the parity gate. The two low findings are documentation-adjacent and outside every AC; neither blocks close.

epic: "#250"
nexus_version: 0.1.0
pr: 328
date: 2026-08-27
head: 4aa418c6dc39163345ace569582fa581480f3e16
mode: full
record: "#325"
record_hash: b4c02eedcdc8089d7943145214dd9abc80feb6e2ff52a77245b4b4ed1b4fec2a
findings: { critical: 0, high: 0, medium: 0, low: 2 }

@sameera
sameera merged commit 8ee6821 into main Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant