@deftai/directive-content 0.95.0 → 0.97.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 (42) hide show
  1. package/QUICK-START.md +2 -0
  2. package/Taskfile.yml +28 -0
  3. package/UPGRADING.md +69 -0
  4. package/commands.md +30 -5
  5. package/contracts/test-boundary.md +18 -0
  6. package/docs/consumer-check-contract.md +27 -0
  7. package/docs/consumer-issue-label-kit.md +211 -0
  8. package/docs/delivery-attempt.md +173 -0
  9. package/docs/getting-started.md +2 -0
  10. package/docs/openclaw-agent-host.md +58 -7
  11. package/docs/operator-log-hygiene-checklist.md +57 -0
  12. package/docs/operator-log-hygiene-consumer-pack-stub.md +75 -0
  13. package/docs/scope-provenance.md +54 -0
  14. package/docs/test-boundary.md +43 -0
  15. package/main.md +29 -0
  16. package/package.json +1 -1
  17. package/packs/patterns/patterns-pack-0.1.json +10 -0
  18. package/packs/skills/skills-pack-0.1.json +8 -11
  19. package/patterns/operator-log-hygiene.md +130 -0
  20. package/scm/github.md +35 -3
  21. package/skills/deft-directive-article-review/SKILL.md +17 -6
  22. package/skills/deft-directive-build/SKILL.md +41 -0
  23. package/skills/deft-directive-refinement/SKILL.md +1 -3
  24. package/skills/deft-directive-review-cycle/SKILL.md +166 -12
  25. package/skills/deft-directive-swarm/SKILL.md +36 -11
  26. package/skills/deft-directive-swarm/references/core-ops.md +16 -3
  27. package/skills/deft-directive-swarm/references/core-phase-0.md +40 -15
  28. package/skills/deft-directive-swarm/references/core-phase-3.md +18 -9
  29. package/skills/deft-directive-swarm/references/core-phase-4.md +21 -2
  30. package/skills/deft-directive-swarm/references/core-phase-5-6.md +18 -1
  31. package/skills/deft-directive-swarm/references/host-claude-code.md +86 -0
  32. package/skills/deft-directive-swarm/references/host-cursor.md +1 -1
  33. package/skills/deft-directive-swarm/references/host-openclaw.md +12 -4
  34. package/skills/deft-directive-triage/SKILL.md +19 -6
  35. package/skills/deft-directive-xbrief/SKILL.md +0 -1
  36. package/tasks/cache.yml +31 -0
  37. package/tasks/engine-invoke.cjs +22 -17
  38. package/tasks/engine-invoke.test.cjs +30 -0
  39. package/tasks/verify.yml +30 -0
  40. package/templates/agent-prompt-preamble.md +15 -4
  41. package/templates/agents-entry.md +11 -0
  42. package/templates/swarm-greptile-poller-prompt.md +4 -2
package/tasks/cache.yml CHANGED
@@ -77,3 +77,34 @@ tasks:
77
77
  - task: :engine:invoke
78
78
  vars:
79
79
  ENGINE_CMD: 'cache prune {{.CLI_ARGS}}'
80
+
81
+ # #1137 — reversible closed-entry archive (NOT TTL prune; never auto-wired)
82
+ archive-closed:
83
+ desc: "Reversible archive of closed github-issue cache entries (#1137). -- task triage:cache-archive -- [--dry-run] [--older-than-days 30] [--repo OWNER/NAME] [--json]"
84
+ dir: '{{.USER_WORKING_DIR}}'
85
+ deps:
86
+ - task: :engine:_ts-build
87
+ cmds:
88
+ - task: :engine:invoke
89
+ vars:
90
+ ENGINE_CMD: 'cache archive-closed --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
91
+
92
+ archive-list:
93
+ desc: "List archived github-issue cache entries (#1137). -- task triage:archive-list -- [--repo OWNER/NAME] [--format=json] [--since ISO] [--limit N]"
94
+ dir: '{{.USER_WORKING_DIR}}'
95
+ deps:
96
+ - task: :engine:_ts-build
97
+ cmds:
98
+ - task: :engine:invoke
99
+ vars:
100
+ ENGINE_CMD: 'cache archive-list --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
101
+
102
+ restore-from-archive:
103
+ desc: "Restore archived github-issue entry to live cache (#1137). -- task triage:restore-from-archive -- --issue N [--repo OWNER/NAME] [--force]"
104
+ dir: '{{.USER_WORKING_DIR}}'
105
+ deps:
106
+ - task: :engine:_ts-build
107
+ cmds:
108
+ - task: :engine:invoke
109
+ vars:
110
+ ENGINE_CMD: 'cache restore-from-archive --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
@@ -113,12 +113,13 @@ function main() {
113
113
  // stdio inherit (not pipe): piped stdout/stderr deadlocks when the child emits
114
114
  // more than the OS pipe buffer before exit — observed as greenfield smoke
115
115
  // hanging then CI SIGTERM exit 143 with no output (#2554 / #2547).
116
+ // shell:false is a literal at the call site (not plan.shell) so static
117
+ // analyzers see a non-shell spawn; win32 global still uses a quoted cmd.exe
118
+ // wrapper inside buildSpawnPlan, never shell:true (#2911 / #3175).
116
119
  const result = spawnSync(plan.command, plan.args, {
117
120
  stdio: "inherit",
118
121
  env: childEnv,
119
- // Never shell:true — even on win32 global (subprocess-scm-01 / #2911). The
120
- // win32 .cmd shim is reached through a tightly quoted cmd.exe wrapper below.
121
- shell: plan.shell,
122
+ shell: false,
122
123
  // CREATE_NO_WINDOW: hide console windows from Cursor Task / nested shells (#2563).
123
124
  windowsHide: true,
124
125
  });
@@ -135,6 +136,11 @@ function main() {
135
136
  * quoted so metacharacters stay inside a single argv token — aligned with
136
137
  * tasks/engine-pm-run.cjs executeAllowlisted().
137
138
  *
139
+ * Vendored vs global branches are deliberately disjoint (#3175 / CodeQL alert
140
+ * #74): process.execPath (AbsolutePathSource) is only the vendored non-shell
141
+ * command and must never flow into the win32 cmd.exe `/c` string, where it
142
+ * would be shell-interpreted as part of a constructed command line.
143
+ *
138
144
  * @param {string} mode
139
145
  * @param {string} target
140
146
  * @param {string[]} argv
@@ -143,26 +149,25 @@ function main() {
143
149
  */
144
150
  function buildSpawnPlan(mode, target, argv, opts = {}) {
145
151
  const platform = opts.platform || process.platform;
146
- const nodePath = opts.nodePath || process.execPath;
147
152
 
148
- let execPath;
149
- let execArgv;
150
153
  if (mode === "vendored") {
151
- execPath = nodePath;
152
- execArgv = [target, ...argv];
153
- } else if (mode === "global") {
154
- execPath = target;
155
- execArgv = argv;
156
- } else {
157
- return null;
154
+ // Non-shell: Node binary + script path + operator argv. process.execPath is
155
+ // only used here as the executable name with shell:false — never joined into
156
+ // a cmd.exe command line (CodeQL js/shell-command-injection-from-environment).
157
+ const nodePath = opts.nodePath || process.execPath;
158
+ return { command: nodePath, args: [target, ...argv], shell: false };
158
159
  }
159
160
 
160
- if (mode === "global" && platform === "win32") {
161
- const commandLine = [execPath, ...execArgv].map(quoteWin32Arg).join(" ");
162
- return { command: "cmd.exe", args: ["/d", "/s", "/c", commandLine], shell: false };
161
+ if (mode === "global") {
162
+ if (platform === "win32") {
163
+ // Only the global shim name/path and operator argv no process.execPath.
164
+ const commandLine = [target, ...argv].map(quoteWin32Arg).join(" ");
165
+ return { command: "cmd.exe", args: ["/d", "/s", "/c", commandLine], shell: false };
166
+ }
167
+ return { command: target, args: argv, shell: false };
163
168
  }
164
169
 
165
- return { command: execPath, args: execArgv, shell: false };
170
+ return null;
166
171
  }
167
172
 
168
173
  if (require.main === module) {
@@ -186,3 +186,33 @@ describe("buildSpawnPlan — other paths keep shell:false", () => {
186
186
  assert.equal(buildSpawnPlan("bogus", "deft", ["release"], WIN32), null);
187
187
  });
188
188
  });
189
+
190
+ describe("buildSpawnPlan — CodeQL absolute-path isolation (#3175 / alert #74)", () => {
191
+ it("never places nodePath/process.execPath into the win32 cmd.exe command line", () => {
192
+ const evilNode = String.raw`C:\Program Files\nodejs\node.exe`;
193
+ const plan = buildSpawnPlan("global", "deft", ["release", "--summary", "ok"], {
194
+ platform: "win32",
195
+ nodePath: evilNode,
196
+ });
197
+ assert.equal(plan.shell, false);
198
+ assert.equal(plan.command, "cmd.exe");
199
+ assert.deepEqual(plan.args.slice(0, 3), ["/d", "/s", "/c"]);
200
+ // Global win32 must only join target + operator argv — not the Node binary.
201
+ assert.equal(plan.args[3].includes(evilNode), false);
202
+ assert.equal(plan.args[3].includes("Program Files"), false);
203
+ assert.equal(plan.args[3].includes("node.exe"), false);
204
+ assert.deepEqual(splitCmdTokens(plan.args[3]), ["deft", "release", "--summary", "ok"]);
205
+ });
206
+
207
+ it("uses nodePath only as the non-shell vendored command (never cmd.exe)", () => {
208
+ const nodePath = String.raw`C:\Program Files\nodejs\node.exe`;
209
+ const plan = buildSpawnPlan("vendored", String.raw`C:\repo\packages\cli\dist\bin.js`, ["session:start"], {
210
+ platform: "win32",
211
+ nodePath,
212
+ });
213
+ assert.equal(plan.shell, false);
214
+ assert.equal(plan.command, nodePath);
215
+ assert.notEqual(plan.command, "cmd.exe");
216
+ assert.deepEqual(plan.args, [String.raw`C:\repo\packages\cli\dist\bin.js`, "session:start"]);
217
+ });
218
+ });
package/tasks/verify.yml CHANGED
@@ -210,6 +210,36 @@ tasks:
210
210
  vars:
211
211
  ENGINE_CMD: 'verify:forward-coverage --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
212
212
 
213
+ test-boundary:
214
+ desc: "Test/source boundary gate (#3145): reject recognized test artifacts under production roots and production references to test/fixture roots unless allowlisted or classified production-liveness. Policy from .deft/test-boundary.policy.json or plan.policy.testBoundary; defaults are warn-only (migration). Flags: --enforce / --warn / --policy <path> / --quiet. Three-state exit (0 clean or warn / 1 violation / 2 config)."
215
+ dir: '{{.USER_WORKING_DIR}}'
216
+ deps:
217
+ - task: :engine:_ts-build
218
+ cmds:
219
+ - task: :engine:invoke
220
+ vars:
221
+ ENGINE_CMD: 'verify:test-boundary --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
222
+
223
+ scope-provenance:
224
+ desc: "Approved-scope provenance gate (#3145): same-PR active xBRIEF file_scope expansion cannot self-authorize new paths. Compares against .deft/approved-scope/<plan-id>.json digests; requires renewed human approval for expansion. Missing digests warn by default; pass --enforce to fail closed. Flags: --base-ref / --enforce / --quiet. Three-state exit (0 clean or warn / 1 self-auth / 2 config)."
225
+ dir: '{{.USER_WORKING_DIR}}'
226
+ deps:
227
+ - task: :engine:_ts-build
228
+ cmds:
229
+ - task: :engine:invoke
230
+ vars:
231
+ ENGINE_CMD: 'verify:scope-provenance --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
232
+
233
+ consumer-check-contract:
234
+ desc: "Consumer check-composition contract (#3145): fail when tasks/verify.yml or check aggregate omit required enforcement gates (verify:test-boundary, verify:scope-provenance, verify:consumer-check-contract). CI omissions warn by default. Flags: --framework-source / --enforce / --warn / --quiet. Three-state exit (0 clean or warn / 1 missing / 2 config)."
235
+ dir: '{{.USER_WORKING_DIR}}'
236
+ deps:
237
+ - task: :engine:_ts-build
238
+ cmds:
239
+ - task: :engine:invoke
240
+ vars:
241
+ ENGINE_CMD: 'verify:consumer-check-contract --project-root "{{.USER_WORKING_DIR}}" {{.CLI_ARGS}}'
242
+
213
243
  vbrief-conformance:
214
244
  desc: "Detect bare non-conformant vBRIEF keys (#1620). Scans tracked vbrief/**/*.vbrief.json and flags any document/plan/item key that is not 0.6 spec-core, x-directive/-namespaced, or x-vbrief/-namespaced. plan.policy + plan.completedNote carry a TEMPORARY allow-list pending vBRIEF #12. Defaults to --all; pass --staged for the pre-commit invocation."
215
245
  dir: '{{.USER_WORKING_DIR}}'
@@ -73,13 +73,13 @@ When the operator supplies an ordered plan (delivery sequence, cohort, checklist
73
73
 
74
74
  ## 2.6 Provider-neutral worker metadata (#1531)
75
75
 
76
- Heterogeneous swarm dispatch (#1531) assigns each worker a **dispatch provider** (the runtime primitive that launched the agent), a **worker role** (what the agent is allowed to do), and a **selected backend** or **routing policy** (how the harness maps that role to a concrete agent). These fields are provider-neutral: Composer-class coding agents, Grok Build (`spawn_subagent`), Cursor/cloud agents, OpenClaw (`sessions_spawn`, #2874 / #2879), and future adapters share the same contract.
76
+ Heterogeneous swarm dispatch (#1531) assigns each worker a **dispatch provider** (the runtime primitive that launched the agent), a **worker role** (what the agent is allowed to do), and a **selected backend** or **routing policy** (how the harness maps that role to a concrete agent). These fields are provider-neutral: Composer-class coding agents, Grok Build (`spawn_subagent`), Cursor/cloud agents, Claude Code (`claude-code` / `claude-agent`, #3134), OpenClaw (`sessions_spawn`, #2874 / #2879), and future adapters share the same contract.
77
77
 
78
78
  ! Every intentional backend-routed dispatch MUST carry a separate `## Worker metadata` section in the dispatch envelope, placed AFTER `## Allocation context` and BEFORE the task body. This section is advisory metadata for the worker and for audit; it does NOT replace, extend, or reorder the five-field #1378 `## Allocation context` recognition contract above.
79
79
 
80
80
  When present, the section documents these fields in order:
81
81
 
82
- - `dispatch_provider`: the runtime primitive that launched this worker -- e.g. `spawn_subagent`, `start_agent`, `sessions_spawn` (OpenClaw host; platform descriptor `openclaw` per #2874 / #2875), `cursor-composer`, `cursor-cloud-agent`, or a future adapter id. Names the harness surface, not the model.
82
+ - `dispatch_provider`: the runtime primitive that launched this worker -- e.g. `spawn_subagent`, `start_agent`, `sessions_spawn` (OpenClaw host; platform descriptor `openclaw` per #2874 / #2875), `cursor-composer`, `cursor-cloud-agent`, `claude-code` (Claude Code host; register primitive `claude-agent` per #3134), or a future adapter id. Names the harness surface, not the model.
83
83
  - `worker_role`: the role boundary for this dispatch -- one of `leaf-implementation`, `orchestrator`, `review-monitor`, or `merge-release` (stable ids from `packages/core/src/swarm/routing.ts` `SWARM_WORKER_ROLES`). Tells the worker which preamble rules and skill surfaces apply.
84
84
  - `selected_backend`: the stable backend id from `plan.policy.swarmSubagentBackend` / `task policy:subagent-backends` (accepted set today: `composer`, `grok-build`, `cursor-cloud` only — see `KNOWN_SUBAGENT_BACKEND_IDS`) | null -- which catalogued **coding** backend the operator selected for this role. OpenClaw is a **host / dispatch_provider** (`sessions_spawn` / descriptor `openclaw`), not a `swarmSubagentBackend` enum value; do not write `selected_backend: openclaw` into policy (#2879 Greptile P1).
85
85
  - `routing_policy`: <path or reference to the operator's routing file / tiering policy> | null -- when backend selection is delegated to harness routing instead of a typed policy field, cite the policy handle here so postmortems can reconstruct the route. The canonical handle is the gitignored, per-machine `.deft/routing.local.json` (#1739), keyed by `(dispatch_provider, worker_role)`; set decisions with `task swarm:routing-set -- --role <role> (--model <slug> | --harness-default)`.
@@ -94,7 +94,7 @@ Populate `selected_backend` OR `routing_policy` (or both when the operator sets
94
94
  - `orchestrator`, `review-monitor`, or `merge-release` + explicit backend routing: at least one MUST be non-null so strong-tier audit traces stay reconstructable.
95
95
  - Any role on the harness-default agent with no tiering decision: both MAY be null; `dispatch_provider` and `worker_role` remain required.
96
96
 
97
- **Role-boundary expectations (all providers):** the same boundaries apply whether the worker runs on Composer, Grok Build, Cursor/cloud, OpenClaw, or a future adapter:
97
+ **Role-boundary expectations (all providers):** the same boundaries apply whether the worker runs on Composer, Grok Build, Cursor/cloud, Claude Code, OpenClaw, or a future adapter:
98
98
 
99
99
  - ! `leaf-implementation` workers implement scoped xBRIEF work in their assigned worktree only -- gates (`task check`, file-scope audit, Greptile review cycle) are model-agnostic and MUST still pass.
100
100
  - ! `orchestrator`, `review-monitor`, and `merge-release` roles MUST run on strong or review-capable agents; dispatchers MUST NOT route these roles to cheap leaf backends.
@@ -461,11 +461,13 @@ These rules bind **orchestrators** dispatching implementation, fix, or review-cy
461
461
  **Worker-owns-lifecycle (Gap C):**
462
462
 
463
463
  - ! When dispatching an implementation worker, the dispatch envelope MUST declare the unit-of-work boundary explicitly: `stop-at: pr-open` (worker opens PR and exits) OR `drive-to: merge-ready` (worker owns PR + Greptile review cycle + fix batches through merge-ready as ONE unit of work, spawning its own review poller per `skills/deft-directive-review-cycle/SKILL.md` monitoring tiers). Default for story implementation dispatches is `drive-to: merge-ready`.
464
+ - ! **Envelope selection SLA (#3153):** Choose the boundary before spawn using the swarm decision tree (`skills/deft-directive-swarm/references/core-phase-0.md` — capacity stall, conf-only residual, wall-clock budget, large multi-gate, host nest limits). Happy-path default remains `drive-to: merge-ready`. A **deliberate** `stop-at: pr-open` is valid only with an immediate partner merge-path owner per `skills/deft-directive-review-cycle/SKILL.md` § Partner merge-path (babysit / Approach 1 lease / parent-retained — not freestyle global babysit). Under human-merge policy, that owner (or an explicit handoff recipient) remains responsible through merge + `scope:complete` — do not stand down at CLEAN alone. Consumer pin: `templates/agents-entry.md` § Envelope selection SLA. Silent PR-open handback for a worker already scoped merge-ready remains **forbidden**.
464
465
  - ! **Cursor Task ownership split (#2797 / #2814):** A Cursor `Task` implementation leaf MUST NOT spawn another Cursor `Task` review-monitor: nested Task (leaf spawning leaf) is unsupported/unreliable. A Cursor `drive-to: merge-ready` leaf instead owns a blocking `task pr:watch -- <N>` in its own process. To use an Approach 1 monitor, scope the leaf `stop-at: pr-open`; the orchestrator that owns the Task primitive then launches the sibling monitor and runs `task review-monitor:register -- --pr <N> --monitor-agent-id <id> --platform-primitive cursor-task` (GitHub sticky `<!-- deft:review-owner -->` lease — not local JSON).
465
466
  - ⊗ Let a Cursor leaf background `task pr:watch` and claim review monitoring is active. The process dies with the leaf and has no GitHub review-owner lease; treat that claim as a regression/eval failure and let `task verify:review-monitor -- --pr <N>` fail closed.
466
467
  - ! **Post-merge scope lifecycle (#2321 / Gap C):** Workers scoped `stop-at: pr-open` MUST NOT run `task scope:complete` before exit — their activation checkpoint rides into master on merge. The **orchestrator** (or Phase 6 `task swarm:finalize-cohort` / `task swarm:complete-cohort` on the headless path) MUST run `task scope:complete` or `task scope:cancel` for each shipped story xBRIEF after its PR merges. Workers scoped `drive-to: merge-ready` (or `drive-to: merge`) MUST include `task scope:complete` on their active xBRIEF as part of the same unit of work (after merge when appropriate).
467
468
  - ! Workers scoped `drive-to: merge-ready` MUST drive to merge-ready in their own tool loop — pre-PR, push, PR open, review-cycle poll/fix loop, and the #1259 Step 6 fail-closed exit — without handing back at PR-open for the orchestrator to re-dispatch separate leaf agents for review or fixes.
468
469
  - ⊗ Re-dispatch a separate review-monitor or fix agent after an implementation worker exits at PR-open when the original envelope scoped `drive-to: merge-ready` — that split recreates cross-agent state-handoff hazards and terminal lifecycle gaps (#1878 / Gap C).
470
+ - ⊗ Dispatch `stop-at: pr-open` without a named review-cycle partner merge-path owner plan (#3153).
469
471
  - ⊗ Leave an `xbrief/active/` brief with `plan.status == running` on master after the story's issue is closed or its PR merged — `task verify:orphan-active` fails closed on that signature (#2321).
470
472
 
471
473
  **Background / independent dispatch (Gap D):**
@@ -526,6 +528,12 @@ The parent monitor watches the heartbeat file directly (three-state exit 0 ok /
526
528
  - Monitors run `task verify:subagent-alive -- --require-agent <agent-id> [--scratch-dir <worktree>/.deft-scratch/subagent-status]` each poll iteration.
527
529
  - Workers run `task agent:monitor` (raw sweep) or the gate verb above; both wrap `subagent-monitor` (#1365).
528
530
 
531
+ ## 10.6 Dual stop for multi-iteration worker loops (#2442)
532
+
533
+ Multi-iteration implement, pre-PR, repair, and monitor loops require **two** stops: **success** (goal / AC / checker met) and **failure or budget** (max iterations, no-progress, or time/token budget). Single-turn tasks are exempt. Principle and defaults: `main.md` `## Dual Stop Rule (#2442)`; build skill dual-stop table; swarm Phase 4 / core-ops.
534
+
535
+ ! On failure stop: halt; emit an operator-visible report (what was tried, what is missing, what human decision is needed). Prefer `BLOCKED:` over silent retry. ⊗ Thrash past the envelope. Durable delivery/acceptance mechanical enforcement is **#3143** (`packages/core/src/delivery-attempt/`; not prompt-only).
536
+
529
537
  ## 11. Mandatory DONE message even on early exit
530
538
 
531
539
  Every worker MUST send a final status message before exiting its tool loop, regardless of outcome:
@@ -563,9 +571,12 @@ Every worker MUST send a final status message before exiting its tool loop, rega
563
571
  - ! **Legal partial:** local work `done` + ship `not_started` / `blocked` **without** PR/SHA/CI/review fields and `proof_status: n/a-no-remote-claim` (or non-pass `status: partial`) is valid — do not invent ship state.
564
572
  - Machine check (library): `validateHandoffEvidence` in `@deftai/directive-core` `handoff-evidence` (`packages/core/src/handoff-evidence/`). Skills: build / pre-pr / review-cycle final checklist.
565
573
 
566
- ! **Parent tool-first after leaf completion (#2943):** When a parent / monitor receives a leaf completion event (`subagent_announce`, parent-push, or host completion notify), its **first response** MUST be a **tool-first** ground-truth batch (`gh` / `git` / worktree or file status) **or** a host **yield** (`sessions_yield` on OpenClaw, or equivalent). ⊗ Multi-sentence progress-only first response with zero tools / yield — the OpenClaw text-repetition hang class (#2943).
574
+ ! **Parent tool-first after leaf completion (#2943 / hard-stop #3131):** When a parent / monitor receives a leaf completion event (`subagent_announce`, parent-push, or host completion notify), its **first response** MUST be exactly one of: (1) a **tool-first** ground-truth batch (`gh` / `git` / worktree or file status) then one consolidate, (2) a host **yield** (`sessions_yield` on OpenClaw, or equivalent), or (3) **one short user answer** that is **not** a repeated progress line. ⊗ Multi-sentence progress-only first response with zero tools / yield — the OpenClaw text-repetition hang class (#2943). ⊗ Emit **N>2** near-identical assistant sentences (or streaming text chunks) in one turn with no `tool_use` / yield — **FC14** illegal shape; hard-stop the turn (#3131). Soft skill prose is **not** sole mitigation.
575
+
576
+ ! **Machine check (FC14 hard-stop):** `evaluateParentTurnShape` in `@deftai/directive-core` `parent-turn-shape` (`packages/core/src/parent-turn-shape/`). Feed ordered turn events (`assistant_text` / `tool_use` / `yield`); when `ok === false` and `failClass` is `FC14` (or post-announce `progress-only-no-tool`), abort / force tool-or-yield. Operator recovery for current OpenClaw beta pins: `docs/openclaw-agent-host.md` § Operator recovery — FC14.
567
577
 
568
578
  ⊗ Treat thin DONE (no PR URL / merge evidence) as success (#2943).
579
+ ⊗ N>2 near-identical assistant sentences with no tool_use / yield, or soft-prose-only mitigation for the parent hang (FC14 / #3131).
569
580
  ⊗ Treat empty/unknown review-monitor settle as DONE without same-turn ground truth, or dual-spawn a second monitor while the first lease is live (#3044).
570
581
  ⊗ Emit freeform `review_cycle: started` / `pending` / `initiated` or L4 `status: pass` without `done` or verifiable `in_progress:<pr>#…` (#3090).
571
582
  ⊗ Claim `status: pass` (or equivalent process-green handoff) with PR URL / SHA / CI green / review score filled from memory without same-turn probe binding — **invented-done** (#3120).
@@ -67,11 +67,21 @@ Legacy `vbrief/` read-accepted; `deft migrate:xbrief` for `xbrief/` (v0.6→v0.8
67
67
  ! Process-critical skills with false-negative risk MUST be named in AGENTS.md (always-pin tier) — tier definitions: `.deft/core/docs/skill-pin-policy.md` (#2508).
68
68
  ! **Default always-pins:** `deft-directive-build`, `deft-directive-pre-pr`, `deft-directive-review-cycle`, `deft-directive-swarm` — read each `SKILL.md` when that work type starts.
69
69
  ⊗ Pin entire language packs, deployment docs, or framework bulk into AGENTS.md — pins are for false-negative-sensitive process gates only (#2508).
70
+ ! **Dual stop (#2442):** multi-iteration work MUST have success + failure/budget stop (max iters / no-progress / budget); single-turn exempt; halt with operator-visible report; ⊗ thrash. Defaults: build, swarm, review-cycle skills. See main.md Dual Stop Rule. Ledger #3143 (`packages/core/src/delivery-attempt/`).
70
71
 
71
72
  ## Through-merge worker dispatch (#3032)
72
73
 
73
74
  ! On **through merge** / **drive to merge** / land-ship / **drive-to: merge-ready** story intent: parent MUST dispatch a `drive-to: merge-ready` worker (worktree, preflight, pre-pr, review-cycle, merge/`scope:complete`) via the **swarm/solo-worker launch path** even if **cohort size is 1** — parent MUST NOT implement as the leaf. Depth: swarm Phase 0 + skill-pin-policy (#3032 / #1880 Gap C).
74
75
  ⊗ Parent conversation implements or babysits product fix/CI loops for drive-to:merge-ready work when background subagent/worktree dispatch is available (#3032).
76
+ ! After leaf announce: tool-first / yield / one short non-repeated answer; ⊗ N>2 near-identical zero-tool (FC14 / #3131). Machine: `evaluateParentTurnShape` (`parent-turn-shape`). Depth: preamble §11 + `docs/openclaw-agent-host.md`.
77
+
78
+ ## Envelope selection SLA (#3153)
79
+
80
+ ! Default story / through-merge unit of work is `drive-to: merge-ready`. Deliberate `stop-at: pr-open` is allowed only when a **partner merge-path owner** is planned (review-cycle babysit / Approach 1 lease / parent-retained) for Greptile + CI + post-merge `scope:complete` — triggers: capacity stall, wall-clock budget, large multi-gate, host nest limits (swarm Phase 0 decision tree). Depth: `deft-directive-swarm` + `deft-directive-review-cycle` partner merge-path.
81
+ ! Under human-merge policy, a **durable** owner (parent/monitor sticky lease or Phase 6 closer) MUST remain for post-merge `scope:complete` — CLEAN alone is not lifecycle complete.
82
+ ⊗ Silent PR-open handback for a worker already scoped `drive-to: merge-ready`.
83
+ ⊗ `stop-at: pr-open` without a named babysit / merge-path owner, or dual review-monitor leases on recovery (#3044 / #2261).
84
+ ⊗ Stand down at CLEAN under human-merge with no reachable post-merge `scope:complete` owner.
75
85
 
76
86
  ## Review-surface precedence (#2308)
77
87
 
@@ -88,6 +98,7 @@ Legacy `vbrief/` read-accepted; `deft migrate:xbrief` for `xbrief/` (v0.6→v0.8
88
98
  ## Branch policy & branch verification
89
99
 
90
100
  ! Feature branches — `deft verify:branch`, `deft verify:forward-coverage`, `deft coverage:hotspots`, hooks, `deft check` (#746 / #747) — `.deft/core/scm/github.md` § Branch policy.
101
+ ! Test placement + scope provenance (#3145) — `deft verify:test-boundary`, `deft verify:scope-provenance`, `deft verify:consumer-check-contract` (docs: `docs/test-boundary.md`, `docs/scope-provenance.md`, `docs/consumer-check-contract.md`); defaults warn-only until authored policy.
91
102
 
92
103
  ## Branch Policy Disclosure (#746)
93
104
 
@@ -446,9 +446,11 @@ Send to parent:
446
446
 
447
447
  `last_reviewed_sha` matches HEAD AND `has_blocking` is True. Do NOT exit on P2 -- those are non-blocking style suggestions per `skills/deft-directive-review-cycle/SKILL.md`.
448
448
 
449
- Address the findings per Phase 2 Step 2-3 of the review-cycle skill: read every finding, plan a single coherent batch, run `task check`, commit with message `fix: address Greptile review findings (batch)`, push. After the push, RESET the poll counter (the new commit triggers a fresh Greptile review pass) and continue polling. Do NOT exit -- this is the loop body of the review-cycle skill.
449
+ Address the findings per Phase 2 Step 2-3 of the review-cycle skill: read every finding, plan a single coherent batch, run `task check`, commit with message `fix: address Greptile review findings (batch)`, push. After the push, you MAY reset the **poll-wait timer** for the new HEAD (Greptile needs a fresh review window) but you MUST NOT reset the dual-stop **fix-batch counter** (#2442).
450
450
 
451
- If the same review surfaces 3 consecutive review cycles (push -> review -> still P0/P1 -> push -> review -> still P0/P1 -> push -> review -> still P0/P1), escalate to parent:
451
+ **Ownership-wide dual-stop state (#2442):** `fix_batch_count` and `primary_fingerprint` span the **whole review ownership for this PR**, not one poller process. On start: read prior state from a sticky PR comment marker `<!-- deft:dual-stop fix_batches=N fingerprint=... -->` (or the review-owner lease body if that surface already carries it); default N=0 when absent. After each substantive fix push: increment N, write the marker back, then continue. When a parent re-dispatches a replacement poller, it MUST pass prior N/fingerprint in the prompt placeholders or rely on the sticky marker — a fresh poller that starts at 0 while the same fingerprint remains is a dual-stop violation. When `fix_batch_count` reaches **3** OR the same primary P0/P1 fingerprint reappears on **2** consecutive re-reviews with no material fix, exit BLOCKED (do not open a fourth automatic fix batch). Mechanical durable ledgers: #3143 `packages/core/src/delivery-attempt/`. Continue polling only while under the dual-stop envelope. Do NOT exit early on a successful fix mid-loop — this is the loop body of the review-cycle skill.
452
+
453
+ If the dual-stop envelope is exhausted (3 fix batches or 2 identical no-progress re-reviews), escalate to parent:
452
454
 
453
455
  Subject: PR #{pr_number} escalation -- 3 review cycles still surfacing P0/P1
454
456
  Body: