@deftai/directive-content 0.108.0 → 0.109.1

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 (102) hide show
  1. package/QUICK-START.md +4 -3
  2. package/SKILL.md +9 -10
  3. package/Taskfile.yml +18 -3
  4. package/UPGRADING.md +7 -6
  5. package/coding/build-output.md +4 -3
  6. package/coding/coding.md +6 -5
  7. package/coding/security.md +3 -3
  8. package/coding/testing.md +2 -1
  9. package/commands.md +21 -16
  10. package/contracts/deposit-required-paths.json +26 -0
  11. package/contracts/design-critique.md +96 -1
  12. package/contracts/deterministic-questions.md +2 -1
  13. package/contracts/host-lifecycle-duties.md +1 -1
  14. package/contracts/path-write-fence.md +72 -7
  15. package/conventions/content-manifest.json +1 -1
  16. package/conventions/references.md +10 -8
  17. package/conventions/task-caching.md +2 -1
  18. package/conventions/vbrief-filenames.md +5 -4
  19. package/docs/consumer-check-contract.md +35 -0
  20. package/docs/delivery-attempt.md +2 -0
  21. package/docs/gate-integrity.md +17 -2
  22. package/docs/hook-root-admission.md +150 -0
  23. package/docs/host-surface-assumptions.md +4 -1
  24. package/docs/host-tool-surface-audit.md +163 -0
  25. package/docs/orphan-active-verdict-basis.md +33 -0
  26. package/docs/skill-pin-policy.md +1 -1
  27. package/events/README.md +12 -13
  28. package/glossary.md +2 -1
  29. package/incidents/README.md +2 -1
  30. package/interfaces/cli.md +2 -1
  31. package/languages/6502-DASM.md +2 -1
  32. package/languages/c.md +2 -1
  33. package/languages/cpp.md +2 -1
  34. package/languages/csharp.md +2 -1
  35. package/languages/dart.md +2 -1
  36. package/languages/delphi.md +2 -1
  37. package/languages/elixir.md +2 -1
  38. package/languages/go.md +2 -1
  39. package/languages/java.md +2 -1
  40. package/languages/javascript.md +2 -1
  41. package/languages/julia.md +2 -1
  42. package/languages/kotlin.md +2 -1
  43. package/languages/markdown.md +2 -1
  44. package/languages/mermaid.md +2 -1
  45. package/languages/officejs.md +2 -1
  46. package/languages/python.md +2 -1
  47. package/languages/r.md +2 -1
  48. package/languages/rust.md +2 -1
  49. package/languages/sql.md +2 -1
  50. package/languages/swift.md +2 -1
  51. package/languages/typescript.md +2 -1
  52. package/languages/vba.md +2 -1
  53. package/languages/vhdl.md +2 -1
  54. package/languages/visual-basic.md +2 -1
  55. package/languages/zig.md +2 -1
  56. package/main.md +47 -44
  57. package/meta/code-field.md +2 -1
  58. package/meta/morals.md +2 -1
  59. package/meta/philosophy.md +3 -2
  60. package/meta/project.md +4 -3
  61. package/meta/ralph.md +2 -1
  62. package/meta/security.md +3 -2
  63. package/meta/versioning.md +2 -1
  64. package/package.json +3 -3
  65. package/packs/patterns/patterns-pack-0.1.json +1 -1
  66. package/packs/rules/rules-pack-0.1.json +6 -6
  67. package/packs/skills/skills-pack-0.1.json +8 -8
  68. package/packs/strategies/strategies-pack-0.1.json +5 -5
  69. package/patterns/executor-layer-credentials.md +1 -1
  70. package/patterns/multi-agent.md +4 -4
  71. package/platforms/2600.md +2 -1
  72. package/platforms/unity.md +2 -1
  73. package/references/ip-risk.md +14 -19
  74. package/scm/changelog.md +1 -1
  75. package/scm/git.md +2 -1
  76. package/scm/github.md +10 -6
  77. package/skills/deft-directive-build/SKILL.md +7 -7
  78. package/skills/deft-directive-gh-slice/SKILL.md +1 -1
  79. package/skills/deft-directive-interview/SKILL.md +5 -5
  80. package/skills/deft-directive-pre-pr/SKILL.md +2 -2
  81. package/skills/deft-directive-refinement/SKILL.md +3 -3
  82. package/skills/deft-directive-release/SKILL.md +9 -9
  83. package/skills/deft-directive-setup/SKILL.md +3 -2
  84. package/skills/deft-directive-sync/SKILL.md +7 -7
  85. package/stage-pack.mjs +31 -0
  86. package/strategies/README.md +2 -1
  87. package/strategies/interview.md +1 -1
  88. package/strategies/research.md +1 -1
  89. package/strategies/speckit.md +2 -2
  90. package/strategies/v0-20-contract.md +2 -2
  91. package/swarm/swarm.md +2 -1
  92. package/tasks/vbrief.yml +6 -2
  93. package/tasks/verify.yml +40 -1
  94. package/templates/agent-prompt-preamble.md +2 -2
  95. package/templates/agents-entry.md +2 -2
  96. package/templates/make-spec.md +1 -1
  97. package/templates/swarm-greptile-poller-prompt.md +2 -2
  98. package/tools/package-manager-network.md +2 -1
  99. package/tools/taskfile-migration.md +2 -1
  100. package/tools/taskfile.md +2 -1
  101. package/tools/telemetry.md +2 -1
  102. package/vbrief/vbrief.md +1 -1
@@ -0,0 +1,150 @@
1
+ # Hook root admission — which tree a gated write is judged against (#3794 / #4013)
2
+
3
+ Every PreToolUse mutation carries **two** roots, and they are not interchangeable:
4
+
5
+ - **payload root** — the project root the host hook payload names.
6
+ - **effective root** — the working tree the write target actually lands in, chosen by
7
+ `admitEffectiveHookRoot` from the target path.
8
+
9
+ Deny messages name both: `payloadRoot=<path> effectiveRoot=<path>`.
10
+
11
+ This page publishes what root admission decides, which gate reads which root, and why the
12
+ no-toplevel case behaves the way it does. It describes shipped behaviour; it changes none of it.
13
+
14
+ ## The outcomes of root admission
15
+
16
+ | Target's nearest existing ancestor resolves to | Outcome | Effective root |
17
+ | --- | --- | --- |
18
+ | The payload root itself | admit | payload root |
19
+ | Another working tree sharing `--git-common-dir` with the payload root | admit | that worktree |
20
+ | Another Git toplevel, while the payload root is not a Git repository | admit — no containment question exists | payload root |
21
+ | Another Git toplevel whose `--git-common-dir` cannot be read | refuse `unproven-identity` | — (deny) |
22
+ | A proven different repository | refuse `foreign-repository` | — (deny) |
23
+ | **No Git toplevel at all** | **admit** | **payload root** |
24
+
25
+ Set level: `admitMutationTargetSet` admits every member of one mutation (the declared ApplyPatch
26
+ path plus every path in the patch body), short-circuits on the first refused member, and then
27
+ requires **one unique** effective root across the members. Two admitted roots is a
28
+ `worktree-span` refusal, so occupancy and ritual cannot follow only the declared path.
29
+
30
+ The last row is the subject of #4013. It fires for any destination whose nearest existing ancestor
31
+ directory is not inside a Git working tree — a path under the OS temp directory, a file in the home
32
+ directory, a scratch directory outside every checkout.
33
+
34
+ ## The no-toplevel case is deliberate
35
+
36
+ **A write target with no Git toplevel is gated against the payload root. That is intended, not a
37
+ gap.** #3794's acceptance text required falling back to the payload root when a target worktree
38
+ could not be admitted, and commit `bcd9d34e` deliberately split the two situations that fallback
39
+ used to hold: a target that resolves to *some other* toplevel whose identity cannot be read now
40
+ fails closed as `unproven-identity`, while a target with *no* toplevel keeps the payload-root
41
+ behaviour. "A question asked and left unanswered" is the rationale for the fail-closed branch. It
42
+ is not the rationale for this one — no containment question was ever posed.
43
+
44
+ Three measured reasons the fallback is not a lease bug (#4013, accepted successor lean
45
+ [5481139589](https://github.com/deftai/directive/issues/4013#issuecomment-5481139589)):
46
+
47
+ 1. **The effective root selects more than a lease.** It is the input to occupancy, the session
48
+ ritual, active scope, the story write fence and assist-scratch classification. "No root, no
49
+ gating" would drop all of those for an out-of-tree write, so an unritualed session — or an agent
50
+ acting on an absolute path it read in an issue — could write a home, config or temp file with no
51
+ ritual and no scope basis. It would also buy nothing on active scope, which already exempts
52
+ outside-root writes (#2885).
53
+ 2. **Occupancy and ritual are cross-checked, and the allow path re-stamps the lease.** The admitted
54
+ owner is compared against the session the verified ritual is bound to. Relaxing only the
55
+ foreign-lease refusal either leaves the writer blocked by that comparison, or suppresses it and
56
+ lets one session ride another's ceremony. The same path re-stamps `heartbeat_at` immediately
57
+ before an allowed write, so an unrelated out-of-tree write would renew the very lease a
58
+ narrowing meant to decouple.
59
+ 3. **A nullable root has no defined behaviour for a multi-target patch.** Target-set admission
60
+ demands one unique root. If an outside member contributed nothing, a patch mixing a
61
+ linked-worktree edit with an injected absolute path could collapse to the linked root and evade
62
+ the `worktree-span` refusal; if absence dominated instead, one outside member would suppress the
63
+ gates for the in-tree edit beside it. Either way an untrusted patch path becomes an
64
+ authority-selection input — the containment risk #3794 closed.
65
+
66
+ The friction is real: a foreign lease holder should not block an unrelated note under the OS temp
67
+ directory. Relieving it costs a change to the authority contract, not a bug fix. See
68
+ [What a narrowing would have to define](#what-a-narrowing-would-have-to-define).
69
+
70
+ ## What still runs for a no-toplevel target, gate by gate
71
+
72
+ | Gate | Root it reads | Disposition |
73
+ | --- | --- | --- |
74
+ | Occupancy lease | effective root (= payload root here) | **Runs.** A live foreign lease on the payload root denies `occupancy-occupied`; the owner or a granted member is admitted. |
75
+ | Session ritual, gated tier | effective root (= payload root here) | **Runs**, including the occupancy↔ritual owner cross-check and the pre-allow re-check. |
76
+ | Authz / UAT grant scoping | payload root | **Payload-root authoritative**, unchanged by admission. |
77
+ | Authz audit trail | payload root | **Payload-root authoritative.** |
78
+ | Kill-switch (`.deft-directive-disable`) | payload root | **Payload-root authoritative.** A flag beside the target does not disable the gate. |
79
+ | `deny().projectRoot` | payload root | **Payload-root authoritative**, so a deny is reported against the tree the host named. |
80
+ | Mutation intent ceiling (#1193) | neither | **Runs independently** of both roots. |
81
+ | Read-only posture | neither | **Runs.** |
82
+ | Runtime authority / path write fence | project policy from the payload root; story `file_scope` and the path it matches from the effective root | **Runs** when enabled. |
83
+ | Active scope | effective root | **Inspected, deny skipped.** The not-ready deny is skipped by the #2885 outside-root carve-out, measured from the payload root. Spawn tools have no write target and still require scope. |
84
+ | Assist-scratch allowlist | effective root | **No match.** An out-of-tree target is not under `.deft-scratch/` or `temp/` relative to the effective root, so the low-ceremony path does not apply. |
85
+
86
+ Read that table as the answer to "what does the lease actually protect here". The case is not a
87
+ lease question with one disposition; it is a root-selection question with a disposition per gate.
88
+
89
+ ## Three surfaces, three behaviours
90
+
91
+ The fallback is a **direct-write** behaviour. It does not generalise across transports.
92
+
93
+ | Surface | Example tool name | Reaches root admission? | Out-of-tree destination |
94
+ | --- | --- | --- | --- |
95
+ | Direct write, bare host name | `Write`, `Edit`, `search_replace`, `ApplyPatch` | **Yes** | Judged against the payload root — the fallback above. |
96
+ | Generic server-prefixed MCP | `tasks__search_replace` | **No** | Routed only through push/merge runtime classification; unrecognized there, so `shell-op-unclassifiable` (fail open). |
97
+ | Recognized Shell file-write | `Set-Content -Path <os-temp>/note.md …` | **No** | `isInRepoShellWritePath` rejects destinations resolving outside the project root, so the #3987 reissue path skips them (fail open). |
98
+
99
+ Catalogued MCP push/merge names (`git_push`, `merge_pull_request`, …) *are* gated — by runtime
100
+ authority scopes, which is a different gate and does not consult root admission either.
101
+
102
+ ⊗ Do not state that the payload-root fallback fires "on the direct-write and MCP surfaces". #4013's
103
+ issue body said that and it is wrong for generic MCP. Three surfaces, three behaviours.
104
+
105
+ ## Known limitation — relative targets have no declared base (#4023)
106
+
107
+ `existingAncestorDir` resolves the write target with `resolve(targetPath)` and no base argument, so
108
+ a **relative** target is canonicalized against the hook **process** working directory rather than
109
+ the payload root or a host-supplied cwd. The same lexical target can therefore land in different
110
+ rows of the first table depending only on where the hook runs. Symlink and Windows-junction targets
111
+ shift the class the same way: a junction into a linked worktree admits that worktree, a junction
112
+ into a non-repository directory falls back to the payload root.
113
+
114
+ Recorded, not fixed by #4013. Canonicalization changes admission classification, so it needs its own
115
+ arc: **#4023**.
116
+
117
+ ## What a narrowing would have to define
118
+
119
+ A narrowing is refused *as filed*, not forever. A viable successor design would:
120
+
121
+ 1. Name a target-class lattice: same admitted worktree, same-repository other worktree, foreign
122
+ repository, unproven identity, proven no-toplevel.
123
+ 2. State the combining rule for every mixed target set, so `admitMutationTargetSet` still yields one
124
+ answer.
125
+ 3. Keep the `foreign-repository`, `unproven-identity` and `worktree-span` refusals ahead of any
126
+ exemption.
127
+ 4. State, per gate in the table above, whether the exemption changes that gate — including whether a
128
+ no-toplevel write may refresh any worktree lease, and whose ritual satisfies it.
129
+ 5. Canonicalize targets first (#4023).
130
+
131
+ None of that would make #4013's filed premise — that the fallback is a defect — true.
132
+
133
+ ## Scope of this guarantee
134
+
135
+ Root admission is **cooperative host-session routing**, the same posture as the occupancy lease it
136
+ selects: hook payloads and local session ids are forgeable by a same-user process. It bounds
137
+ careless cross-tree writes; it is not an authentication boundary against an adversarial agent. See
138
+ [`contracts/path-write-fence.md`](../contracts/path-write-fence.md) for the same limit stated for
139
+ the write fence.
140
+
141
+ ## Code
142
+
143
+ | Concern | Where |
144
+ | --- | --- |
145
+ | Root admission, single target and target set | `packages/core/src/hooks/dispatcher.ts` — `admitEffectiveHookRoot`, `admitMutationTargetSet` |
146
+ | Which gate reads which root | `packages/core/src/hooks/dispatcher.ts` — `inspectMutationGates` header comment |
147
+ | Nearest existing ancestor, toplevel and common-dir lookups | `packages/core/src/session/git.ts` — `existingAncestorDir`, `worktreePathOrNull`, `gitCommonDir` |
148
+ | Shell write-dest classification and the in-repo predicate | `packages/core/src/hooks/shell-write-targets.ts` — `isInRepoShellWritePath` |
149
+ | Tool-name surfaces | `packages/core/src/hooks/tools.ts` — `isDirectWriteTool`, `isShellTool`, `isMcpTool` |
150
+ | Behaviour lock | `packages/core/src/hooks/dispatcher-effective-root.test.ts` |
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/docs/host-surface-assumptions.md" -->
1
2
  # Host-surface assumptions: file gates vs REPL / self-mutating hosts (#3162)
2
3
 
3
4
  Directive control surfaces assume two host behaviors that **some** modern agent hosts break by design.
@@ -115,6 +116,8 @@ Where the host supports hooks, **deny writes to directive-managed paths** withou
115
116
  | **Tier-1 host hooks** | PreToolUse / equivalent intercept Write/Edit/Shell that target managed paths (AGENTS managed section, deposit, pin skill paths the host exposes) |
116
117
  | **Path write fence** | Project + story `file_scope` evaluation — [`path-write-fence.md`](../contracts/path-write-fence.md) |
117
118
  | **Agent hook readiness** | `deft verify:hooks-installed --scope=agent --live` — [`agent-hook-readiness.md`](../contracts/agent-hook-readiness.md) |
119
+ | **Host tool-surface coverage** | Which tool names each host's matcher actually selects, and which are recorded out of scope — [`host-tool-surface-audit.md`](./host-tool-surface-audit.md) (#3987) |
120
+ | **Root admission** | Which working tree a gated write is judged against, gate by gate, and which surfaces consult it — [`hook-root-admission.md`](./hook-root-admission.md) (#3794 / #4013) |
118
121
  | **Policy** | `plan.policy.hostHooks.<host>` — inspect with `deft policy:show --field=hostHooks` |
119
122
 
120
123
  ! When hooks are available, treat managed-path deny as the enforcement home for constitution bytes — not agent good behavior alone.
@@ -131,7 +134,7 @@ Named hook home for managed-path deny: **Tier-1 host hooks** (`packages/core/src
131
134
 
132
135
  | Topic | Where |
133
136
  |-------|--------|
134
- | Self-improving, not self-editing stance | [main.md § Self-Improving, Not Self-Editing (#3164)](../../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md) |
137
+ | Self-improving, not self-editing stance | [main.md § Self-Improving, Not Self-Editing (#3164)](../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md) |
135
138
  | Gate integrity (do not clear red by editing the gate) | [gate-integrity.md](./gate-integrity.md) (#3156); refine-internal #2436 separate |
136
139
  | Skill pin tiers (process pins, not full #830) | [skill-pin-policy.md](./skill-pin-policy.md) |
137
140
  | Host lifecycle duty list | [host-lifecycle-duties.md](../contracts/host-lifecycle-duties.md) |
@@ -0,0 +1,163 @@
1
+ # Host tool-surface audit (#3987)
2
+
3
+ The PreToolUse write gate only runs on tool names the host's deposited matcher
4
+ selects. A name nobody listed is not a permissive policy — it is a gate that
5
+ never executes. #3987 was exactly that: Grok Build's shell tool
6
+ `run_terminal_command` was in no matcher, so a write the gate refused could be
7
+ reissued through the shell and land unobserved.
8
+
9
+ This file is the recorded answer to acceptance item 2: **for every tool name a
10
+ supported host emits, either the deposited matcher covers it, or there is a
11
+ written reason it stays out of scope.** The machine-readable form is
12
+ `HOST_TOOL_SURFACE_AUDIT` in `packages/core/src/hooks/tools.ts`, and
13
+ `deft verify:hooks-installed --scope=agent` fails closed on any silence in it.
14
+
15
+ Legend (RFC2119): `!`=MUST, `~`=SHOULD, `⊗`=MUST NOT.
16
+
17
+ ---
18
+
19
+ ## Two layers that can disagree
20
+
21
+ | Layer | Form | Where |
22
+ |-------|------|-------|
23
+ | Deposited matcher | literal `a\|b\|c` alternation the host matches against | `.claude/settings.json`, `.grok/hooks/deft.json`, `.cursor/hooks.json`, `.codex/hooks.json` |
24
+ | Runtime classifier | lowercases and strips non-alphanumerics, then set-membership | `isDirectWriteTool` / `isShellTool` / `isSpawnTool` |
25
+
26
+ Both read the same constants, so they normally agree — but a name can be
27
+ present in one and useless in the other. A matcher entry the classifier does not
28
+ recognize invokes the hook and then falls through to `not-direct-write`; a
29
+ classifier entry the deposit omits is never reached at all. The coverage check
30
+ asserts **both** for every catalogued name, so a fix has to say which layer it
31
+ changed.
32
+
33
+ ---
34
+
35
+ ## Grok Build — fully observed
36
+
37
+ Source: this host's published tool list, plus the 5,354-call session census
38
+ recorded on [#3987](https://github.com/deftai/directive/issues/3987).
39
+
40
+ | Tool | Disposition |
41
+ |------|-------------|
42
+ | `write` | covered — direct write |
43
+ | `search_replace` | covered — direct write |
44
+ | `run_terminal_command` | covered — shell (#3990) |
45
+ | `monitor` | covered — shell; it runs an arbitrary background shell command, so omitting it reproduces the #3987 gap one tool over |
46
+ | `spawn_subagent` | covered — spawn |
47
+ | `read_file`, `grep`, `list_dir`, `search_tool`, `web_search`, `web_fetch` | out of scope — read |
48
+ | `todo_write` | out of scope — session-local non-product scratch |
49
+ | `get_command_or_subagent_output`, `wait_commands_or_subagents` | out of scope — poll over already-dispatched work |
50
+ | `kill_command_or_subagent` | out of scope — process control |
51
+ | `scheduler_delete`, `scheduler_list` | out of scope — scheduler control and read; mutate no product path |
52
+ | `enter_plan_mode`, `exit_plan_mode` | out of scope — session posture |
53
+ | `image_gen`, `image_edit`, `image_to_video`, `reference_to_video` | out of scope — generated media lands in session scratch, never a tracked product path |
54
+ | `scheduler_create` | **known gap, not covered** — see below |
55
+ | `use_tool` | **known gap, not covered** — see below |
56
+
57
+ ### `scheduler_create` — spawn-class, needs a policy decision
58
+
59
+ It dispatches a background subagent on an interval, which is spawn-class by the
60
+ same reading that puts `spawn_subagent` in the matcher. Covering it routes a
61
+ scheduling primitive through the full spawn stack (session ritual plus an active
62
+ xBRIEF), which denies a shape that works today — a new deny class, not a
63
+ coverage repair. That is a deliberate policy call and belongs in its own change,
64
+ not in a matcher edit.
65
+
66
+ ### `use_tool` — mcp-class, needs a classifier change
67
+
68
+ `use_tool` is a proxy: the MCP tool actually invoked is nested in
69
+ `tool_input.tool_name`, and the dispatcher classifies on the outer name only. A
70
+ matcher entry alone would buy a hook invocation and no enforcement, because
71
+ `isMcpTool("use_tool")` is false and `classifyMcpTool` returns null. Reading the
72
+ inner name is a classifier change with its own untrusted-input surface.
73
+
74
+ ---
75
+
76
+ ## Claude, Codex, Cursor — partly or wholly unobserved
77
+
78
+ The deposit asserting a spelling is **not** evidence that the host emits it. All
79
+ four deposits carry the identical shell matcher, which is a matcher-string fact,
80
+ not a coverage fact ([#3987 comment 5471374558](https://github.com/deftai/directive/issues/3987#issuecomment-5471374558) F8).
81
+
82
+ | Host | Established | Unobserved |
83
+ |------|-------------|------------|
84
+ | Claude Code | shell `Bash` | direct-write and spawn spellings |
85
+ | Codex | shell `shell`; `apply_patch` write form (#3614) | everything else |
86
+ | Cursor | nothing | the whole surface |
87
+
88
+ Cursor is the one that matters: nothing in this tree observes which tool names
89
+ it emits on `preToolUse`. The fixture corpus asserts the framework's own
90
+ assumption, and `agent-hooks.test.ts` asserts the deposit contains a string.
91
+ Neither observes the host. Cursor could be a second zero-coverage host by the
92
+ exact mechanism that produced #3987, and the evidence available here would not
93
+ distinguish that from working coverage.
94
+
95
+ ! Closing these requires an **observed** PreToolUse payload per host, not a
96
+ re-reading of the deposit.
97
+
98
+ ⊗ Fill a host's catalog from the deposit, the matcher constants, or the fixture
99
+ corpus. That makes the check assert its own input and hides the gap it exists
100
+ to find.
101
+
102
+ ---
103
+
104
+ ## The check
105
+
106
+ `deft verify:hooks-installed --scope=agent` (also run by `deft doctor`) fails
107
+ closed on four silences:
108
+
109
+ | Finding | Meaning |
110
+ |---------|---------|
111
+ | `missing-audit` | a supported host has no entry here at all — the "new host drops out of coverage" case |
112
+ | `uncovered-tool` | a catalogued mutation name is absent from every **deposited** matcher — the "renamed tool" case, read from the file rather than regenerated |
113
+ | `unclassified-tool` | a catalogued name the runtime classifier does not place in the same group |
114
+ | `unexplained-entry` | an out-of-scope entry with no reason, a host claiming full observation while naming no mutation tool, or a non-mutation entry the classifier actually gates |
115
+
116
+ A hand-edited deposit reads as **stale** first, and `deft update` is the remedy
117
+ for that; the coverage message is for gaps a current deposit still leaves.
118
+
119
+ Implementation: `packages/core/src/init-deposit/host-tool-coverage.ts`.
120
+ Catalog: `packages/core/src/hooks/tools.ts`.
121
+
122
+ ---
123
+
124
+ ## Why coverage alone did not close #3987
125
+
126
+ Matcher coverage is necessary and not sufficient — all three seats of the #3987
127
+ panel agreed. Coverage closes the reissue bypass. It does not keep the occupancy
128
+ lease alive, because the lease renews on a **gated write**, and eligibility
129
+ keeps almost all real shell traffic off that path: five recognized write verbs,
130
+ no dest carrying `$` / `*` / `?`, nothing out of root or in OS temp, and any
131
+ compound command refused before the gate. `cd <root>; <command>` is compound,
132
+ and the mandated Windows body-file flow (#2646 / #2744) fails all three
133
+ conditions at once.
134
+
135
+ The renewal half is `restampOwnerLivenessOnHookEvent`
136
+ (`packages/core/src/hooks/owner-liveness.ts`): the hook already resolves the
137
+ owner from the host payload, so a matched tool call renews the lease even when
138
+ it writes nothing. It is bounded to a host-authoritative owner, keeps
139
+ `markWrite` false, and never touches `claimed_at`, so `OCCUPANCY_MAX_LEASE_MS`
140
+ still ends the lease at 12 hours. It renews the tree the mutation gates
141
+ authorized against — a linked worktree under `.deft-scratch/`, not the payload
142
+ root — so a worktree write does not keep the primary checkout's lease alive
143
+ while the tree actually in use expires.
144
+
145
+ ! A call with **no write target** proves only the tree the host named.
146
+ `projectRootFromHookPayload` takes that from the payload's own `cwd`-class
147
+ fields, so a session working inside a worktree names the worktree and renews
148
+ it. When the host reports a root that is not the tree the work is in, and no
149
+ write target says otherwise, nothing is renewed. That is deliberate: renewing a
150
+ guessed tree would keep a lease alive for a tree nobody occupies, and reclaiming
151
+ an unused tree is what the TTL is for. `deft occupancy:heartbeat --session-id
152
+ <owner>` remains the explicit path for a long, quiet session.
153
+
154
+ ---
155
+
156
+ ## Cross-links
157
+
158
+ | Topic | Where |
159
+ |-------|-------|
160
+ | Gate integrity (do not clear red by editing the gate) | [`gate-integrity.md`](./gate-integrity.md) |
161
+ | Host honesty limits | [`host-surface-assumptions.md`](./host-surface-assumptions.md) |
162
+ | Agent hook readiness | [`agent-hook-readiness.md`](../contracts/agent-hook-readiness.md) |
163
+ | Occupancy lease and its bounds | `packages/core/src/session/occupancy.ts` |
@@ -35,6 +35,37 @@ aggregate unknown would break `--skip-gh`, offline runs, and fresh clones.
35
35
 
36
36
  ---
37
37
 
38
+ ## What each run is allowed to look at (#3893)
39
+
40
+ Mode decides *how* a verdict is reached. Scope decides *which briefs* are in
41
+ the question at all.
42
+
43
+ | | Bare / `--issue N` | `--changed-only` |
44
+ |---|---|---|
45
+ | Caller | doctor, manual runs, swarm finalize, after-merge DONE proof | `check:framework-source` / `check:consumer` merge chokepoint |
46
+ | Briefs evaluated | every running brief under `active/` | only briefs in the candidate's own diff against `origin/<deliveryBranch>` |
47
+ | On the delivery line | n/a | falls back to the full sweep -- this run *is* the delivery-tip check |
48
+ | Unresolvable git or base ref | n/a | falls back to the full sweep, never to a narrower scan |
49
+
50
+ The merge chokepoint was unscoped until #3893. Measured 2026-08-28: four
51
+ unrelated PRs failed the same two lanes on one stranded brief, one of them
52
+ passed an unchanged re-run once the strandings landed, and two single-brief
53
+ lifecycle PRs could not pass alone because each left the other orphan.
54
+
55
+ The pre-merge run was never preventive for its own candidate either: the gate
56
+ reads REST `merged_at`, and the candidate's linked PR still reads null while
57
+ that candidate's gate runs. The pre-merge signal for closeout residue is
58
+ `verify:pr-closeout-attestable` (#3781), which keys on the PR's structured
59
+ closing references rather than the branch diff.
60
+
61
+ The detector is unchanged. The linked-PR/open-issue signature still fails a
62
+ candidate whose own diff carries it.
63
+
64
+ ⊗ Do not read a `--changed-only` pass as repo-wide truth. Every run prints its
65
+ scope; `EvaluateResult.scope` carries the same thing structurally.
66
+
67
+ ---
68
+
38
69
  ## The freshness choice
39
70
 
40
71
  **Age bound plus re-validation, applied per mode.** Both options offered by
@@ -164,3 +195,5 @@ mode for callers.
164
195
  - [#3476](https://github.com/deftai/directive/issues/3476) — `verify:completed-tracked`
165
196
  - [#3752](https://github.com/deftai/directive/issues/3752) — the open-inventory mechanism reused here
166
197
  - [#3156](https://github.com/deftai/directive/issues/3156) — gate integrity; this was a deliberate gate-definition change
198
+ - [#3893](https://github.com/deftai/directive/issues/3893) — merge-chokepoint scoping; the named check-composition change
199
+ - [#3781](https://github.com/deftai/directive/issues/3781) — `verify:pr-closeout-attestable`, the pre-merge closeout signal
@@ -13,7 +13,7 @@ Pins are for **false-negative-sensitive process gates**, not for copying entire
13
13
  | Tier | Meaning | How the agent discovers it |
14
14
  |---|---|---|
15
15
  | **always-pin** | Named in AGENTS.md (managed or consumer unmanaged header) as a required `SKILL.md` load when a matching work type starts | Always-loaded AGENTS.md |
16
- | **on-demand** | Routed via Skills Index triggers in `REFERENCES.md` — scan Level-0, read Level-1 on match | Skills Index → `SKILL.md` |
16
+ | **on-demand** | Routed via Skills Index triggers (consumer: `npx deft packs:slice skills list`; maintainer: `REFERENCES.md`) — scan Level-0, read Level-1 on match | Skills Index → `SKILL.md` |
17
17
  | **reference-only** | External, rare, or maintainer-only corpora; no standing trigger routing | Explicit doc pointer only |
18
18
 
19
19
  ### Criteria for always-pin
package/events/README.md CHANGED
@@ -27,11 +27,11 @@ data-file-convention check ([#710](https://github.com/deftai/directive/issues/71
27
27
 
28
28
  - **`detection-bound`** — detectable from filesystem state alone (e.g. dirty
29
29
  tree, vBRIEF schema invalidity, version drift). Emitted via
30
- `scripts/_event_detect.py`. Detector lives at the call site documented in
30
+ `task lifecycle:event`. Detector lives at the call site documented in
31
31
  the entry's `trigger` field.
32
32
  - **`behavioral`** — requires runtime instrumentation (paired
33
33
  `session:interrupted` / `session:resumed`, `plan:approved`,
34
- `legacy:detected`). Emitted via `scripts/_events.py`, which manages 1:1
34
+ `legacy:detected`). Emitted via `task lifecycle:event`, which manages 1:1
35
35
  session-pair invariants and a JSONL append-only log at
36
36
  `<project_root>/.deft-cache/events.jsonl`.
37
37
 
@@ -48,29 +48,28 @@ conforming to `event-record.schema.json`:
48
48
  }
49
49
  ```
50
50
 
51
- `scripts/_event_detect.py::emit(name, payload)` validates against the full
51
+ `task lifecycle:event -- emit <name> ...` validates against the full
52
52
  registry (any registered name is accepted) and is silent by default; when the
53
53
  `DEFT_EVENT_LOG` environment variable points to a writable path, each
54
54
  emission is appended as a single JSON line.
55
55
 
56
- `scripts/_events.py::emit(name, payload)` validates against the
57
- `category="behavioral"` subset of the registry, generates a sortable event id
58
- for pairing semantics, enforces required-payload contracts, and persists to
59
- `<project_root>/.deft-cache/events.jsonl` (or a path injected via `log_path` /
56
+ Behavioral emits validate against the `category="behavioral"` subset of the
57
+ registry, generate a sortable event id for pairing semantics, enforce
58
+ required-payload contracts, and persist to
59
+ `<project_root>/.deft-cache/events.jsonl` (or a path injected via `--log` /
60
60
  `DEFT_EVENT_LOG`). The log lives under the already-gitignored `.deft-cache/`
61
61
  rather than `.deft/`, because `.deft/` is not blanket-gitignored on hybrid
62
62
  installs (`.deft/core/` is gitignored and reconstituted by `directive init`,
63
- per #1942 / #1465). Use this helper when emitting behavioral events from
64
- skills (`python -m scripts._events emit ...`).
63
+ per #1942 / #1465). Skills emit behavioral events with
64
+ `task lifecycle:event -- emit <name> ...`.
65
65
 
66
66
  ## Adding an event
67
67
 
68
68
  1. Append the entry to `registry.json` with the appropriate `category`,
69
69
  payload contract, trigger pointer, and consumer pointers (validate via
70
- `tests/cli/test_events.py` and `tests/cli/test_behavioral_events.py`).
71
- 2. Add the detection / emission call site in `scripts/` or the relevant
72
- surface (use `scripts/_event_detect.py` for detection-bound,
73
- `scripts/_events.py` for behavioral).
70
+ `packages/core/src/lifecycle/events.ts` tests).
71
+ 2. Add the detection / emission call site in the TypeScript lifecycle surface
72
+ (use `task lifecycle:event` for both detection-bound and behavioral events).
74
73
  3. Reference the event by name from at least one consumer (skill, task,
75
74
  script) so the surface stays usable -- the schema requires `consumers`
76
75
  to be a non-empty array.
package/glossary.md CHANGED
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/glossary.md" -->
1
2
  # Glossary
2
3
 
3
4
  The authoritative vocabulary for the Deft framework.
@@ -120,7 +121,7 @@ Canonical vocabulary for the xBRIEF lifecycle. **xBRIEF** / `xbrief/` is the sol
120
121
 
121
122
  - **Cross-scope dependency** -- Dependency between two scope xBRIEFs at `plan.metadata.dependencies` (array of dependency IDs). See [vbrief/vbrief.md](./vbrief/vbrief.md).
122
123
 
123
- - **Exit Commands** -- The seven deterministic `task scope:*` commands that transition a scope xBRIEF between lifecycle folders: `scope:promote`, `scope:activate`, `scope:complete`, `scope:cancel`, `scope:restore`, `scope:block`, `scope:unblock` (see [tasks/scope.yml](../tasks/scope.yml)).
124
+ - **Exit Commands** -- The seven deterministic `task scope:*` commands that transition a scope xBRIEF between lifecycle folders: `scope:promote`, `scope:activate`, `scope:complete`, `scope:cancel`, `scope:restore`, `scope:block`, `scope:unblock` (see [tasks/scope.yml](tasks/scope.yml)).
124
125
 
125
126
  - **Origin provenance** -- A `references` entry linking a scope xBRIEF to its origin issue / ticket / user-request. Required for ingestion dedup. See [vbrief/vbrief.md](./vbrief/vbrief.md).
126
127
 
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/incidents/README.md" -->
1
2
  # Incidents Library
2
3
 
3
4
  This directory captures real-world incidents that motivated specific Deft
@@ -53,7 +54,7 @@ Examples:
53
54
  - `2026-04-15-some-other-incident.md`
54
55
 
55
56
  Filenames use hyphens, not underscores -- this mirrors the project-wide
56
- filename convention in [`main.md`](../../main.md).
57
+ filename convention in [`main.md`](../main.md).
57
58
 
58
59
  ## When to add an entry
59
60
 
package/interfaces/cli.md CHANGED
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/interfaces/cli.md" -->
1
2
  # CLI Best Practices
2
3
 
3
4
  Opinionated patterns for command-line interfaces with AI agents.
@@ -110,4 +111,4 @@ else:
110
111
 
111
112
  ---
112
113
 
113
- **See also**: [python.md](../languages/python.md) | [typescript.md](../languages/typescript.md) | [tui.md](../interfaces/tui.md)
114
+ **See also**: [python.md](../languages/python.md) | [typescript.md](../languages/typescript.md) | [tui.md](tui.md)
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/6502-DASM.md" -->
1
2
  # 6502 + DASM Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  ## Scope
8
9
  - This guide covers:
package/languages/c.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/c.md" -->
1
2
  # C Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: C17/C23, CMake 3.25+, clang/GCC; Testing: Unity/cmocka; Analysis: clang-tidy, cppcheck, AddressSanitizer
8
9
 
package/languages/cpp.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/cpp.md" -->
1
2
  # C++ Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: C++20/23, CMake 3.25+, Catch2/GoogleTest, GSL (Guidelines Support Library); CLI: CLI11; TUI: FTXUI; Async: Asio/libcoro
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/csharp.md" -->
1
2
  # C# Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: C# 12+/.NET 8+ (LTS), SDK-style projects; Web: ASP.NET Core/Minimal APIs; Testing: xUnit + NSubstitute + FluentAssertions; Analysis: Roslyn Analyzers, StyleCop.Analyzers
8
9
 
package/languages/dart.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/dart.md" -->
1
2
  # Dart Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Dart 3.x+; Framework: Flutter (mobile/web) or standalone; Testing: `package:test` / `flutter_test`; Lint: `analysis_options.yaml` + custom_lint; Format: `dart format`; Docs: dartdoc
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/delphi.md" -->
1
2
  # Delphi / Object Pascal Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: Delphi 12+/Object Pascal, RAD Studio; GUI: VCL (Windows), FMX (cross-platform); DB: FireDAC; Testing: DUnitX
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/elixir.md" -->
1
2
  # Elixir Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Elixir 1.16+ / OTP 26+; Build: Mix; Testing: ExUnit; Lint: Credo; Format: `mix format`; Docs: ExDoc; Types: Dialyxir
8
9
 
package/languages/go.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/go.md" -->
1
2
  # Go Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  ## Standards
8
9
 
package/languages/java.md CHANGED
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/java.md" -->
1
2
  # Java Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: Java 21+ (LTS), Maven/Gradle; Web: Spring Boot 3+; Testing: JUnit 5 + Mockito + AssertJ; Analysis: Checkstyle, SpotBugs, Error Prone
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/javascript.md" -->
1
2
  # JavaScript Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md) | [telemetry.md](../tools/telemetry.md)
6
7
 
7
8
  **Stack**: ES2022+, Node 20+; Build: Vite/esbuild; Testing: Vitest/Jest; Lint: ESLint + Prettier; Web: React/Next.js
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/julia.md" -->
1
2
  # Julia Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Julia 1.10+; Packages: Pkg.jl; Testing: `Test` stdlib + Aqua.jl; Docs: Documenter.jl; Format: JuliaFormatter.jl; Lint: JET.jl / Aqua.jl
8
9
 
@@ -1,8 +1,9 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/languages/kotlin.md" -->
1
2
  # Kotlin Standards
2
3
 
3
4
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
4
5
 
5
- **⚠️ See also**: [main.md](../../main.md) | [PROJECT.md](../../PROJECT.md)
6
+ **⚠️ See also**: [main.md](../main.md) | [PROJECT.md](../../PROJECT.md)
6
7
 
7
8
  **Stack**: Kotlin 2.0+; Build: Gradle (Kotlin DSL); Testing: JUnit 5 + kotest; Lint: detekt; Format: ktfmt/ktlint; Docs: KDoc
8
9