Repository navigation
Conversation
A plan whose source-spec names a spec must now carry completes-spec: true or false. The key is refused beside source-spec: null. Each variant has exactly one legal form, and every command that validates a plan (plans lint, approve, run, the loose-plan runnable check) refuses a violation with exit 12, naming completes-spec. readSourceSpec returns the value with the declared spec path. The key stays fingerprinted (fingerprintSource still drops only status and approved), so flipping it on an Approved plan reports self-changed. Test fixtures that declare a source spec now carry the key. --- Run-Id: a-plan-says-whether-its-run-completes-its-source-spec-1791294874107 Short-Name: a-plan-says-whether-its-run-completes-its-source-spec Phase-Id: phase-01 Phase-Title: Plan frontmatter carries completes-spec beside a source spec Model: claude-opus-5-5 Effort: high Worktree: /Users/remyloubradou/.phax/worktrees/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-01 Session-Id: 0b60f330-019e-4a46-8e72-fea6185b0974 Gate-Log: /Users/remyloubradou/.phax/runs/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-01/checks-attempt-01.log
… last Run completion now reads completes-spec from the pre-transition plan. With true it rides the source spec along exactly as before, chain gate included. With false it makes no spec transition and no spec commit, and leaves the spec's status, location and approval record file untouched. The completion report replaces skippedSpec with a keptSpec carrying named variants: blocked by live plans (with the blockers), or not completed by this plan. phax run and phax resume print a kept line for each. Run completion writes the spec outcome to source-spec-outcome.md in the run folder; the review handoff renders it as a Source spec section, so the PR body states it too. --- Run-Id: a-plan-says-whether-its-run-completes-its-source-spec-1791294874107 Short-Name: a-plan-says-whether-its-run-completes-its-source-spec Phase-Id: phase-02 Phase-Title: Run completion honours completes-spec and reports the spec outcome Model: claude-opus-5-5 Effort: high Worktree: /Users/remyloubradou/.phax/worktrees/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-02 Session-Id: ba270fe6-0f0c-4b18-baac-515907b168db Gate-Log: /Users/remyloubradou/.phax/runs/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-02/checks-attempt-01.log
phax artifact new plan --spec now needs exactly one of --last (this plan is the spec's last) or --not-last (more plans follow). It writes completes-spec: true or false right after source-spec. Neither or both flags with --spec, or either flag without --spec, refuse with exit 12 before anything is written, in interactive and headless mode alike. The pairing rule is a pure domain function applied in the shared resolveArtifactTarget. The headless path renders the same frontmatter from the flags. The confirmation line names the value. phax.usage.kdl, docs/cli/reference.md and the README's generated CLI block are regenerated. --- Run-Id: a-plan-says-whether-its-run-completes-its-source-spec-1791294874107 Short-Name: a-plan-says-whether-its-run-completes-its-source-spec Phase-Id: phase-03 Phase-Title: artifact new plan requires --last or --not-last with --spec Model: claude-opus-5-5 Effort: medium Worktree: /Users/remyloubradou/.phax/worktrees/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-03 Session-Id: cb65ec97-cdda-427c-b8f6-55daa9f0cc29 Gate-Log: /Users/remyloubradou/.phax/runs/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-03/checks-attempt-01.log
The plan document (the headless plan's JSON sidecar and the authoring contract) now requires completesSpec. It is a boolean beside a sourceSpec path and null beside sourceSpec: null; the cross pairs are refused. Headless authoring sets it from --last/--not-last whatever the session returned, as it does for sourceSpec, and the authoring prompt states the value. phax-plan.json stays lineage-free. The shape change follows the schemas package's history procedure. Today's shape is frozen as src/schemas/history/plan-document/0.17.0.ts, listed in the format's releases and pinned in history.lock.json. The new shape is recorded as next.schema.json, and CURRENT_SHAPES names next. toLatestPlanDocument upgrades older shapes with completesSpec null beside no spec and Unknown otherwise, never an invented value. --- Run-Id: a-plan-says-whether-its-run-completes-its-source-spec-1791294874107 Short-Name: a-plan-says-whether-its-run-completes-its-source-spec Phase-Id: phase-04 Phase-Title: The plan document mirrors completesSpec Model: claude-opus-5-5 Effort: high Worktree: /Users/remyloubradou/.phax/worktrees/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-04 Session-Id: 63d66017-1361-4e3a-b09e-5621e35a82d5 Gate-Log: /Users/remyloubradou/.phax/runs/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-04/checks-attempt-01.log
The phax-planning skill's plan frontmatter block now defines completes-spec: true or false, absent when source-spec is null, and false on every plan of a spec except the last. It shows the --last/--not-last flags and adds completesSpec to the headless document keys. The README lifecycle text says a run completes its spec only when its plan says it is the last, with the multi-plan example; its examples carry the flags, and an upgrade note covers plans that lack the key. NEXT_STEPS ticks the follow-up and drops the note about reverting a run's spec completion by hand. No live plan with a source spec needed the key. --- Run-Id: a-plan-says-whether-its-run-completes-its-source-spec-1791294874107 Short-Name: a-plan-says-whether-its-run-completes-its-source-spec Phase-Id: phase-05 Phase-Title: Docs and live-plan migration for completes-spec Model: claude-sonnet-5-5 Effort: medium Worktree: /Users/remyloubradou/.phax/worktrees/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-05 Session-Id: e9358973-c6d4-4669-9525-094584fbfea6 Gate-Log: /Users/remyloubradou/.phax/runs/phax.a-plan-says-whether-its-run-completes-its-source-spec/phase-05/checks-attempt-01.log
Transitions docs/plans/2610061202-completes-spec-plan.md to Completed (complete).
Transitions docs/specs/2610060955-completes-spec.md to Completed (complete).
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.
PHAX Run Review Handoff
Generated by PHAX.
Run Review Handoff
Run summary
phax/a-plan-says-whether-its-run-completes-its-source-spec--phase-05Global File Reconciliation
Run: phax.a-plan-says-whether-its-run-completes-its-source-spec
Global unplanned changes
None.
Global missing planned changes
None.
Global review attention points
tests/integration/approvalRecordMerge.test.ts(extra-touch) — see phase-01/phase-handoff.md for detailsDeviations not explained in any handoff
None.
Plan compliance review
Plan-compliance review
Verdict
conformant-with-deviations. All five phases delivered their objectives. The file reconciliation shows no missing files. The only unplanned file touches are two small, justified ones. Commit subjects could not be checked directly (no shell access in this review); the visible log confirms the phase-05 subject only.
Per-phase findings
phase-01 — frontmatter key
ALLOWED_KEYS, andreadSourceSpecreturningcompletesSpecare all in the handoff. The refusal wording for all three cases is documented.frontmatter.tswas touched.readSourceSpecreturns thenoneandspecvariants.phase-02 — run completion
keptSpecreplacesskippedSpec, withrenderSourceSpecOutcomeandSOURCE_SPEC_OUTCOME_FILENAME.executePlanwrites the fragment, the handoff renders## Source spec, and the CLI prints the kept line.loadReviewHandoffInputs.ts,reviewHandoff.test.ts).buildReviewHandoffContentowns the heading.buildReviewHandoffContentsignature changed to anextrasobject.phase-03 — creation flags
resolveCompletesSpecis pure,resolveArtifactTargetapplies it,planSkeletonemits the key, and the flags are registered onartifact new planonly. The generated docs were regenerated, not hand-edited.persistedProducer.test.tswas touched beyond the phase list. It is an optional file in phase 4. The handoff justifies it by thetest:typefailure.phase-04 — plan document
releases.completesSpecis required in the document.next.schema.jsonis created andCURRENT_SHAPESpoints tonext. The authoring override and the prompt bullet are in place.phax-plan.jsonand the repo's own sidecars were untouched.approvalRecordMerge.test.tswas an extra touch. The handoff justifies it: the pre-schema fixture could no longer be authored.packages/schemas/src/index.tswas touched as an optional file.phase-05 — docs and migration
completes-speckey (confirmed by search). Generated blocks were not edited.completesSpecDocs.test.tsis present.docs: teach completes-spec and close the multi-plan follow-upmatches the recent log.Unplanned-change ledger
tests/integration/approvalRecordMerge.test.tsin phase-04. A fixture was adapted; justified.tests/integration/persistedProducer.test.tsin phase-03. It is optional in phase 4 but was touched earlier, for the type change; justified.Unmet-promise ledger
None found at file level. Test content was not re-inspected.
Attention points
Phase details
phase-01 — Plan frontmatter carries completes-spec beside a source spec
File reconciliation
PHAX File Reconciliation
Planned to edit
Optional files touched
Summary: No deviations from the planned file lists.
Phase handoff
What was delivered
src/schemas/artifactFrontmatter.tsdefines two schemas:SpecLessPlanFrontmatterSchema:status,source-spec: null,approved?;SpecBoundPlanFrontmatterSchema:status,source-spec: NonEmptyString,completes-spec: Boolean,approved?.PlanFrontmatterSchemais now their union, andPlanFrontmatteris the union type.decodePlanFrontmatter(value)is now a function, not adecodeUnknownEitherconstant. It returnsEither<PlanFrontmatter, ParseError>.SourceSpecDeclaration(src/domain/artifact/lineage.ts) is{ kind: "spec"; path; completesSpec: boolean } | { kind: "none" }.readSourceSpecfillscompletesSpec.ALLOWED_KEYS.plan(src/domain/artifact/document.ts) readsstatus, source-spec, completes-spec (required with a source spec, absent without), approved.Key decisions and why
decodePlanFrontmatterpicks a variant fromsource-specbefore decoding: a non-null value goes to the spec-bound schema, anything else to the spec-less one. Decoding the union directly reported every member's issues.artifactFrontmatter.ts. Each refusal surfaces asFrontmatterProblem{kind:"schema"}and thenArtifactValidationError(exit 12):completes-spec: is missing — …(amissingMessageannotation);completes-spec: must be true or false, actual "yes" — …(amessageannotation). Bare YAMLyesdecodes as the string"yes";completes-spec: is inconsistent with source-spec: null — … remove the key. This is a hand-builtParseResult.Unexpected, because the default excess-key message cannot say "inconsistent".decodeArtifactFrontmatteris overloaded:"plan"returnsPlanFrontmatter, and"spec"returnsSpecFrontmatter.readSourceSpecnarrows onsource-spec === nullwith no cast.Exact locations (file paths and exported names)
src/domain/artifact/lineage.ts—SourceSpecDeclaration,readSourceSpecsrc/schemas/artifactFrontmatter.ts—PlanFrontmatter,PlanFrontmatterSchema,SpecLessPlanFrontmatterSchema,SpecBoundPlanFrontmatterSchema,decodePlanFrontmattersrc/domain/artifact/frontmatter.ts—decodeArtifactFrontmatter(overloaded)What the next phase needs to know
phax artifact new plan --specwrites a skeleton (planSkeletonincreateArtifact.ts) withoutcompletes-spec, and validation refuses it. This is expected.completes-spec: trueafter a non-nullsource-spec:planMdin the integration testscompleteRunArtifacts,runCarriesCompletion,migrateApprovals,approvalRecordMerge,approvalLedgerRefusal,artifactStatusandplanStaleness;deterministicPlanMdinplanStaleness;frontmatterWithSpecinlintPlan;planFmin thedocumentandlineageunit tests;PLAN_DOCin thefrontmatterunit test.falseto thecompleteRunArtifacts/runCarriesCompletionhelpers.lintPlan'sfrontmatterWithSpecandplanFmin thedocumentandlineageunit tests already take a value.fingerprintSourceis unchanged, socompletes-specis fingerprinted. A tested flip reportsself-changed.tests/unit/renderPlan.test.ts:198builds a keyless frontmatter but never validates it. It is left for phase 4.src/domain/artifact/frontmatter.tswas edited.phase-02 — Run completion honours completes-spec and reports the spec outcome
File reconciliation
PHAX File Reconciliation
Planned to edit
Optional files touched
Summary: No deviations from the planned file lists.
Phase handoff
What was delivered
RunCompletionReport(src/app/completeRunArtifacts.ts) now carrieskeptSpec?: RunCompletionKeptSpecin place ofskippedSpec. The type is{ reason: "blocked"; path; blockedBy } | { reason: "not-completing"; path }.completeInWorktreereturnskeptSpec: { reason: "not-completing" }for a plan withcompletes-spec: false. It does this right after the plan transition (or the archive re-entry read). The spec is never resolved, read, validated or transitioned. Withtrue, the existing path runs unchanged.renderSourceSpecOutcome(report): string | undefinedandSOURCE_SPEC_OUTCOME_FILENAME = "source-spec-outcome.md"are exported from the same module.loadSourceSpecOutcome(info)(src/app/loadReviewHandoffInputs.ts) reads the fragment frominfo.runPath, or returns undefined when it is absent.ReviewHandoffInputsgainssourceSpecOutcomeMd: string | undefined.## Source specsection right after## Run summarywhen the fragment exists, and no section otherwise.Key decisions and why
buildReviewHandoffContent's fifth parameter is nowextras: ReviewHandoffExtras = {}({ complianceReviewMd?, sourceSpecOutcomeMd? }), replacing the positionalcomplianceReviewMd?.executePlanwrites the fragment (${outcome}\n) throughFileSystem.writeAtomic. The write sits inside theplanRepoRelPathbranch, right aftercompleteRunArtifactssucceeds and before theFinalReviewOpeneddispatch. Nothing is written when the renderer returns undefined (loose plan, spec-less plan, spec in Draft or Abandoned).`<archive path>` — completed on this branch (<7-char hash>);`<archive path>` — already complete;`<path>` — kept: live plans remain (<plan>, <status>; ...);`<path>` — kept: this plan does not complete it (completes-spec: false).○ spec <path> kept: non-terminal dependent plans remain, plus one indented blocker line per plan;○ spec <path> kept: this plan does not complete it (completes-spec: false).Exact locations (file paths and exported names)
src/app/completeRunArtifacts.ts—RunCompletionReport,RunCompletionKeptSpec,renderSourceSpecOutcome,SOURCE_SPEC_OUTCOME_FILENAME,completeRunArtifactssrc/app/reviewHandoff.ts—buildReviewHandoffContent,ReviewHandoffExtras,generateReviewHandoffsrc/app/loadReviewHandoffInputs.ts—loadSourceSpecOutcome,ReviewHandoffInputssrc/cli/commands/run.ts—renderArtifactCompletionsWhat the next phase needs to know
planMdhelpers intests/integration/completeRunArtifacts.test.tsandrunCarriesCompletion.test.tstake a thirdcompletesSpec = trueargument.seedWorktreeArtifactstakes a fourth.publishRunreads the fragment throughloadReviewHandoffInputs.generateReviewHandoff(first generation andphax review-handoff) reads it throughloadSourceSpecOutcome.artifact new plan --specwrites a skeleton that validation refuses.src/app/loadReviewHandoffInputs.tsandtests/integration/reviewHandoff.test.ts(regeneration coverage) were edited.phase-03 — artifact new plan requires --last or --not-last with --spec
File reconciliation
PHAX File Reconciliation
Planned to edit
Unplanned files edited
Summary: Deviations detected — see sections above.
Phase handoff
What was delivered
resolveCompletesSpec(flags: CompletesSpecFlags): Either<boolean | null, string>insrc/domain/artifact/lineage.tsis the pure pairing rule for--spec/--last/--not-last.resolveArtifactTargetapplies the rule for a plan before any filesystem read and fails withArtifactCreationError(exit 12). The interactive and headless paths share it.ArtifactTarget.completesSpec: boolean | null,CreateArtifactResult.completesSpec,PlanLineageandtargetLineage(target)are new insrc/app/createArtifact.ts.planSkeleton(lineage: PlanLineage | null)writescompletes-spec: <bool>on the line aftersource-speconly when a spec is bound.artifact new planregisters--lastand--not-last.phax.usage.kdl,docs/cli/reference.mdand the README's generated CLI block were regenerated withpnpm gen:usage-specandpnpm docs:cli, not hand-edited.Key decisions and why
--spec needs --last (this plan is the spec's last) or --not-last (more plans follow)--last and --not-last are opposites: pass exactly one with --spec--last needs --spec: a plan without a source spec completes none(or--not-last …, naming the flag given)resolveArtifactTarget, before the slug check, so a bad pair refuses without touching the filesystem.completion, andcompletesSpecis always null for it.runCreateArtifact(kind, slug, specArg, completion, out)andrunCreateArtifactHeadless(kind, slug, specArg, completion, opts, out, deps)take a new positionalcompletion: CompletionFlags.new specpasses{ last: false, notLast: false }.created <path> (Draft, source-spec <spec>, completes-spec <true|false>). Without a spec it stayscreated <path> (Draft, source-spec null).exitCodeForAuthoringErroralready mapsArtifactCreationErrorto 12, sorunLayers.tsis unchanged.Exact locations (file paths and exported names)
src/domain/artifact/lineage.ts—resolveCompletesSpec,CompletesSpecFlagssrc/app/createArtifact.ts—CompletionFlags,ArtifactTargetInput.completion,CreateArtifactInput.completion,ArtifactTarget.completesSpec,CreateArtifactResult.completesSpec,PlanLineage,planSkeleton,targetLineagesrc/app/authorArtifact.ts—AuthorArtifactInput.completion;renderArtifactnow takes theArtifactTargetsrc/cli/commands/artifact.ts—runCreateArtifact,runCreateArtifactHeadlessWhat the next phase needs to know
completesSpecfromtarget.completesSpecinrunAuthoringSession, next to the existingsourceSpecoverride. The plan document is unchanged here, so headless sidecars do not yet carrycompletesSpec.buildAuthoringPromptdoes not receivecompletesSpecyet; phase 4 adds it.NO_FLAGS/LAST/NOT_LASTare local constants inauthorArtifact.test.ts,createArtifact.test.tsandtests/unit/cli/artifact.test.ts.tests/integration/persistedProducer.test.ts(not listed for this phase) gained the requiredcompletionfield in itsauthorArtifactinputs, because the type change made it failtest:type.src/cli/commands/runLayers.ts,src/cli/program.ts,usageOutput.test.tsandcliErrors.test.ts(optional) were not touched.phase-04 — The plan document mirrors completesSpec
File reconciliation
PHAX File Reconciliation
Planned to create
Planned to edit
Optional files touched
Unplanned files edited
Summary: Deviations detected — see sections above.
Phase handoff
What was delivered
src/schemas/history/plan-document/0.17.0.tsis the frozen copy of the plan-document file shape from beforecompletesSpec. It is listed inplanDocumentFormat.releases, pinned inhistory.lock.jsonand re-exported asPlanDocumentV0_17_0Schema/PlanDocumentV0_17_0.PlanDocumentSchemaandPlanDocumentFileSchemarequirecompletesSpec: a boolean beside asourceSpecpath, null besidesourceSpec: null.packages/schemas/snapshots/plan-document/next.schema.jsonexists, andCURRENT_SHAPES["plan-document"]isnext.sourceSpecandcompletesSpecfrom--spec/--last/--not-last(targetLineage(target)). The prompt saysSet `completesSpec` to `true|false`orto null: this plan has no source spec.Key decisions and why
completesSpecis a type-guard refinement over the struct, with the messagecompletesSpec is a boolean beside a sourceSpec path and null beside sourceSpec: null. The JSON Schema keepscompletesSpecinrequiredand addsallOf: [{ oneOf: [...] }]for the two legal pairs. A bareoneOfannotation would make Effect replace the whole struct.toLatestPlanDocumentupgrades older shapes with null beside no spec and withUNKNOWNbeside a spec path, never an invented boolean.readPlanDocumentFile:lacks completesSpec, which phax needs — not supported;$schemasidecar without the field fails withcompletesSpec: is missing.Exact locations (file paths and exported names)
src/schemas/planDocument.ts:PlanDocumentLineage,PlanDocument(a correlated union),PlanDocumentSchema,PlanDocumentFileSchemasrc/schemas/history/plan-document/0.17.0.ts:PlanDocumentV0_17_0Schema,PlanDocumentV0_17_0,decodePlanDocumentV0_17_0packages/schemas/src/formats/repository.ts:LatestPlanDocument,toLatestPlanDocument,PlanDocumentShapes(adds"0.17.0")src/domain/authoring/prompt.ts:AuthoringPromptInput.completesSpectests/unit/schemasPackage/documents.ts:latestPreSchema(id)What the next phase needs to know
sourceSpec: null), so phax's bridge and the package agree on it.tests/integration/approvalRecordMerge.test.ts(not listed for this phase) wrote a pre-schema sidecar that a decodable authored fixture can no longer produce. It now writes the current$schemashape withcompletesSpec: null.nextrejects is re-read by the 0.17.0 decoder, whose violation is the one reported.parity.test.tsrecords this asreleasedShapePath.defineFormatis unchanged.completesSpecto the skill's §Headless authoring.phase-05 — Docs and live-plan migration for completes-spec
File reconciliation
PHAX File Reconciliation
Planned to create
Planned to edit
Summary: No deviations from the planned file lists.
Phase handoff
What was delivered
.claude/skills/phax-planning/SKILL.mdnow definescompletes-specin §Plan frontmatter block: the example block, a bullet, and the--last|--not-lastcreation sentence. §Headless authoring listscompletesSpec.README.mdstates that a run completes its spec only when the plan sayscompletes-spec: trueand no other live plan needs it. The--specexamples carry--last. A "One spec, several plans" example (--not-last×2,--last, plan 1's frontmatter) sits in "Create them". A Troubleshooting note covers plans that lack the key.NEXT_STEPS.mdticks the follow-up "A run completes its source spec even when more plans are to come". Theartifact-deciderevert note is dropped.tests/unit/completesSpecDocs.test.tsasserts the skill's key definition, the absent-when-null rule, the "every plan except the last saysfalse" rule, the creation flags, and--not-lastin the README.Key decisions and why
false" phrase. The skill hard-wraps lines, and the phrase starts a sentence..jsonsidecars lackcompletesSpecand must be re-authored or deleted. This follows the phase 4 arbitration.Exact locations (file paths and exported names)
.claude/skills/phax-planning/SKILL.md— §Plan frontmatter block, §Headless authoringREADME.md— "Create them" (multi-plan example), lifecycle/Run/Approve sentences, TroubleshootingNEXT_STEPS.md— §Small follow-ups, artifact-decide entrytests/unit/completesSpecDocs.test.ts— docs assertions (no exports)What the next phase needs to know
docs/plans/2606291247-smolvm-isolation-spike-plan.mdhassource-spec: null, so it needs no key.docs/plans/2610061202-completes-spec-plan.mdwas left withoutcompletes-specon purpose. The installed phax 0.19.0 refuses unknown frontmatter keys.phax.usage.kdlanddocs/cli/reference.mdwere not edited, and no archived plan or sidecar was touched.pnpm check:fullpasses.