Skip to content

PHAX: A plan says whether its run completes its source spec - #123

Merged
lbdremy merged 7 commits into
mainfrom
phax/a-plan-says-whether-its-run-completes-its-source-spec--phase-05
Oct 7, 2026
Merged

lbdremy merged 7 commits into
mainfrom
phax/a-plan-says-whether-its-run-completes-its-source-spec--phase-05

Conversation

@lbdremy

@lbdremy lbdremy commented Oct 6, 2026

Copy link
Copy Markdown
Owner

PHAX Run Review Handoff

Generated by PHAX.

Run Review Handoff

Run summary

  • Short Name: a-plan-says-whether-its-run-completes-its-source-spec
  • Run ID: a-plan-says-whether-its-run-completes-its-source-spec-1791294874107
  • Base Branch: phax/a-plan-says-whether-its-run-completes-its-source-spec
  • Final Phase Branch: phax/a-plan-says-whether-its-run-completes-its-source-spec--phase-05
  • Gate Profile: standard
  • Phases: 5/5 passed
  • See final-report.md for security details and entry/resume instructions.

Global File Reconciliation

Run: phax.a-plan-says-whether-its-run-completes-its-source-spec

File Planned in Touched in Status Notes
.claude/skills/phax-planning/SKILL.md phase-05 phase-05 matched —
docs/cli/reference.md phase-03 phase-03 matched —
NEXT_STEPS.md phase-05 phase-05 matched —
packages/schemas/history.lock.json phase-04 phase-04 matched —
packages/schemas/snapshots/plan-document/next.schema.json phase-04 phase-04 matched —
packages/schemas/src/formats/repository.ts phase-04 phase-04 matched —
packages/schemas/src/generated/index.ts phase-04 phase-04 matched —
packages/schemas/src/index.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
phax.usage.kdl phase-03 phase-03 matched —
README.md phase-03, phase-05 phase-03, phase-05 matched —
src/app/authorArtifact.ts phase-03, phase-04 phase-03, phase-04 matched —
src/app/completeRunArtifacts.ts phase-02 phase-02 matched —
src/app/createArtifact.ts phase-03 phase-03 matched —
src/app/executePlan.ts phase-02 phase-02 matched —
src/app/loadReviewHandoffInputs.ts phase-02 (optional) phase-02 optional-touched optional in: phase-02
src/app/publishRun.ts phase-02 phase-02 matched —
src/app/reviewHandoff.ts phase-02 phase-02 matched —
src/cli/cliDocs.ts phase-03 phase-03 matched —
src/cli/commands/artifact.ts phase-03 phase-03 matched —
src/cli/commands/run.ts phase-02 phase-02 matched —
src/domain/artifact/document.ts phase-01 phase-01 matched —
src/domain/artifact/frontmatter.ts phase-01 (optional) phase-01 optional-touched optional in: phase-01
src/domain/artifact/lineage.ts phase-01, phase-03 phase-01, phase-03 matched —
src/domain/authoring/prompt.ts phase-04 phase-04 matched —
src/schemas/artifactFrontmatter.ts phase-01 phase-01 matched —
src/schemas/history/plan-document/0.17.0.ts phase-04 phase-04 matched —
src/schemas/persisted.ts phase-04 phase-04 matched —
src/schemas/planDocument.ts phase-04 phase-04 matched —
tests/integration/approvalLedgerRefusal.test.ts phase-01 phase-01 matched —
tests/integration/approvalRecordMerge.test.ts phase-01 phase-01, phase-04 extra-touch extra touch in: phase-04
tests/integration/artifactNewHeadlessCommand.test.ts phase-03 phase-03 matched —
tests/integration/artifactStatus.test.ts phase-01 phase-01 matched —
tests/integration/authorArtifact.test.ts phase-03, phase-04 phase-03, phase-04 matched —
tests/integration/completeRunArtifacts.test.ts phase-01, phase-02 phase-01, phase-02 matched —
tests/integration/createArtifact.test.ts phase-03 phase-03 matched —
tests/integration/lintPlan.test.ts phase-01 phase-01 matched —
tests/integration/migrateApprovals.test.ts phase-01 phase-01 matched —
tests/integration/persistedProducer.test.ts phase-04 (optional) phase-03, phase-04 optional-touched optional in: phase-04
tests/integration/planStaleness.test.ts phase-01 phase-01 matched —
tests/integration/publishRun.test.ts phase-02 phase-02 matched —
tests/integration/reviewHandoff.test.ts phase-02 (optional) phase-02 optional-touched optional in: phase-02
tests/integration/runCarriesCompletion.test.ts phase-01, phase-02 phase-01, phase-02 matched —
tests/type/schemasPackage.ts phase-04 phase-04 matched —
tests/unit/architecturalGuards.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
tests/unit/artifact/document.test.ts phase-01 phase-01 matched —
tests/unit/artifact/frontmatter.test.ts phase-01 phase-01 matched —
tests/unit/artifact/lineage.test.ts phase-01, phase-03 phase-01, phase-03 matched —
tests/unit/artifact/sidecar.test.ts phase-04 phase-04 matched —
tests/unit/authoringPrompt.test.ts phase-04 phase-04 matched —
tests/unit/cli/artifact.test.ts phase-03 phase-03 matched —
tests/unit/cli/run.test.ts phase-02 phase-02 matched —
tests/unit/completesSpecDocs.test.ts phase-05 phase-05 matched —
tests/unit/persisted.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
tests/unit/planDocument.test.ts phase-04 phase-04 matched —
tests/unit/renderPlan.test.ts phase-04 phase-04 matched —
tests/unit/reviewHandoffContent.test.ts phase-02 phase-02 matched —
tests/unit/schemasPackage/currentShapes.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
tests/unit/schemasPackage/documents.ts phase-04 phase-04 matched —
tests/unit/schemasPackage/exports.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
tests/unit/schemasPackage/frozenHistory.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
tests/unit/schemasPackage/jsonSchemas.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
tests/unit/schemasPackage/parity.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04
tests/unit/schemasPackage/repositoryFormats.test.ts phase-04 (optional) phase-04 optional-touched optional in: phase-04

Global unplanned changes

None.

Global missing planned changes

None.

Global review attention points

Deviations 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

  • objective: delivered. Per-variant plan frontmatter, ALLOWED_KEYS, and readSourceSpec returning completesSpec are all in the handoff. The refusal wording for all three cases is documented.
  • excluded-scope: respected. No changes to creation, run completion or docs.
  • files: matched. Only the optional frontmatter.ts was touched.
  • tests: unit and integration test files are all touched, as planned.
  • boundaries: respected. readSourceSpec returns the none and spec variants.
  • commit: not verifiable here.
  • handoff: covers every expected item.

phase-02 — run completion

  • objective: delivered. keptSpec replaces skippedSpec, with renderSourceSpecOutcome and SOURCE_SPEC_OUTCOME_FILENAME. executePlan writes the fragment, the handoff renders ## Source spec, and the CLI prints the kept line.
  • excluded-scope: respected.
  • files: only optional files were touched (loadReviewHandoffInputs.ts, reviewHandoff.test.ts).
  • tests: planned test files were touched.
  • boundaries: respected. The fragment is the bridge, and buildReviewHandoffContent owns the heading.
  • commit: not verifiable here.
  • handoff: complete. It notes that the buildReviewHandoffContent signature changed to an extras object.

phase-03 — creation flags

  • objective: delivered. resolveCompletesSpec is pure, resolveArtifactTarget applies it, planSkeleton emits the key, and the flags are registered on artifact new plan only. The generated docs were regenerated, not hand-edited.
  • excluded-scope: respected.
  • files: persistedProducer.test.ts was touched beyond the phase list. It is an optional file in phase 4. The handoff justifies it by the test:type failure.
  • tests: planned test files were touched.
  • boundaries: respected.
  • commit: not verifiable here.
  • handoff: complete.

phase-04 — plan document

  • objective: delivered. The 0.17.0 module is frozen and listed in releases. completesSpec is required in the document. next.schema.json is created and CURRENT_SHAPES points to next. The authoring override and the prompt bullet are in place.
  • excluded-scope: respected. phax-plan.json and the repo's own sidecars were untouched.
  • files: approvalRecordMerge.test.ts was an extra touch. The handoff justifies it: the pre-schema fixture could no longer be authored. packages/schemas/src/index.ts was touched as an optional file.
  • tests: planned tests were touched, and the bridge-parity tests were kept meaningful.
  • boundaries: respected.
  • commit: not verifiable here.
  • handoff: complete.

phase-05 — docs and migration

  • objective: delivered. SKILL.md, README and NEXT_STEPS were updated, and the docs test was created.
  • excluded-scope: respected. This run's plan file has no completes-spec key (confirmed by search). Generated blocks were not edited.
  • files: matched.
  • tests: completesSpecDocs.test.ts is present.
  • boundaries: n/a.
  • commit: the subject docs: teach completes-spec and close the multi-plan follow-up matches the recent log.
  • handoff: covers the live-plan check and the key decisions.

Unplanned-change ledger

  • tests/integration/approvalRecordMerge.test.ts in phase-04. A fixture was adapted; justified.
  • tests/integration/persistedProducer.test.ts in 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

  • Older headless plan sidecars (0.17.0–0.19.x) now read as invalid. This is an accepted loss, documented in the README troubleshooting note.
  • Commit subjects and bodies for phases 1–4 were not verified here.

Phase details

phase-01 — Plan frontmatter carries completes-spec beside a source spec

File reconciliation

PHAX File Reconciliation

Planned to edit

  • src/schemas/artifactFrontmatter.ts
  • src/domain/artifact/document.ts
  • src/domain/artifact/lineage.ts
  • tests/unit/artifact/frontmatter.test.ts
  • tests/unit/artifact/document.test.ts
  • tests/unit/artifact/lineage.test.ts
  • tests/integration/lintPlan.test.ts
  • tests/integration/artifactStatus.test.ts
  • tests/integration/planStaleness.test.ts
  • tests/integration/completeRunArtifacts.test.ts
  • tests/integration/runCarriesCompletion.test.ts
  • tests/integration/migrateApprovals.test.ts
  • tests/integration/approvalRecordMerge.test.ts
  • tests/integration/approvalLedgerRefusal.test.ts

Optional files touched

  • src/domain/artifact/frontmatter.ts

Summary: No deviations from the planned file lists.

Phase handoff

What was delivered

  • src/schemas/artifactFrontmatter.ts defines two schemas:
    • SpecLessPlanFrontmatterSchema: status, source-spec: null, approved?;
    • SpecBoundPlanFrontmatterSchema: status, source-spec: NonEmptyString, completes-spec: Boolean, approved?.
  • PlanFrontmatterSchema is now their union, and PlanFrontmatter is the union type.
  • decodePlanFrontmatter(value) is now a function, not a decodeUnknownEither constant. It returns Either<PlanFrontmatter, ParseError>.
  • SourceSpecDeclaration (src/domain/artifact/lineage.ts) is { kind: "spec"; path; completesSpec: boolean } | { kind: "none" }. readSourceSpec fills completesSpec.
  • ALLOWED_KEYS.plan (src/domain/artifact/document.ts) reads status, source-spec, completes-spec (required with a source spec, absent without), approved.

Key decisions and why

  • decodePlanFrontmatter picks a variant from source-spec before 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.
  • The refusal wording lives in artifactFrontmatter.ts. Each refusal surfaces as FrontmatterProblem{kind:"schema"} and then ArtifactValidationError (exit 12):
    • missing: completes-spec: is missing — … (a missingMessage annotation);
    • non-boolean: completes-spec: must be true or false, actual "yes" — … (a message annotation). Bare YAML yes decodes as the string "yes";
    • beside null: completes-spec: is inconsistent with source-spec: null — … remove the key. This is a hand-built ParseResult.Unexpected, because the default excess-key message cannot say "inconsistent".
  • decodeArtifactFrontmatter is overloaded: "plan" returns PlanFrontmatter, and "spec" returns SpecFrontmatter. readSourceSpec narrows on source-spec === null with no cast.

Exact locations (file paths and exported names)

  • src/domain/artifact/lineage.ts — SourceSpecDeclaration, readSourceSpec
  • src/schemas/artifactFrontmatter.ts — PlanFrontmatter, PlanFrontmatterSchema, SpecLessPlanFrontmatterSchema, SpecBoundPlanFrontmatterSchema, decodePlanFrontmatter
  • src/domain/artifact/frontmatter.ts — decodeArtifactFrontmatter (overloaded)

What the next phase needs to know

  • Until phase 3, phax artifact new plan --spec writes a skeleton (planSkeleton in createArtifact.ts) without completes-spec, and validation refuses it. This is expected.
  • These fixture helpers emit completes-spec: true after a non-null source-spec:
    • planMd in the integration tests completeRunArtifacts, runCarriesCompletion, migrateApprovals, approvalRecordMerge, approvalLedgerRefusal, artifactStatus and planStaleness;
    • deterministicPlanMd in planStaleness;
    • frontmatterWithSpec in lintPlan;
    • planFm in the document and lineage unit tests;
    • PLAN_DOC in the frontmatter unit test.
  • Phase 2 must add a way to emit false to the completeRunArtifacts/runCarriesCompletion helpers. lintPlan's frontmatterWithSpec and planFm in the document and lineage unit tests already take a value.
  • fingerprintSource is unchanged, so completes-spec is fingerprinted. A tested flip reports self-changed.
  • tests/unit/renderPlan.test.ts:198 builds a keyless frontmatter but never validates it. It is left for phase 4.
  • No file-plan deviations. Of the optional files, only src/domain/artifact/frontmatter.ts was edited.

phase-02 — Run completion honours completes-spec and reports the spec outcome

File reconciliation

PHAX File Reconciliation

Planned to edit

  • src/app/completeRunArtifacts.ts
  • src/app/executePlan.ts
  • src/app/reviewHandoff.ts
  • src/app/publishRun.ts
  • src/cli/commands/run.ts
  • tests/integration/completeRunArtifacts.test.ts
  • tests/integration/runCarriesCompletion.test.ts
  • tests/unit/cli/run.test.ts
  • tests/unit/reviewHandoffContent.test.ts
  • tests/integration/publishRun.test.ts

Optional files touched

  • src/app/loadReviewHandoffInputs.ts
  • tests/integration/reviewHandoff.test.ts

Summary: No deviations from the planned file lists.

Phase handoff

What was delivered

  • RunCompletionReport (src/app/completeRunArtifacts.ts) now carries keptSpec?: RunCompletionKeptSpec in place of skippedSpec. The type is { reason: "blocked"; path; blockedBy } | { reason: "not-completing"; path }.
  • completeInWorktree returns keptSpec: { reason: "not-completing" } for a plan with completes-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. With true, the existing path runs unchanged.
  • renderSourceSpecOutcome(report): string | undefined and SOURCE_SPEC_OUTCOME_FILENAME = "source-spec-outcome.md" are exported from the same module.
  • loadSourceSpecOutcome(info) (src/app/loadReviewHandoffInputs.ts) reads the fragment from info.runPath, or returns undefined when it is absent. ReviewHandoffInputs gains sourceSpecOutcomeMd: string | undefined.
  • The handoff renders a ## Source spec section right after ## Run summary when the fragment exists, and no section otherwise.

Key decisions and why

  • buildReviewHandoffContent's fifth parameter is now extras: ReviewHandoffExtras = {} ({ complianceReviewMd?, sourceSpecOutcomeMd? }), replacing the positional complianceReviewMd?.
  • executePlan writes the fragment (${outcome}\n) through FileSystem.writeAtomic. The write sits inside the planRepoRelPath branch, right after completeRunArtifacts succeeds and before the FinalReviewOpened dispatch. Nothing is written when the renderer returns undefined (loose plan, spec-less plan, spec in Draft or Abandoned).
  • Handoff wording (one line, paths in backticks):
    • completed: `<archive path>` — completed on this branch (<7-char hash>);
    • re-entry: `<archive path>` — already complete;
    • blocked: `<path>` — kept: live plans remain (<plan>, <status>; ...);
    • not-completing: `<path>` — kept: this plan does not complete it (completes-spec: false).
  • CLI kept lines:
    • blocked, unchanged: ○ spec <path> kept: non-terminal dependent plans remain, plus one indented blocker line per plan;
    • new: ○ 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, completeRunArtifacts
  • src/app/reviewHandoff.ts — buildReviewHandoffContent, ReviewHandoffExtras, generateReviewHandoff
  • src/app/loadReviewHandoffInputs.ts — loadSourceSpecOutcome, ReviewHandoffInputs
  • src/cli/commands/run.ts — renderArtifactCompletions

What the next phase needs to know

  • The planMd helpers in tests/integration/completeRunArtifacts.test.ts and runCarriesCompletion.test.ts take a third completesSpec = true argument. seedWorktreeArtifacts takes a fourth.
  • publishRun reads the fragment through loadReviewHandoffInputs. generateReviewHandoff (first generation and phax review-handoff) reads it through loadSourceSpecOutcome.
  • Phase 3 still owns plan creation: artifact new plan --spec writes a skeleton that validation refuses.
  • File deviations: none. Of the optional files, only src/app/loadReviewHandoffInputs.ts and tests/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

  • src/domain/artifact/lineage.ts
  • src/app/createArtifact.ts
  • src/app/authorArtifact.ts
  • src/cli/commands/artifact.ts
  • src/cli/cliDocs.ts
  • phax.usage.kdl
  • docs/cli/reference.md
  • README.md
  • tests/unit/artifact/lineage.test.ts
  • tests/integration/createArtifact.test.ts
  • tests/integration/authorArtifact.test.ts
  • tests/unit/cli/artifact.test.ts
  • tests/integration/artifactNewHeadlessCommand.test.ts

Unplanned files edited

Deviation — agent must explain in phase-handoff.md under "What the next phase needs to know".

  • tests/integration/persistedProducer.test.ts

Summary: Deviations detected — see sections above.

Phase handoff

What was delivered

  • resolveCompletesSpec(flags: CompletesSpecFlags): Either<boolean | null, string> in src/domain/artifact/lineage.ts is the pure pairing rule for --spec/--last/--not-last.
  • resolveArtifactTarget applies the rule for a plan before any filesystem read and fails with ArtifactCreationError (exit 12). The interactive and headless paths share it.
  • ArtifactTarget.completesSpec: boolean | null, CreateArtifactResult.completesSpec, PlanLineage and targetLineage(target) are new in src/app/createArtifact.ts.
  • planSkeleton(lineage: PlanLineage | null) writes completes-spec: <bool> on the line after source-spec only when a spec is bound.
  • artifact new plan registers --last and --not-last. phax.usage.kdl, docs/cli/reference.md and the README's generated CLI block were regenerated with pnpm gen:usage-spec and pnpm docs:cli, not hand-edited.

Key decisions and why

  • Refusal messages:
    • --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)
  • The pairing check runs first in resolveArtifactTarget, before the slug check, so a bad pair refuses without touching the filesystem.
  • A spec ignores completion, and completesSpec is always null for it.
  • runCreateArtifact(kind, slug, specArg, completion, out) and runCreateArtifactHeadless(kind, slug, specArg, completion, opts, out, deps) take a new positional completion: CompletionFlags. new spec passes { last: false, notLast: false }.
  • Confirmation line: created <path> (Draft, source-spec <spec>, completes-spec <true|false>). Without a spec it stays created <path> (Draft, source-spec null).
  • exitCodeForAuthoringError already maps ArtifactCreationError to 12, so runLayers.ts is unchanged.

Exact locations (file paths and exported names)

  • src/domain/artifact/lineage.ts — resolveCompletesSpec, CompletesSpecFlags
  • src/app/createArtifact.ts — CompletionFlags, ArtifactTargetInput.completion, CreateArtifactInput.completion, ArtifactTarget.completesSpec, CreateArtifactResult.completesSpec, PlanLineage, planSkeleton, targetLineage
  • src/app/authorArtifact.ts — AuthorArtifactInput.completion; renderArtifact now takes the ArtifactTarget
  • src/cli/commands/artifact.ts — runCreateArtifact, runCreateArtifactHeadless

What the next phase needs to know

  • Phase 4 sets the sidecar's completesSpec from target.completesSpec in runAuthoringSession, next to the existing sourceSpec override. The plan document is unchanged here, so headless sidecars do not yet carry completesSpec.
  • buildAuthoringPrompt does not receive completesSpec yet; phase 4 adds it.
  • Test helpers NO_FLAGS / LAST / NOT_LAST are local constants in authorArtifact.test.ts, createArtifact.test.ts and tests/unit/cli/artifact.test.ts.
  • Deviation: tests/integration/persistedProducer.test.ts (not listed for this phase) gained the required completion field in its authorArtifact inputs, because the type change made it fail test:type.
  • src/cli/commands/runLayers.ts, src/cli/program.ts, usageOutput.test.ts and cliErrors.test.ts (optional) were not touched.

phase-04 — The plan document mirrors completesSpec

File reconciliation

PHAX File Reconciliation

Planned to create

  • src/schemas/history/plan-document/0.17.0.ts
  • packages/schemas/snapshots/plan-document/next.schema.json

Planned to edit

  • src/schemas/planDocument.ts
  • src/schemas/persisted.ts
  • src/app/authorArtifact.ts
  • src/domain/authoring/prompt.ts
  • packages/schemas/src/formats/repository.ts
  • packages/schemas/src/generated/index.ts
  • packages/schemas/history.lock.json
  • tests/unit/planDocument.test.ts
  • tests/unit/renderPlan.test.ts
  • tests/unit/artifact/sidecar.test.ts
  • tests/unit/authoringPrompt.test.ts
  • tests/integration/authorArtifact.test.ts
  • tests/unit/schemasPackage/documents.ts
  • tests/type/schemasPackage.ts

Optional files touched

  • packages/schemas/src/index.ts
  • tests/unit/persisted.test.ts
  • tests/unit/schemasPackage/currentShapes.test.ts
  • tests/unit/schemasPackage/parity.test.ts
  • tests/unit/schemasPackage/exports.test.ts
  • tests/unit/schemasPackage/repositoryFormats.test.ts
  • tests/unit/schemasPackage/frozenHistory.test.ts
  • tests/unit/schemasPackage/jsonSchemas.test.ts
  • tests/unit/architecturalGuards.test.ts
  • tests/integration/persistedProducer.test.ts

Unplanned files edited

Deviation — agent must explain in phase-handoff.md under "What the next phase needs to know".

  • tests/integration/approvalRecordMerge.test.ts

Summary: Deviations detected — see sections above.

Phase handoff

What was delivered

  • src/schemas/history/plan-document/0.17.0.ts is the frozen copy of the plan-document file shape from before completesSpec. It is listed in planDocumentFormat.releases, pinned in history.lock.json and re-exported as PlanDocumentV0_17_0Schema / PlanDocumentV0_17_0.
  • PlanDocumentSchema and PlanDocumentFileSchema require completesSpec: a boolean beside a sourceSpec path, null beside sourceSpec: null.
  • packages/schemas/snapshots/plan-document/next.schema.json exists, and CURRENT_SHAPES["plan-document"] is next.
  • Headless authoring forces sourceSpec and completesSpec from --spec/--last/--not-last (targetLineage(target)). The prompt says Set `completesSpec` to `true|false` or to null: this plan has no source spec.

Key decisions and why

  • completesSpec is a type-guard refinement over the struct, with the message completesSpec is a boolean beside a sourceSpec path and null beside sourceSpec: null. The JSON Schema keeps completesSpec in required and adds allOf: [{ oneOf: [...] }] for the two legal pairs. A bare oneOf annotation would make Effect replace the whole struct.
  • toLatestPlanDocument upgrades older shapes with null beside no spec and with UNKNOWN beside a spec path, never an invented boolean.
  • readPlanDocumentFile:
    • a spec-less pre-schema sidecar steps to null;
    • beside a spec path it is refused with lacks completesSpec, which phax needs — not supported;
    • a $schema sidecar without the field fails with completesSpec: is missing.

Exact locations (file paths and exported names)

  • src/schemas/planDocument.ts: PlanDocumentLineage, PlanDocument (a correlated union), PlanDocumentSchema, PlanDocumentFileSchema
  • src/schemas/history/plan-document/0.17.0.ts: PlanDocumentV0_17_0Schema, PlanDocumentV0_17_0, decodePlanDocumentV0_17_0
  • packages/schemas/src/formats/repository.ts: LatestPlanDocument, toLatestPlanDocument, PlanDocumentShapes (adds "0.17.0")
  • src/domain/authoring/prompt.ts: AuthoringPromptInput.completesSpec
  • tests/unit/schemasPackage/documents.ts: latestPreSchema(id)

What the next phase needs to know

  • The shared pre-schema plan-document fixture is now spec-less (sourceSpec: null), so phax's bridge and the package agree on it.
  • Deviation: 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 $schema shape with completesSpec: null.
  • Known package behaviour: a document at the package's own release that next rejects is re-read by the 0.17.0 decoder, whose violation is the one reported. parity.test.ts records this as releasedShapePath. defineFormat is unchanged.
  • No docs/plans sidecar was touched. Phase 5 adds completesSpec to the skill's §Headless authoring.

phase-05 — Docs and live-plan migration for completes-spec

File reconciliation

PHAX File Reconciliation

Planned to create

  • tests/unit/completesSpecDocs.test.ts

Planned to edit

  • README.md
  • .claude/skills/phax-planning/SKILL.md
  • NEXT_STEPS.md

Summary: No deviations from the planned file lists.

Phase handoff

What was delivered

  • .claude/skills/phax-planning/SKILL.md now defines completes-spec in §Plan frontmatter block: the example block, a bullet, and the --last|--not-last creation sentence. §Headless authoring lists completesSpec.
  • README.md states that a run completes its spec only when the plan says completes-spec: true and no other live plan needs it. The --spec examples 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.md ticks the follow-up "A run completes its source spec even when more plans are to come". The artifact-decide revert note is dropped.
  • tests/unit/completesSpecDocs.test.ts asserts the skill's key definition, the absent-when-null rule, the "every plan except the last says false" rule, the creation flags, and --not-last in the README.

Key decisions and why

  • The docs test lowercases and collapses whitespace before matching the "every plan of a spec except the last says false" phrase. The skill hard-wraps lines, and the phrase starts a sentence.
  • The Troubleshooting note also says older headless .json sidecars lack completesSpec and 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 authoring
  • README.md — "Create them" (multi-plan example), lifecycle/Run/Approve sentences, Troubleshooting
  • NEXT_STEPS.md — §Small follow-ups, artifact-decide entry
  • tests/unit/completesSpecDocs.test.ts — docs assertions (no exports)

What the next phase needs to know

  • This is the last phase.
  • Live-plan check:
    • docs/plans/2606291247-smolvm-isolation-spike-plan.md has source-spec: null, so it needs no key.
    • This run's plan docs/plans/2610061202-completes-spec-plan.md was left without completes-spec on purpose. The installed phax 0.19.0 refuses unknown frontmatter keys.
  • Needs a human decision: none.
  • No deviations from the file plan. The generated CLI block, phax.usage.kdl and docs/cli/reference.md were not edited, and no archived plan or sidecar was touched.
  • pnpm check:full passes.

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).
@lbdremy
lbdremy merged commit 8d4167c into main Oct 7, 2026
2 checks passed
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