👀 Report xmd plan authorship while it happens - #754
Conversation
`xmd plan` took minutes, asked one question in the middle and said nothing about what it was doing. Now the packaged command root reports itself: one Markdown phase announced on stderr before each piece of work — preparing, drafting, checking, repairing, waiting for review, revising, finalizing, stopping, and the ending a tenth unrepaired draft gets. The phases are authored in `Plan.md`, beside the operations they precede, and written through a new private paired `<PlanProgress>`. It renders its content, sends it through the current document-output operation, and returns the empty string — returning it would put a phase heading inside `<Plan as>`'s capture and inside the declaration's exact-source disposition. Which surface is asking and whether `--verbose` was written are sealed host facts, so an ordinary `<Plan>` announces nothing and expands no progress body at all. `Plan.md` now binds its two bounds once and derives every loop bound, condition and sentence from them, so the ordinals a person reads cannot drift from the counters the workflow keeps. The catalog moves behind `<PlanInputs>`, which is what lets a phase precede the preparation it names; the command adapter loses its explanatory prose and its redundant `syntax` prop, because that root's transcript is now what an operator reads. Two long-only options come back, describing authorship and nothing after it: `--verbose` adds every cleared draft and every failed check's structured findings, and `--journal <path>` exclusively creates a diagnostic JSONL record of the invocation. `-V` and `-j` stay unsupported and are refused by naming the long spelling. Every event crosses the existing serialized pre-append secret gate, so a rejected draft or diagnostic reaches neither the progress nor the file, and the prefix before it stays readable. The host owns the stream and the terminal: normalization always, terminal formatting only when the entrypoint states its own stderr is one, and the transcript drained inside the scope that owns the execution — so a destination that stops accepting bytes cancels the live turn, waits for every owned teardown, and gets no stdout fallback. Stdout and `--output` are untouched by all of it. Closes#676
`xmd plan "do it" --journal --verbose` passed fixed grammar: `--verbose` was consumed as the path, so the command exclusively created a file called `--verbose` and dropped the verbosity that was asked for. A token naming an option this command defines is now that option rather than a filename, and the value is refused with the sentence `--journal` already had. Only that position changes — `--journal <path> --verbose`, `--verbose --journal <path>` and the inline `--journal=` form all parse as before, and a removed spelling written there keeps its own more specific refusal. Beside it, PO16 pins what the file holds when an invocation ends for a reason of its own: a failed turn after an entry has committed exits non-zero, delivers no source and no artifact, completes teardown, and leaves a file whose bytes are exactly the entries it took, re-serialized — so a truncated or unterminated trailing record is caught rather than assumed absent. The specification now states readable-prefix retention for all four endings: approval, an ordinary failure, a refused entry and a secret rejection.
Two claims were wider than what the product does. PO7 asserted that every retained option written where the `--journal` path goes refuses. `--help` and `-h` are lifted out of the command line before any command's own grammar runs, so `xmd plan "do it" --journal --help` prints help and exits successfully — and it should: pre-help refusal is reserved for the options this command removed, and a caller who asked for help beside an incomplete option is asking for help. The missing-value check stays where it is. The unit list now holds only the spellings that reach this grammar, and the command-level row proves both halves: each of those spellings refuses before any catalog, session, provider, filesystem or artifact work, and help answers instead while still creating no journal and beginning no authorship. The durability prose claimed a complete, wholly parseable file after every ending, including a failed append. An appending write is not a transaction, so that is not this command's to promise. Success, an ordinary failure and a secret rejection each leave a wholly parseable file because no append failed; after a failed append the guaranteed evidence is the sequence committed before it, and the specification now says so and says why making it stronger would take an atomic protocol #676 does not need. PO12 reads terminated records accordingly, and keeps its subject: the preserved prefix and the approved diagnostic. PO16 is unchanged and still proves byte-exact reserialization with no partial trailing record, where no append failed.
PR #754: 👀 Report |
The `smoke` job asserts what `xmd plan --help` says from `dist/xmd`, and its list of options that must not appear still held `--journal` and `--verbose`. This stack gives the command both, so the check was pinning the grammar it replaced. It shells out to a binary, so no module graph connects it to the change and affected-test selection could not have found it — which is what the run demonstrated: every deno, node and bun shard passed while this one step failed. The list now names `--run`, `--props`, `--raw`, `--deny-all` and the two short aliases, matched as whole tokens because `-j` is a substring of the `--journal` this command does define. Beside it the step now asserts the two options and the journal warning *are* in the compiled help, so the boundary that would drop them from a release is the one that reports it. `PlanProgress` joins the private names no build lets a document write, here and in the npm bin's equivalent check.
Uh oh!
There was an error while loading. Please reload this page.
Closes#676.
Why
xmd plantakes minutes, asks you one question somewhere in the middle, anduntil now said nothing at all about what it was doing. There was no way to tell
a slow Agent turn from a stuck one, no way to see the draft that was rejected,
and nothing to attach to a bug report when planning went wrong.
What changes
Before:
After:
Every phase is announced before the work it names, so what you read is what
is happening rather than an account of what already finished. It all goes to
stderr;
release.mdabove holds the approved source and nothing else.Two long-only options come back, describing authorship and nothing after it:
--verboseadds every cleared draft and every failed check's structuredfindings;
--journal <path>exclusively creates a diagnostic JSONL record of theinvocation, in the existing
serializeDurableEvent()format.-Vand-jstay unsupported — they arexmd run's aliases for options abouta program's run — and are refused by naming the long spelling.
How it works
The phases are authored in
packages/cli/src/documents/Plan.md, beside theoperations they precede, and written through a new private paired
<PlanProgress>. It renders its content, sends it through the currentDocumentOutputoperation as ordinary prose, and returns the empty string.Returning it instead would put a phase heading inside
<Plan as="approved">'scapture and inside the declaration's exact-source disposition — contaminating
the approved program and bypassing the presentation every other line of progress
gets. Which surface is asking and whether
--verbosewas written are sealed hostfacts the declaration carries, so an ordinary
<Plan>in someone's documentannounces nothing and expands no progress body at all.
The command root's rendered transcript is that progress, so the adapter loses
its explanatory prose: its body is now two elements. The syntax catalog moves
behind
<PlanInputs>, which is what lets a phase precede the preparation itnames — before this, no authored phase could run before the catalog was built.
Review guide
Start with:
specs/plan-command-spec.md§ "Watching a Plan being written"and § "The
--journalfile"Then review:
packages/cli/src/documents/Plan.md— every phase a person reads, and thetwo bounds every loop, condition and ordinal is derived from
packages/cli/src/plan-component.ts—<PlanProgress>, the fifth privatecapability, and the sealed
verbosefactpackages/cli/src/authorship-profile.ts— the output lifecycle:normalization, conditional terminal formatting, the host-selected stream, and
the in-scope drain
packages/cli/src/plan-journal.ts— exclusive creation, the JSONL passthroughand the three diagnostics
packages/cli/src/plan-args.ts,src/cli.ts— grammar, help, the stderrwriter and the TTY fact
Look carefully at:
runPlanCommandDocument: consumption is inside the scope thatowns the execution, so a stderr that stops accepting bytes cancels the live
turn and waits for provider, Prompt, Elicitation, session-directory and
execution teardown before anything is reported — and gets no stdout fallback
journalRefusal()inplan-journal.ts: a refused append is reported from thecause chain, because the durable runtime wraps it in its own message
What must stay true
<PlanProgress>returning""and writing throughDocumentOutput, checkedby
PO4(both ordinary forms) andPO6(stdout and--outputbyte-identical).<Plan>announces nothing. — enforced by the sealed surfacediscriminator, checked by
PO4, which uses the command surface as its control.existing serialized pre-append gate under
secretDetection: true, checked byPO10/PO11, each with a clean-value control.xmd planproduce approved XMD source only #724's three structural checks and their ordering. — onevalidatedependency asked by
<CheckDraft>,<AdmitPlan>and the final host gate,checked by the unchanged
PS6–PS9and byPO14.PO14, which assertsFinalizing is the last phase and that no delivery word appears.
How to verify it
PO1proves phases precede their work — including an early phase observedwhile an Agent turn is still blocked, so it fails if the transcript is
buffered into an end-of-run summary.
PO2proves the ordinals come from the loop bounds; it fails if a counter isoff by one or a sentence carries its own number.
PO5proves default progress excludes the request, drafts, diagnostics andapproved source, and that verbose adds exactly two blocks in phase order.
PO8/PO9/PO12/PO16cover the journal: created before any authorship,the existing JSONL in commit order, both creation refusals byte-exact, an
append failure preserving its committed prefix, and an ordinary failure
leaving a wholly parseable file.
PO13proves a failed stderr cancels the live turn and completes teardown; itfails if consumption is detached or if a stdout fallback appears.
I checked these are not vacuous by breaking the behavior they cover: making
<PlanProgress>return its content turnsPO4red; an eager catalog turnsPO15red;secretDetection: falseturnsPO10/PO11red; dropping threebytes per journal record turns
PO8/PO12/PO16red.Every phase block and both terminal diagnostics were also compared byte for byte
against the rendered transcript of the packaged document.
Scope
Included
from the two bounds
Plan.mdbinds once.--verboseand--journal <path>, long spellings only, with help copy andthe journal warning.
<PlanInputs>, and an output-free commandadapter.
architecture.md,specs/plan-command-spec.mdandspecs/executable-mdx-spec.mdupdated for all of it.Intentionally unchanged
integration; nothing outside
packages/cli,architecture.mdandspecs/is touched.
xmd planremoved in Makexmd planproduce approved XMD source only #724 —--run, the root properties,--raw, the exec and fetch deadlines, the permission flags and bothsecret-detection spellings — stays removed, with its exact refusal.
--session --verbosehas the same value-swallowing shape as the--journalcase fixed here. It predates this work and belongs to Make
xmd planproduce approved XMD source only #724's grammar, so it isleft alone rather than widening this change.
than gaining an artificial runtime path; reaching it needs a review answer the
schema forbids.
New abstractions
<PlanProgress>exists because progress must be a side effect of the command'sown output rather than something
<Plan>produces — its content is used ateleven authored sites in
Plan.md.ProgressOutputexists because only the entrypoint knows whether its stderris a terminal and whether a write landed; it is implemented once in
cli.tsand once in the test harness.
packages/cli/src/plan-journal.tsexists because the journal diagnostics arethis command's own words, while the bytes stay
xmd run's format.Risks and limitations
checks every entry before it is recorded, but it may not catch every sensitive
detail. Help says so, in the exact words the contract settled.
committed before it. An appending write is not a transaction, so a
filesystem failing partway can leave part of a record. The specification says
this plainly rather than promising atomicity; making it stronger would take a
protocol Make
xmd planauthorship observable during and after an invocation #676 does not need.it is absent in a test process.
PO6therefore asserts the colour-independenthalf — a fenced block rendered as indented text — rather than escape codes.
xmd plan; reverting thethree commits restores Make
xmd planproduce approved XMD source only #724's behavior exactly.Scope confirmation