@deftai/directive-content 0.87.0 → 0.89.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 (58) hide show
  1. package/.agents/skills/deft-directive-swarm/SKILL.md +1 -996
  2. package/QUICK-START.md +4 -0
  3. package/Taskfile.yml +16 -0
  4. package/UPGRADING.md +34 -2
  5. package/coding/coding.md +5 -1
  6. package/coding/security.md +13 -1
  7. package/commands.md +27 -1
  8. package/contracts/closed-verb-authz.md +117 -0
  9. package/contracts/escalation.md +114 -0
  10. package/contracts/finish-loop.md +121 -0
  11. package/contracts/host-lifecycle-duties.md +86 -0
  12. package/contracts/human-origin-authz.md +109 -0
  13. package/contracts/intent-ceiling.md +44 -0
  14. package/contracts/path-write-fence.md +128 -0
  15. package/contracts/runtime-authority.md +44 -8
  16. package/docs/getting-started.md +4 -0
  17. package/docs/no-deft-directive.md +87 -0
  18. package/docs/openclaw-agent-host.md +34 -1
  19. package/docs/product-signal.md +2 -0
  20. package/docs/writing-ste100.md +53 -0
  21. package/glossary.md +37 -39
  22. package/package.json +2 -1
  23. package/packs/rules/rules-pack-0.1.json +66 -2
  24. package/packs/skills/skills-pack-0.1.json +24 -24
  25. package/packs/strategies/strategies-pack-0.1.json +4 -4
  26. package/patterns/install-trust.md +117 -0
  27. package/scm/github.md +14 -2
  28. package/skills/deft-directive-article-review/SKILL.md +4 -1
  29. package/skills/deft-directive-release/SKILL.md +15 -0
  30. package/skills/deft-directive-setup/SKILL.md +43 -15
  31. package/skills/deft-directive-swarm/SKILL.md +81 -978
  32. package/skills/deft-directive-swarm/references/core-ops.md +144 -0
  33. package/skills/deft-directive-swarm/references/core-phase-0.md +200 -0
  34. package/skills/deft-directive-swarm/references/core-phase-1-2.md +73 -0
  35. package/skills/deft-directive-swarm/references/core-phase-3.md +145 -0
  36. package/skills/deft-directive-swarm/references/core-phase-4.md +71 -0
  37. package/skills/deft-directive-swarm/references/core-phase-5-6.md +317 -0
  38. package/skills/deft-directive-swarm/references/host-cursor.md +25 -0
  39. package/skills/deft-directive-swarm/references/host-generic.md +27 -0
  40. package/skills/deft-directive-swarm/references/host-grok-build.md +37 -0
  41. package/skills/deft-directive-swarm/references/host-openclaw.md +93 -0
  42. package/skills/deft-directive-swarm/references/host-warp.md +37 -0
  43. package/skills/deft-directive-write-skill/SKILL.md +17 -0
  44. package/strategies/artifact-guards.md +24 -14
  45. package/strategies/discuss.md +40 -1
  46. package/strategies/interview.md +103 -30
  47. package/strategies/probe.md +27 -1
  48. package/tasks/directive.yml +22 -0
  49. package/tasks/engine-invoke.cjs +69 -13
  50. package/tasks/engine-invoke.test.cjs +188 -0
  51. package/tasks/pr.yml +16 -0
  52. package/tasks/scm.yml +20 -0
  53. package/tasks/verify.yml +17 -0
  54. package/templates/agent-prompt-preamble.md +18 -0
  55. package/templates/agents-entry.md +6 -2
  56. package/templates/project.md.template +6 -0
  57. package/vbrief/schemas/vbrief-core.schema.json +33 -0
  58. package/vbrief/vbrief.md +4 -2
@@ -12,6 +12,29 @@
12
12
 
13
13
  const { spawnSync } = require("node:child_process");
14
14
 
15
+ /**
16
+ * cmd.exe command separators / metacharacters. Free-text DEFT_ENGINE_CMD_JSON
17
+ * tokens (release --summary text, CLI_ARGS, #2547) may legitimately contain
18
+ * these; double-quoting renders them literal to cmd.exe's parser so a token can
19
+ * never break out of its argv slot (subprocess-scm-01 / #2911).
20
+ */
21
+ const WIN32_CMD_METACHAR_RE = /[\s"&|<>^()%!]/;
22
+
23
+ /**
24
+ * Quote a single argument for `cmd.exe /d /s /c` so that shell metacharacters
25
+ * stay inside one argv token. Mirrors tasks/engine-pm-run.cjs quoteWin32Arg but
26
+ * also quotes cmd.exe separators (& | < > ^ ( ) % !) because engine-invoke
27
+ * forwards operator free-text, not an allowlisted command.
28
+ * @param {string} arg
29
+ */
30
+ function quoteWin32Arg(arg) {
31
+ const s = String(arg);
32
+ if (s.length > 0 && !WIN32_CMD_METACHAR_RE.test(s)) {
33
+ return s;
34
+ }
35
+ return `"${s.replace(/"/g, '""')}"`;
36
+ }
37
+
15
38
  /** Minimal POSIX-ish shell word splitter (double/single quotes, escapes). */
16
39
  function shellSplit(input) {
17
40
  const out = [];
@@ -75,15 +98,8 @@ function main() {
75
98
  process.exit(2);
76
99
  }
77
100
 
78
- let execPath;
79
- let execArgv;
80
- if (mode === "vendored") {
81
- execPath = process.execPath;
82
- execArgv = [target, ...argv];
83
- } else if (mode === "global") {
84
- execPath = target;
85
- execArgv = argv;
86
- } else {
101
+ const plan = buildSpawnPlan(mode, target, argv);
102
+ if (!plan) {
87
103
  console.error(`deft: engine-invoke unknown mode ${JSON.stringify(mode)}`);
88
104
  process.exit(2);
89
105
  }
@@ -97,11 +113,12 @@ function main() {
97
113
  // stdio inherit (not pipe): piped stdout/stderr deadlocks when the child emits
98
114
  // more than the OS pipe buffer before exit — observed as greenfield smoke
99
115
  // hanging then CI SIGTERM exit 143 with no output (#2554 / #2547).
100
- const result = spawnSync(execPath, execArgv, {
116
+ const result = spawnSync(plan.command, plan.args, {
101
117
  stdio: "inherit",
102
118
  env: childEnv,
103
- // Global deft/directive on Windows are .cmd shims; shell:false cannot spawn them (#2415).
104
- shell: mode === "global" && process.platform === "win32",
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,
105
122
  // CREATE_NO_WINDOW: hide console windows from Cursor Task / nested shells (#2563).
106
123
  windowsHide: true,
107
124
  });
@@ -109,8 +126,47 @@ function main() {
109
126
  process.exit(code === null ? 1 : code);
110
127
  }
111
128
 
129
+ /**
130
+ * Resolve the concrete spawn command/args for a mode+target without ever using
131
+ * shell:true. On the win32 global path the target is a `.cmd` shim that Node
132
+ * refuses to spawn with shell:false (CVE-2024-27980 / #2415); shell:true would
133
+ * let cmd.exe re-parse free-text DEFT_ENGINE_CMD_JSON tokens (subprocess-scm-01
134
+ * / #2911). Instead route through `cmd.exe /d /s /c` with every token tightly
135
+ * quoted so metacharacters stay inside a single argv token — aligned with
136
+ * tasks/engine-pm-run.cjs executeAllowlisted().
137
+ *
138
+ * @param {string} mode
139
+ * @param {string} target
140
+ * @param {string[]} argv
141
+ * @param {{ platform?: string, nodePath?: string }} [opts]
142
+ * @returns {{ command: string, args: string[], shell: false } | null}
143
+ */
144
+ function buildSpawnPlan(mode, target, argv, opts = {}) {
145
+ const platform = opts.platform || process.platform;
146
+ const nodePath = opts.nodePath || process.execPath;
147
+
148
+ let execPath;
149
+ let execArgv;
150
+ 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;
158
+ }
159
+
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 };
163
+ }
164
+
165
+ return { command: execPath, args: execArgv, shell: false };
166
+ }
167
+
112
168
  if (require.main === module) {
113
169
  main();
114
170
  }
115
171
 
116
- module.exports = { shellSplit };
172
+ module.exports = { shellSplit, quoteWin32Arg, buildSpawnPlan, WIN32_CMD_METACHAR_RE };
@@ -0,0 +1,188 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const assert = require("node:assert/strict");
5
+ const { describe, it } = require("node:test");
6
+ const {
7
+ shellSplit,
8
+ quoteWin32Arg,
9
+ buildSpawnPlan,
10
+ WIN32_CMD_METACHAR_RE,
11
+ } = require("./engine-invoke.cjs");
12
+
13
+ const WIN32 = { platform: "win32", nodePath: "/node" };
14
+ const POSIX = { platform: "linux", nodePath: "/node" };
15
+
16
+ /**
17
+ * Split a `cmd.exe`-quoted command line into top-level tokens, honouring
18
+ * double-quote grouping and the `""` escaped-quote convention. Used to assert
19
+ * that a metacharacter-bearing arg survives as exactly one argv token and is
20
+ * never seen by cmd.exe as a command separator.
21
+ * @param {string} line
22
+ */
23
+ function splitCmdTokens(line) {
24
+ const tokens = [];
25
+ let cur = "";
26
+ let inQuote = false;
27
+ let started = false;
28
+ for (let i = 0; i < line.length; i++) {
29
+ const c = line[i];
30
+ if (inQuote) {
31
+ if (c === '"') {
32
+ if (line[i + 1] === '"') {
33
+ cur += '"';
34
+ i++;
35
+ continue;
36
+ }
37
+ inQuote = false;
38
+ continue;
39
+ }
40
+ cur += c;
41
+ continue;
42
+ }
43
+ if (c === '"') {
44
+ inQuote = true;
45
+ started = true;
46
+ continue;
47
+ }
48
+ if (c === " ") {
49
+ if (started) {
50
+ tokens.push(cur);
51
+ cur = "";
52
+ started = false;
53
+ }
54
+ continue;
55
+ }
56
+ started = true;
57
+ cur += c;
58
+ }
59
+ if (started) {
60
+ tokens.push(cur);
61
+ }
62
+ return tokens;
63
+ }
64
+
65
+ describe("shellSplit", () => {
66
+ it("keeps quoted free-text (apostrophes, spaces, metachars) as one token", () => {
67
+ assert.deepEqual(shellSplit(`release --summary "It's a & test"`), [
68
+ "release",
69
+ "--summary",
70
+ "It's a & test",
71
+ ]);
72
+ });
73
+ });
74
+
75
+ describe("quoteWin32Arg", () => {
76
+ it("passes safe tokens through unquoted", () => {
77
+ assert.equal(quoteWin32Arg("release"), "release");
78
+ assert.equal(quoteWin32Arg("--summary=fixed"), "--summary=fixed");
79
+ });
80
+
81
+ it("double-quotes whitespace, quotes, and cmd.exe metacharacters", () => {
82
+ assert.equal(quoteWin32Arg("a b"), '"a b"');
83
+ assert.equal(quoteWin32Arg("a&b"), '"a&b"');
84
+ assert.equal(quoteWin32Arg("a|b"), '"a|b"');
85
+ assert.equal(quoteWin32Arg("a>b"), '"a>b"');
86
+ assert.equal(quoteWin32Arg("a<b"), '"a<b"');
87
+ assert.equal(quoteWin32Arg("(a)"), '"(a)"');
88
+ assert.equal(quoteWin32Arg("%PATH%"), '"%PATH%"');
89
+ assert.equal(quoteWin32Arg("a^b"), '"a^b"');
90
+ assert.equal(quoteWin32Arg("a!b"), '"a!b"');
91
+ });
92
+
93
+ it("escapes embedded double quotes by doubling", () => {
94
+ assert.equal(quoteWin32Arg('a"b'), '"a""b"');
95
+ });
96
+
97
+ it("regex flags every cmd.exe separator", () => {
98
+ for (const meta of [" ", '"', "&", "|", "<", ">", "^", "(", ")", "%", "!"]) {
99
+ assert.ok(WIN32_CMD_METACHAR_RE.test(`x${meta}y`), meta);
100
+ }
101
+ });
102
+ });
103
+
104
+ describe("buildSpawnPlan — win32 global (subprocess-scm-01 / #2911)", () => {
105
+ it("never uses shell:true and routes through cmd.exe /d /s /c", () => {
106
+ const plan = buildSpawnPlan("global", "deft", ["release"], WIN32);
107
+ assert.equal(plan.shell, false);
108
+ assert.equal(plan.command, "cmd.exe");
109
+ assert.deepEqual(plan.args.slice(0, 3), ["/d", "/s", "/c"]);
110
+ assert.equal(plan.args[3], "deft release");
111
+ });
112
+
113
+ it("keeps injection-shaped free-text args as a single quoted token", () => {
114
+ const injections = [
115
+ "& calc.exe",
116
+ "&calc",
117
+ "| whoami",
118
+ "&& shutdown /s",
119
+ "; rm -rf /",
120
+ "$(reboot)",
121
+ "`reboot`",
122
+ "> C:\\pwn.txt",
123
+ "< C:\\secret",
124
+ "(malicious)",
125
+ "%USERPROFILE%",
126
+ "^escaped",
127
+ "!DELAYED!",
128
+ ];
129
+ for (const evil of injections) {
130
+ const plan = buildSpawnPlan("global", "deft", ["release", "--summary", evil], WIN32);
131
+ assert.equal(plan.shell, false, evil);
132
+ const commandLine = plan.args[3];
133
+ const tokens = splitCmdTokens(commandLine);
134
+ // deft + release + --summary + evil == 4 top-level tokens, evil intact.
135
+ assert.deepEqual(tokens, ["deft", "release", "--summary", evil], `injection ${evil}`);
136
+ // Any cmd.exe metacharacter must be neutralised inside a quoted span so it
137
+ // can never act as a bare command separator (POSIX-only chars like the
138
+ // backtick are literal to cmd.exe and need no quoting).
139
+ if (WIN32_CMD_METACHAR_RE.test(evil)) {
140
+ assert.ok(commandLine.includes(`"${evil.replace(/"/g, '""')}"`), `quoted ${evil}`);
141
+ }
142
+ }
143
+ });
144
+
145
+ it("routes end-to-end from a quoted DEFT_ENGINE_CMD string", () => {
146
+ const argv = shellSplit('release --summary "pwn & calc | whoami"');
147
+ const plan = buildSpawnPlan("global", "directive", argv, WIN32);
148
+ assert.equal(plan.shell, false);
149
+ assert.deepEqual(splitCmdTokens(plan.args[3]), [
150
+ "directive",
151
+ "release",
152
+ "--summary",
153
+ "pwn & calc | whoami",
154
+ ]);
155
+ });
156
+
157
+ it("leaves safe args unquoted for readability", () => {
158
+ const plan = buildSpawnPlan("global", "deft", ["session:start", "--json"], WIN32);
159
+ assert.equal(plan.args[3], "deft session:start --json");
160
+ });
161
+ });
162
+
163
+ describe("buildSpawnPlan — other paths keep shell:false", () => {
164
+ it("win32 vendored spawns node directly (no cmd.exe, no shell)", () => {
165
+ const plan = buildSpawnPlan("vendored", "/bin.js", ["release", "a&b"], WIN32);
166
+ assert.equal(plan.shell, false);
167
+ assert.equal(plan.command, "/node");
168
+ assert.deepEqual(plan.args, ["/bin.js", "release", "a&b"]);
169
+ });
170
+
171
+ it("posix global spawns the shim directly with shell:false", () => {
172
+ const plan = buildSpawnPlan("global", "deft", ["release", "a&b"], POSIX);
173
+ assert.equal(plan.shell, false);
174
+ assert.equal(plan.command, "deft");
175
+ assert.deepEqual(plan.args, ["release", "a&b"]);
176
+ });
177
+
178
+ it("posix vendored spawns node with shell:false", () => {
179
+ const plan = buildSpawnPlan("vendored", "/bin.js", ["release"], POSIX);
180
+ assert.equal(plan.shell, false);
181
+ assert.equal(plan.command, "/node");
182
+ assert.deepEqual(plan.args, ["/bin.js", "release"]);
183
+ });
184
+
185
+ it("returns null for unknown modes (caller exits 2)", () => {
186
+ assert.equal(buildSpawnPlan("bogus", "deft", ["release"], WIN32), null);
187
+ });
188
+ });
package/tasks/pr.yml CHANGED
@@ -124,3 +124,19 @@ tasks:
124
124
  - task: :engine:invoke
125
125
  vars:
126
126
  ENGINE_CMD: 'pr-watch {{.CLI_ARGS}}'
127
+
128
+ # pr:finish-loop -- walk-away PR shepherd (#871 Wave 5 / #2948).
129
+ # Grant-gated wrap of pr:watch until CLEAN; address path is agent-
130
+ # orchestrated (exit 1 on NEW_P0_P1). Respects requireHumanMerge —
131
+ # never force bot merge. Fail closed without finish-loop grant.
132
+ #
133
+ # Companion module: packages/core/src/finish-loop/
134
+ # Contract: content/contracts/finish-loop.md
135
+ finish-loop:
136
+ desc: "Walk-away PR finish loop: grant gate + pr:watch until CLEAN; exit 1 address / human-merge; exit 2 BLOCKED without grant (#871)"
137
+ deps: [":engine:_ts-build"]
138
+ dir: '{{.USER_WORKING_DIR}}'
139
+ cmds:
140
+ - task: :engine:invoke
141
+ vars:
142
+ ENGINE_CMD: 'pr-finish-loop {{.CLI_ARGS}}'
package/tasks/scm.yml CHANGED
@@ -123,6 +123,26 @@ tasks:
123
123
  vars:
124
124
  ENGINE_CMD: 'github-body issue-fetch {{.CLI_ARGS}}'
125
125
 
126
+ body:issue:lint:
127
+ desc: "[#2960] Lint live issue body for CP1252/CP437-as-UTF-8 mojibake (same patterns as verify:encoding)"
128
+ dir: '{{.USER_WORKING_DIR}}'
129
+ deps:
130
+ - task: :engine:_ts-build
131
+ cmds:
132
+ - task: :engine:invoke
133
+ vars:
134
+ ENGINE_CMD: 'github-body issue-lint {{.CLI_ARGS}}'
135
+
136
+ body:pr:lint:
137
+ desc: "[#2960] Lint live PR body for CP1252/CP437-as-UTF-8 mojibake (same patterns as verify:encoding)"
138
+ dir: '{{.USER_WORKING_DIR}}'
139
+ deps:
140
+ - task: :engine:_ts-build
141
+ cmds:
142
+ - task: :engine:invoke
143
+ vars:
144
+ ENGINE_CMD: 'github-body pr-lint {{.CLI_ARGS}}'
145
+
126
146
  body:comment:create:
127
147
  desc: "[#1555] Safely create an issue/PR comment body from --body-file and live gh read-back"
128
148
  dir: '{{.USER_WORKING_DIR}}'
package/tasks/verify.yml CHANGED
@@ -47,6 +47,16 @@ tasks:
47
47
  vars:
48
48
  ENGINE_CMD: 'verify-biome-config --project-root "{{.DEFT_ROOT}}"'
49
49
 
50
+ contained-writes:
51
+ desc: "Inventory raw product write sinks outside the contained-write allowlist (#2951 / #2980). Default CLI remains fail-open; task check wires --enforce fail-closed. -- task verify:contained-writes [-- --enforce]"
52
+ deps:
53
+ - task: :engine:_ts-build
54
+ # Framework-source-only gate: scans packages/core/src in THIS repo.
55
+ cmds:
56
+ - task: :engine:invoke
57
+ vars:
58
+ ENGINE_CMD: 'verify-contained-writes --project-root "{{.DEFT_ROOT}}" {{.CLI_ARGS}}'
59
+
50
60
  content-manifest:
51
61
  desc: "Verify the Content Manifest (conventions/content-manifest.json) classifies every git-tracked top-level entry (#1821). Fails on an unclassified entry, a stale classified path, an invalid bucket, or a duplicate path. Wave-1 shippability audit for the engine/content split (#1669)."
52
62
  deps:
@@ -59,6 +69,13 @@ tasks:
59
69
  vars:
60
70
  ENGINE_CMD: 'verify-content-manifest --project-root "{{.DEFT_ROOT}}"'
61
71
 
72
+ license-sync:
73
+ desc: "Drift guard for root LICENSE ↔ content/LICENSE.md and published package.json license fields (#2902). Three-state exit (0 clean / 1 drift / 2 config). Framework-source only."
74
+ # Framework-source-only gate: reads THIS repo's LICENSE + package manifests.
75
+ # Plain node script (no engine build required).
76
+ cmds:
77
+ - node "{{.DEFT_ROOT}}/scripts/verify-license-sync.mjs" --project-root "{{.DEFT_ROOT}}"
78
+
62
79
  skill-external-fetch-gate:
63
80
  desc: "Verify shipped skills do not pair external fetch/follow-through with execute/install without Security context mitigation (#1936 / #1532)."
64
81
  deps:
@@ -352,6 +352,18 @@ Anti-pattern: reading only the issue body and building a dispatch envelope from
352
352
 
353
353
  Reference: AGENTS.md `## Issue body→comments reading (#2143)`, `## Umbrella current-shape convention (#1152)`, issue #2143.
354
354
 
355
+ ## 5.6.1 Typed escalation channel (#518 slim / #2948 Wave 5)
356
+
357
+ When blocked on human input under multi-agent load, file a **typed** escalation instead of a synchronous interrupt storm:
358
+
359
+ - Types: `cmd_approval` | `design_decision` | `approval` | `resource` | `external` | `question`
360
+ - CLI: `deft escalation:file` / `list` / `resolve` / `batch-approve` (bulk only for non-dangerous `cmd_approval` + `question`)
361
+ - Store: `.deft/escalations/<id>.json`
362
+ - Mark write-scope shell / merge / release requests `dangerous: true` so they stay individual
363
+ - Escalations are **not** implement authority — compose with `deft authz:grant` (Wave 1) after approval
364
+
365
+ Contract + residual full priority-inbox UI: `content/contracts/escalation.md`.
366
+
355
367
  ## 5.7 Value feedback opt-in and gap escalation (#1709)
356
368
 
357
369
  Value attribution, budgeted session readbacks, and upstream gap escalation are gated on `plan.policy.valueFeedback` (default OFF). Workers MUST NOT emit value claims, session readback lines, or file upstream framework-gap issues unless the relevant sub-flag is ON and the operator has confirmed enablement where required.
@@ -529,6 +541,12 @@ Every worker MUST send a final status message before exiting its tool loop, rega
529
541
 
530
542
  ⊗ Emit `DONE` from a `drive-to: merge-ready` worker while merge-ready is false — a false-terminal `DONE` pulls the cohort monitor into inline Greptile fixes and violates Gap D (#2843 monitor-as-implementer recurrence).
531
543
 
544
+ ! **Thin DONE is not success (#2943):** A terminal message that lacks PR URL / merge evidence (no `PR #N`, no PR URL, no merge confirmation) is a **thin DONE** / failed-leaf signal for the parent monitor — re-dispatch or take over after ground truth. Prefer structured completion fields when the host supplies them (`prUrl`, `mergeStatus`, `emptyDiff`). Workers MUST NOT exit with mid-edit prose and call it `DONE` when the envelope required a PR or merge-ready outcome.
545
+
546
+ ! **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).
547
+
548
+ ⊗ Treat thin DONE (no PR URL / merge evidence) as success (#2943).
549
+
532
550
  Per-step acks during the run are noise. ONE start message, ONE final message; intermediate messages only on `BLOCKED` / `FAILED`. The final message lets the dispatcher distinguish a clean exit from a silent timeout when the lifecycle event arrives.
533
551
 
534
552
  ## 12. Session ritual + `task verify:cache-fresh` gates before `start_agent` (#1348 / #1127)
@@ -93,9 +93,13 @@ Legacy `vbrief/` read-accepted; `deft migrate:xbrief` for `xbrief/` (v0.6→v0.8
93
93
 
94
94
  ## Development Process
95
95
 
96
- ### Implementation Intent Gate (#810)
96
+ ### Implementation Intent Gate (#810 / #1193)
97
97
 
98
- ! `deft xbrief:preflight -- <path>` on `xbrief/active/` before code-writing; action-verb (`build`, `implement`, `ship`, `swarm`, `run agents`, `start agent`) (#810) — `commands.md` § Scope xBRIEF Lifecycle.
98
+ ! `deft xbrief:preflight -- <path>` on `xbrief/active/` before code-writing; action-verb (`build`, `implement`, `ship`, `swarm`, `run agents`, `start agent`) (#810). Slash-command sessions inherit only that verb (`DEFT_SESSION_SLASH_VERB`); non-implement verbs (`/github-issue`, `/triage`, …) MUST NOT authorize implement/push/PR/merge/deploy (#1193) — `commands.md` / `contracts/intent-ceiling.md`.
99
+
100
+ ## Human merge gate (#1193)
101
+
102
+ ! When `plan.policy.requireHumanMerge` is true (default if `autoDeployOnMerge`), agents may open PRs, may not merge. Override: `deft policy:allow-bot-merge -- --confirm` or `DEFT_ALLOW_BOT_MERGE=1` — `commands.md` / `contracts/intent-ceiling.md`.
99
103
 
100
104
  ### Story Start Gate
101
105
 
@@ -39,6 +39,12 @@ cp secrets/[example].example secrets/[example] # [Description]
39
39
  - **API**: [api.md](./docs/api.md)
40
40
  - **Deployment**: [deployment.md](./docs/deployment.md)
41
41
 
42
+ ## xBRIEF envelope (#2971)
43
+
44
+ <!-- Copy sources for project identity / scope JSON must write 0.8 only. -->
45
+ - ! New `xbrief/*.xbrief.json` MUST use `"xBRIEFInfo": { "version": "0.8" }` (schema const)
46
+ - ⊗ Emit `"version": "0.6"` on any new write path — run `deft migrate:xbrief` for existing 0.6 docs
47
+
42
48
  ## Branching
43
49
 
44
50
  <!-- Uncomment the line below to allow direct commits to master (trunk-based workflow). -->
@@ -609,12 +609,45 @@
609
609
  "minimum": 1,
610
610
  "description": "Maximum age, in hours, for .deft/ritual-state.json before the fail-closed session ritual verifier requires task session:start to run again. Default: 4."
611
611
  },
612
+ "requireHumanMerge": {
613
+ "type": "boolean",
614
+ "description": "When true, agents may open PRs but must not merge (#1193). Defaults true when autoDeployOnMerge is also true. Override: policy:allow-bot-merge --confirm or DEFT_ALLOW_BOT_MERGE=1."
615
+ },
616
+ "autoDeployOnMerge": {
617
+ "type": "boolean",
618
+ "description": "When true, merges to the default branch auto-deploy to production. Couples with requireHumanMerge defaulting (#1193)."
619
+ },
620
+ "hotfixCriteria": {
621
+ "$ref": "#/$defs/HotfixCriteria"
622
+ },
612
623
  "projectionProviders": {
613
624
  "$ref": "#/$defs/ProjectionProviderPolicies"
614
625
  }
615
626
  },
616
627
  "additionalProperties": true
617
628
  },
629
+ "HotfixCriteria": {
630
+ "type": "object",
631
+ "description": "Structural hotfix eligibility thresholds (#1193). Agent may label hotfix-candidate only; human promotes hotfix.",
632
+ "properties": {
633
+ "maxLines": {
634
+ "type": "integer",
635
+ "minimum": 0,
636
+ "description": "Max changed lines for a small-fix hotfix candidate. Default: 10."
637
+ },
638
+ "maxFiles": {
639
+ "type": "integer",
640
+ "minimum": 0,
641
+ "description": "Max changed files for a small-fix hotfix candidate. Default: 2."
642
+ },
643
+ "forbiddenPathGlobs": {
644
+ "type": "array",
645
+ "items": { "type": "string" },
646
+ "description": "Path globs that never qualify as hotfix (deploy/CI/migrations/auth defaults apply when omitted)."
647
+ }
648
+ },
649
+ "additionalProperties": true
650
+ },
618
651
  "ProjectionProviderPolicies": {
619
652
  "type": "object",
620
653
  "description": "Projection provider artifact policies keyed by projection kind. Values point at durable artifacts; runner command strings are not canonical policy.",
package/vbrief/vbrief.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # vBRIEF Usage in Deft
2
2
 
3
- Canonical reference for vBRIEF file conventions within Deft-managed projects.
3
+ > **Public name (#2907):** The sole current work-state name is **xBRIEF** under `xbrief/` (`.xbrief.json`). **vBRIEF** / `vbrief/` / `.vbrief.json` in this document are **legacy / schema-lineage** names (this path still hosts core schemas). Prefer xbrief in product docs and new guidance. Authoritative rename/history: [UPGRADING.md — xBRIEF rename](../UPGRADING.md#xbrief-rename-2034--2110--2907). Migrate on-disk layouts with `deft migrate:xbrief`.
4
+
5
+ Canonical **schema and convention** reference for durable work-state files within Deft-managed projects (historical filename: vBRIEF).
4
6
 
5
7
  Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
6
8
 
7
- **⚠️ See also**: [context/working-memory.md](../context/working-memory.md) | [resilience/continue-here.md](../resilience/continue-here.md) | [context/long-horizon.md](../context/long-horizon.md) | [glossary.md](../glossary.md)
9
+ **⚠️ See also**: [glossary.md](../glossary.md) | [UPGRADING.md — xBRIEF rename](../UPGRADING.md#xbrief-rename-2034--2110--2907) | [context/working-memory.md](../context/working-memory.md) | [resilience/continue-here.md](../resilience/continue-here.md)
8
10
 
9
11
  ---
10
12