✨ Address a document's sections from the command line - #427
Merged
Conversation
`xmd targets <document.md>` prints one full document reference per addressable section, by inspection alone. A file argument to `xmd run` becomes a document reference, so `xmd run README.md#Release/Publish` and `xmd README.md#Release/*` run exactly one section of a document. The selector is replaced by its answer before anything executes. Run preparation already inspects the root to discover its props; it now retains the exact canonical target that inspection resolved and asks execution for that, never for the glob. A file replaced between the two reads therefore fails on the target the run chose rather than silently running whatever the glob would name now, and `readsValue()` raises a recognized target failure so that refusal lands before the service installer and before `execute()`. Diagnostics keep the core's own first line and render its target lists as full references, because a reference is what a caller can act on. A filename holding `#` or a literal `%HH` sequence is now written `%23` and `%25HH` for `xmd run`. `xmd test` keeps its own path grammar and gains no target selection.
PR #427: ✨ Address a document's sections from the command line13 files, +1311 / -31 Scope🔴 PR has 1342 lines changed. Split into focused PRs. 🟡 1342 lines changed. PRs under 400 receive more thorough review. 🟡 Changes span 7 directories. StructuralOxlint structural signals:
SlopOxlint slop signals:
Static AnalysisOxlint: 6 diagnostics across 1 file (4 rules) no-console (3): packages/cli/src/cli.ts CorrectnessNo extraneous code patterns detected. |
Two documented claims were stronger than the code. The run reads its document three times — preparation, the value-mode inspection, then execution — so a replacement that first appears on the third read is refused by `execute()`, after the host's provider installer has run. The specification said every such refusal precedes the installer. It does not, and the guarantee worth stating is the one that holds either way: installing a provider is not using one, so a refused run starts no service and expands nothing, whichever read caught it. CT12 keeps the early boundary and CT12a pins the later one, asserting that the installed provider's start operation is never called. `fileSource()` reads a raw `%` as the start of escape syntax wherever it appears, so every literal percent in a reference must be written `%25` — not only one that begins a valid `%HH` sequence. `pct%zz.md` is refused as a malformed reference rather than read as that filename. The help text already said this correctly; the specifications and the shared `fileSource()` docstring did not. The rejection cases are split three invocations to a case. Each row is a subprocess, and Bun's fixed 5s per-test budget leaves no room for six of them on a loaded runner.
taras
marked this pull request as ready for review
August 10, 2026 17:22
This was referenced Aug 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Issue #412 makes a root document's sections individually addressable. PR #421 (merged as
f57ae74) built the core: the target catalog, selector resolution, projection, and durable identity. Nothing on the command line could reach it.This is the second layer: the two CLI consumers. It does not implement workflow-definition version 2 or workflow start/resume — that is PR C, which depends on PR A rather than on this.
What changes
Before:
xmd run <path>ran the whole document. A path was a filesystem path, byte for byte.After:
xmd targets <document.md>prints one full document reference per addressable section.xmd run README.md#Release/Publishandxmd README.md#Release/*run exactly one section.xmd runis a document reference: the first raw#starts a target selector.A failed selection reports full references rather than bare fragments:
xmd testis untouched: its path grammar is unchanged and it gains no target selection.How it works
The CLI consumes PR A's API and adds no heading scanner, selector matcher, projection routine, or error type of its own.
The selector is replaced by its answer before anything executes. The run preparation path already inspects the root to discover its props; it now builds that root with
fileSource()and, when inspection resolves a target, retains{ …root, target: info.target }— the exact canonical target, never the caller's glob. Execution reads the file again from that exact root.That replacement is the load-bearing part. If a wildcard resolves to
Alphaduring inspection and the file is then replaced so the same wildcard would nameBeta, the run fails on the absentAlpha. It never silently runsBeta.readsValue()used to turn every inspection failure into "not a value root" so execution could report it. It now rethrows a recognizedDocumentTargetError, so a replacement the value-mode inspection can see leavesrunDocument()before the provider installer. Every other inspection failure still returnsfalse.That is the earlier of two refusals, not the only one.
runDocument()reads the document three times — preparation, value-mode inspection, thenexecute()— and a replacement that first appears on the third read is refused byexecute(), after the provider installer has run. Installing a provider is not using one: neither refusal starts or attaches a service, and neither expands anything. Closing that third-read interval would need a prepared-root/execution boundary, which is deliberately out of scope here.Review guide
Start with:
packages/cli/tests/targets-cli.test.tsThen review:
packages/cli/src/cli.ts—exactRoot()and its call site inpreparePropsPhase()readsValue()— the narrowed failure behaviortargetFailureReport()— the one formatter, used by preparation and by the final run failurelistTargets()/targetsGrammarError()— the inspection-only commandLook carefully at:
API.Fsmiddleware around the exportedrunXmd()operation rather than racing a real file, and asserts the exact-Alphadiagnostic, no service installation, and no read of what the replacement's section would have read.What must stay true
exactRoot()replacing the selector beforeexecute(), checked by CT12. MutatingexactRootto keep the glob reds CT12 alone.serviceInstalled === false, refused before the installer) and CT12a (serviceInstalled === true,serviceStarted === false, refused after it).xmd targetsnever invokes the installer at all.<File>read never happened; the harness installs the host Files provider and points the contextual cwd at the fixture, so that read would succeed if it were attempted.xmd targetsperforms inspection only — enforced by the command never enteringrunDocument(), checked by CT4 (an unresolvable component, an executable block, and an authored write, none of which run).xmd testpath grammar is unchanged — checked by DT30, which runs literalwe#ird.test.mdandpct%25.test.mdand shows the percent-encoded spelling names no file.How to verify it
%is escape syntax:pct%25zz.mdlists and runs,pct%zz.mdis refused as an invalid reference, and DT30 showsxmd teststill reads that literal filename./,*,#,%, spaces, and Unicode — listed and runnable through those references. They fail if the CLI re-encoded a canonical fragment or printed the core's bare fragments.xmd targetsreached expansion.-e, props, journal/verbose/raw/component-dir/secret-detection/pattern, agent flags, an unknown option, and--— exits 1 with empty stdout and leaves no journal behind. They are split across several cases because each row is a subprocess and one case has to fit the shortest per-test budget of the three runtimes.*, embedded*, and**execute one target and exclude both earlier and later siblings.execute()'s own read does. CT12a pins the boundary the contract now states: provider installed, service never started, nothing expanded.<Output>roots, inline exclusivity, help discoverability, and the unchangedxmd testgrammar.Manually, against the compiled binary:
deno task build ./dist/xmd targets smoke-test/document-targets.md ./dist/xmd run 'smoke-test/document-targets.md#Alpha' --rawScope
Included
xmd targets <document-reference>, inspection only.xmd runforms.smoke-test/document-targets.mdfixture and one step in the existingsmokeCI job.Intentionally unchanged
xmd test— no target selection, no reference grammar.xmd run -e— untargeted.xmd run <path>for ordinary paths.API.Files, its adapters, provider installation, and every runtime adapter (deno.ts,node.ts,bun.ts,compiled.ts).--targetoption, no multi-match fan-out, no workflow definition or storage.New abstractions
exactRoot()exists because the requested root and the executed root are different values, and the difference is the invariant this PR ships.targetFailureReport()exists because both failure locations must render one diagnostic identically; it keeps the core's own first line and only re-renders the target lists as references.readReference()/inspectCatalog()exist to handfileSource()'s throw andinspectDocument()'s throw back asResultat the two call sites that report rather than raise.Each new abstraction has multiple concrete uses or a clear justification.
No speculative functionality is included.
Risks and limitations
xmd runorxmd targetspath containing a raw#, or any literal%, no longer means that literal filename.%begins escape syntax wherever it appears, sopct%zz.mdis refused as a malformed reference rather than read literally; it is writtenpct%25zz.md. Help and both specifications teach%23and%25, andxmd testis deliberately exempt.Scope confirmation
Base and verification
f57ae74e5b18799a6c031f00b9d98d1eb42b9d0b.f57ae74e5b18799a6c031f00b9d98d1eb42b9d0b(origin/mainat the time of writing).31a8ac9(review corrections on top of541fc03).Every command run locally, and its result:
deno task lintdeno task checkdeno task check:jsrSuccess Dry run completegit diff --checkpnpm exec tsc --project tsconfig.node.json --noEmitdeno task test(full Deno suite, at541fc03)deno test scripts/tests/ci-workflow.test.tsdeno task buildthen./dist/xmd targets smoke-test/document-targets.md./dist/xmd run 'smoke-test/document-targets.md#Alpha' --rawALPHA_RANpresent,BETA_RANabsentThe full Deno suite was run rather than affected-file selection, because this change crosses subprocess dispatch, dynamic CLI parsing, and a compiled-binary smoke boundary.
Mutation probes show the replacement cases are discriminating:
exactRoot()returning the requested root unchanged — CT12 and CT12a both fail with status 0: the run silently succeeds executing the replacement's section, and the recorder shows its<File>read did happen. That is precisely the defect the invariant exists to prevent.readsValue()swallowing the target failure again — CT12 fails onserviceInstalled, which is the early-refusal boundary.CI owns the exhaustive Node and Bun suites,
site,smoke,review, the filesystem-contract rows, and the aggregategreencheck.composabilityskips offmainby repository policy.