Close
You are executing formal closure, not just marking work done.
Inputs:
- completed, audited, reconciled implementation phases
- the reconciliation review (RV) with every finding terminal and the
## Reconciliation Outcome recorded (see /reconcile)
- the governing slice id
Process
Pre-check:
- Phase exit criteria (
EX-) and verification (VT-) are met. Confirm the
rollup: doctrine slice list should show the slice as X/X complete with
no !N blocked, no ?N anomalous, no — untracked.
/reconcile is done (via /audit → /reconcile): the RV ledger is resolved,
every governance/spec finding is dispositioned, and the RV carries a
## Reconciliation Outcome section. If the reconciliation brief was empty
(no-op), the outcome confirms that explicitly.
- Durable findings from the slice are harvested per
harvest.md before
closure — or consciously rejected. A clean slice may harvest nothing;
the conscious-rejection gate is what closure checks.
doctrine check gate is green.
Spec-coherence gate — confirm reconciliation is complete before done:
Before the terminal transition, verify every item from the audit's
reconciliation brief is resolved through one of four paths:
- REV done — governance/spec items covered by a
done REV
(revision status REV-N done). The REV rationale (revision-NNN.md)
carries the reconcile narrative.
- Withdrawn — finding withdrawn in the RV with rationale.
- Tolerated — finding tolerated in the RV with rationale.
- Escalated to design — slice transitioned back to
design via the
ADR-009 §1 back-edge (reconcile → design).
Additionally:
- Every per-slice direct-edit item is applied to
design.md /
slice-NNN.md and recorded in the ## Reconciliation Outcome.
- The RV ledger is resolved (
done · await=none). A zero-finding review
is terminal for close purposes whatever its derived status string says —
the absence of unresolved blockers is what gates the transition (ADR-007
D-C9b), never the status string (D7: a display summary, never a gate).
The string itself is unsettled for the empty ledger — D-C8 fixes it at
active, the engine currently answers done, and ISS-314 tracks the
gap. Do not raise a token finding to force it terminal.
- The reconcile outcome is recorded (REV rationale and/or RV
## Reconciliation Outcome).
No free-floating "rejected" disposition is permitted — every finding
must land in one of the terminal states above.
If any item is unresolved, refuse close and return to /reconcile.
(The structural closure seam is the mechanical backstop; this gate is the
substantive check — the verb enforces; the skill verifies.)
Commit cleanly: land .doctrine/** workflow artefacts in small, clean
conventional commits scoped with the slice id, rather than letting them
accumulate. Code and workflow edits go together or separately, whichever
commits cleanly first.
Stage the slug symlink. */new verbs lay out <kind>/NNN/ and a
sibling slug symlink <kind>/NNN-slug -> NNN (slice, review, revision, rec, …).
A path-scoped git add <kind>/NNN/ stages the dir but misses the symlink,
stranding it uncommitted. Stage both — git add <kind>/NNN* — or add the
symlink explicitly. Check git status for a dangling NNN-slug before the
final commit.
3a. Dispatched slice — integrate the admitted OID (post-audit only). If the
slice was driven by /dispatch, project the audited units now:
# 0. Resolve the trunk delivery ref once (config-driven; default refs/heads/main):
trunk=$(doctrine dispatch deliver-to)
# 1. Admit a close_target candidate (if not already done during /reconcile):
doctrine dispatch candidate create --slice <N> --label close-001 \
--role close_target --payload code --base "$trunk" \
--source refs/heads/review/<N>
doctrine dispatch candidate admit --slice <N> --role close_target \
--candidate refs/heads/candidate/<N>/close-001 --review RV-NNN
# 2. Project onto trunk (--trunk is REQUIRED; omitting it is a dry run):
doctrine dispatch sync --slice <N> --integrate --trunk "$trunk"
When a candidate workflow is active this targets the immutable admitted
close_target OID (and --edge the admitted review_surface OID) under a
fast-forward-only CAS row — never a raw phase/*/review/* tip or the
mutable candidate ref, and never a close-time merge. A moved trunk refuses
(admit a superseding close-target candidate on the new base). This is the
only place --integrate runs — never at /dispatch conclude, only here,
post-audit.
Verify (tree-true, ISS-030). After --integrate --trunk, both checks
below must pass — do not proceed to step 4 unless they do:
# (a) No phantom reverse-diff: the tracked working tree matches HEAD. A nonzero
# exit means integrate advanced the ref but desynced the live checkout — STOP.
git diff --quiet HEAD
# (b) The projected delta genuinely landed (not a silent dry-run): the committed
# journal's trunk row holds the planned tip; it must equal the trunk ref.
trunk=$(doctrine dispatch deliver-to)
planned=$(doctrine dispatch sync --slice <N> --show-journal-trunk-oid \
--trunk "$trunk")
git diff --quiet "$planned" "$trunk"
(a) is the ISS-030 detector — the whole tracked tree, not path-limited (a
phantom reverse-diff can span any file the slice projected, not just src/).
(b) reads the trunk row's planned_new_oid from the committed dispatch/<N>
journal — a tree-read, stable from this checkout (SL-121) — and diffs it against
the resolved trunk delivery ref (doctrine dispatch deliver-to, default
refs/heads/main); a difference means trunk does not hold the projected tip, so
integration did not land.
3b. Split lineage — the payload reached trunk out-of-band
(--record-integration). If the reviewed code already sits on trunk by a
sanctioned route the dispatch journal never recorded — an operator direct-land,
a manual --no-ff merge of the payload, an external integration — then
--integrate (step 3a) cannot run: it advances trunk fast-forward-only, but
a landed payload is an ancestor, not a descendant, of the trunk merge tip
(non-ff → refused). The close gate still refuses done ("dispatched but no
trunk row"). Record the earned row instead of hand-editing journal.toml:
trunk=$(doctrine dispatch deliver-to)
doctrine dispatch sync --slice <N> --record-integration --trunk "$trunk"
It resolves the earned trunk payload (phase-chain tip, or the admitted
close_target when a candidate workflow is active — never review/<N>, a
review surface SPEC-022 forbids as a trunk payload), asserts it is already an
ancestor of trunk, and commits a single Verified trunk row — mutating no
ref. Then slice status <N> done reads that row and passes. This is the
sanctioned replacement for the SL-190 hand-written journal row (SL-211 /
IMP-236); land the payload out-of-band as git merge --no-ff phase/<N>-NN (or
the admitted candidate), not review/<N>. Deep recovery detail:
doctrine memory show mem.pattern.dispatch.split-lineage-close-conflict-direct-land.
- Transition lifecycle: confirm the slice is in
reconcile (flip it with
doctrine slice status <id> reconcile if /audit didn't), then
doctrine slice status <id> done (<id> is the bare number, e.g. 40).
The closure seam enforces the order and refuses while an RV targeting the
slice carries an unresolved blocker. Re-run doctrine slice list and confirm
the ⚠ divergence marker is gone — authored status now agrees with the
rollup. (The terminal set is {done, abandoned}; closure here is the done
path — abandoning a slice is a separate decision, not this skill.)
- Close the originating backlog item: if a backlog item (ISS/IMP/CHR/RSK)
spawned this slice, transition it too —
doctrine backlog edit <ID> --status resolved --resolution fixed (or the resolution that fits). A closed
slice with its origin still open is hygiene debt.
- If a blocking finding remains unresolved, do not close — return to
/reconcile. If closure depends on tolerated drift with material
tradeoffs, /consult before normalising around it.
Drift discharge recipe
When slice status <id> done refuses with undischarged residual drift, each
flagged requirement needs an accept REC owned by the closing slice that
satisfies all three clauses:
- (a)
move = accept — an affirm, not revise/redesign.
- (b) a
[[status_delta]] naming the requirement whose to equals its
current authored status (the status the refusal prints).
- (c) the REC's
[[evidence_ref]] set ⊇ every distinct coverage key feeding
that requirement's composite, so fresh contradictory evidence cannot be excused
by a stale REC.
Author it, then hand-edit the two tables:
doctrine rec new --move accept --owning-slice SL-NNN --title "accept REQ-NNN"
# then hand-author the [[status_delta]] and [[evidence_ref]] tables
Full predicate, rationale, and a worked example:
doctrine memory show mem.pattern.doctrine.close-drift-discharge-rec.
Outcomes
- The slice is
done, with the rollup in agreement.
- The originating backlog item (if any) is resolved.
- Durable guidance is captured in memory or consciously rejected.
- The tree is clean and the workflow artefacts are committed.
1---2name: close3description: Use to formally close a slice once its phases are complete, audited, and reconciled — confirm the rollup, verify spec-coherence, harvest durable findings, reconcile lifecycle status, and land a clean final commit. Routed to from /reconcile.4---56# Close78You are executing formal closure, not just marking work done.910Inputs:1112- completed, audited, reconciled implementation phases13- the reconciliation review (RV) with every finding terminal and the14 `## Reconciliation Outcome` recorded (see `/reconcile`)15- the governing slice id1617## Process18191. **Pre-check:**20 - Phase exit criteria (`EX-`) and verification (`VT-`) are met. Confirm the21 rollup: `doctrine slice list` should show the slice as `X/X complete` with22 no `!N` blocked, no `?N` anomalous, no `—` untracked.23 - `/reconcile` is done (via `/audit → /reconcile`): the RV ledger is resolved,24 every governance/spec finding is dispositioned, and the RV carries a25 `## Reconciliation Outcome` section. If the reconciliation brief was empty26 (no-op), the outcome confirms that explicitly.27 - Durable findings from the slice are harvested per `harvest.md` before28 closure — or **consciously rejected**. A clean slice may harvest nothing;29 the conscious-rejection gate is what closure checks.30 - `doctrine check gate` is green.312. **Spec-coherence gate — confirm reconciliation is complete before `done`:**32 Before the terminal transition, verify every item from the audit's33 reconciliation brief is resolved through one of four paths:34 * **REV done** — governance/spec items covered by a `done` REV35 (`revision status REV-N done`). The REV rationale (`revision-NNN.md`)36 carries the reconcile narrative.37 * **Withdrawn** — finding withdrawn in the RV with rationale.38 * **Tolerated** — finding tolerated in the RV with rationale.39 * **Escalated to design** — slice transitioned back to `design` via the40 ADR-009 §1 back-edge (`reconcile → design`).41 Additionally:42 * Every per-slice direct-edit item is applied to `design.md` /43 `slice-NNN.md` and recorded in the `## Reconciliation Outcome`.44 * The RV ledger is resolved (`done · await=none`). **A zero-finding review45 is terminal for close purposes whatever its derived status string says —46 the absence of unresolved blockers is what gates the transition (ADR-00747 D-C9b), never the status string (D7: a display summary, never a gate).48 The string itself is unsettled for the empty ledger — D-C8 fixes it at49 `active`, the engine currently answers `done`, and `ISS-314` tracks the50 gap. Do not raise a token finding to force it terminal.**51 * The reconcile outcome is recorded (REV rationale and/or RV52 `## Reconciliation Outcome`).53 No free-floating "rejected" disposition is permitted — every finding54 must land in one of the terminal states above.55 If any item is unresolved, **refuse close** and return to `/reconcile`.56 (The structural closure seam is the mechanical backstop; this gate is the57 substantive check — the verb enforces; the skill verifies.)583. **Commit cleanly:** land `.doctrine/**` workflow artefacts in small, clean59 conventional commits scoped with the slice id, rather than letting them60 accumulate. Code and workflow edits go together or separately, whichever61 commits cleanly first.62 > **Stage the slug symlink.** `*/new` verbs lay out `<kind>/NNN/` **and** a63 > sibling slug symlink `<kind>/NNN-slug -> NNN` (slice, review, revision, rec, …).64 > A path-scoped `git add <kind>/NNN/` stages the dir but misses the symlink,65 > stranding it uncommitted. Stage both — `git add <kind>/NNN*` — or add the66 > symlink explicitly. Check `git status` for a dangling `NNN-slug` before the67 > final commit.683a. **Dispatched slice — integrate the admitted OID (post-audit only).** If the69 slice was driven by `/dispatch`, project the audited units now:7071 ```bash72 # 0. Resolve the trunk delivery ref once (config-driven; default refs/heads/main):73 trunk=$(doctrine dispatch deliver-to)7475 # 1. Admit a close_target candidate (if not already done during /reconcile):76 doctrine dispatch candidate create --slice <N> --label close-001 \77 --role close_target --payload code --base "$trunk" \78 --source refs/heads/review/<N>79 doctrine dispatch candidate admit --slice <N> --role close_target \80 --candidate refs/heads/candidate/<N>/close-001 --review RV-NNN8182 # 2. Project onto trunk (--trunk is REQUIRED; omitting it is a dry run):83 doctrine dispatch sync --slice <N> --integrate --trunk "$trunk"84 ```8586 When a candidate workflow is active this targets the immutable **admitted87 `close_target` OID** (and `--edge` the admitted `review_surface` OID) under a88 fast-forward-only CAS row — **never a raw `phase/*`/`review/*` tip or the89 mutable candidate ref**, and never a close-time merge. A moved trunk refuses90 (admit a superseding close-target candidate on the new base). This is the91 **only** place `--integrate` runs — never at `/dispatch` conclude, only here,92 post-audit.9394 **Verify (tree-true, ISS-030).** After `--integrate --trunk`, both checks95 below must pass — do **not** proceed to step 4 unless they do:96 ```bash97 # (a) No phantom reverse-diff: the tracked working tree matches HEAD. A nonzero98 # exit means integrate advanced the ref but desynced the live checkout — STOP.99 git diff --quiet HEAD100101 # (b) The projected delta genuinely landed (not a silent dry-run): the committed102 # journal's trunk row holds the planned tip; it must equal the trunk ref.103 trunk=$(doctrine dispatch deliver-to)104 planned=$(doctrine dispatch sync --slice <N> --show-journal-trunk-oid \105 --trunk "$trunk")106 git diff --quiet "$planned" "$trunk"107 ```108 (a) is the ISS-030 detector — the **whole tracked tree**, not path-limited (a109 phantom reverse-diff can span any file the slice projected, not just `src/`).110 (b) reads the trunk row's `planned_new_oid` from the committed `dispatch/<N>`111 journal — a tree-read, stable from this checkout (SL-121) — and diffs it against112 the resolved trunk delivery ref (`doctrine dispatch deliver-to`, default113 `refs/heads/main`); a difference means trunk does not hold the projected tip, so114 integration did not land.1151163b. **Split lineage — the payload reached trunk out-of-band117 (`--record-integration`).** If the reviewed code already sits on trunk by a118 sanctioned route the dispatch journal never recorded — an operator direct-land,119 a manual `--no-ff` merge of the payload, an external integration — then120 `--integrate` (step 3a) **cannot** run: it advances trunk fast-forward-only, but121 a landed payload is an *ancestor*, not a descendant, of the trunk merge tip122 (non-ff → refused). The close gate still refuses `done` ("dispatched but no123 trunk row"). Record the earned row instead of hand-editing `journal.toml`:124125 ```bash126 trunk=$(doctrine dispatch deliver-to)127 doctrine dispatch sync --slice <N> --record-integration --trunk "$trunk"128 ```129130 It resolves the earned trunk payload (phase-chain tip, or the admitted131 `close_target` when a candidate workflow is active — **never** `review/<N>`, a132 review surface SPEC-022 forbids as a trunk payload), asserts it is **already an133 ancestor** of trunk, and commits a single **Verified** trunk row — mutating no134 ref. Then `slice status <N> done` reads that row and passes. This is the135 sanctioned replacement for the SL-190 hand-written journal row (SL-211 /136 IMP-236); land the payload out-of-band as `git merge --no-ff phase/<N>-NN` (or137 the admitted candidate), not `review/<N>`. Deep recovery detail:138 `doctrine memory show mem.pattern.dispatch.split-lineage-close-conflict-direct-land`.1391404. **Transition lifecycle:** confirm the slice is in `reconcile` (flip it with141 `doctrine slice status <id> reconcile` if `/audit` didn't), then142 `doctrine slice status <id> done` (`<id>` is the bare number, e.g. `40`).143 The closure seam enforces the order and refuses while an RV targeting the144 slice carries an unresolved blocker. Re-run `doctrine slice list` and confirm145 the `⚠` divergence marker is gone — authored status now agrees with the146 rollup. (The terminal set is `{done, abandoned}`; closure here is the `done`147 path — abandoning a slice is a separate decision, not this skill.)1485. **Close the originating backlog item:** if a backlog item (ISS/IMP/CHR/RSK)149 spawned this slice, transition it too — `doctrine backlog edit <ID>150 --status resolved --resolution fixed` (or the resolution that fits). A closed151 slice with its origin still open is hygiene debt.1526. If a blocking finding remains unresolved, do **not** close — return to153 `/reconcile`. If closure depends on tolerated drift with material154 tradeoffs, `/consult` before normalising around it.155156### Drift discharge recipe157158When `slice status <id> done` refuses with *undischarged residual drift*, each159flagged requirement needs an `accept` REC **owned by the closing slice** that160satisfies all three clauses:161162- **(a)** `move = accept` — an affirm, not `revise`/`redesign`.163- **(b)** a `[[status_delta]]` naming the requirement whose `to` equals its164 current authored status (the status the refusal prints).165- **(c)** the REC's `[[evidence_ref]]` set ⊇ every distinct coverage key feeding166 that requirement's composite, so fresh contradictory evidence cannot be excused167 by a stale REC.168169Author it, then hand-edit the two tables:170171```bash172doctrine rec new --move accept --owning-slice SL-NNN --title "accept REQ-NNN"173# then hand-author the [[status_delta]] and [[evidence_ref]] tables174```175176Full predicate, rationale, and a worked example:177`doctrine memory show mem.pattern.doctrine.close-drift-discharge-rec`.178179## Outcomes180181- The slice is `done`, with the rollup in agreement.182- The originating backlog item (if any) is resolved.183- Durable guidance is captured in memory or consciously rejected.184- The tree is clean and the workflow artefacts are committed.