@deftai/directive-core 0.106.0 → 0.107.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 (100) hide show
  1. package/dist/check/gate-lists.js +2 -0
  2. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  3. package/dist/design-critique/exclusive-chip.js +68 -0
  4. package/dist/design-critique/parent-audit.d.ts +59 -0
  5. package/dist/design-critique/parent-audit.js +121 -0
  6. package/dist/doctor/checks.d.ts +6 -0
  7. package/dist/doctor/checks.js +35 -0
  8. package/dist/hooks/dest-form.d.ts +46 -0
  9. package/dist/hooks/dest-form.js +575 -0
  10. package/dist/hooks/dispatcher.d.ts +17 -1
  11. package/dist/hooks/dispatcher.js +82 -11
  12. package/dist/hooks/index.d.ts +1 -0
  13. package/dist/hooks/index.js +1 -0
  14. package/dist/intake/github-auth-modes-cli.js +14 -2
  15. package/dist/intake/github-auth-modes.d.ts +54 -9
  16. package/dist/intake/github-auth-modes.js +326 -123
  17. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  18. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  19. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  20. package/dist/lifecycle/completed-write-guard.js +373 -0
  21. package/dist/lifecycle/index.d.ts +1 -0
  22. package/dist/lifecycle/index.js +1 -0
  23. package/dist/literal-acceptance/capture.d.ts +16 -0
  24. package/dist/literal-acceptance/capture.js +75 -7
  25. package/dist/literal-acceptance/evaluate.js +25 -3
  26. package/dist/literal-acceptance/index.d.ts +2 -2
  27. package/dist/literal-acceptance/index.js +2 -2
  28. package/dist/literal-acceptance/run.js +9 -3
  29. package/dist/literal-acceptance/types.d.ts +6 -0
  30. package/dist/literal-acceptance/types.js +6 -0
  31. package/dist/policy/runtime-authority.d.ts +18 -0
  32. package/dist/policy/runtime-authority.js +11 -0
  33. package/dist/policy/write-fence.js +3 -0
  34. package/dist/preflight-cache/evaluate.js +9 -3
  35. package/dist/product-first-done-gate/evaluate.js +36 -5
  36. package/dist/scm/design-critique-chip.d.ts +37 -0
  37. package/dist/scm/design-critique-chip.js +171 -0
  38. package/dist/scm/index.d.ts +1 -0
  39. package/dist/scm/index.js +1 -0
  40. package/dist/scm/main.d.ts +3 -0
  41. package/dist/scm/main.js +15 -1
  42. package/dist/scm/readiness-cli.d.ts +2 -0
  43. package/dist/scm/readiness-cli.js +42 -1
  44. package/dist/scm/readiness.d.ts +2 -1
  45. package/dist/scm/readiness.js +7 -1
  46. package/dist/scope/decompose.js +2 -1
  47. package/dist/scope/delivery-evidence.d.ts +27 -1
  48. package/dist/scope/index.d.ts +1 -0
  49. package/dist/scope/index.js +1 -0
  50. package/dist/scope/lifecycle-write.d.ts +24 -0
  51. package/dist/scope/lifecycle-write.js +64 -0
  52. package/dist/scope/main.js +3 -1
  53. package/dist/scope/transition.js +90 -11
  54. package/dist/scope-provenance/evaluate.js +5 -1
  55. package/dist/session/git.d.ts +18 -0
  56. package/dist/session/git.js +81 -1
  57. package/dist/session/occupancy.d.ts +2 -1
  58. package/dist/session/occupancy.js +11 -4
  59. package/dist/session/session-start.d.ts +12 -0
  60. package/dist/session/session-start.js +17 -0
  61. package/dist/session/verify-session-ritual.d.ts +22 -3
  62. package/dist/session/verify-session-ritual.js +29 -4
  63. package/dist/swarm/launch.d.ts +69 -0
  64. package/dist/swarm/launch.js +191 -1
  65. package/dist/triage/actions/index.d.ts +4 -0
  66. package/dist/triage/actions/index.js +6 -0
  67. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  68. package/dist/triage/evaluate/evaluate.js +169 -0
  69. package/dist/triage/evaluate/github.d.ts +16 -0
  70. package/dist/triage/evaluate/github.js +153 -0
  71. package/dist/triage/evaluate/index.d.ts +9 -0
  72. package/dist/triage/evaluate/index.js +8 -0
  73. package/dist/triage/evaluate/paths.d.ts +7 -0
  74. package/dist/triage/evaluate/paths.js +25 -0
  75. package/dist/triage/evaluate/sink.d.ts +5 -0
  76. package/dist/triage/evaluate/sink.js +52 -0
  77. package/dist/triage/evaluate/types.d.ts +93 -0
  78. package/dist/triage/evaluate/types.js +13 -0
  79. package/dist/triage/evaluate/validity.d.ts +8 -0
  80. package/dist/triage/evaluate/validity.js +87 -0
  81. package/dist/triage/evaluate/value.d.ts +12 -0
  82. package/dist/triage/evaluate/value.js +33 -0
  83. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  84. package/dist/triage/evaluate/wip-census.js +39 -0
  85. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  86. package/dist/triage/evaluate/worktrees.js +68 -0
  87. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  88. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  89. package/dist/triage/help/registry-data.d.ts +21 -7
  90. package/dist/triage/help/registry-data.js +53 -5
  91. package/dist/triage/index.d.ts +1 -0
  92. package/dist/triage/index.js +1 -0
  93. package/dist/value/feedback-file.d.ts +17 -2
  94. package/dist/value/feedback-file.js +80 -6
  95. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  96. package/dist/vbrief-reconcile/labels.js +30 -0
  97. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  98. package/dist/vbrief-validation/story-quality.js +8 -1
  99. package/dist/xbrief-migrate/agents-header.js +69 -7
  100. package/package.json +3 -3
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Refuse newly added completed/ blobs that bypass runTransition (#3679).
3
+ *
4
+ * Historical corpus is advisory (doctor). New work in the change set is hard
5
+ * (verify:completed-write-guard). Does not read completionProvenance and does
6
+ * not change verify:completed-tracked.
7
+ *
8
+ * Disk reads are capped at COMPLETED_WRITE_GUARD_MAX_BYTES so a huge
9
+ * contributor-controlled completed/ add fails through the guard instead of
10
+ * exhausting memory on the required gate path.
11
+ */
12
+ export type CompletedWriteGuardCode = 0 | 1 | 2;
13
+ export interface CompletedWriteGuardFinding {
14
+ readonly relPath: string;
15
+ readonly detail: string;
16
+ }
17
+ export interface CompletedWriteGuardResult {
18
+ readonly code: CompletedWriteGuardCode;
19
+ readonly message: string;
20
+ readonly findings: readonly CompletedWriteGuardFinding[];
21
+ }
22
+ export interface CompletedWriteGuardOptions {
23
+ readonly baseRef?: string;
24
+ /** Inject added repo-relative POSIX paths (skips git). */
25
+ readonly addedFiles?: readonly string[];
26
+ /** Inject payloads: relPath -> raw JSON. */
27
+ readonly payloads?: ReadonlyMap<string, string>;
28
+ }
29
+ /**
30
+ * In-repo completed xBRIEFs average ~5.5 KiB and peak near 44 KiB.
31
+ * 1 MiB is ~23× that peak and still refuses a multi-hundred-MB add
32
+ * before the bytes are loaded.
33
+ */
34
+ export declare const COMPLETED_WRITE_GUARD_MAX_BYTES = 1048576;
35
+ /**
36
+ * Hard check: newly added completed/ artifacts must show runTransition evidence.
37
+ */
38
+ export declare function evaluateCompletedWriteGuard(projectRoot: string, options?: CompletedWriteGuardOptions): CompletedWriteGuardResult;
39
+ export interface CompletedWriteCorpusResult {
40
+ readonly findings: readonly CompletedWriteGuardFinding[];
41
+ readonly scanned: number;
42
+ }
43
+ /**
44
+ * Corpus scan for doctor: artifacts without transition evidence.
45
+ * Caller marks these advisory so historical pre-stamp files do not red doctor.
46
+ */
47
+ export declare function scanCompletedWriteCorpus(projectRoot: string): CompletedWriteCorpusResult;
48
+ //# sourceMappingURL=completed-write-guard.d.ts.map
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Refuse newly added completed/ blobs that bypass runTransition (#3679).
3
+ *
4
+ * Historical corpus is advisory (doctor). New work in the change set is hard
5
+ * (verify:completed-write-guard). Does not read completionProvenance and does
6
+ * not change verify:completed-tracked.
7
+ *
8
+ * Disk reads are capped at COMPLETED_WRITE_GUARD_MAX_BYTES so a huge
9
+ * contributor-controlled completed/ add fails through the guard instead of
10
+ * exhausting memory on the required gate path.
11
+ */
12
+ import { spawnSync } from "node:child_process";
13
+ import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
14
+ import { dirname, join, resolve } from "node:path";
15
+ import { GitCommandError, GitNotFoundError } from "../encoding/git.js";
16
+ import { hasArtifactSuffix, LEGACY_ARTIFACT_DIR, MIGRATED_ARTIFACT_DIR, } from "../layout/resolve.js";
17
+ import { hasTransitionWrite, LEFTOVER_LAND_PR_REMEDIATION } from "../scope/lifecycle-write.js";
18
+ import { resolveDefaultBaseRef, unquoteGitPath } from "../scope-provenance/evaluate.js";
19
+ /**
20
+ * In-repo completed xBRIEFs average ~5.5 KiB and peak near 44 KiB.
21
+ * 1 MiB is ~23× that peak and still refuses a multi-hundred-MB add
22
+ * before the bytes are loaded.
23
+ */
24
+ export const COMPLETED_WRITE_GUARD_MAX_BYTES = 1_048_576;
25
+ const COMPLETED_REL_RE = /^(?:xbrief|vbrief)\/completed\/[^/]+$/;
26
+ function normalizeRepoRelPath(raw) {
27
+ return raw.replace(/\\/g, "/").replace(/^\.\//, "");
28
+ }
29
+ function isCompletedArtifactRel(relPath) {
30
+ const n = normalizeRepoRelPath(relPath);
31
+ if (!COMPLETED_REL_RE.test(n)) {
32
+ return false;
33
+ }
34
+ const base = n.split("/").pop() ?? "";
35
+ return hasArtifactSuffix(base);
36
+ }
37
+ function parsePlan(raw) {
38
+ try {
39
+ const data = JSON.parse(raw);
40
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
41
+ return null;
42
+ }
43
+ const plan = data.plan;
44
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
45
+ return null;
46
+ }
47
+ return plan;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ function gitEnv(projectRoot) {
54
+ const env = { ...process.env };
55
+ delete env.GIT_DIR;
56
+ delete env.GIT_WORK_TREE;
57
+ env.GIT_CEILING_DIRECTORIES = dirname(resolve(projectRoot));
58
+ return env;
59
+ }
60
+ function git(args, projectRoot) {
61
+ const result = spawnSync("git", args, {
62
+ cwd: projectRoot,
63
+ encoding: "utf8",
64
+ stdio: ["ignore", "pipe", "pipe"],
65
+ env: gitEnv(projectRoot),
66
+ });
67
+ if (result.error !== undefined) {
68
+ const e = result.error;
69
+ if (e.code === "ENOENT") {
70
+ throw new GitNotFoundError("'git' executable not found on PATH");
71
+ }
72
+ throw new GitCommandError(`git ${args.join(" ")} failed: ${String(e.message)}`);
73
+ }
74
+ if (result.signal !== null && result.signal !== undefined) {
75
+ throw new GitCommandError(`git ${args.join(" ")} killed by signal ${String(result.signal)}`);
76
+ }
77
+ const status = result.status ?? 1;
78
+ const stderr = String(result.stderr ?? "").trim();
79
+ if (status !== 0 && stderr.length > 0) {
80
+ return { status, stdout: `${result.stdout ?? ""}\n${stderr}` };
81
+ }
82
+ return { status, stdout: result.stdout ?? "" };
83
+ }
84
+ function addedPathsFromNameStatus(stdout) {
85
+ const out = [];
86
+ for (const line of stdout.split("\n")) {
87
+ const t = line.replace(/\r$/, "").trim();
88
+ if (t.length === 0) {
89
+ continue;
90
+ }
91
+ const parts = t.split("\t");
92
+ const status = parts[0] ?? "";
93
+ if (status.startsWith("A")) {
94
+ const path = parts[1];
95
+ if (path !== undefined) {
96
+ out.push(unquoteGitPath(path));
97
+ }
98
+ }
99
+ else if (status.startsWith("R")) {
100
+ const dest = parts[2] ?? parts[1];
101
+ if (dest !== undefined) {
102
+ out.push(unquoteGitPath(dest));
103
+ }
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+ function discoverAddedFiles(projectRoot, baseRef) {
109
+ const inside = git(["rev-parse", "--is-inside-work-tree"], projectRoot);
110
+ if (inside.status !== 0) {
111
+ throw new GitCommandError("not a git working tree");
112
+ }
113
+ let resolved = baseRef;
114
+ if (baseRef === "HEAD" || baseRef === "") {
115
+ const upgraded = resolveDefaultBaseRef(projectRoot);
116
+ if (upgraded === null) {
117
+ throw new GitCommandError("no merge-base ref (origin/master|main or DEFT_BASE_REF/GITHUB_BASE_REF)");
118
+ }
119
+ resolved = upgraded;
120
+ }
121
+ const hasBase = git(["rev-parse", "--verify", "-q", resolved], projectRoot).status === 0;
122
+ if (!hasBase) {
123
+ throw new GitCommandError(`base ref '${resolved}' not found; pass --base-ref`);
124
+ }
125
+ const out = new Set();
126
+ const range = resolved.includes("...") ? resolved : `${resolved}...HEAD`;
127
+ const committed = git(["diff", "--name-status", "--diff-filter=AR", range], projectRoot);
128
+ if (committed.status !== 0) {
129
+ const detail = committed.stdout.trim() || `git diff ${range} exited ${String(committed.status)}`;
130
+ throw new GitCommandError(`committed change-set unavailable for '${range}': ${detail}. ` +
131
+ "Pass --base-ref to a merge-base ancestor of HEAD.");
132
+ }
133
+ for (const p of addedPathsFromNameStatus(committed.stdout)) {
134
+ out.add(normalizeRepoRelPath(p));
135
+ }
136
+ const vsHead = git(["diff", "--name-status", "--diff-filter=AR", "HEAD"], projectRoot);
137
+ if (vsHead.status !== 0) {
138
+ const detail = vsHead.stdout.trim() || `git diff HEAD exited ${String(vsHead.status)}`;
139
+ throw new GitCommandError(`working-tree change-set unavailable: ${detail}`);
140
+ }
141
+ for (const p of addedPathsFromNameStatus(vsHead.stdout)) {
142
+ out.add(normalizeRepoRelPath(p));
143
+ }
144
+ const untracked = git(["ls-files", "--others", "--exclude-standard"], projectRoot);
145
+ if (untracked.status !== 0) {
146
+ const detail = untracked.stdout.trim() || `git ls-files exited ${String(untracked.status)}`;
147
+ throw new GitCommandError(`untracked change-set unavailable: ${detail}`);
148
+ }
149
+ for (const line of untracked.stdout.split("\n")) {
150
+ const p = normalizeRepoRelPath(unquoteGitPath(line));
151
+ if (p.length > 0) {
152
+ out.add(p);
153
+ }
154
+ }
155
+ return [...out];
156
+ }
157
+ function readPayload(projectRoot, relPath, payloads) {
158
+ const n = normalizeRepoRelPath(relPath);
159
+ const injected = payloads?.get(n);
160
+ if (injected !== undefined) {
161
+ if (injected.length > COMPLETED_WRITE_GUARD_MAX_BYTES) {
162
+ return {
163
+ kind: "unsafe",
164
+ detail: `${n}: completed/ artifact is ${String(injected.length)} bytes; ` +
165
+ `exceeds the ${String(COMPLETED_WRITE_GUARD_MAX_BYTES)}-byte read limit`,
166
+ };
167
+ }
168
+ return { kind: "ok", raw: injected };
169
+ }
170
+ const parts = n.split("/").filter((part) => part.length > 0 && part !== ".");
171
+ let abs = resolve(projectRoot);
172
+ let st;
173
+ for (const part of parts) {
174
+ if (part === "..") {
175
+ return {
176
+ kind: "unsafe",
177
+ detail: `${n}: completed/ path escapes project root; refuse without following`,
178
+ };
179
+ }
180
+ abs = join(abs, part);
181
+ try {
182
+ st = lstatSync(abs);
183
+ }
184
+ catch {
185
+ return { kind: "missing" };
186
+ }
187
+ if (st.isSymbolicLink()) {
188
+ return {
189
+ kind: "unsafe",
190
+ detail: `${n}: completed/ path contains a symlink; refuse without following`,
191
+ };
192
+ }
193
+ }
194
+ if (st === undefined || !st.isFile()) {
195
+ return {
196
+ kind: "unsafe",
197
+ detail: `${n}: completed/ add is not a regular file; refuse without reading`,
198
+ };
199
+ }
200
+ if (st.size > COMPLETED_WRITE_GUARD_MAX_BYTES) {
201
+ return {
202
+ kind: "unsafe",
203
+ detail: `${n}: completed/ artifact is ${String(st.size)} bytes; ` +
204
+ `exceeds the ${String(COMPLETED_WRITE_GUARD_MAX_BYTES)}-byte read limit`,
205
+ };
206
+ }
207
+ try {
208
+ return { kind: "ok", raw: readFileSync(abs, "utf8") };
209
+ }
210
+ catch {
211
+ return { kind: "missing" };
212
+ }
213
+ }
214
+ /**
215
+ * Hard check: newly added completed/ artifacts must show runTransition evidence.
216
+ */
217
+ export function evaluateCompletedWriteGuard(projectRoot, options = {}) {
218
+ const root = resolve(projectRoot);
219
+ let added;
220
+ try {
221
+ if (options.addedFiles !== undefined) {
222
+ added = options.addedFiles.map(normalizeRepoRelPath);
223
+ }
224
+ else {
225
+ const inside = git(["rev-parse", "--is-inside-work-tree"], root);
226
+ if (inside.status !== 0 || inside.stdout.trim() !== "true") {
227
+ return {
228
+ code: 0,
229
+ findings: [],
230
+ message: "verify_completed_write_guard: skipped -- not a git working tree.",
231
+ };
232
+ }
233
+ let baseRef = options.baseRef ?? "";
234
+ if (baseRef.length === 0 || baseRef === "HEAD") {
235
+ const resolved = resolveDefaultBaseRef(root);
236
+ if (resolved === null) {
237
+ return {
238
+ code: 2,
239
+ findings: [],
240
+ message: "verify_completed_write_guard: no merge-base ref found " +
241
+ "(origin/master|main, DEFT_BASE_REF, or GITHUB_BASE_REF). " +
242
+ "Pass --base-ref. Unguarded completed/ adds cannot be skipped.",
243
+ };
244
+ }
245
+ baseRef = resolved;
246
+ }
247
+ added = discoverAddedFiles(root, baseRef);
248
+ }
249
+ }
250
+ catch (err) {
251
+ if (err instanceof GitNotFoundError) {
252
+ return {
253
+ code: 2,
254
+ findings: [],
255
+ message: "verify_completed_write_guard: 'git' executable not found on PATH.\n" +
256
+ " Recovery: install git or run inside a git working tree.",
257
+ };
258
+ }
259
+ if (err instanceof GitCommandError) {
260
+ const msg = err.message.toLowerCase();
261
+ if (msg.includes("not a git repository") ||
262
+ msg.includes("outside repository") ||
263
+ msg.includes("not a git working tree")) {
264
+ return {
265
+ code: 0,
266
+ findings: [],
267
+ message: `verify_completed_write_guard: skipped -- not a git working tree (${err.message}).`,
268
+ };
269
+ }
270
+ return {
271
+ code: 2,
272
+ findings: [],
273
+ message: `verify_completed_write_guard: git failed -- ${err.message}`,
274
+ };
275
+ }
276
+ throw err;
277
+ }
278
+ const findings = [];
279
+ for (const rel of added) {
280
+ if (!isCompletedArtifactRel(rel)) {
281
+ continue;
282
+ }
283
+ const payload = readPayload(root, rel, options.payloads);
284
+ if (payload.kind === "missing") {
285
+ findings.push({
286
+ relPath: rel,
287
+ detail: `${rel}: added under completed/ but unreadable`,
288
+ });
289
+ continue;
290
+ }
291
+ if (payload.kind === "unsafe") {
292
+ findings.push({
293
+ relPath: rel,
294
+ detail: payload.detail,
295
+ });
296
+ continue;
297
+ }
298
+ const plan = parsePlan(payload.raw);
299
+ if (plan === null) {
300
+ findings.push({
301
+ relPath: rel,
302
+ detail: `${rel}: added under completed/ with unreadable plan`,
303
+ });
304
+ continue;
305
+ }
306
+ if (!hasTransitionWrite(plan)) {
307
+ findings.push({
308
+ relPath: rel,
309
+ detail: `${rel}: added under completed/ without a runTransition write`,
310
+ });
311
+ }
312
+ }
313
+ if (findings.length === 0) {
314
+ return {
315
+ code: 0,
316
+ findings: [],
317
+ message: "verify_completed_write_guard: clean -- no unguarded completed/ adds",
318
+ };
319
+ }
320
+ return {
321
+ code: 1,
322
+ findings,
323
+ message: `verify_completed_write_guard: ${findings.length} unguarded completed/ add(s) (#3679).\n` +
324
+ findings.map((f) => ` - ${f.detail}`).join("\n") +
325
+ `\n${LEFTOVER_LAND_PR_REMEDIATION}`,
326
+ };
327
+ }
328
+ function listCompletedArtifactRels(projectRoot) {
329
+ const out = [];
330
+ for (const dirName of [MIGRATED_ARTIFACT_DIR, LEGACY_ARTIFACT_DIR]) {
331
+ const completedDir = join(projectRoot, dirName, "completed");
332
+ if (!existsSync(completedDir)) {
333
+ continue;
334
+ }
335
+ let names = [];
336
+ try {
337
+ names = readdirSync(completedDir).filter((n) => hasArtifactSuffix(n));
338
+ }
339
+ catch {
340
+ continue;
341
+ }
342
+ for (const name of names) {
343
+ out.push(`${dirName}/completed/${name}`);
344
+ }
345
+ }
346
+ return out.sort();
347
+ }
348
+ /**
349
+ * Corpus scan for doctor: artifacts without transition evidence.
350
+ * Caller marks these advisory so historical pre-stamp files do not red doctor.
351
+ */
352
+ export function scanCompletedWriteCorpus(projectRoot) {
353
+ const findings = [];
354
+ const rels = listCompletedArtifactRels(projectRoot);
355
+ for (const rel of rels) {
356
+ const payload = readPayload(resolve(projectRoot), rel, undefined);
357
+ if (payload.kind !== "ok") {
358
+ continue;
359
+ }
360
+ const plan = parsePlan(payload.raw);
361
+ if (plan === null) {
362
+ continue;
363
+ }
364
+ if (!hasTransitionWrite(plan)) {
365
+ findings.push({
366
+ relPath: rel,
367
+ detail: `${rel}: completed/ artifact has no runTransition write (historical advisory)`,
368
+ });
369
+ }
370
+ }
371
+ return { findings, scanned: rels.length };
372
+ }
373
+ //# sourceMappingURL=completed-write-guard.js.map
@@ -1,5 +1,6 @@
1
1
  export * from "./completed-consistency.js";
2
2
  export * from "./completed-tracked-on-delivery.js";
3
+ export * from "./completed-write-guard.js";
3
4
  export * from "./event.js";
4
5
  export * as eventDetect from "./event-detect.js";
5
6
  export * as events from "./events.js";
@@ -1,5 +1,6 @@
1
1
  export * from "./completed-consistency.js";
2
2
  export * from "./completed-tracked-on-delivery.js";
3
+ export * from "./completed-write-guard.js";
3
4
  export * from "./event.js";
4
5
  export * as eventDetect from "./event-detect.js";
5
6
  export * as events from "./events.js";
@@ -9,6 +9,17 @@
9
9
  * so distinct execution contexts are not collapsed.
10
10
  */
11
11
  import type { LiteralAcceptanceCommand, RejectedLiteralCommand } from "./types.js";
12
+ /** Reason stamped when an inline backtick is recorded as a mention, not AC (#3721). */
13
+ export declare const INLINE_PROSE_MENTION_REASON = "inline backtick in prose is a mention, not a stated acceptance command (#3721)";
14
+ /**
15
+ * True when a capture-only row came from an inline backtick in prose (#3721).
16
+ * Those mentions are not stated acceptance commands. Labeled, prompt, and fence
17
+ * spans stay stated.
18
+ */
19
+ export declare function isInlineProseMention(cmd: {
20
+ readonly source?: string;
21
+ readonly sourceSpan?: string | null;
22
+ }): boolean;
12
23
  /** Result of capture including the operator-visible rejected ledger. */
13
24
  export interface CaptureLiteralAcceptanceResult {
14
25
  readonly commands: readonly LiteralAcceptanceCommand[];
@@ -57,6 +68,11 @@ export declare function isProseDerivedRejection(rejected: RejectedLiteralCommand
57
68
  * prose-derived rejections no longer depends on this (#3511).
58
69
  */
59
70
  export declare function hasStructuredAcceptanceCommands(plan: Record<string, unknown> | null | undefined): boolean;
71
+ /**
72
+ * Command strings the operator recorded as not acceptance commands (#3721).
73
+ * Listing a string here dispositions a capture without asserting it is AC.
74
+ */
75
+ export declare function readNotAcceptanceCommands(plan: Record<string, unknown> | null | undefined): ReadonlySet<string>;
60
76
  /** Format rejected ledger for CLI / complete-gate messages. */
61
77
  export declare function formatRejectedLedger(rejected: readonly RejectedLiteralCommand[]): string;
62
78
  //# sourceMappingURL=capture.d.ts.map
@@ -9,7 +9,7 @@
9
9
  * so distinct execution contexts are not collapsed.
10
10
  */
11
11
  import { evaluateCommandSafety } from "./safety.js";
12
- import { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY } from "./types.js";
12
+ import { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_NOT_COMMANDS_METADATA_KEY, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, } from "./types.js";
13
13
  /** Labeled keywords (single-token, linear match). */
14
14
  const LABELED_KEYWORDS = new Set(["verify", "command", "run", "check", "exec", "shell"]);
15
15
  /** Words that look like a CLI invocation start (not prose). */
@@ -85,8 +85,24 @@ function pushCommand(buckets, cmd) {
85
85
  return;
86
86
  }
87
87
  const key = commandDedupeKey(cmd);
88
- if (buckets.seen.has(key))
88
+ if (buckets.seen.has(key)) {
89
+ const idx = buckets.out.findIndex((row) => commandDedupeKey(row) === key);
90
+ const existing = idx >= 0 ? buckets.out[idx] : undefined;
91
+ if (existing !== undefined &&
92
+ idx >= 0 &&
93
+ isInlineProseMention(existing) &&
94
+ isExecutableSource(cmd.source)) {
95
+ buckets.out[idx] = {
96
+ command: cmd.command,
97
+ cwd: cmd.cwd ?? null,
98
+ expectedStdout: cmd.expectedStdout ?? null,
99
+ expectedExitCode: cmd.expectedExitCode ?? 0,
100
+ source: cmd.source,
101
+ sourceSpan: cmd.sourceSpan ?? null,
102
+ };
103
+ }
89
104
  return;
105
+ }
90
106
  buckets.seen.add(key);
91
107
  buckets.out.push({
92
108
  command: cmd.command,
@@ -382,8 +398,9 @@ function matchMarkdownHeading(line) {
382
398
  * marker). Mid-line label capture is deliberately not attempted (#3484): a
383
399
  * `verify:<verb>` token inside ordinary prose has no terminator, so the capture
384
400
  * swallowed the rest of the paragraph and the phantom then blocked completion on
385
- * the safety ledger. Commands stated mid-sentence must live in a fence, a `$`
386
- * prompt, or an inline backtick span all of which have real delimiters.
401
+ * the safety ledger. Commands stated mid-sentence must live in a fence or a `$`
402
+ * prompt. An inline backtick inside a prose sentence is a reference, not a
403
+ * stated command (#3721).
387
404
  */
388
405
  function extractFromLabeledLines(text, buckets) {
389
406
  const lines = text.split(/\r?\n/);
@@ -412,9 +429,13 @@ function extractFromLabeledLines(text, buckets) {
412
429
  }
413
430
  }
414
431
  }
432
+ /** Reason stamped when an inline backtick is recorded as a mention, not AC (#3721). */
433
+ export const INLINE_PROSE_MENTION_REASON = "inline backtick in prose is a mention, not a stated acceptance command (#3721)";
415
434
  /**
416
435
  * Extract inline `` `command` `` spans that follow verify/run language on the same line.
417
- * Example: `run \`task check\` before done`
436
+ * These are references, not stated acceptance commands (#3721). Recorded on the
437
+ * rejected ledger with an `inline@` span so #3511 demotes them to advisory.
438
+ * They must not enter the stated-command list (that would require promotion).
418
439
  */
419
440
  function extractInlineVerifySpans(text, buckets) {
420
441
  const lines = text.split(/\r?\n/);
@@ -440,10 +461,23 @@ function extractInlineVerifySpans(text, buckets) {
440
461
  const normalized = normalizeCommand(inner);
441
462
  if (normalized === null || !looksLikeShellCommand(normalized))
442
463
  continue;
443
- pushUnique(buckets, normalized, "task_statement", `inline@L${i + 1}`);
464
+ recordRejected(buckets, normalized, INLINE_PROSE_MENTION_REASON, `inline@L${i + 1}`);
444
465
  }
445
466
  }
446
467
  }
468
+ /**
469
+ * True when a capture-only row came from an inline backtick in prose (#3721).
470
+ * Those mentions are not stated acceptance commands. Labeled, prompt, and fence
471
+ * spans stay stated.
472
+ */
473
+ export function isInlineProseMention(cmd) {
474
+ if (cmd.source !== "task_statement")
475
+ return false;
476
+ const span = cmd.sourceSpan;
477
+ if (span === null || span === undefined)
478
+ return false;
479
+ return span.startsWith("inline@");
480
+ }
447
481
  function emptyBuckets() {
448
482
  return {
449
483
  out: [],
@@ -519,7 +553,7 @@ export function readStoredLiteralAcceptanceDetailed(plan) {
519
553
  // swarm.verify_commands is a string list (legacy). Prefer richer
520
554
  // literal_acceptance_commands rows already loaded — do not invent a
521
555
  // null-cwd duplicate for the same command text.
522
- const alreadyHasCommand = (command) => buckets.out.some((c) => c.command === command);
556
+ const alreadyHasCommand = (command) => buckets.out.some((c) => c.command === command && !isInlineProseMention(c));
523
557
  for (const cmd of coerceCommandList(swarm.verify_commands, "verify_commands", "swarm.verify_commands")) {
524
558
  if (alreadyHasCommand(cmd.command))
525
559
  continue;
@@ -793,6 +827,40 @@ export function hasStructuredAcceptanceCommands(plan) {
793
827
  return false;
794
828
  return hasNonEmptyCommandList(swarm.verify_commands);
795
829
  }
830
+ /**
831
+ * Command strings the operator recorded as not acceptance commands (#3721).
832
+ * Listing a string here dispositions a capture without asserting it is AC.
833
+ */
834
+ export function readNotAcceptanceCommands(plan) {
835
+ const rec = asRecord(plan);
836
+ if (rec === null)
837
+ return new Set();
838
+ const metadata = asRecord(rec.metadata);
839
+ if (metadata === null)
840
+ return new Set();
841
+ const raw = metadata[LITERAL_ACCEPTANCE_NOT_COMMANDS_METADATA_KEY] ??
842
+ metadata.literalAcceptanceNotCommands ??
843
+ null;
844
+ const out = new Set();
845
+ if (typeof raw === "string" && raw.trim().length > 0) {
846
+ out.add(raw.trim());
847
+ return out;
848
+ }
849
+ if (!Array.isArray(raw))
850
+ return out;
851
+ for (const entry of raw) {
852
+ if (typeof entry === "string" && entry.trim().length > 0) {
853
+ out.add(entry.trim());
854
+ continue;
855
+ }
856
+ const row = asRecord(entry);
857
+ if (row === null)
858
+ continue;
859
+ if (isNonEmptyString(row.command))
860
+ out.add(row.command.trim());
861
+ }
862
+ return out;
863
+ }
796
864
  /** Format rejected ledger for CLI / complete-gate messages. */
797
865
  export function formatRejectedLedger(rejected) {
798
866
  if (rejected.length === 0)
@@ -3,7 +3,7 @@
3
3
  */
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { resolve } from "node:path";
6
- import { captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, isProseDerivedRejection, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
6
+ import { captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, INLINE_PROSE_MENTION_REASON, isInlineProseMention, isProseDerivedRejection, readNotAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
7
7
  import { runLiteralAcceptanceCommands } from "./run.js";
8
8
  function asRecord(value) {
9
9
  if (typeof value === "object" && value !== null && !Array.isArray(value)) {
@@ -40,11 +40,33 @@ function partitionRejected(rejected) {
40
40
  */
41
41
  export function resolveLiteralAcceptanceDetailed(plan, options = {}) {
42
42
  const raw = resolveRawLiteralAcceptance(plan, options);
43
+ const notCommands = readNotAcceptanceCommands(plan);
44
+ const stated = [];
45
+ const mentionAdvisory = [];
46
+ for (const c of raw.commands) {
47
+ if (isInlineProseMention(c)) {
48
+ mentionAdvisory.push({
49
+ command: c.command,
50
+ reason: INLINE_PROSE_MENTION_REASON,
51
+ sourceSpan: c.sourceSpan ?? null,
52
+ });
53
+ continue;
54
+ }
55
+ if (c.source === "task_statement" && notCommands.has(c.command)) {
56
+ mentionAdvisory.push({
57
+ command: c.command,
58
+ reason: "operator recorded this capture as not an acceptance command (#3721)",
59
+ sourceSpan: c.sourceSpan ?? null,
60
+ });
61
+ continue;
62
+ }
63
+ stated.push(c);
64
+ }
43
65
  const split = partitionRejected(raw.rejected);
44
66
  return {
45
- commands: raw.commands,
67
+ commands: stated,
46
68
  rejected: split.blocking,
47
- advisoryRejected: split.advisory,
69
+ advisoryRejected: [...split.advisory, ...mentionAdvisory],
48
70
  transcriptPromptSkipped: raw.transcriptPromptSkipped,
49
71
  };
50
72
  }
@@ -4,9 +4,9 @@
4
4
  * Capture exact stated shell commands at intake; run them verbatim before done.
5
5
  * Extends #973; survives ceremony dial rapid/minimal (#3214).
6
6
  */
7
- export { attachLiteralAcceptanceCommands, type CaptureLiteralAcceptanceResult, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, hasStructuredAcceptanceCommands, isProseDerivedRejection, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
7
+ export { attachLiteralAcceptanceCommands, type CaptureLiteralAcceptanceResult, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, hasStructuredAcceptanceCommands, INLINE_PROSE_MENTION_REASON, isInlineProseMention, isProseDerivedRejection, readNotAcceptanceCommands, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
8
8
  export { appendLiteralAcceptanceAdvisory, type EvaluateLiteralAcceptanceOptions, evaluateLiteralAcceptanceFromPath, evaluateLiteralAcceptanceFromPlan, formatLiteralAcceptanceAdvisory, isLiteralAcceptanceRequiredAtCeremonyDepth, LITERAL_ACCEPTANCE_ADVISORY_MARKER, type ResolvedLiteralAcceptance, resolveLiteralAcceptanceCommands, resolveLiteralAcceptanceDetailed, stripLiteralAcceptanceAdvisory, } from "./evaluate.js";
9
9
  export { defaultLiteralAcceptanceRunner, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
10
10
  export { type CommandSafetyResult, evaluateCommandSafety, evaluateNoopDenylist, evaluateStampAcceptanceSafety, isExecutableLiteralSource, isNoopRefusalReason, isVerbatimStatementSpan, NOOP_ACCEPTANCE_REMEDIATION, REJECTED_NOOP_OUTCOME, type RejectedNoopOutcome, type StampAcceptanceCommand, type StampAcceptanceSafetyInput, type StampAcceptanceSafetyResult, type StampSourceRung, } from "./safety.js";
11
- export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, type LiteralAcceptanceCommand, type LiteralAcceptanceGateResult, type LiteralAcceptanceRunner, type LiteralAcceptanceRunResult, type LiteralAcceptanceSource, type RejectedLiteralCommand, } from "./types.js";
11
+ export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_NOT_COMMANDS_METADATA_KEY, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, type LiteralAcceptanceCommand, type LiteralAcceptanceGateResult, type LiteralAcceptanceRunner, type LiteralAcceptanceRunResult, type LiteralAcceptanceSource, type RejectedLiteralCommand, } from "./types.js";
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -4,9 +4,9 @@
4
4
  * Capture exact stated shell commands at intake; run them verbatim before done.
5
5
  * Extends #973; survives ceremony dial rapid/minimal (#3214).
6
6
  */
7
- export { attachLiteralAcceptanceCommands, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, hasStructuredAcceptanceCommands, isProseDerivedRejection, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
7
+ export { attachLiteralAcceptanceCommands, captureAndAttachLiteralAcceptance, captureLiteralAcceptanceCommands, captureLiteralAcceptanceCommandsDetailed, formatRejectedLedger, hasStructuredAcceptanceCommands, INLINE_PROSE_MENTION_REASON, isInlineProseMention, isProseDerivedRejection, readNotAcceptanceCommands, readStoredLiteralAcceptanceCommands, readStoredLiteralAcceptanceDetailed, } from "./capture.js";
8
8
  export { appendLiteralAcceptanceAdvisory, evaluateLiteralAcceptanceFromPath, evaluateLiteralAcceptanceFromPlan, formatLiteralAcceptanceAdvisory, isLiteralAcceptanceRequiredAtCeremonyDepth, LITERAL_ACCEPTANCE_ADVISORY_MARKER, resolveLiteralAcceptanceCommands, resolveLiteralAcceptanceDetailed, stripLiteralAcceptanceAdvisory, } from "./evaluate.js";
9
9
  export { defaultLiteralAcceptanceRunner, runLiteralAcceptanceCommand, runLiteralAcceptanceCommands, } from "./run.js";
10
10
  export { evaluateCommandSafety, evaluateNoopDenylist, evaluateStampAcceptanceSafety, isExecutableLiteralSource, isNoopRefusalReason, isVerbatimStatementSpan, NOOP_ACCEPTANCE_REMEDIATION, REJECTED_NOOP_OUTCOME, } from "./safety.js";
11
- export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, } from "./types.js";
11
+ export { EXECUTABLE_LITERAL_SOURCES, LITERAL_ACCEPTANCE_METADATA_KEY, LITERAL_ACCEPTANCE_METADATA_KEY_CAMEL, LITERAL_ACCEPTANCE_NOT_COMMANDS_METADATA_KEY, LITERAL_ACCEPTANCE_REJECTED_METADATA_KEY, } from "./types.js";
12
12
  //# sourceMappingURL=index.js.map