@deftai/directive-content 0.110.0 → 0.112.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/QUICK-START.md +16 -14
  2. package/SKILL.md +13 -13
  3. package/Taskfile.yml +20 -15
  4. package/UPGRADING.md +64 -24
  5. package/commands.md +20 -18
  6. package/contracts/design-critique.md +36 -13
  7. package/conventions/machine-generated-banner.md +9 -9
  8. package/conventions/rule-ownership.json +3 -3
  9. package/docs/SUPPORT.md +43 -0
  10. package/docs/capabilities.md +110 -0
  11. package/docs/capability-overlay.json +197 -0
  12. package/docs/getting-started.md +131 -49
  13. package/docs/scope-provenance.md +2 -0
  14. package/main.md +22 -22
  15. package/package.json +1 -1
  16. package/packs/rules/rules-pack-0.1.json +1 -1
  17. package/packs/skills/skills-pack-0.1.json +9 -7
  18. package/scm/github.md +2 -0
  19. package/skills/deft-directive-design-critique/SKILL.md +10 -4
  20. package/skills/deft-directive-release/SKILL.md +22 -17
  21. package/skills/deft-directive-review-cycle/SKILL.md +52 -15
  22. package/skills/deft-directive-swarm/SKILL.md +8 -4
  23. package/skills/deft-directive-swarm/references/core-ops.md +1 -0
  24. package/skills/deft-directive-swarm/references/core-phase-0.md +2 -1
  25. package/skills/deft-directive-swarm/references/core-phase-3.md +8 -5
  26. package/skills/deft-directive-swarm/references/host-claude-code.md +3 -2
  27. package/skills/deft-directive-swarm/references/host-grok-build.md +19 -3
  28. package/skills/deft-directive-swarm/references/host-grokbot.md +151 -0
  29. package/tasks/docs.yml +19 -0
  30. package/tasks/migrate.yml +3 -1
  31. package/tasks/policy.yml +2 -2
  32. package/tasks/scm.yml +12 -1
  33. package/tasks/scope.yml +16 -25
  34. package/tasks/triage-actions.yml +3 -4
  35. package/tasks/triage-bootstrap.yml +2 -2
  36. package/tasks/triage-bulk.yml +6 -7
  37. package/tasks/triage-classify.yml +2 -2
  38. package/tasks/triage-queue.yml +2 -2
  39. package/tasks/triage-reconcile.yml +2 -2
  40. package/tasks/triage-scope-drift.yml +2 -2
  41. package/tasks/triage-scope.yml +2 -2
  42. package/tasks/triage-smoketest.yml +3 -3
  43. package/tasks/triage-subscribe.yml +2 -2
  44. package/tasks/triage-summary.yml +2 -2
  45. package/tasks/triage-welcome.yml +2 -2
  46. package/tasks/verify.yml +30 -1
  47. package/templates/PULL_REQUEST_TEMPLATE.md +12 -0
  48. package/templates/agent-prompt-preamble.md +6 -3
  49. package/templates/agents-entry.md +5 -5
  50. package/templates/design-critique-brief.md +2 -0
package/QUICK-START.md CHANGED
@@ -52,17 +52,20 @@ Run these deterministic checks, in order:
52
52
 
53
53
  ### 1b. Does `../AGENTS.md`'s managed section match the current template? Do referenced paths resolve?
54
54
 
55
+ Do **not** parse managed-section markers, byte-compare an attributed block against the template, or locate a hardcoded v2 managed-section marker. Detection is the four-state classifier (`current | stale | missing | absent`) from `deft agents:refresh --check` — the same plan `deft doctor` uses. v1/v2 markers are force-stale. Truncated or future markers are unreadable (do not append).
56
+
55
57
  Three checks here, in this order. The first match wins; later checks only run when earlier checks pass.
56
58
 
57
- 1. **Template-content byte comparison (Case G gate).** Locate the managed section in `../AGENTS.md` (the block bounded by the `<!-- deft:managed-section v2 -->` and `<!-- /deft:managed-section -->` markers). Compare those bytes against the current `./templates/agents-entry.md` rendered managed-section output.
58
- - ! If the managed section is **byte-different** from the current template render (or the markers are absent in `../AGENTS.md`), treat as **stale content** -- jump to Case G ("Stale AGENTS.md") in Step 2. Case G is the right remediation for byte-different staleness because the refresh actually rewrites the content.
59
- 2. **Install-path resolution (Case K gate -- #1046 PR-A).** When the managed section IS byte-current, parse the section for its install-path declaration (`Full guidelines: <root>/main.md`, e.g. `.deft/core/main.md` for the canonical install layout or `deft/main.md` for the legacy install layout). Verify that `../<root>/main.md` exists on disk.
60
- - ! If the managed section is **byte-identical** to the current template render BUT the declared install path does NOT resolve, jump to **Case K ("Install location mismatch")** in Step 2. Refreshing the managed section is a documented no-op when the content already matches -- Case K is a different failure class than Case G and demands a different remediation (#1046 finding #2).
59
+ 1. **Classifier (Case G gate).** Run `deft agents:refresh --check` (or `deft doctor`). If the CLI is not on PATH, run `npx @deftai/directive agents:refresh --check` or `npm i -g @deftai/directive` then the same verb. If the payload is missing, run `deft update`. Pre-canonical layout: frozen Go bridge (see UPGRADING.md / GitHub releases).
60
+ - ! If state is **stale**, **missing**, **absent**, or **unreadable**, treat as **stale content** -- jump to Case G ("Stale AGENTS.md") in Step 2. Case G remediates with the registered rewrite, not a hand-append.
61
+ - If state is **current**, continue to the install-path check below.
62
+ 2. **Install-path resolution (Case K gate -- #1046 PR-A).** When the classifier reports **current**, parse the section for its install-path declaration (`Full guidelines: <root>/main.md`, e.g. `.deft/core/main.md` for the canonical install layout or `deft/main.md` for the legacy install layout). Verify that `../<root>/main.md` exists on disk.
63
+ - ! If the classifier reports **current** BUT the declared install path does NOT resolve, jump to **Case K ("Install location mismatch")** in Step 2. Refreshing the managed section is a documented no-op when the content already matches -- Case K is a different failure class than Case G and demands a different remediation (#1046 finding #2).
61
64
  3. **Legacy skill-path resolution (v0.19 AGENTS.md backstop).** Parse `../AGENTS.md` for any token matching `deft/skills/<name>/SKILL.md` (the legacy v0.19 path shape) and verify the file exists under `./skills/<name>/SKILL.md` (relative to this QUICK-START.md).
62
65
  - ! If any referenced path does not exist on disk, treat `../AGENTS.md` as **stale** -- jump to Case G in Step 2.
63
66
  - If all referenced paths exist, continue to 1c.
64
67
 
65
- Priority ordering: Case G (byte-different content) always wins over Case K (install-path mismatch) because the refresh path is the higher-priority remediation -- when the template content has moved on, the refresh closes BOTH the content drift and any incidental install-path mismatch that the new content might re-introduce. Case K only fires when the content is byte-current AND the path is unresolved -- the exact "refresh would be a no-op" failure class issue #1046 documents.
68
+ Priority ordering: Case G (classifier not current) always wins over Case K (install-path mismatch) because the registered refresh is the higher-priority remediation -- when the template content has moved on, the refresh closes BOTH the content drift and any incidental install-path mismatch that the new content might re-introduce. Case K only fires when the classifier is current AND the path is unresolved -- the exact "refresh would be a no-op" failure class issue #1046 documents.
66
69
 
67
70
  **Big-jump joint check (Case G+H gate).** Before acting on ANY Case G routing above (a byte-different managed section, or an unresolved legacy skill path), first ALSO evaluate the 1c pre-cutover check below against `../`. ! If 1c ALSO holds (real pre-v0.20 `SPECIFICATION.md` / `PROJECT.md` present), the project is in the **joint big-jump state** where both the AGENTS.md refresh (Case G) and the pre-cutover migration (Case H) are due — jump to **Case G+H** (combined single-session remediation) in Step 2 instead of Case G. The combined path runs the refresh and the migration in one session and emits a single restart, avoiding the wasted Case G → restart → Case H round-trip. If 1c does not hold, route to Case G as usual.
68
71
 
@@ -91,16 +94,15 @@ Pick exactly one case from Step 1 and follow its instructions. Do not mix cases.
91
94
 
92
95
  ### Case F — No AGENTS.md (fresh install)
93
96
 
94
- 1. Read `./templates/agents-entry.md` (this directory).
95
- 2. Write that content to `../AGENTS.md`.
96
- 3. Tell the user: "✓ Created AGENTS.md at your project root."
97
- 4. Continue to Step 3.
97
+ 1. ! Run `deft agents:refresh` (the registered rewrite for classifier state `absent`). ⊗ Do not copy `templates/agents-entry.md` by hand. If the CLI is not on PATH: `npx @deftai/directive agents:refresh` or `npm i -g @deftai/directive`, then the same verb. Payload missing: `deft update`. Pre-canonical layout: frozen Go bridge (UPGRADING.md / GitHub releases).
98
+ 2. Tell the user: "✓ Created AGENTS.md at your project root."
99
+ 3. Continue to Step 3.
98
100
 
99
101
  ### Case G — Stale AGENTS.md (v0.19 → v0.20 upgrade)
100
102
 
101
- 1. Read `../AGENTS.md` and identify the **Deft-managed section** bounded by the `deft/main.md` sentinel marker.
102
- 2. If the `deft/main.md` sentinel is **absent**, treat the entire existing file as user-authored and do NOT rewrite it. Instead, read `./templates/agents-entry.md` and **append** its content to `../AGENTS.md` with two blank lines between the existing content and the appended block. This matches the idempotent append behavior documented in `setup.go::WriteAgentsMD` for brownfield projects with a pre-existing AGENTS.md.
103
- 3. If the `deft/main.md` sentinel is **present**, replace only the sentinel-bounded section with the current content of `./templates/agents-entry.md`. Preserve everything outside that region verbatim.
103
+ 1. Do not locate the `deft/main.md` sentinel, do not append `templates/agents-entry.md`, and do not hand-rewrite markers. QUICK-START is not a second parser.
104
+ 2. ! Run `deft agents:refresh`. That command is the four-state plan (`current | stale | missing | absent`; v1/v2 force-stale). Truncated close refuses to write a second section.
105
+ 3. If the CLI is not on PATH: `npx @deftai/directive agents:refresh` or `npm i -g @deftai/directive`, then the same verb. Payload missing or drifted: `deft update`. Pre-canonical layout: frozen Go bridge (UPGRADING.md / GitHub releases).
104
106
  4. Tell the user: "✓ Refreshed Deft-managed section of AGENTS.md. Your existing additions outside that region were preserved."
105
107
  5. ! Instruct the user: **"Framework updated. Start a new agent session to pick up the changes. The current session has stale context."** Do not continue past this instruction in the current session.
106
108
 
@@ -118,7 +120,7 @@ Reached only via the **Big-jump joint check** in 1b: the managed section in `../
118
120
 
119
121
  ! Run the two remediations in this exact order — **AGENTS.md refresh first, frozen migration guidance second** — then emit a **single** restart instruction at the very end:
120
122
 
121
- 1. **Refresh AGENTS.md first (Case G work).** Perform Case G steps 1-4 verbatim: identify the managed section, append when the sentinel is absent or byte-replace it when present, and preserve everything outside the managed region. ⊗ Do NOT emit the Case G step-5 restart instruction here — the combined path defers the single restart to step 3.
123
+ 1. **Refresh AGENTS.md first (Case G work).** Perform Case G steps 1-4: run `deft agents:refresh` (registered rewrite). Do not append. ⊗ Do NOT emit the Case G step-5 restart instruction here — the combined path defers the single restart to step 3.
122
124
  2. **Surface frozen migration path second (Case H work).** Perform Case H steps 1-3 verbatim: explain the v0.59.0 pinned migrator path (#2068), run `task migrate:preflight`, and point at UPGRADING.md. The operator (or a machine with v0.59.0 deposited) runs `task migrate:vbrief` outside the current npm deposit. ⊗ Do NOT perform Case H steps 4-5 until migration has completed on the pinned release and the operator has upgraded to current npm.
123
125
  3. **Single restart, exactly once.** Only after BOTH the refresh and the operator-confirmed migration + npm upgrade have completed, ! instruct the user EXACTLY ONCE: **"Framework updated and project migrated. Start a new agent session to pick up the changes. The current session has stale context."** ⊗ Do NOT emit a second restart instruction.
124
126
 
@@ -140,7 +142,7 @@ For the version-by-version context of a big jump, see the [big-jump triage entry
140
142
  The managed section in `../AGENTS.md` is byte-identical to the current `./templates/agents-entry.md` render, BUT the install path the managed section declares (e.g. `.deft/core/main.md`) does NOT resolve on disk. This is the failure class issue #1046 finding #2 documents: Case G's "refresh the managed section" prescription is a byte-for-byte no-op against the current template, so re-running just re-detects the same staleness next session.
141
143
 
142
144
  1. Tell the user (verbatim phrasing, naming the unresolved path): "AGENTS.md's managed section is byte-identical to the current template, but the install path it declares (`<declared-path>`) does NOT exist on disk. Refreshing the managed section would be a no-op -- Case G's remediation does not fix install-location mismatches."
143
- 2. ! Direct the user to run `task framework:doctor` (forthcoming in PR-B of the #1046 cohort -- the diagnostic + remediation surface that owns Case K's fix path) OR to manually verify that the install path AGENTS.md claims actually exists on disk. Until PR-B merges, the manual check is the operator's only path: confirm the framework is deposited at the path AGENTS.md declares, OR re-run the installer / relocator to deposit at that path, OR hand-edit AGENTS.md to point at the path where the framework actually lives.
145
+ 2. ! Direct the user to run `deft doctor` (the diagnostic + remediation surface that owns Case K's fix path) OR to manually verify that the install path AGENTS.md claims actually exists on disk. Confirm the framework is deposited at the path AGENTS.md declares, OR run `deft update` / `npx @deftai/directive update` to deposit at that path, OR hand-edit AGENTS.md to point at the path where the framework actually lives. Pre-canonical layout: frozen Go bridge (UPGRADING.md / GitHub releases).
144
146
  3. ⊗ Run a Case G refresh -- it is a documented no-op for Case K. The managed section already byte-matches the current template; refreshing the bytes back to the same bytes does not change which install path is declared.
145
147
  4. ! Instruct the user: **"Stop here. Do not continue to Step 3 until the install-path mismatch is resolved -- subsequent sessions will re-enter Case K until then."**
146
148
 
package/SKILL.md CHANGED
@@ -45,20 +45,20 @@ as your FIRST and ONLY response — no summary, no menu, no preamble:
45
45
 
46
46
  Then continue with `skills/deft-directive-setup/SKILL.md` Phase 1 for remaining questions.
47
47
 
48
- **If USER.md exists but `./vbrief/PROJECT-DEFINITION.vbrief.json` is missing**: Skip to
48
+ **If USER.md exists but `./xbrief/PROJECT-DEFINITION.xbrief.json` is missing**: Skip to
49
49
  `skills/deft-directive-setup/SKILL.md` Phase 2.
50
50
 
51
- **If USER.md and `./vbrief/PROJECT-DEFINITION.vbrief.json` both exist but no scope vBRIEFs in `./vbrief/` lifecycle folders**:
51
+ **If USER.md and `./xbrief/PROJECT-DEFINITION.xbrief.json` both exist but no scope xBRIEFs in `./xbrief/` lifecycle folders**:
52
52
  Skip to `skills/deft-directive-setup/SKILL.md` Phase 3. Start the specification interview
53
53
  imediately — ask what to build and features as the first question.
54
54
 
55
55
  ### ⊗ Project Root vs Framework Internals
56
56
 
57
- ! When checking for project-level files (`PROJECT-DEFINITION.vbrief.json`, scope vBRIEFs,
58
- `PRD.md`), ONLY look at `./vbrief/` and its lifecycle subdirectories.
57
+ ! When checking for project-level files (`PROJECT-DEFINITION.xbrief.json`, scope xBRIEFs,
58
+ `PRD.md`), ONLY look at `./xbrief/` and its lifecycle subdirectories.
59
59
 
60
- - ! `./vbrief/PROJECT-DEFINITION.vbrief.json` — the user's project config
61
- - ! `./vbrief/proposed/`, `./vbrief/pending/`, `./vbrief/active/`, `./vbrief/completed/` — scope vBRIEFs in lifecycle folders
60
+ - ! `./xbrief/PROJECT-DEFINITION.xbrief.json` — the user's project config
61
+ - ! `./xbrief/proposed/`, `./xbrief/pending/`, `./xbrief/active/`, `./xbrief/completed/` — scope xBRIEFs in lifecycle folders
62
62
  - ⊗ Count ANY file inside `.deft/core/` (canonical) or `./deft/` (legacy pre-v0.27) as a project-level artifact — those are
63
63
  framework-internal (e.g. `.deft/core/PROJECT.md`, `.deft/core/specs/`, `.deft/core/templates/`
64
64
  are all part of the framework, NOT the user's project)
@@ -75,7 +75,7 @@ USER.md has two sections with different precedence:
75
75
  ```
76
76
  USER.md Personal ← HIGHEST (name, custom rules — always wins)
77
77
 
78
- PROJECT-DEFINITION.vbrief.json ← Project-specific (strategy, coverage, languages, tech stack)
78
+ PROJECT-DEFINITION.xbrief.json ← Project-specific (strategy, coverage, languages, tech stack)
79
79
 
80
80
  USER.md Defaults ← Fallback defaults (used when PROJECT-DEFINITION doesn't specify)
81
81
 
@@ -85,11 +85,11 @@ USER.md Defaults ← Fallback defaults (used when PROJECT-DEFINITION doesn
85
85
 
86
86
  main.md ← General AI behavior
87
87
 
88
- scope vBRIEFs ← LOWEST precedence (requirements in lifecycle folders)
88
+ scope xBRIEFs ← LOWEST precedence (requirements in lifecycle folders)
89
89
  ```
90
90
 
91
91
  **IMPORTANT**: USER.md `Personal` section always wins. For project-scoped settings
92
- (strategy, coverage, languages), `PROJECT-DEFINITION.vbrief.json` overrides USER.md `Defaults`.
92
+ (strategy, coverage, languages), `PROJECT-DEFINITION.xbrief.json` overrides USER.md `Defaults`.
93
93
 
94
94
  ## File Reading Strategy (Lazy Loading)
95
95
 
@@ -97,7 +97,7 @@ scope vBRIEFs ← LOWEST precedence (requirements in lifecycle folders)
97
97
 
98
98
  1. **Always start with**: `.deft/core/main.md` (general guidelines)
99
99
  2. **Check for**: `~/.config/deft/USER.md` (personal overrides - highest precedence)
100
- 3. **Check for**: `./vbrief/PROJECT-DEFINITION.vbrief.json` (project-specific rules)
100
+ 3. **Check for**: `./xbrief/PROJECT-DEFINITION.xbrief.json` (project-specific rules)
101
101
  4. **Then read language-specific** only if working with that language:
102
102
  - `.deft/core/languages/python.md`
103
103
  - `.deft/core/languages/go.md`
@@ -128,8 +128,8 @@ See `.deft/core/tools/taskfile.md` for complete task standards and common comman
128
128
 
129
129
  **Spec-Driven Development (SDD)** for new features/projects:
130
130
  1. Run `skills/deft-directive-setup/SKILL.md` Phase 3 — sizing gate selects Light or Full path
131
- 2. Light: Interview → scope vBRIEFs in `vbrief/proposed/` (embedded requirements) → Implement
132
- 3. Full: Interview → rich narratives in `specification.vbrief.json` (approval gate) → scope vBRIEFs → Implement
131
+ 2. Light: Interview → scope xBRIEFs in `xbrief/proposed/` (embedded requirements) → Implement
132
+ 3. Full: Interview → rich narratives in `specification.xbrief.json` (approval gate) → scope xBRIEFs → Implement
133
133
 
134
134
  See `.deft/core/coding/testing.md` for complete testing standards.
135
135
 
@@ -167,7 +167,7 @@ task project:render # Project config → xbrief/PROJECT-DEFINITION.xbrief.
167
167
  1. **First time?** If USER.md doesn't exist, run `skills/deft-directive-setup/SKILL.md` Phase 1
168
168
  2. Read `.deft/core/main.md` (general guidelines)
169
169
  3. Read `~/.config/deft/USER.md` (personal preferences - highest precedence)
170
- 4. Read `./vbrief/PROJECT-DEFINITION.vbrief.json` (project rules)
170
+ 4. Read `./xbrief/PROJECT-DEFINITION.xbrief.json` (project rules)
171
171
  5. Run `task --list` to see available tasks
172
172
 
173
173
  See `.deft/core/main.md` for complete workflow details.
package/Taskfile.yml CHANGED
@@ -370,11 +370,11 @@ includes:
370
370
  packs:
371
371
  taskfile: ./tasks/packs.yml
372
372
  optional: true
373
- # Maintainer-only Python self-test lanes (#1813 contributor path / #2022
373
+ # Maintainer-only packaging lane (#1813 contributor path / #2022
374
374
  # Phase 2). `internal: true` hides `core:*` / `ci:*` from `task -l` and
375
375
  # blocks direct CLI invocation on consumer installs; only
376
376
  # `check:framework-source` below wires them as deps. Consumer `task check`
377
- # dispatches to `check:consumer` (TS / deft verbs — no uv/python).
377
+ # dispatches to `check:consumer` (TS / deft verbs).
378
378
  core:
379
379
  taskfile: ./tasks/core.yml
380
380
  optional: true
@@ -464,8 +464,15 @@ tasks:
464
464
  - verify-eval-health-relocation-framework-check
465
465
  - verify-eval-triggers-relocation-framework-check
466
466
  - vbrief:validate
467
- - codebase:validate-structure
467
+ - task: codebase:validate-structure
468
+ vars:
469
+ CLI_ARGS: "--enforce"
468
470
  - verify:codebase-map-fresh
471
+ - verify:spec-prd-fresh
472
+ # #4095: committed RULE-MAP freshness (byte-identical renderer output).
473
+ - docs:rule-map:check
474
+ # #4099: committed capability index freshness (overlay vs registries).
475
+ - docs:capability-map:check
469
476
  - verify-strategy-output
470
477
  # #2980 residual: fail-closed product raw-write inventory (allowlist primitives + temporary residual).
471
478
  - verify-contained-writes-enforce
@@ -796,18 +803,16 @@ tasks:
796
803
  # `triage-bootstrap`) -- a single shared `triage:` include namespace
797
804
  # is not supported by go-task v3 (two includes cannot share a key).
798
805
  # The aliases below provide the documented `task triage:<verb>`
799
- # user-facing surface that vBRIEFs / UPGRADING.md / scripts/triage_*.py
800
- # describe. Each alias delegates to the underlying namespaced task
801
- # and forwards `{{.CLI_ARGS}}` so flags (`--repo`, `--reason`, etc.)
802
- # reach the script. The inner tasks in each fragment are
806
+ # user-facing surface that xBRIEFs / UPGRADING.md describe. Each alias
807
+ # delegates to the underlying namespaced task and forwards
808
+ # `{{.CLI_ARGS}}` so flags (`--repo`, `--reason`, etc.) reach
809
+ # engine:invoke / packages/cli/dist. The inner tasks in each fragment are
803
810
  # `internal: true` so the fragment-namespace forms
804
811
  # (`triage-cache:cache`, `triage-actions:accept`, `triage-bulk:bulk-defer`,
805
812
  # `triage-bootstrap:bootstrap`) drop out of `task -l`; only the
806
813
  # documented `triage:*` aliases below appear in the listing. The
807
- # internal tasks remain CALLABLE for legacy invocations (e.g. recap
808
- # text in `scripts/triage_bootstrap.py` still prints the namespaced
809
- # forms, which continue to dispatch correctly via the fragment
810
- # include).
814
+ # internal tasks remain CALLABLE for legacy invocations via the fragment
815
+ # include.
811
816
  #
812
817
  # Aliases are inline at the root Taskfile rather than in a separate
813
818
  # fragment because they cross include namespaces and must exist
@@ -826,10 +831,10 @@ tasks:
826
831
  # categorized verb list grouped by role (Session-start, State verbs,
827
832
  # Read verbs, Lifecycle, Subscription mutation, Archive-rotation /
828
833
  # Promote-demote, Activate-complete, Reversibility, Decomposition).
829
- # The registry + renderer live in scripts/triage_help.py so every
830
- # verb is documented in one place. Each existing `triage:X` /
831
- # `scope:X` alias forwards `--help` to the same registry via the
832
- # `intercept_help` shim called from each verb script's `main()`.
834
+ # The registry + renderer live in
835
+ # packages/core/src/triage/help/registry-data.ts (edit in place; no
836
+ # generator). Each existing `triage:X` / `scope:X` alias forwards
837
+ # `--help` to the same registry via interceptHelp (handler + dispatch).
833
838
  #
834
839
  # Defined at the root Taskfile (not in a fragment) because the bare
835
840
  # target shares the `triage:` / `scope:` prefix with the documented
package/UPGRADING.md CHANGED
@@ -9,6 +9,20 @@ Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
9
9
 
10
10
  <!-- xbrief-backcompat-2111 -->
11
11
 
12
+ ## Current path
13
+
14
+ Ordinary upgrades use the three-command model. This section is the current recovery path; frozen version-to-version hops stay below as history and keep their existing `UPGRADING.md#` anchors.
15
+
16
+ | Your situation | Run this |
17
+ | --- | --- |
18
+ | Ordinary upgrade of an existing Directive project | `npm i -g @deftai/directive@latest`, then `directive update` in the project (`pnpm add -g @deftai/directive@latest` on pnpm) |
19
+ | Not sure, or something looks broken | `directive doctor` |
20
+ | First-time adoption, or a legacy / pre-v0.20 layout | `directive init` |
21
+
22
+ Walkthrough: [Canonical upgrade — npm](#canonical-upgrade--npm-v0551). Multi-version jumps: [Big-jump triage](#big-jump-triage--multi-version-upgrades-start-here). Corporate or mirrored registry: [Corporate or mirrored npm registry](#corporate-or-mirrored-npm-registry). Frozen hop 1: [Frozen pre-v0.20 document-model migration](#frozen-pre-v020-document-model-migration-2068).
23
+
24
+ ---
25
+
12
26
  ## Dual policy block recovery (#3609)
13
27
 
14
28
  - **Applies when:** PROJECT-DEFINITION contains both legacy bare `plan.policy` and namespaced `plan["x-directive/policy"]`. The namespaced block wins reads, so the bare block is shadowed even when one or more values match.
@@ -98,7 +112,8 @@ Install source of truth for this repo is **pnpm** (`packageManager` field; CI us
98
112
  | `*.vbrief.json` | `*.xbrief.json` |
99
113
  | `vBRIEF` / “scope vBRIEF” in guidance | `xBRIEF` / “scope xBRIEF” |
100
114
  | `x-vbrief/*` reference tokens | `x-xbrief/*` (and migrate-accepted legacy tokens) |
101
- | `task vbrief:*` / `migrate:vbrief` aliases | Prefer `task xbrief:*` / `deft migrate:xbrief`; keep `vbrief:*` only as deprecated aliases when required for back-compat |
115
+ | `task vbrief:*` aliases | Prefer `task xbrief:*`; keep `vbrief:*` only as deprecated aliases when required for back-compat |
116
+ | Frozen `task migrate:vbrief` (v0.59.0 hop 1 only) | Current hop 2 is `deft migrate:xbrief` / `task migrate:xbrief`. Frozen hop 1 is **not** a current alias of hop 2. |
102
117
  | Envelope keys `vBRIEFInfo` | `xBRIEFInfo` (legacy keys still read-accepted on unmigrated files) |
103
118
 
104
119
  **Why two names existed:** consumer layout and disk SoT moved to `xbrief/` while docs, glossary, and marketing still taught vBRIEF as current. That dual present-day naming is retired (#2907). Schema lineage, fixtures, migrate paths, changelog history, and archive prose may still say vBRIEF — always as **legacy/historical**, never as competing current guidance.
@@ -358,6 +373,8 @@ Non-native-skill harnesses (Codex CLI, OpenCode) report 0 B frontmatter; set `ha
358
373
 
359
374
  ## xBRIEF layout migration (#2034 / #2110)
360
375
 
376
+ This section is **hop 2** for projects already on vBRIEF 0.6 (`vbrief/` lifecycle). Already-current xBRIEF 0.8: skip. Flat pre-v0.20 root docs: start at hop 1 in [Frozen pre-v0.20 document-model migration](#frozen-pre-v020-document-model-migration-2068). ⊗ Pin v0.59.0 or run frozen `migrate:vbrief` for this hop.
377
+
361
378
  After upgrading to a release that ships the xbrief rename, convert legacy on-disk layout if `deft doctor` reports a `vbrief/` tree or `x-vbrief/` reference tokens:
362
379
 
363
380
  ```bash
@@ -424,44 +441,67 @@ import schema from "@deftai/directive-types/schemas/xbrief-core-0.8.schema.json"
424
441
 
425
442
  ### `deft migrate` vs pre-v0.20 document-model migration
426
443
 
427
- These commands are unrelated — do not confuse them:
444
+ These three commands are unrelated — do not confuse them:
428
445
 
429
- | Command | When to use | What it does |
430
- | --- | --- | --- |
431
- | `deft migrate` / `directive migrate` | Canonical-vendored `.deft/core/` deposit after npm upgrade (#1941) | Stamps `managed_by: npm` into the install manifest. Idempotent; never downloads payload. |
432
- | Pre-v0.20 document-model migration | Legacy authoritative `SPECIFICATION.md` / `PROJECT.md` only | **Not shipped on current npm releases (#2068).** Use the [frozen-release path](#frozen-pre-v020-document-model-migration-2068) below. |
446
+ | Command | Runtime | When to use | What it does |
447
+ | --- | --- | --- | --- |
448
+ | `deft migrate` / `directive migrate` | Current npm | Canonical-vendored `.deft/core/` deposit after npm upgrade (#1941) | Stamps `managed_by: npm` into the install manifest. Idempotent; never downloads payload. **Not** document-model migration. |
449
+ | `deft migrate:xbrief` / `task migrate:xbrief` | Current npm | Hop 2: vBRIEF 0.6 (`vbrief/`) xBRIEF 0.8 (`xbrief/`) | Layout + envelope migration. Current Taskfiles ship this as `migrate:xbrief`. |
450
+ | Frozen `task migrate:vbrief` | **v0.59.0 only** | Hop 1: pre-v0.20 flat `SPECIFICATION.md` / `PROJECT.md` → vBRIEF 0.6 | Python migrator. **Not shipped on current npm.** Current Taskfiles have no `migrate:vbrief` target. |
433
451
 
434
452
  ### Frozen pre-v0.20 document-model migration (#2068)
435
453
 
436
- Current `@deftai/directive` npm releases no longer ship `task migrate:vbrief` or `scripts/migrate_vbrief.py` on the consumer deposit path (#2022 Phase 3). If your project still uses the pre-v0.20 flat document model (authoritative root `SPECIFICATION.md` / `PROJECT.md` without vBRIEF lifecycle folders), migrate **once** on a pinned release that still bundles the Python migrator, then join the normal npm upgrade path.
454
+ Current `@deftai/directive` npm releases no longer ship `task migrate:vbrief` or `scripts/migrate_vbrief.py` on the consumer deposit path (#2022 Phase 3).
455
+
456
+ **This is a two-hop chain.** The pre-v0.20 flat model does not migrate straight to the current layout:
457
+
458
+ - **Hop 1** — pin **v0.59.0**, run frozen `task migrate:vbrief` (flat → vBRIEF v0.6).
459
+ - **Hop 2** — on **current npm**, run `deft migrate:xbrief` (vBRIEF v0.6 → xBRIEF v0.8). Not `deft migrate`.
460
+
461
+ This file owns the numbered hop commands. [docs/BROWNFIELD.md](./docs/BROWNFIELD.md) should point here rather than restate a second procedure. Live `frozenPreCutoverMigrationGuidance()`, QUICK-START Case H, and README are other envelopes.
462
+
463
+ #### Who runs which hop
464
+
465
+ | Starting layout | Next step |
466
+ | --- | --- |
467
+ | Flat authoritative root `SPECIFICATION.md` / `PROJECT.md` (no vBRIEF/xBRIEF lifecycle) | Hop 1, then hop 2 |
468
+ | Valid vBRIEF 0.6 (`vbrief/` lifecycle) | Hop 2 only — do not pin v0.59.0 |
469
+ | xBRIEF 0.8 with incomplete lifecycle folders | Current repair/validation only. Not hop 1. |
470
+ | Complete xBRIEF 0.8 | No document-model migration |
471
+
472
+ ⊗ Pin v0.59.0 or run frozen `migrate:vbrief` on an already-current xBRIEF 0.8 project. Incomplete `xbrief/` is not hop-1 input.
473
+
474
+ `deft doctor` / current `task migrate:preflight` may still print `Pre-cutover: migration needed` for a missing lifecycle folder on an otherwise current tree (runtime `detectPreCutover`). That predicate is not this docs slice. Hop 1 in **this** numbered list is only for flat authoritative root docs.
437
475
 
438
- > **Durability & support horizon (#2297).** This is a **best-effort** path for a document model that predates v0.20. The permanence anchor is the **`v0.59.0` git tag** — GitHub serves a source tarball for any tag on demand (`https://github.com/deftai/directive/archive/refs/tags/v0.59.0.tar.gz`), so recovery does **not** depend on any uploaded release asset staying attached. As long as the tag exists, the migrator is reachable. If you cannot reach the frozen payload at all, use the **[Fresh-start fallback](#fresh-start-fallback-2297)** below.
476
+ > **Durability & support horizon (#2297).** This is a **best-effort** path for a document model that predates v0.20. The permanence anchor is the **`v0.59.0` git tag** — GitHub serves a source tarball for any tag on demand (`https://github.com/deftai/directive/archive/refs/tags/v0.59.0.tar.gz`), so recovery does **not** depend on any uploaded release asset staying attached. Verify the checkout SHA before hop 1: tag `v0.59.0` currently names commit `e8bfd5aa21511a38e4b45b3d5b80edb30e9e2dd0` (a tag is a movable ref). If you cannot reach the frozen payload at all, use the **[Fresh-start fallback](#fresh-start-fallback-2297)** below.
439
477
 
440
- **Applies when:** `deft doctor` reports `Pre-cutover: migration needed`, or `task migrate:preflight` exits non-zero with a `document-model` FAIL line.
478
+ **Applies when:** the project still has authoritative pre-v0.20 root `SPECIFICATION.md` / `PROJECT.md` (no `<!-- deft:deprecated-redirect -->` sentinel, and no `vbrief/` or `xbrief/` lifecycle).
441
479
 
442
480
  **Pinned tag:** `v0.59.0` — the last release before the Python-free npm deposit; the tagged tree includes `scripts/migrate_vbrief.py`.
443
481
 
444
- **This is a two-hop chain.** The pre-v0.20 flat model does not migrate straight to the current layout: hop 1 is `task migrate:vbrief` on **v0.59.0** (flat → vBRIEF v0.6); hop 2 is `deft migrate:xbrief` on **current npm** (vBRIEF v0.6 xBRIEF v0.8). Steps 5–6 below cover hop 2.
482
+ **Trust:** run hop 1 only against a **trusted** project checkout. The frozen Python migrator writes under repo-controlled paths (it replaces root docs with redirect stubs). Do not point it at an untrusted tree.
445
483
 
446
484
  **Steps:**
447
485
 
448
- 1. Install **Python 3.11+** and **[uv](https://docs.astral.sh/uv/)** on the migration machine.
449
- 2. Deposit framework **v0.59.0** using one of (git-tag methods first — they survive even if release assets are removed):
450
- - **Source tarball from the tag:** `curl -fsSL https://github.com/deftai/directive/archive/refs/tags/v0.59.0.tar.gz | tar xz` (full source tree including the migrator), or
451
- - **Git clone / submodule:** `git checkout v0.59.0` in your framework checkout, or
486
+ 1. Confirm the starting layout in the table above. Already-current xBRIEF 0.8: stop. Valid vBRIEF 0.6: skip to hop 2 (step 7).
487
+ 2. Install **Python 3.11+** and **[uv](https://docs.astral.sh/uv/)** on the migration machine.
488
+ 3. Fetch framework **v0.59.0** and verify SHA `e8bfd5aa21511a38e4b45b3d5b80edb30e9e2dd0` (git-tag methods first they survive even if release assets are removed):
489
+ - **Git clone (preferred):** `git clone --branch v0.59.0 https://github.com/deftai/directive.git deft-v0.59.0`, then `git -C deft-v0.59.0 rev-parse HEAD` must equal that SHA, or
490
+ - **Source tarball from the tag:** download `https://github.com/deftai/directive/archive/refs/tags/v0.59.0.tar.gz`, extract to a directory, then confirm the tree matches the SHA, or
452
491
  - **Frozen Go installer** at [GitHub Releases tag v0.59.0](https://github.com/deftai/directive/releases/tag/v0.59.0) (legacy bridge; relies on the uploaded asset, so prefer a git-tag method above for durability).
453
- 3. From the project root, preview then apply:
492
+ 4. From the **consumer project root** (working directory = the project being migrated), run hop 1 against the **pinned** Taskfile — not the current deposit:
454
493
  ```bash
455
- task migrate:preflight
456
- task migrate:vbrief -- --dry-run
457
- task migrate:vbrief
494
+ task -t /path/to/deft-v0.59.0/Taskfile.yml migrate:vbrief -- --dry-run
495
+ task -t /path/to/deft-v0.59.0/Taskfile.yml migrate:vbrief
458
496
  ```
459
- Fallback when the consumer Taskfile has no deft include: `task -t ./.deft/core/Taskfile.yml migrate:vbrief`.
460
- 4. Regenerate exports once: `task roadmap:render`, `task project:render`, and `task prd:render -- --force` when you maintain a `PRD.md`.
461
- 5. Upgrade to current npm: `npm i -g @deftai/directive@latest`, then `deft update`, `deft migrate`, `deft doctor`.
462
- 6. Start a **new agent session** so refreshed AGENTS.md and skills load from a clean context.
497
+ Frozen `migrate:vbrief` self-invokes `migrate:preflight` on that same pinned Taskfile. Do not run current-npm `task migrate:preflight` as the hop-1 gate. Current deposits have no `migrate:vbrief` target — `task -t ./.deft/core/Taskfile.yml migrate:vbrief` is a dead fallback.
498
+ 5. **Hop 1 postcondition** (frozen runtime, not current `deft doctor`): the migrator exits 0, and the project has `vbrief/specification.vbrief.json`, `vbrief/PROJECT-DEFINITION.vbrief.json`, and `vbrief/{proposed,pending,active,completed,cancelled}/`. Root `SPECIFICATION.md` / `PROJECT.md` are deprecation redirects. This is vBRIEF 0.6, not xBRIEF.
499
+ 6. Upgrade to current npm: `npm i -g @deftai/directive@latest`, then `deft update` from the project root. Optional: regenerate exports with `task roadmap:render`, `task project:render`, and `task prd:render -- --force` when you maintain a `PRD.md` (frozen-payload renderers; hop 2 will rewrite layout).
500
+ 7. **Hop 2:** `deft migrate:xbrief` (or `task migrate:xbrief` from a maintainer checkout). Requires a clean working tree unless `--force`.
501
+ 8. **Hop 2 postcondition** (current npm): `deft migrate:xbrief` reports migrated or already done; `xbrief/` exists with `xBRIEFInfo.version` `0.8`; and the xBRIEF signpost is `xBrief migration: none` (or `converged`). A `vbrief/`-only tree must fail this proof. Do not treat `deft doctor` exit 0 as hop-2 proof. `deft migrate` (provenance stamp) is a separate npm-channel step — run it after hop 2 if needed, never instead of hop 2.
502
+ 9. Start a **new agent session** so refreshed AGENTS.md and skills load from a clean context.
463
503
 
464
- ⊗ Run `npm i -g @deftai/directive@latest` / `deft update` on a project that still has authoritative pre-v0.20 root docs — the current deposit cannot run the migrator; follow the frozen path first.
504
+ ⊗ Run `npm i -g @deftai/directive@latest` / `deft update` on a project that still has authoritative pre-v0.20 root docs — the current deposit cannot run hop 1; follow the frozen path first.
465
505
 
466
506
  #### Fresh-start fallback (#2297)
467
507
 
@@ -1031,7 +1071,7 @@ The contract is byte-stable by construction:
1031
1071
 
1032
1072
  ## From any pre-v0.20 version → v0.20.0 (historical; use frozen path)
1033
1073
 
1034
- > **Current releases (#2068):** follow [Frozen pre-v0.20 document-model migration](#frozen-pre-v020-document-model-migration-2068) instead of upgrading straight to latest npm. The section below documents what the v0.20 cutover changed; commands assume framework **v0.59.0** (or another pinned release that still ships `migrate_vbrief.py`).
1074
+ > **History.** This From-X-to-Y list records the v0.20.0 cutover. It is **not** the current hop-2 instruction. Current hop 2 is `deft migrate:xbrief` in [Frozen pre-v0.20 document-model migration](#frozen-pre-v020-document-model-migration-2068). Commands below assume pinned **v0.59.0** (hop 1 payload only).
1035
1075
 
1036
1076
  - **Applies when:** `deft doctor` / `task migrate:preflight` reports pre-cutover state. Legacy `SPECIFICATION.md` / `PROJECT.md` without the `<!-- deft:deprecated-redirect -->` sentinel is the canonical signal.
1037
1077
  - **Safe to auto-run:** No — `task migrate:vbrief` on the pinned release rewrites `SPECIFICATION.md` and `PROJECT.md` into deprecation-redirect stubs and creates lifecycle folders; review `--dry-run` first.
package/commands.md CHANGED
@@ -4,7 +4,7 @@ Current command surfaces for scoped work, generated documents, triage/cache work
4
4
 
5
5
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
6
6
 
7
- **See also**: [verification/verification.md](./verification/verification.md) | [resilience/continue-here.md](./resilience/continue-here.md) | [vbrief/vbrief.md](./vbrief/vbrief.md) | [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md)
7
+ **See also**: [verification/verification.md](./verification/verification.md) | [resilience/continue-here.md](./resilience/continue-here.md) | [vbrief/vbrief.md](./vbrief/vbrief.md) | [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md) | [docs/capabilities.md](./docs/capabilities.md) (generated capability index; `task docs:capability-map`)
8
8
 
9
9
  ---
10
10
 
@@ -107,8 +107,8 @@ On-demand **write + check** dense xBRIEF SoT artifacts at an explicit path. Thes
107
107
 
108
108
  | Verb | Meaning |
109
109
  |------|---------|
110
- | `deft xbrief:create` / `task xbrief:create` | Write json, md, or both at `--out` |
111
- | `deft xbrief:verify` / `task xbrief:verify` | Fail-closed check at `--out` |
110
+ | `deft xbrief:create` | Write json, md, or both at `--out` |
111
+ | `deft xbrief:verify` | Fail-closed check at `--out` |
112
112
  | `scope:*` / intake | Lifecycle birth and folder/status transitions |
113
113
  | `xbrief:preflight` | Implementation-intent gate (unchanged) |
114
114
 
@@ -146,7 +146,7 @@ Common commands:
146
146
  - `task scope:fail -- xbrief/active/<file>.xbrief.json` -- mark running work failed when the scope cannot complete.
147
147
  - `task scope:cancel -- <path>` -- move a scope to `cancelled/`.
148
148
  - `task scope:restore`, `task scope:block`, `task scope:unblock`, `task scope:demote`, and `task scope:undo:*` -- repair or reverse lifecycle transitions.
149
- - `task issue:sync-from-xbrief -- <path>` -- post a GitHub issue comment summarizing material AC/status changes for an origin-linked scope xBRIEF (`plan.references` with `x-xbrief/github-issue`). Supports `--dry-run` (print without posting), `--repo OWNER/NAME` when the reference URI lacks a repo slug, and `--allow-cross-repo` for intentional cross-repo sync (refused by default; #2633). Skips when no material changes since the last successful sync. Closes the reverse-sync gap after `task issue:ingest` (#2540).
149
+ - `deft issue:sync-from-xbrief -- <path>` -- post a GitHub issue comment summarizing material AC/status changes for an origin-linked scope xBRIEF (`plan.references` with `x-xbrief/github-issue`). Supports `--dry-run` (print without posting), `--repo OWNER/NAME` when the reference URI lacks a repo slug, and `--allow-cross-repo` for intentional cross-repo sync (refused by default; #2633). Skips when no material changes since the last successful sync. Closes the reverse-sync gap after `task issue:ingest` (#2540).
150
150
  - `task issue:ingest -- <N>` / `task issue:ingest -- --all [--label L] [--status S] [--dry-run]` -- ingest GitHub issues as scope xBRIEFs (deduplicates via existing references).
151
151
  - `task reconcile:issues [-- --apply-lifecycle-fixes]` -- scan origin-linked xBRIEFs for stale or closed GitHub issues.
152
152
 
@@ -168,7 +168,7 @@ The implementation gate succeeds only for active scope xBRIEFs with `plan.status
168
168
 
169
169
  **Slash-command intent containment (#1193):** When a session is originated by a slash command, that command is the *only* authorized verb for the session. Set `DEFT_SESSION_SLASH_VERB` (e.g. `/github-issue`) so `task xbrief:preflight` and PreToolUse hooks enforce the ceiling. Non-implement verbs (`/github-issue`, `/triage`, `/refine`, `/discuss`, `/research`, …) MUST NOT authorize implement, push, PR, merge, or deploy — adjacent bugs noticed during RCA become a second filed issue, not a second PR. Implement verbs: `/build`, `/ship`, `/ship-hotfix`, `/swarm`, `/implement`.
170
170
 
171
- **Human merge gate (#1193):** Typed `plan.policy.requireHumanMerge` (defaults true when `plan.policy.autoDeployOnMerge` is true). Agents may open PRs but must not merge when the gate is ON. Surfaces: (1) `task pr:wait-mergeable-and-merge` refuses agent merge, (2) `task verify:branch` advisory note, (3) branch-protection / setup requiring ≥1 human reviewer. Session-start discloses when ON. Override: `task policy:allow-bot-merge -- --confirm` or `DEFT_ALLOW_BOT_MERGE=1`.
171
+ **Human merge gate (#1193):** Typed `plan.policy.requireHumanMerge` (defaults true when `plan.policy.autoDeployOnMerge` is true). Agents may open PRs but must not merge when the gate is ON. Surfaces: (1) `task pr:wait-mergeable-and-merge` refuses agent merge, (2) `task verify:branch` advisory note, (3) branch-protection / setup requiring ≥1 human reviewer. Session-start discloses when ON. Override: `deft policy:allow-bot-merge -- --confirm` or `DEFT_ALLOW_BOT_MERGE=1`.
172
172
 
173
173
  **Hotfix classifier (#1193):** Typed `plan.policy.hotfixCriteria` + pure `evaluateHotfixEligibility`. Small fix / pure revert may propose label `hotfix-candidate` only; a human promotes to `hotfix`. Refactors, new exports/handlers, and forbidden paths (Dockerfile, fly.toml, workflows, migrations, auth/secrets) never qualify.
174
174
 
@@ -286,7 +286,6 @@ Current status: the validation, extractor, provider, registry, generated MAP, an
286
286
  - `task check:merge` -- explicit merge-chokepoint alias for `check:framework-source` in the framework source repo (#1704).
287
287
  - `task check:framework-source` -- framework-source lane.
288
288
  - `task check:consumer` -- consumer-shape lane.
289
- - `task check:slow` -- slower/full checks.
290
289
 
291
290
  ### Gate throughput — iteration fast lane (#1704)
292
291
 
@@ -302,7 +301,7 @@ Current status: the validation, extractor, provider, registry, generated MAP, an
302
301
  - `task verify:branch` -- enforce default-branch protection.
303
302
  - `task verify:hooks-installed` -- ensure local git hooks are configured; use `deft verify:hooks-installed --scope=agent --live` for fail-closed agent-host registration + command functionality.
304
303
  - `task verify:encoding` -- detect mojibake and BOM issues.
305
- - `task verify:xbrief-conformance` -- validate xBRIEF conformance surfaces.
304
+ - `task verify:vbrief-conformance` -- validate xBRIEF conformance surfaces.
306
305
  - `task verify:cache-fresh` -- validate cache freshness where required.
307
306
  - `task verify:capacity`, `task verify:wip-cap`, and `task verify:judgment-gates` -- policy/capacity gates.
308
307
  - `task verify:orphan-active` -- fail closed when active/running xBRIEFs still point at closed issues or merged PRs (#2321). After merge, `task verify:orphan-active -- --issue N` scans briefs that reference that issue. Confirmed shipped prints `task scope:complete -- <path>`; unresolved lookup prints a retry remediation and still exits 1 (#3429). PR-only briefs stay on the unscoped scan or `task swarm:complete-cohort`.
@@ -403,7 +402,7 @@ After CLI/deposit upgrade, disk can show the new generation while a long-lived s
403
402
 
404
403
  - ! Successful `init` / payload `update` stamps a monotonic live generation at `.deft/GENERATION.json` (outside `.deft/core` so replace does not wipe the counter).
405
404
  - ! Mutation `session:start` (cold and re-arm) binds that generation into `.deft/session-bind.json` when payload surfaces load.
406
- - ! Query with `deft freshness:report` / `task freshness:report` / `task session:freshness` (`--json` supported). States: `current` | `stale_soft` | `stale_hard` | `unbound`. Exit `0` only when `current`.
405
+ - ! Query with `deft freshness:report` / `deft session:freshness` (`--json` supported). States: `current` | `stale_soft` | `stale_hard` | `unbound`. Exit `0` only when `current`.
407
406
  - ! Rebind without restarting a shared host runtime: re-load surfaces into the session, then `deft freshness:bind` (or re-arm / `session:ready`).
408
407
  - ! Mid-mission: park and hand off before a hard refresh; an empty session after refresh is not work complete.
409
408
  - Soft vs hard meanings, surfaces, and API: `content/docs/freshness-contract.md`.
@@ -413,20 +412,22 @@ After CLI/deposit upgrade, disk can show the new generation while a long-lived s
413
412
  - ! Default interactive sessions to **read-only posture** until mutation or implementation intent (questions, research, Plan Mode, ticket-shaping). Load AGENTS.md / main.md / USER.md / PROJECT-DEFINITION; confirm alignment with addressing-name; ⊗ do not write `.deft/ritual-state.json`, run install/build side effects, or emit triage welcome, branch-policy, default-branch sync, sync-skill lifecycle checks, or eval/value readback writes unless the operator asks or the task is implementation-ready.
414
413
  - ! **USER.md path (#2544):** resolve via `deft session:start` output (`USER.md resolved …`); default platform paths: Windows `%APPDATA%\deft\USER.md`, Unix `~/.config/deft/USER.md`; override `$DEFT_USER_PATH`; workspace `<project>/.deft/USER.md`. ⊗ Invent or search `~/.config/deft` on Windows — AppData Roaming is canonical.
415
414
  - ! At mutation boundaries (code-writing, scope lifecycle moves, `start_agent`, commits, pushes, PR-from-local-changes, release work): run the mutable quick tier then gated verifier below before proceeding.
416
- - ? Explicit read-only alignment only: `deft session:start -- --read-only` (no ritual-state write).
415
+ - ? Explicit read-only alignment only: `deft session:start --read-only` (no ritual-state write). Task: `task session:start -- --read-only`.
417
416
  - ! **Worktree occupancy contract (#3433 / #3604 / #3611 / #3926):** mutation `session:start` / `session:ready` claim a gitignored `.deft/occupancy.json` lease. Occupancy is cooperative host-session routing between processes that follow the protocol, not authentication against another same-user process: hook JSON and local owner IDs are forgeable. Name it for what it is (#3755): a **cooperative bearer-id boundary, not a lineage** — the lease admits whoever presents an id the record itself names, nothing observes parentage, and a dispatched child is admitted only because a grant records it. A live lease allows only a matching presented owner or member and a ritual state whose exact verified owner agrees; missing, conflicting, or mismatched identity fails closed. `--read-only` does not claim. A stored lease whose `worktree_path` is not this checkout is residue, not a holder: first mutation `session:start` claims over it. Same-tree two-session conflict still fails closed.
418
417
  - ! **Which tree the lease is read from — root admission (#3794 / #4013):** a gated write is judged against the Git working tree its **target** lands in, not always the tree the host payload names. A target in another worktree sharing `--git-common-dir` is admitted and carries occupancy, ritual and active scope with it; a proven different repository, an unreadable target identity, and a mutation spanning two worktrees are refused. A target with **no Git toplevel at all** (OS temp, a home file, anything outside every checkout) is gated against the payload root — deliberate since #3794, published with its reason and a gate-by-gate disposition in [docs/hook-root-admission.md](./docs/hook-root-admission.md), which also records the three-surface matrix (direct write consults admission; generic server-prefixed MCP and out-of-repo Shell writes do not) and the relative-target canonicalization limitation (#4023).
419
418
  - ! **Host owner namespaces (#3611):** canonical owners are `host:<provider>:v1:<base64url(raw-id)>`. Codex uses payload `session_id` for the parent and its subagents. Claude Code uses `session_id`; `agent_id` does not replace the session-family owner. Cursor uses `conversation_id` and requires a simultaneously supplied `session_id` to agree; no Cursor subagent-granularity claim is made until verified. Grok has no verified payload identity, so its owner is the hook process variable `GROK_SESSION_ID` the host publishes (#3873): the hook is a sibling the host spawns, so an agent shell export cannot reach it, and the payload `session_id` is never read. When that variable is absent the host keeps explicit `--session-id` / `DEFT_SESSION_ID` ownership; a malformed one fails closed, as does an ambient `DEFT_SESSION_ID` that contradicts a resolved host owner.
420
419
  - ! **Occupancy identity on a host with no session derivation (#3877):** the hook process environment (`GROK_SESSION_ID` on Grok, or `DEFT_SESSION_ID` inherited by the hook) or an explicit `--session-id`. ⊗ a worker-shell `export DEFT_SESSION_ID`. A shell export reaches vitest, CLI verbs, and `scope:complete`; it never reaches `deft-hook`, a sibling process. Nothing in `content/templates` or `content/skills` mentions the variable. Do not invent an envelope mandate.
421
- - ! **One actor-resolution chain, four terminals (#3954):** claim, release, heartbeat and grant/revoke resolve the acting session by the same lookup order — explicit `--session-id`, then `DEFT_SESSION_ID`, then the owner the running host published. Only the **terminal** differs, and deliberately: `session:start` / `session:ready` **mint**, because claiming establishes an identity where none exists; `occupancy:release`, `occupancy:heartbeat`, `occupancy:grant` and `--revoke` resolve to **empty** and keep the "this process presented no session identity" diagnosis, because they prove an identity rather than establish one. A shared mint would hand those surfaces a plausible id no later hook will present. The practical effect: on a host that publishes an owner, the printed recovery `occupancy:release` is a command the occupant can actually run, instead of one that resolved an empty caller and refused the owner its own lease. Disagreement is reported, not reordered: when `DEFT_SESSION_ID` and the host owner name different sessions the documented order still stands, and a refusal names both ids and the `--session-id` that would act as the host owner. Anonymous release of the occupant recorded in the lease file stays **refused** — possession of that path is not authority to delete a live lease.
420
+ - ! **One actor-resolution chain, four terminals (#3954 / #4066):** claim, release, heartbeat and grant/revoke resolve the acting session by the same lookup order — explicit `--session-id`, then `DEFT_SESSION_ID`, then the owner the running host published. **Claim** is host-authoritative when those disagree: inherited `DEFT_SESSION_ID` must not beat the host-published owner (mirrors the write-gate env drop). Release, heartbeat and grant/revoke still report disagreement without reordering, and still refuse to mint. Anonymous release of the occupant recorded in the lease file stays **refused** — possession of that path is not authority to delete a live lease.
422
421
  - ! **Where the transport still does not reach (#3873 residue):** the CLI resolves the host owner; the write gate runs in the `deft-hook` sibling process, and on a deployment where the host does not publish `GROK_SESSION_ID` into that sibling the hook presents whatever `DEFT_SESSION_ID` it inherited — possibly another host's session. Nothing in this tree can set that process's environment, and the hook writes no env or payload log, so the condition is inferred from the deny text rather than observed. Recovery meanwhile is the ambient step above: the occupant releases its own lease from its own shell. Closing it needs either host cooperation (publish the variable to the hook sibling) or a hook-process capture facility, and both are separate work.
423
422
  - ! **Lifecycle identity transport (#3611):** for every host with a resolved owner, PreToolUse rewrites only exact, simple canonical `deft` / `directive` lifecycle commands and source-repo `task <verb> [-- ...]` forms to add the host's `--session-id`. Direct CLI spellings are `session:start`, `session:ready`, `session:end`, `occupancy:steal`, `occupancy:release`, `occupancy:heartbeat`, `occupancy:grant` (#3954, its `--revoke` arm included), and `swarm-launch`; the Task spelling for the last verb is `swarm:launch`. For owner-requiring lifecycle commands, the shell execution directory must be absent or realpath-align exactly with the hook project root. Path-bearing executables (`./deft`, `/path/deft`), path/destination flags (`--project-root`, `--paths`, path-valued `--stories`, `--output`, `--worktree-map`, `--gate-clearances`), `--no-audit`, consumer-repo Task indirection, Task aliases (`task deft:<verb>`), and compound, redirected, quoted, aliased, wrapped, or otherwise ambiguous forms are not auto-rewritten. A supported-host hook rejects a recognized lifecycle form outside the narrow rewrite surface until it carries the explicit matching `--session-id`; an execution-root mismatch on an owner-requiring command always denies. With a matching ID on other excluded forms, the original command remains under normal host permission handling. Manual/integrated-terminal callers must likewise pass one matching `--session-id` (or `DEFT_SESSION_ID`) through the complete lifecycle.
424
423
  - ! **Transition, steal, and release (#3611):** an existing live UUID lease cannot silently become a host-namespaced owner. Prefer one aligned transition: `session:start --steal --confirm --occupant <reported-session-id> --session-id=<your-session-id>` (an exact host-mediated command receives the final option automatically). Bare `occupancy:steal --confirm --occupant <reported-session-id> --session-id=<your-session-id>` changes only the lease; direct writes remain denied unless ritual state already names the new owner. If they differ, align the same writer ID with `session:start --rearm --session-id=<same-session-id>` when re-arm is eligible, or cold `session:start --session-id=<same-session-id>` otherwise. The occupant drops a live lease with `occupancy:release` or `session:end`; a non-owner cannot clear it. Confirmed steal remains required for a live occupant and prints existing `claimed_at` / `heartbeat_at` / `last_write_at` when available.
425
424
  - ! **Lease refresh (#3599):** a gated write by the owner re-stamps `heartbeat_at`, so a session that keeps working keeps its lease, up to the absolute age cap below. The re-stamp is floored at a quarter of the TTL to avoid rewriting the lease per event, and it records `last_write_at` separately — `heartbeat_at` moves on any lease touch, `last_write_at` only on a gated product write, which is what tells a would-be stealer that the occupant is mid-work. When the owner's own lease is inside the staleness window (three quarters of the TTL) and the automatic re-stamp could not run, the write gate says so instead of letting the lease lapse silently; a write that did re-stamp stays quiet, because the warning would name a state that write just cleared. A re-stamp blocked by the lock leaves the lease untouched and the gate re-decides against the file on disk: contention alone keeps the rightful owner writing, while a takeover that completed during the wait denies. Long quiet stretches produce no gated write to ride on, so the owner refreshes explicitly with `deft occupancy:heartbeat` (Task: `task occupancy:heartbeat`). Refresh extends a lease the caller already holds — it never claims a free worktree, never takes a foreign lease, and never mints an owner; an unheld lease exits non-zero. Under a supported host the exact command receives the matching `--session-id` automatically; manual and Grok callers pass `--session-id <id>` or `DEFT_SESSION_ID`.
426
425
  - ! **Absolute lease age cap (#3599):** a lease is also dead 12 hours after `claimed_at`, whatever its heartbeat says, and no refresh path may extend that — `claimed_at` survives every re-stamp. Occupancy admits whoever presents the occupant's session id, so refresh alone would let any process holding that string keep a worktree forever and disable the crash-recovery reclaim the TTL exists for. The two dead states call for different responses and read differently: a stale heartbeat means refresh sooner; past the cap the tree is no longer held, so the capped holder's own gated writes are **refused** until it re-claims with `session:start --session-id=<your-session-id>` — allowing them would let the very bearer the cap bounds keep mutating a worktree a peer may claim at any moment. A lease that is both stale and capped reads as capped, because refreshing cannot revive it and the stale wording would send the holder somewhere that does not help. A different session is unaffected: taking over an aged-out tree is the reclaim the cap exists to enable. Twelve hours is sized by the stalled owner, not the busy one: refresh keys on writes, so an agent that finishes overnight and waits for its operator stops refreshing while staying legitimate, and 12 hours spans a 23:00 dispatch to a 09:00 handoff while still bounding reclaim inside a day. Reaching the cap costs one re-claim, never the work. A time cap cannot tell a stalled-but-live owner from a dead one — the only liveness signal on this path is a write; if that bites, the answer is a write-free liveness signal (explicit parked state, or refresh on non-write activity), not a larger number.
427
426
  - ! **Lease membership (#3755):** the occupant admits a dispatched child with `deft occupancy:grant --session-id=<owner> --child-session-id=<child> --role <worker-role> [--ttl-minutes N] [--worktree <path>] [--host <id>] [--address <name>] [--join-protocol none|heartbeat-file|parent-message]` (Task: `task occupancy:grant`), and withdraws it with the same command plus `--revoke`. The grant records owner id, child id, worktree, role and expiry, so a write resolves to a session rather than to whoever held the string. Admission splits: **writes** are admitted for the owner **or** a valid unexpired member; **release, steal, heartbeat and cohort close-out stay owner-only**, so a grant never escalates into the lease itself. An expired grant is refused on read, expiry is clamped to the lease's own absolute age cap, a lease carries at most 32 live grants, and a steal or a fresh claim starts with none — grants belong to the lease that issued them. The child id is checked at grant time (#3954): a value under the reserved `host:` prefix must be a well-formed canonical owner, and one that re-prefixes the owner's own host payload under a second provider is refused as the self-grant it is. Both were granted before, and the write gate then admitted them as `member`, so the lease read as membership while admitting nobody. An id outside that prefix is still accepted, because a child on a host with no identity contract presents whatever `DEFT_SESSION_ID` holds. Withdrawal skips the check, so a malformed grant written earlier stays revocable. A member's write re-stamps `heartbeat_at` and `last_write_at` on the same quarter-TTL floor as the owner's: a tree a granted child is actively writing is in use, and letting it lapse would hand the worktree to a peer mid-edit. `claimed_at` is untouched, so the absolute age cap and the grant's own expiry still bound it. The verb is inside the rewrite surface since #3954, so a supported host adds the matching `--session-id`; a `--worktree` rebind stays outside it and must carry the owner explicitly. All of it is cooperative, like the rest of this lease: it names who may write, it does not authenticate them. Ritual state stays single-owner, so a member writes under the occupant's ceremony: the composite hook write gate measures the verified ritual owner against the occupant that issued the grant rather than against the writer, and a revoked or expired member is refused there as well.
428
- - ! **Parent and child, per identity-source kind (#3954 / #3999):** there is no single answer, so do not look for one. On a **`host-env`** host (Grok) the host publishes a different id into each agent session, so a parent and its dispatched children are **different actors**: each resolves its own owner through the chain above and claims its own worktree, which is where the dispatch envelope already puts it. Membership is **not** automatic there — 32 grants at a four-hour expiry against a twenty-minute lease means granting on every dispatch exhausts a busy parent's lease inside a day — and the revocation trigger stays the owner's own `--revoke` or the grant's expiry. A dispatched child's owner lease is released when orchestration observes `phase: terminal`, using the occupancy owner and exact worktree root the parent recorded at dispatch under `.deft/child-occupancy/` (lease-gated; not `.deft-scratch/**`). Release is compare-and-release under the occupancy lock: only if that recorded child still owns that recorded tree. A successor owner is left alone. On a **`payload`** host (Codex, Claude Code, Cursor) parent and subagents share one id, so there is no foreign child lease to admit and nothing to grant; the live consequence is the inverse one, that a parent's `occupancy:release` removes a working child's lease mid-flight with no denial, because both present the same string. The terminal transition is a no-op on that arm — auto-release would drop the parent's live lease. Swarm close-out still releases only the launcher's `occupancy_session_id`. No new reap verb.
429
- - ! **Lifetime and enforcement boundaries:** expired heartbeat (20 min) is free for claim-over-expired, and the release path may clear residue; no `reap` verb. No host-to-lease map or credential file is persisted; supported hooks re-derive their owner from each payload. Gated writes renew the heartbeat (#3599), so the 20-minute window runs from the owner's last write rather than from its claim, bounded by the absolute age cap above. Missing or drifted hooks remain #3742, and spawn tools remain outside occupancy enforcement. Join negotiation is owner-issued membership (`occupancy:grant`, #3755); the parked child-initiated `occupancy:request` queue stays unimplemented and is no longer named in remediation. `swarm:launch` carries the same explicit owner and persists `occupancy_session_id` in a cohort-keyed record; close-out uses that cohort entry as authority, requires any ambient owner to corroborate it, and never substitutes the current live lease owner. Swarm close-out stays `releaseSwarmOccupancy` on complete-cohort.
427
+ - ! **Parent and child, per identity-source kind (#3954 / #3999 / #4066):** there is no single answer, so do not look for one. On a **`host-env`** host (Grok) the host publishes a different id into each agent session, so a parent and its dispatched children are **different actors**: each resolves its own owner through the chain above and claims **its own worktree**. Membership is **not** automatic there — 32 grants at a four-hour expiry against a twenty-minute lease means granting on every dispatch exhausts a busy parent's lease inside a day — and the revocation trigger stays the owner's own `--revoke` or the grant's expiry. Spawned mutating work takes a reserved linked worktree before occupancy claim; sharing the primary checkout with a live occupant is refuse, not grant-across-host. On a **`payload`** host (Codex, Claude Code, Cursor) parent and subagents share one id, so there is no foreign child lease to admit and nothing to grant on the same tree; own-tree children still record a dispatch lease. A parent's `occupancy:release` on the parent tree does not drop a child tree. No new reap verb.
428
+ - ! **Spawn occupancy seam (#4066):** implement-class spawn (`Task`, `spawn_subagent`, `start_agent`, …) inspects `tool_input.isolation=worktree` / `worktree_path` / `cwd`. Absent field fails closed (do not inherit parent cwd). Unique dispatch reservation (incarnation) is persisted before launch. Occupancy consult is on the destination tree, not a write-gate flip with `actor=null`. Explore and ephemeral spawns stay exempt. Grok PreToolUse cannot rewrite spawn input; hosts that can (`claude`, `cursor`, `codex`) re-root cwd onto the reserved tree. Primary-checkout claim is the exception (`release-cut`, `policy-restore`, `operator-default-branch`) from a trusted producer; `--read-only` never claims.
429
+ - ! **Dispatcher-owned terminal release (#4066 / #3999):** recorded-child close-out is dispatcher lifecycle on a dispatch-provenance `(owner, worktree, incarnation)` tuple. Release requires parent-id match, incarnation match, skip invalid heartbeats, and refuse a tree that is not the heartbeat tree or a dispatcher-allocated tree. Ordinary self-claim records are not close-out. Payload-kind skip remains for same-tree shared identity; a linked worktree distinct from the observer is released. Heartbeats should echo `incarnation` from the dispatch envelope.
430
+ - ! **Lifetime and enforcement boundaries:** expired heartbeat (20 min) is free for claim-over-expired, and the release path may clear residue; no `reap` verb. No host-to-lease map or credential file is persisted; supported hooks re-derive their owner from each payload. Gated writes renew the heartbeat (#3599), so the 20-minute window runs from the owner's last write rather than from its claim, bounded by the absolute age cap above. Missing or drifted hooks remain #3742. Implement-class spawn is occupancy-consulted on the destination worktree (#4066); explore/ephemeral spawn stay outside that consult. Join negotiation is owner-issued membership (`occupancy:grant`, #3755); the parked child-initiated `occupancy:request` queue stays unimplemented and is no longer named in remediation. `swarm:launch` carries the same explicit owner and persists `occupancy_session_id` in a cohort-keyed record; close-out uses that cohort entry as authority, requires any ambient owner to corroborate it, and never substitutes the current live lease owner. Swarm close-out stays `releaseSwarmOccupancy` on complete-cohort.
430
431
  - ~ Operators MAY still explicitly request full `deft session:start`, `deft triage:welcome`, sync, or doctor in read-only sessions.
431
432
 
432
433
  ### Assist / research posture (#1802)
@@ -455,17 +456,17 @@ Cross-link: spawn three postures and deny recoveries live under § Agent-host di
455
456
  ### Mutable ritual (mutation posture)
456
457
 
457
458
  - ! On **mutation** session start, run `deft session:start` (or `task session:start` in framework source) after loading AGENTS.md. Records quick-tier ritual in `.deft/ritual-state.json`: alignment confirmation, branch-policy disclosure, `deft verify:tools` guidance, default-branch sync warnings, and `deft triage:welcome` one-liner. State is worktree- and HEAD-bound; stale after `plan.policy.sessionRitualStalenessHours` hours (default 4). Mutation start also claims the worktree occupancy lease (`.deft/occupancy.json`); see Session routing (#3433).
458
- - ! **Orientation compression Now (#3286):** mutation cold `session:start` composes `doctor` + #3282 toolchain preflight (and deposit-sha fast-paths for `agents:refresh` / `verify:cache-fresh`) as inline sections with per-section status lines — composition of existing steps, not a new monolith. When the deposit fingerprint (payload + templates + engine) is unchanged, refresh surfaces print one-line `unchanged - sha match` no-ops. Opt-in compact output: `deft session:start -- --compact` or `DEFT_SESSION_COMPACT=1` (verbose remains the default). #2176 read-only default is unchanged. Dual-path Later (`deft orient`) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
459
+ - ! **Orientation compression Now (#3286):** mutation cold `session:start` composes `doctor` + #3282 toolchain preflight (and deposit-sha fast-paths for `agents:refresh` / `verify:cache-fresh`) as inline sections with per-section status lines — composition of existing steps, not a new monolith. When the deposit fingerprint (payload + templates + engine) is unchanged, refresh surfaces print one-line `unchanged - sha match` no-ops. Opt-in compact output: `deft session:start --compact` or `DEFT_SESSION_COMPACT=1` (verbose remains the default). #2176 read-only default is unchanged. Dual-path Later (orient) stays open until run-summary telemetry shows ritual+gate share ≥ 25% after Now ships (#2899).
459
460
  - ! Cold `session:start` does **not** run the live agent-hook probe. Functional readiness belongs to the gated mutation path so cold ceremony retains the #2990/#2991 latency profile.
460
461
  - ! **Hot path latency (#2991):** by default, mutation `session:start` does **not** block ritual-state write on optional network. It skips the npm release-availability probe and triage cache empty-hydrate / self-heal (`ensureTriageCacheHydrated` / `maybeSelfHealCache`). Targets (operator-facing, not CI-hard): warm hot path typically under a few seconds once tools are on PATH; cold path dominated by local `verify:tools` and git, usually well under ~30s when optional network is off. Empty-cache GitHub fetch-all and npm `view` previously accounted for multi-minute hangs in the WWYSYDH pilot — those stay off the critical path unless opted in.
461
462
  - ! **Cold vs re-arm ceremony tiers (#2992):** default `session:start` is the **cold** (full) path. After age staleness or compact re-arm (#2113) on the **same worktree** with continuous HEAD and previously-passing quick steps, prefer `deft session:start --rearm` (alias `--tier=rearm`) to refresh the ritual clock + HEAD/worktree bind without `verify:tools`, triage welcome, release probe, or staleness tickler. Full cold remains required for missing/invalid state, worktree change, discontinuous HEAD, first install, or failed/missing quick steps. Compact marks `rearm_needed`; PreToolUse denial and inspect/verify messages prefer re-arm recovery when cold is unnecessary.
462
- - ? Opt into optional network: `deft session:start -- --with-network` or `DEFT_SESSION_START_NETWORK=1`. When enabled, the bounded release-availability probe runs against the public npm registry (skips framework source checkouts, non-release pins, and `DEFT_NO_NETWORK=1`; identical latest-version notices throttle for 24 hours in `xbrief/.triage-cache/release-availability-state.json`). Default-mode triage welcome then also hydrates/self-heals the triage cache. This is separate from `deft doctor`, whose bare and gated invocations remain offline by default (#2182). Refs #1692 / #2991. Re-arm never runs optional network.
463
+ - ? Opt into optional network: `deft session:start --with-network` or `DEFT_SESSION_START_NETWORK=1`. When enabled, the bounded release-availability probe runs against the public npm registry (skips framework source checkouts, non-release pins, and `DEFT_NO_NETWORK=1`; identical latest-version notices throttle for 24 hours in `xbrief/.triage-cache/release-availability-state.json`). Default-mode triage welcome then also hydrates/self-heals the triage cache. This is separate from `deft doctor`, whose bare and gated invocations remain offline by default (#2182). Refs #1692 / #2991. Re-arm never runs optional network.
463
464
  - ~ `session:start --json` includes `steps[]` with `name` + `duration_ms` for major phases (`alignment`, `branch_policy`, `verify_tools`, `triage_welcome`, `release_probe`, `ritual_write`), plus total `duration_ms`, `optional_network`, and `ceremony_tier` (`cold` | `rearm`). Skipped optional steps report `skipped: true` and `duration_ms: 0`. Use this for attribution when investigating ceremony wall-clock.
464
465
  - ~ **Process-cost events (#2994 / #3508):** on mutation `session:start` completion (cold or re-arm), Directive appends a local `session:start` behavioral event to `.deft-cache/events.jsonl` with `ceremony_tier`, `duration_ms`, `exit_code`, and optional `steps[]` (same labels as `--json`). Mutation `session:start` also prints one operator-visible `ceremony <tier> <ms>` line (hidden under `--compact` / `DEFT_SESSION_COMPACT`). When PreToolUse denies for `ritual-not-ready`, a local `session:ritual-blocked` event records `tool_name`, `code`, and optional `recovery_tier` / `detail`. Always-on best-effort (never blocks ceremony or deny path); not gated on `valueFeedback`; **no remote upload** (Product Insights #2603 is a separate opt-in). Pull the rollup with `task value:show` (composed reader; CLI process time, not agent-turn wall clock). See § Process-cost events below. ⊗ Do not use the printed CLI duration as #3286 Later graduation input.
465
466
  - ~ At safe idle points (clean tree, no in-flight story), mutation session start and `deft scope:complete` may also run the staleness tickler: an interactive, consent-based offer to upgrade Directive (`npm i -g @deftai/directive@latest`) and/or migrate xBRIEF (`deft migrate:xbrief`). Escalation tiers, snooze windows, and opt-out live under `plan.policy.stalenessTickler` — inspect with `deft policy:show --field=stalenessTickler`. State persists in `xbrief/.triage-cache/staleness-tickler-state.json`. Skips framework source checkouts, dirty trees, CI/headless (`DEFT_SESSION_RITUAL_SKIP=1`), and typed opt-out. Refs #2488 / #2489.
466
- - ! Before any code-writing tool call or `start_agent` implementation dispatch, run `deft verify:session-ritual -- --tier=gated`. Gated tier fails closed unless quick-tier state is fresh; lazily records the non-deferrable `agent_hooks` readiness gate plus `deft doctor` and `deft verify:cache-fresh` entrypoints. Agent-hook correctness is independent of doctor warnings and throttling. Step 0 of the pre-`start_agent` gate stack.
467
+ - ! Before any code-writing tool call or `start_agent` implementation dispatch, run `deft verify:session-ritual -- --tier=gated`. Gated tier fails closed unless quick-tier state is fresh; lazily records the non-deferrable `agent_hooks` readiness gate plus `deft doctor` and `task verify:cache-fresh` entrypoints. Agent-hook correctness is independent of doctor warnings and throttling. Step 0 of the pre-`start_agent` gate stack.
467
468
  - ! **One-shot recovery (#2993 / #3100):** when PreToolUse denies writes for a stale/missing gated ritual, run `deft session:ready` (or `task session:ready`). It composes `session:start` (only when quick-tier is not green) + `verify:session-ritual -- --tier=gated` + `cache fetch-all --force` when `cache_fresh` is the remaining blocker, then re-verifies. Even when gated inspect is already fresh, the fast path forces one live `agent_hooks` check so later drift cannot hide behind cached ritual state; it still avoids unnecessary fetch-all. Flags: `--json`, `--repo OWNER/NAME`, `--with-network` (forwarded to session:start). Prefer this over juggling the multi-step recovery sequence under hook pressure.
468
- - ? Postpone with `deft session:start -- --defer step=reason` (`alignment`, `branch_policy`, `triage_welcome`, `doctor`, `cache_fresh`). `agent_hooks` is non-deferrable.
469
+ - ? Postpone with `deft session:start --defer step=reason` (`alignment`, `branch_policy`, `triage_welcome`, `doctor`, `cache_fresh`). `agent_hooks` is non-deferrable.
469
470
  - Headless workers / CI MAY set `DEFT_SESSION_RITUAL_SKIP=1`; verifier exits 0 but warns when bypass hides failure.
470
471
  - ⊗ Self-report ritual complete without fresh `deft session:start` state; ⊗ bypass `deft verify:session-ritual` before implementation dispatch; ⊗ reorder/skip/merge ritual tiers without operator override.
471
472
 
@@ -486,7 +487,7 @@ Agents use this signal to prefer portable syntax and quote zsh-sensitive data su
486
487
  - Credential bridging: host-gh (`gh auth login` in the execution env) or injected-token (`GH_TOKEN` / `GITHUB_TOKEN` / `GH_ENTERPRISE_TOKEN`). Never put token values in prompts or transcripts.
487
488
  - Contract: `content/contracts/scm-readiness.md`; operator docs: `content/scm/github.md` § Mismatched/headless SCM readiness.
488
489
 
489
- **Pre-`start_agent` gate stack (#1149/#1348):** (0) `deft verify:session-ritual -- --tier=gated` → (1) `deft verify:story-ready` → (2) `deft xbrief:preflight` → (3) `deft verify:cache-fresh` → (4) `deft verify:branch` + hooks → (5) `start_agent`.
490
+ **Pre-`start_agent` gate stack (#1149/#1348):** (0) `deft verify:session-ritual -- --tier=gated` → (1) `deft verify:story-ready` → (2) `deft xbrief:preflight` → (3) `task verify:cache-fresh` → (4) `deft verify:branch` + hooks → (5) `start_agent`.
490
491
 
491
492
  ```mermaid
492
493
  flowchart TD
@@ -595,7 +596,8 @@ Directive does not guess your mix. Either you name the next units in order (**or
595
596
  - `task triage:summary`, `task triage:scope`, `task triage:scope-drift`, `task triage:subscribe`, `task triage:unsubscribe`, `task triage:classify`, `task triage:strip-withdrawn-chips`, `task triage:welcome`, and `task triage:smoketest` -- supporting workflow and onboarding commands.
596
597
  - `task triage:classify -- --mirror` -- **Withdrawn (#4070).** Dry-run and `--apply` both fail closed with a pointer at #4070. Replacement sieve is #4071. #3579 is transitively withdrawn for the gap. Do not close #1423, #3579, #2611, or #3923. `--list` / `--validate` remain. Strip leftover chips with `task triage:strip-withdrawn-chips` (remaining-set apply, not classify).
597
598
  - **Triage write-back pairing (#1423 / ADR-005).** When the author stamps that a lean is mechanism-shaped, write **both**: body-text field `mechanism-shaped: true` (the artifact) **and** mirrored label `design-critique:mechanism-shaped` (what `plan.policy.judgmentGates` matches and lists show). Do not compute the classification. No stamp -> the design-critique gate never fires. Clearance is a separate line on the thread: `design-critique: warranted | not warranted, because ...` -- engine evaluates presence, shape, and authority, never the because-clause content. Advisory/observe; `verify:judgment-gates --enforce` stays opt-in unused; `scope:promote` does not gain `--enforce` in this story.
598
- - **Design-critique catalog chip (#3642).** Parent attach of `triage-ready` / recut `mechanism-shaped` MUST use `task scm:issue:design-critique-chip -- --issue N --chip triage-ready|mechanism-shaped [--repo OWNER/NAME]` (or `deft scm issue design-critique-chip`). Closed catalog remaining-set replace, one write. ⊗ `gh api POST .../labels`. ⊗ additive `scm:issue:edit --add-label`.
599
+ - **Design-critique catalog chip (#3642 / #4205).** Parent attach of `triage-ready` / `recut-needed` / recut `mechanism-shaped` MUST use `task scm:issue:design-critique-chip -- --issue N --chip triage-ready|mechanism-shaped|recut-needed [--repo OWNER/NAME]` (or `deft scm issue design-critique-chip`). Closed catalog remaining-set replace, one write. Auto-stamp chip from `resolveAutoStampCatalogChip` (`Recut:` token → recut-needed). ⊗ `gh api POST .../labels`. ⊗ additive `scm:issue:edit --add-label`.
600
+ - **Work claim (#4200).** Same-issue busy flag `status:claimed`. `task scm:issue:work-claim -- claim|show|release --issue N [--repo OWNER/NAME]` (`deft scm issue work-claim`). Session-start and `xbrief:preflight` MUST show. Warn is success; not a lock. Claim refuses read-only / no occupancy. Last-write-wins: the board can lie about who. v1 does not detect two-issue path overlap. Catalog in `.github/ISSUE_LABELS.md`; ⊗ invent the name per issue.
599
601
  - **Operator discovery for SCM label mirror (#3124).** Withdrawn with classify `--mirror` (#4070). The session/welcome tip does not fire. #3124 discovery and #1423 Wave 1–2 apply are unusable after this lands; those issues stay open.
600
602
 
601
603
  ### Cache Tasks