@deftai/directive-content 0.107.0 → 0.109.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 (118) hide show
  1. package/QUICK-START.md +4 -3
  2. package/SKILL.md +9 -10
  3. package/Taskfile.yml +19 -4
  4. package/UPGRADING.md +29 -9
  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 +35 -15
  10. package/contracts/deposit-required-paths.json +26 -0
  11. package/contracts/design-critique.md +180 -16
  12. package/contracts/deterministic-questions.md +2 -1
  13. package/contracts/host-lifecycle-duties.md +1 -1
  14. package/contracts/path-write-fence.md +67 -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 +4 -1
  21. package/docs/freshness-contract.md +6 -1
  22. package/docs/gate-integrity.md +17 -2
  23. package/docs/getting-started.md +10 -11
  24. package/docs/hook-runtime-unavailable.md +54 -0
  25. package/docs/host-surface-assumptions.md +2 -1
  26. package/docs/orphan-active-verdict-basis.md +199 -0
  27. package/docs/skill-pin-policy.md +1 -1
  28. package/events/README.md +12 -13
  29. package/glossary.md +2 -1
  30. package/incidents/README.md +2 -1
  31. package/interfaces/cli.md +2 -1
  32. package/languages/6502-DASM.md +2 -1
  33. package/languages/c.md +2 -1
  34. package/languages/cpp.md +2 -1
  35. package/languages/csharp.md +2 -1
  36. package/languages/dart.md +2 -1
  37. package/languages/delphi.md +2 -1
  38. package/languages/elixir.md +2 -1
  39. package/languages/go.md +2 -1
  40. package/languages/java.md +2 -1
  41. package/languages/javascript.md +2 -1
  42. package/languages/julia.md +2 -1
  43. package/languages/kotlin.md +2 -1
  44. package/languages/markdown.md +2 -1
  45. package/languages/mermaid.md +2 -1
  46. package/languages/officejs.md +2 -1
  47. package/languages/python.md +2 -1
  48. package/languages/r.md +2 -1
  49. package/languages/rust.md +2 -1
  50. package/languages/sql.md +2 -1
  51. package/languages/swift.md +2 -1
  52. package/languages/typescript.md +2 -1
  53. package/languages/vba.md +2 -1
  54. package/languages/vhdl.md +2 -1
  55. package/languages/visual-basic.md +2 -1
  56. package/languages/zig.md +2 -1
  57. package/main.md +47 -44
  58. package/meta/code-field.md +2 -1
  59. package/meta/morals.md +2 -1
  60. package/meta/philosophy.md +3 -2
  61. package/meta/project.md +4 -3
  62. package/meta/ralph.md +2 -1
  63. package/meta/security.md +3 -2
  64. package/meta/versioning.md +2 -1
  65. package/package.json +3 -3
  66. package/packs/patterns/patterns-pack-0.1.json +1 -1
  67. package/packs/rules/rules-pack-0.1.json +6 -6
  68. package/packs/skills/skills-pack-0.1.json +11 -11
  69. package/packs/strategies/strategies-pack-0.1.json +5 -5
  70. package/patterns/executor-layer-credentials.md +1 -1
  71. package/patterns/multi-agent.md +4 -4
  72. package/platforms/2600.md +2 -1
  73. package/platforms/unity.md +2 -1
  74. package/references/ip-risk.md +14 -19
  75. package/scm/changelog.md +1 -1
  76. package/scm/git.md +2 -1
  77. package/scm/github.md +41 -7
  78. package/skills/deft-directive-build/SKILL.md +8 -8
  79. package/skills/deft-directive-cost/SKILL.md +7 -11
  80. package/skills/deft-directive-design-critique/SKILL.md +8 -1
  81. package/skills/deft-directive-design-critique/references/motion-shape.md +19 -0
  82. package/skills/deft-directive-gh-slice/SKILL.md +1 -1
  83. package/skills/deft-directive-interview/SKILL.md +15 -15
  84. package/skills/deft-directive-pre-pr/SKILL.md +2 -2
  85. package/skills/deft-directive-refinement/SKILL.md +3 -3
  86. package/skills/deft-directive-release/SKILL.md +19 -15
  87. package/skills/deft-directive-review-cycle/SKILL.md +33 -0
  88. package/skills/deft-directive-setup/SKILL.md +56 -24
  89. package/skills/deft-directive-swarm/references/core-ops.md +4 -0
  90. package/skills/deft-directive-swarm/references/core-phase-1-2.md +1 -1
  91. package/skills/deft-directive-swarm/references/core-phase-3.md +3 -1
  92. package/skills/deft-directive-swarm/references/core-phase-4.md +11 -8
  93. package/skills/deft-directive-swarm/references/host-cursor.md +1 -0
  94. package/skills/deft-directive-swarm/references/host-grok-build.md +1 -0
  95. package/skills/deft-directive-sync/SKILL.md +7 -7
  96. package/stage-pack.mjs +31 -0
  97. package/strategies/README.md +2 -1
  98. package/strategies/interview.md +1 -1
  99. package/strategies/research.md +1 -1
  100. package/strategies/speckit.md +2 -2
  101. package/strategies/v0-20-contract.md +2 -2
  102. package/swarm/swarm.md +2 -1
  103. package/tasks/engine.yml +2 -0
  104. package/tasks/occupancy.yml +24 -2
  105. package/tasks/prd.yml +4 -5
  106. package/tasks/session.yml +3 -3
  107. package/tasks/toolchain.yml +2 -2
  108. package/tasks/vbrief.yml +6 -2
  109. package/tasks/verify.yml +50 -1
  110. package/templates/agent-prompt-preamble.md +11 -4
  111. package/templates/agents-entry.md +8 -3
  112. package/templates/make-spec.md +1 -1
  113. package/templates/swarm-greptile-poller-prompt.md +2 -2
  114. package/tools/package-manager-network.md +2 -1
  115. package/tools/taskfile-migration.md +2 -1
  116. package/tools/taskfile.md +2 -1
  117. package/tools/telemetry.md +2 -1
  118. package/vbrief/vbrief.md +1 -1
@@ -39,6 +39,41 @@ A **partial local** root check aggregate still fails closed — the include must
39
39
 
40
40
  Root cause of red `greenfield-python-free-smoke` after #3145: the gate only inspected the root Taskfile, treated include-only greenfield as “no check composition,” and hard-failed (`exit 201` via #3188). That was a **gate false positive** for the intentional deposit shape, not a missing deposit wiring bug.
41
41
 
42
+ ## Merge-chokepoint gate scoping (#3893)
43
+
44
+ Some gates are repo-wide by default and must be **narrowed** when composed on a
45
+ merge chokepoint. `verify:orphan-active` is the first: composed unscoped it
46
+ fails a candidate for lifecycle residue another merge stranded, and N stranded
47
+ briefs make N single-brief lifecycle PRs mutually unmergeable.
48
+
49
+ The contract therefore records a required argument per gate
50
+ (`MERGE_CHOKEPOINT_SCOPED_GATE_ARGS`) and reports a check aggregate that lists
51
+ the gate without it:
52
+
53
+ ```yaml
54
+ check:consumer:
55
+ deps:
56
+ - task: verify:orphan-active
57
+ vars:
58
+ CLI_ARGS: "--changed-only"
59
+ ```
60
+
61
+ The check reads the dependency's effective `CLI_ARGS` value, not the raw entry
62
+ text, so the flag appearing in a comment, a sibling variable, or a descriptive
63
+ value does not satisfy it.
64
+
65
+ - **`--framework-source`: fail closed.** This repo owns its own composition, so
66
+ a regression to the unscoped form is a hard failure here.
67
+ - **Consumer deposits: warn.** `deft update` re-deposits the Taskfile; the
68
+ warning names the exact one-line repair in the meantime.
69
+ - **Aggregates that do not list the gate are silent.** Include-only greenfield
70
+ roots and orchestrator-body `check` tasks are unaffected.
71
+
72
+ This is a strengthening, not a relaxation: nothing about the detector, the
73
+ required-gate set, or any exit code is weakened. Repo-wide residue truth still
74
+ runs on the bare verb, at the delivery tip, and on the after-merge
75
+ `verify:orphan-active -- --issue N` DONE gate (#3429).
76
+
42
77
  ## Repair path
43
78
 
44
79
  1. Restore deposit Taskfiles: `deft update` (includes `tasks/verify.yml` under `.deft/core/`)
@@ -34,6 +34,8 @@ MUST evaluate this gate before automatic retry or re-dispatch.
34
34
  | `BLOCK_ELAPSED_BUDGET` | Wall-clock budget exhausted |
35
35
  | `BLOCK_TOOL_OR_TOKEN_BUDGET` | Tool-call (or host-token when telemetried) budget exhausted |
36
36
 
37
+ evaluateInFlight(ledger, input) applies the same elapsed budget to a run that is already queued or running. Pre-dispatch never sees that case: an active attempt is DENY_DUPLICATE_ACTIVE. In-flight elapsed is totalElapsedSeconds plus wall-clock since startedAt. Under budget returns ALLOW_RESUME. Exhausted returns BLOCK_ELAPSED_BUDGET. No second budget store.
38
+
37
39
  Once a **block** decision is emitted, automatic re-dispatch MUST stop until a
38
40
  declared resume condition is satisfied or an audited operator override is
39
41
  recorded. Persist the terminal handoff (`buildTerminalHandoff` /
@@ -102,7 +104,8 @@ task swarm:pre-dispatch -- --scope-id <story|issue|xbrief-id> --target-id <workt
102
104
  Default unit key: `scopeId` + `targetId` + `workflowId=drive-to:merge-ready`.
103
105
  Actions: `begin` (default), `complete` (`--status succeeded|failed|cancelled|blocked`), `cancel` (takeover step 1).
104
106
  Monitors MUST run begin before any peer implement spawn; spawn only on exit 0.
105
- Takeover: cancel prior attempt, then begin again — never concurrent dual active.
107
+ Takeover after REDISPATCH_OK: cancel prior attempt, then begin again — never concurrent dual active.
108
+ A killed worker stays `queued`/`running` until cancel; REDISPATCH_OK does not lift `DENY_DUPLICATE_ACTIVE` (#3730).
106
109
  Implementation: `packages/core/src/swarm/pre-dispatch.ts` + `task swarm:pre-dispatch`.
107
110
 
108
111
  ## Skill routing
@@ -35,10 +35,15 @@ When a mutation `session:start` (cold or re-arm) loads payload surfaces into
35
35
  runtime context, it binds the live generation:
36
36
 
37
37
  ```text
38
- .deft/session-binds/<safe-session-id>.json # multi-agent isolation (preferred)
38
+ .deft/session-binds/<sha256-slice>.json # multi-agent isolation (preferred)
39
39
  .deft/session-bind.json # default / last-bind convenience
40
40
  ```
41
41
 
42
+ The per-session record is named by a 24-hex SHA-256 slice of the session id, so
43
+ a directory listing carries no fragment of the id (#3768). Records written
44
+ before that rename also carried a 32-character id prefix; they are read-accepted
45
+ until the next bind supersedes them.
46
+
42
47
  Multi-agent hosts **must** pin session identity so one session cannot certify
43
48
  another as current:
44
49
 
@@ -1,3 +1,4 @@
1
+ <!-- deft:deposit-link-rewrite v=1 source="content/docs/gate-integrity.md" -->
1
2
  # Gate integrity — a failing gate must not be fixed by editing the gate (#3156)
2
3
 
3
4
  General product and process rule for Directive fix loops, refine loops, and quality-gate repair: **when a gate is red, clear red by fixing the work under test — not by mutating the gate.**
@@ -80,7 +81,7 @@ Field notes and parent framing: issue [#3156](https://github.com/deftai/directiv
80
81
  ## Discoverability
81
82
 
82
83
  - Pre-PR Diff phase checklist: [deft-directive-pre-pr](../skills/deft-directive-pre-pr/SKILL.md) (gate-integrity bullet).
83
- - Stance / propose-not-apply: [main.md § Self-Improving, Not Self-Editing (#3164)](../../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md).
84
+ - Stance / propose-not-apply: [main.md § Self-Improving, Not Self-Editing (#3164)](../main.md#self-improving-not-self-editing-3164), [philosophy.md](../meta/philosophy.md).
84
85
  - Verification outcomes: [verification.md](../verification/verification.md).
85
86
  - Goal/gate rigidity: [goal-gate-determinism.md](../patterns/goal-gate-determinism.md) (#852).
86
87
  - Scope self-auth instance: [scope-provenance.md](./scope-provenance.md) (#3145).
@@ -94,13 +95,27 @@ Full CI automation that blocks “diff touches a gate that just failed” withou
94
95
  | Topic | Where |
95
96
  |-------|--------|
96
97
  | Parent epic | [#3179](https://github.com/deftai/directive/issues/3179) |
97
- | Stance (propose-not-apply) | [#3164](https://github.com/deftai/directive/issues/3164), [main.md](../../main.md#self-improving-not-self-editing-3164) |
98
+ | Stance (propose-not-apply) | [#3164](https://github.com/deftai/directive/issues/3164), [main.md](../main.md#self-improving-not-self-editing-3164) |
98
99
  | Refine-internal SkillOpt | [#2436](https://github.com/deftai/directive/issues/2436) |
99
100
  | Fixed evaluator / agent-loop | [#782](https://github.com/deftai/directive/issues/782) |
100
101
  | Verification independence | [#1499](https://github.com/deftai/directive/issues/1499) |
101
102
  | Scope self-authorization | [#3145](https://github.com/deftai/directive/issues/3145), [scope-provenance.md](./scope-provenance.md) |
102
103
  | Host self-mutate honesty | [#3162](https://github.com/deftai/directive/issues/3162), [host-surface-assumptions.md](./host-surface-assumptions.md) |
103
104
  | Safety via formal gates | [#1200](https://github.com/deftai/directive/issues/1200) |
105
+ | Poisoned product-oracle history from a safety refusal | [#3615](https://github.com/deftai/directive/issues/3615), this page § Product-oracle history poisoned by a safety refusal |
106
+
107
+ ---
108
+
109
+ ## Product-oracle history poisoned by a safety refusal (#3615)
110
+
111
+ A safety-refused acceptance command is not a product measurement. `verify:ac` keeps the acceptance gate red, and it does not write `verification.outcome=fail` for an all-refused walk. A mixed walk that still has a refusal does not write a product-oracle pass.
112
+
113
+ Records already written still pair on `session_id` + `check_id`. A correct classifier going forward does not unwrite a `fail` already on disk. Recovery is an operator workaround, not independent re-derivation:
114
+
115
+ 1. Start a new Deft session (new `DEFT_SESSION_ID`) so the integrity layer does not pair against the poisoned fail, or
116
+ 2. Truncate or delete the run-summary JSONL (default `.deft-run-summary.json`, typically gitignored).
117
+
118
+ ⊗ Set `independent_rederivation=true` to clear this class. That flag asserts both sides were rebuilt from scratch. The refused side never executed.
104
119
 
105
120
  ---
106
121
 
@@ -16,21 +16,18 @@ Before the mechanics below, it helps to see the whole loop. Directive is two con
16
16
 
17
17
  ## Prerequisites
18
18
 
19
- - **Node 20+** and **pnpm** for live gates and the npm distribution channel (see `.nvmrc` in the framework payload).
19
+ - **Node 20+** and the package manager declared by the project (`npm` or `pnpm`) for live consumer gates and the npm distribution channel (see `.nvmrc` in the framework payload). npm is bundled with Node; a pnpm-pinned project enables pnpm through Corepack.
20
+ - **Git** and **GitHub CLI (`gh`)** are also checked by the consumer toolchain gate.
20
21
  - **Go 1.22+** only if you use the bootstrap Go installer or build from source.
21
- - **Python 3.11+**, **uv**, **task**, **git**, and **gh** for full framework workflows run `directive toolchain:check` (or `deft toolchain:check`) after install.
22
+ - **Python 3.11+**, **uv**, and **task** for the framework-maintainer and session workflows that name them. The consumer toolchain probe itself does not check those tools. Run `directive toolchain:check --consumer --project-root .` after install.
22
23
 
23
24
  ---
24
25
 
25
26
  ## Installation
26
27
 
27
- ### npm (coming soon — not yet published)
28
+ ### npm (canonical)
28
29
 
29
- > ⚠️ **The `@deftai/directive` npm package is not yet published** to the registry (provisioning tracked by [#1909](https://github.com/deftai/directive/issues/1909)). **Until it goes live, install via the [Go installer](#go-installer-bootstrap) below.** The commands shown here are what the install will become once the package is published.
30
-
31
- <!-- TODO(#1909): flip to npm-canonical and remove this "coming soon" notice when @deftai/directive is published -->
32
-
33
- Once published, when Node is already available you will install Directive globally:
30
+ With Node already available, install Directive globally from npm:
34
31
 
35
32
  ```bash
36
33
  npm i -g @deftai/directive
@@ -38,16 +35,18 @@ directive --version # primary command
38
35
  deft --version # alias — same binary
39
36
  ```
40
37
 
41
- One-shot without a global install (also once published):
38
+ One-shot without a global install:
42
39
 
43
40
  ```bash
44
41
  npx @deftai/directive doctor
45
42
  npx @deftai/directive session:start
46
43
  ```
47
44
 
48
- This npm path is the emerging primary distribution channel under `@deftai/directive` ([#11](https://github.com/deftai/directive/issues/11)); the Go installer below remains the install/bootstrap option today and during the staged retire window.
45
+ This npm path has been the canonical distribution channel since v0.55.1. A pnpm-managed repository can install the same package with `pnpm add -g @deftai/directive` or project-locally with `pnpm add -D @deftai/directive`.
46
+
47
+ ### Go installer (legacy bridge)
49
48
 
50
- ### Go installer (bootstrap)
49
+ The Go installer is a frozen legacy bridge for older installs and source-oriented recovery. New consumer installs should use npm above; see [UPGRADING.md](../UPGRADING.md#one-time-migration-from-the-go-installer-legacy--npm) when migrating an existing Go-installer layout.
51
50
 
52
51
  Download a platform installer from the [Directive release page](https://github.com/deftai/directive/releases) and run it from the project you want to adopt:
53
52
 
@@ -0,0 +1,54 @@
1
+ # Hook runtime unavailable: `deft-hook` is not on PATH
2
+
3
+ On a host that cannot execute `deft-hook`, the Cursor `preToolUse` registration is `failClosed: true`, so **every mutation is denied** — and because the binary never runs, no Directive code is left to say why. The visible symptom is an opaque non-zero exit, typically **127** (command not found).
4
+
5
+ Tracker: [#3785](https://github.com/deftai/directive/issues/3785). Related: [#3736](https://github.com/deftai/directive/issues/3736) (timeout), [#3571](https://github.com/deftai/directive/issues/3571) (unused-host recovery), [#2752](https://github.com/deftai/directive/issues/2752) (per-host toggles).
6
+
7
+ ## Who hits this
8
+
9
+ The registration travels via git; the runtime does not. `.cursor/hooks.json` is trackable by design and the `.deft/core/` deposit is born-ignored, so any environment that gets the repo without a Node install of the CLI inherits the fence without the implementation:
10
+
11
+ - cloud agent VMs whose image has no Node and no global install
12
+ - CI runners and containers that never run `npm i -g @deftai/directive`
13
+ - a fresh clone on a new workstation
14
+
15
+ ## Recovery is out of band
16
+
17
+ There is no in-session escape. Run this from a shell where Node and the CLI **are** available — your workstation, or the repo before you push it:
18
+
19
+ ```bash
20
+ deft policy:disable-host-hooks --host cursor --confirm
21
+ ```
22
+
23
+ That sets `plan.policy.hostHooks.cursor = false` in `xbrief/PROJECT-DEFINITION.xbrief.json`, and the next `deft update` strips the Cursor registration. Commit and push; the locked-out environment clears on its next pull or fresh clone.
24
+
25
+ ⚠ Capability cost: disabling `hostHooks` for a host removes `deft-hook` pre-execution guardrails for anyone who later opens this repo in that host. The change is tracked and recorded to `meta/policy-changes.log`. Inspect with `deft policy:show --field=hostHooks`; reverse by setting the host back to `true` and running `deft update`.
26
+
27
+ ## Preferred fix: make the runtime travel with the registration
28
+
29
+ If Cursor is a host you rely on, restore the runtime rather than removing the fence. Either:
30
+
31
+ - commit a `package.json` dependency on `@deftai/directive` — a published version or another spec a clone can fetch, since a location spec (`file:`, `link:`, `../directive`, an absolute path) resolves only on the machine that wrote it — and run `npm install` in the image or clone, which puts `deft-hook` in `node_modules/.bin`; or
32
+ - add `npm i -g @deftai/directive` to the image build.
33
+
34
+ `deft init` and `deft update` warn when a hook registration travels with the repository — already committed, or merely untracked and not ignored, so the next `git add` carries it — while neither anchor is present. The warning names the affected file and this document.
35
+
36
+ ## ⊗ Do not hand-edit `failClosed` in the deposited hook file
37
+
38
+ Setting `failClosed: false` in `.cursor/hooks.json` clears the block exactly once. That file is a managed deposit: the next `deft update` rewrites the entries with `failClosed: true` and **silently re-arms the lockout**, usually long after anyone remembers editing it. Use the policy verb above, which is durable and tracked.
39
+
40
+ ## Why the flags do not help
41
+
42
+ | Escape hatch | Why it does not reach this failure |
43
+ |---|---|
44
+ | `.deft-directive-disable` | Evaluated *inside* `deft-hook`. When the binary is missing, nothing reads the flag. |
45
+ | `.no-deft-directive` | Consulted at `session.start` only; it never reaches `preToolUse`. |
46
+ | Host-side "skip hook" | A host concept. Directive has no such bypass. |
47
+
48
+ ## Absent, crashed, and timed out are one class
49
+
50
+ The hook exit contract already decouples the exit code from the verdict: exit `0` means a decision was **rendered** — allow *or* deny — so every non-zero exit is by construction a *non-decision*. Absence, a crash, and a host timeout kill are the same state, and all three stay fail-closed.
51
+
52
+ They stay fail-closed because fail-open-on-absence is a bypass primitive, not a lenience: removing the binary is an ordinary shell call, so treating absence as allow would convert a self-inflicted denial into a bypass of the write fence, the intent ceiling, the session ritual, and the occupancy lease ([#3156](https://github.com/deftai/directive/issues/3156)). What these cases need is legibility and an out-of-band escape — this page — not a relaxed fence.
53
+
54
+ For a timeout specifically, retry the gated ritual first when machine load is the likely cause; the Cursor `tool.before` budget is sized for a gated ritual plus live readiness, so a repeated timeout is a real signal rather than noise.
@@ -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.
@@ -131,7 +132,7 @@ Named hook home for managed-path deny: **Tier-1 host hooks** (`packages/core/src
131
132
 
132
133
  | Topic | Where |
133
134
  |-------|--------|
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) |
135
+ | 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
136
  | Gate integrity (do not clear red by editing the gate) | [gate-integrity.md](./gate-integrity.md) (#3156); refine-internal #2436 separate |
136
137
  | Skill pin tiers (process pins, not full #830) | [skill-pin-policy.md](./skill-pin-policy.md) |
137
138
  | Host lifecycle duty list | [host-lifecycle-duties.md](../contracts/host-lifecycle-duties.md) |
@@ -0,0 +1,199 @@
1
+ # `verify:orphan-active`: verdict basis and freshness (#3767)
2
+
3
+ `verify:orphan-active` decides whether an `xbrief/active/` brief with
4
+ `plan.status == running` is really still live work. Until #3767 it answered
5
+ that question from a triage-cache hit returned **unconditionally** — no age
6
+ bound, no re-validation. A cached `open` written twelve hours earlier beat
7
+ reality and suppressed the live read that would have corrected it, so the gate
8
+ exited 0 while scanning the very brief whose issue had already closed.
9
+
10
+ This document records what the gate now does, why, and what it still cannot
11
+ promise.
12
+
13
+ Legend (RFC2119): `!`=MUST, `~`=SHOULD, `⊗`=MUST NOT, `?`=MAY.
14
+
15
+ ---
16
+
17
+ ## Two modes, split by query shape
18
+
19
+ The gate has two callers with different obligations, so it has two mechanisms.
20
+
21
+ | | Scoped `--issue N` | Unscoped aggregate |
22
+ |---|---|---|
23
+ | Caller | after-merge DONE proof (#3429 / #3476) | `task check`, pre-commit, pre-push sweep |
24
+ | Mechanism | authoritative per-issue REST read | one complete open-issue inventory |
25
+ | N | 1 | one call regardless of brief count |
26
+ | Unknown state | **fail closed** (exit 1, `unresolved`) | **fail open** (exit 0, reported `unverified`) |
27
+ | Latency budget | 5 s | 15 s |
28
+
29
+ The unknown asymmetry is deliberate and predates this change. Scoped is a proof
30
+ obligation before a worker claims DONE about one named origin. The aggregate
31
+ sweep must not make offline work network-authorized — hard-failing every
32
+ aggregate unknown would break `--skip-gh`, offline runs, and fresh clones.
33
+
34
+ ⊗ Do not flatten the two unknown policies into one rule.
35
+
36
+ ---
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
+
69
+ ## The freshness choice
70
+
71
+ **Age bound plus re-validation, applied per mode.** Both options offered by
72
+ #3767 are used, because each mode needs a different one.
73
+
74
+ - A cache hit is honoured only when it is at most **15 minutes** old
75
+ (`ISSUE_CACHE_MAX_AGE_MS`). Older entries are not evidence.
76
+ - Scoped `--issue N` re-validates first: it takes the authoritative read and
77
+ falls back to the cache only inside the age bound, when the live read is
78
+ unavailable. Unknown after that is `unresolved`.
79
+ - The aggregate sweep resolves from the open-issue inventory when the network
80
+ is allowed, and uses an in-bound cache hit only under `--skip-gh` or when the
81
+ inventory itself is unavailable.
82
+
83
+ 15 minutes keeps a warm-cache offline run working while making an overnight
84
+ entry non-authoritative — the measured entry was roughly twelve hours old.
85
+
86
+ ---
87
+
88
+ ## The aggregate inventory
89
+
90
+ The sweep reuses `restIssueListOpenInventory` (#3752): one
91
+ `gh api --paginate --slurp repos/<owner>/<repo>/issues?state=open&per_page=100`
92
+ subprocess. It excludes pull-request rows and **fails closed** on command
93
+ failure, non-JSON output, a non-array payload, a malformed row, buffer
94
+ exhaustion, and the pagination cap.
95
+
96
+ ! The inventory MUST be complete. `probeCacheDrift`'s helper defaults to a
97
+ 1,000-item limit; reusing that capped set as a closed-state oracle would
98
+ misclassify open issue 1001+ in a larger repository.
99
+
100
+ Membership in a successful inventory means open. **Absence means "not open"**,
101
+ which is the direction that tells an operator to run `scope:complete` on what
102
+ may be live work — so absence is confirmed by one authoritative per-issue read
103
+ before the gate acts on it. That confirming result is reused for the rest of
104
+ the run when several briefs name the same issue. In the ordinary case
105
+ (everything open) that costs zero extra calls.
106
+
107
+ When the inventory is unavailable the gate reports `unverified` rather than
108
+ inferring closed. Fail-closed here means never manufacturing a false "closed",
109
+ not turning the sweep into a connectivity check.
110
+
111
+ ### Why not a live read per brief
112
+
113
+ Per-brief live reads are the mechanism #3752 removed. Measured: the inventory
114
+ is **4.2 s constant**, against **14.2 s** and **76.6 s** for sequential
115
+ per-brief reads at the WIP cap of 20 on two different hosts. Crossover is about
116
+ 5 briefs on `gh` and about 1 on `ghx`. `verify:orphan-active` sits in the
117
+ fast-preflight tier, where wall clock rather than REST quota is the constraint.
118
+
119
+ ---
120
+
121
+ ## Reported basis
122
+
123
+ Every run now says how it decided, so a verified pass is distinguishable from
124
+ an unverified one:
125
+
126
+ ```text
127
+ verify:orphan-active: no orphaned active/running xBRIEFs (scanned 3 running briefs in active/).
128
+ Basis: inventory 2, cache 1 (max age 4m).
129
+ ```
130
+
131
+ ```text
132
+ verify:orphan-active: no orphaned active/running xBRIEFs (scanned 1 running brief in active/).
133
+ Basis: unverified 1.
134
+ UNVERIFIED: state could not be established for the references below, so this run is
135
+ not evidence that they are unshipped:
136
+ - #8001 (open-issue inventory unavailable: gh api failed: ...)
137
+ ```
138
+
139
+ `EvaluateResult.basis` carries the same counts structurally
140
+ (`inventory`, `live`, `cache`, `unverified`, `maxCacheAgeMs`, `proxied`,
141
+ `elapsedMs`, `budgetMs`).
142
+
143
+ ⊗ Do not cite an exit 0 with `unverified > 0` as evidence that a tree is clean.
144
+
145
+ ---
146
+
147
+ ## The `ghx` caveat
148
+
149
+ "Live" is itself a cache. `defaultRunGh` resolves through `resolveBinary()`,
150
+ which prefers **`ghx`**, a cached read-only GET proxy whose age nothing in this
151
+ gate can inspect.
152
+
153
+ This gate therefore **pins plain `gh`** for its authoritative reads when `gh` is
154
+ on PATH. When only `ghx` is available the gate still runs, sets
155
+ `basis.proxied`, and prints:
156
+
157
+ ```text
158
+ Note: reads resolved through `ghx`, a cached GET proxy; freshness is bounded by that
159
+ proxy, which this gate cannot inspect (#3737).
160
+ ```
161
+
162
+ ⊗ Do not claim the gate detects a closed origin "regardless of cache age" while
163
+ `ghx` is in the path. Whether `resolveBinary` should prefer `ghx` at all is
164
+ [#3737](https://github.com/deftai/directive/issues/3737).
165
+
166
+ ---
167
+
168
+ ## Latency budgets
169
+
170
+ There were none before #3767, which is why "affordable" was unfalsifiable.
171
+
172
+ | Mode | Budget | Basis |
173
+ |---|---|---|
174
+ | Scoped `--issue N` | 5 s | one issue read plus at most one linked-PR read, measured ~0.75 s each |
175
+ | Unscoped aggregate | 15 s | one 4.2 s inventory plus confirming reads for apparent closes |
176
+
177
+ Exceeding a budget prints an advisory line and does **not** change the exit
178
+ code. Budget drift is a signal to re-measure the mechanism, not a new failure
179
+ mode for callers.
180
+
181
+ ---
182
+
183
+ ## Out of scope
184
+
185
+ - The offline crash when neither `gh` nor `ghx` is on PATH
186
+ ([#3774](https://github.com/deftai/directive/issues/3774)).
187
+ - Whether `resolveBinary` should prefer `ghx`
188
+ ([#3737](https://github.com/deftai/directive/issues/3737)).
189
+ - The `cache_fresh` forge-error fail-open decided on
190
+ [#3738](https://github.com/deftai/directive/issues/3738).
191
+
192
+ ## Related
193
+
194
+ - [#3429](https://github.com/deftai/directive/issues/3429) — the gate's contract
195
+ - [#3476](https://github.com/deftai/directive/issues/3476) — `verify:completed-tracked`
196
+ - [#3752](https://github.com/deftai/directive/issues/3752) — the open-inventory mechanism reused here
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