@deftai/directive-core 0.104.0 → 0.105.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 (225) hide show
  1. package/dist/authz/classify.js +526 -42
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/named-cause.d.ts +1 -1
  5. package/dist/check/named-cause.js +33 -3
  6. package/dist/codebase/default-extractor.d.ts +5 -0
  7. package/dist/codebase/default-extractor.js +7 -15
  8. package/dist/deposit/copy-tree.js +60 -0
  9. package/dist/deposit/python-free.js +26 -18
  10. package/dist/doctor/checks.d.ts +8 -0
  11. package/dist/doctor/checks.js +37 -25
  12. package/dist/doctor/doctor-state.d.ts +4 -0
  13. package/dist/doctor/doctor-state.js +7 -1
  14. package/dist/doctor/main.js +9 -19
  15. package/dist/doctor/types.d.ts +9 -0
  16. package/dist/fs/contained-write.d.ts +115 -1
  17. package/dist/fs/contained-write.js +241 -5
  18. package/dist/fs/mutation-ledger.d.ts +54 -0
  19. package/dist/fs/mutation-ledger.js +133 -0
  20. package/dist/fs/non-product-dirs.d.ts +79 -0
  21. package/dist/fs/non-product-dirs.js +96 -0
  22. package/dist/hooks/dispatcher.d.ts +3 -1
  23. package/dist/hooks/dispatcher.js +14 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/init-deposit/agent-hooks.js +17 -15
  27. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  28. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  29. package/dist/init-deposit/gitignore.js +9 -0
  30. package/dist/init-deposit/hygiene.d.ts +47 -11
  31. package/dist/init-deposit/hygiene.js +172 -37
  32. package/dist/init-deposit/legacy-detect.js +6 -2
  33. package/dist/init-deposit/refresh.d.ts +21 -1
  34. package/dist/init-deposit/refresh.js +106 -9
  35. package/dist/init-deposit/scaffold.js +28 -279
  36. package/dist/init-deposit/slash-deposit.js +13 -7
  37. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  38. package/dist/init-deposit/xbrief-projections.js +24 -11
  39. package/dist/intake/clause-derivation.d.ts +38 -1
  40. package/dist/intake/clause-derivation.js +211 -9
  41. package/dist/intake/issue-ingest.d.ts +3 -1
  42. package/dist/intake/issue-ingest.js +35 -6
  43. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  44. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  45. package/dist/literal-acceptance/capture.d.ts +10 -0
  46. package/dist/literal-acceptance/capture.js +224 -75
  47. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  48. package/dist/literal-acceptance/evaluate.js +70 -2
  49. package/dist/literal-acceptance/index.d.ts +3 -3
  50. package/dist/literal-acceptance/index.js +3 -3
  51. package/dist/literal-acceptance/safety.d.ts +41 -0
  52. package/dist/literal-acceptance/safety.js +126 -2
  53. package/dist/literal-acceptance/types.d.ts +8 -0
  54. package/dist/orphan-active/evaluate.d.ts +7 -1
  55. package/dist/orphan-active/evaluate.js +102 -23
  56. package/dist/platform/platform-capabilities.js +2 -3
  57. package/dist/policy/base-branch.d.ts +37 -0
  58. package/dist/policy/base-branch.js +114 -0
  59. package/dist/policy/branch-sync.d.ts +80 -0
  60. package/dist/policy/branch-sync.js +211 -0
  61. package/dist/policy/ceremony-dial-escalation.js +1 -0
  62. package/dist/policy/ceremony-dial.js +4 -14
  63. package/dist/policy/delivery-branch.d.ts +5 -3
  64. package/dist/policy/delivery-branch.js +7 -3
  65. package/dist/policy/file-size-thresholds.d.ts +24 -0
  66. package/dist/policy/file-size-thresholds.js +23 -0
  67. package/dist/policy/forge-outage-retry.d.ts +47 -0
  68. package/dist/policy/forge-outage-retry.js +157 -0
  69. package/dist/policy/index.d.ts +7 -0
  70. package/dist/policy/index.js +66 -7
  71. package/dist/policy/project-invariants.d.ts +50 -0
  72. package/dist/policy/project-invariants.js +205 -0
  73. package/dist/policy/sync-default.d.ts +129 -0
  74. package/dist/policy/sync-default.js +452 -0
  75. package/dist/policy/sync-max-files.d.ts +93 -0
  76. package/dist/policy/sync-max-files.js +192 -0
  77. package/dist/pr-monitor/absent-required.d.ts +11 -0
  78. package/dist/pr-monitor/absent-required.js +61 -0
  79. package/dist/pr-monitor/constants.d.ts +7 -0
  80. package/dist/pr-monitor/constants.js +7 -0
  81. package/dist/pr-monitor/index.d.ts +2 -1
  82. package/dist/pr-monitor/index.js +2 -1
  83. package/dist/pr-monitor/main.js +4 -0
  84. package/dist/pr-monitor/monitor.js +24 -1
  85. package/dist/pr-wait-mergeable/classify.js +6 -0
  86. package/dist/preflight/evaluate.d.ts +4 -0
  87. package/dist/preflight/evaluate.js +35 -1
  88. package/dist/preflight/index.d.ts +4 -0
  89. package/dist/preflight/index.js +2 -0
  90. package/dist/preflight/intended-placement.d.ts +62 -0
  91. package/dist/preflight/intended-placement.js +275 -0
  92. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  93. package/dist/preflight/project-invariants-gate.js +82 -0
  94. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  95. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  96. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  97. package/dist/product-first-done-gate/acceptance.js +79 -8
  98. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  99. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  100. package/dist/product-first-done-gate/evaluate.d.ts +12 -0
  101. package/dist/product-first-done-gate/evaluate.js +254 -31
  102. package/dist/product-first-done-gate/index.d.ts +2 -0
  103. package/dist/product-first-done-gate/index.js +1 -0
  104. package/dist/release/build-dist.d.ts +101 -0
  105. package/dist/release/build-dist.js +428 -46
  106. package/dist/run-summary/emit.d.ts +16 -10
  107. package/dist/run-summary/emit.js +26 -15
  108. package/dist/run-summary/index.d.ts +3 -2
  109. package/dist/run-summary/index.js +2 -1
  110. package/dist/run-summary/session-id.d.ts +21 -0
  111. package/dist/run-summary/session-id.js +92 -0
  112. package/dist/run-summary/types.d.ts +29 -4
  113. package/dist/run-summary/types.js +1 -0
  114. package/dist/scope/acceptance-evidence.d.ts +19 -7
  115. package/dist/scope/acceptance-evidence.js +38 -11
  116. package/dist/scope/delivery-evidence.d.ts +3 -3
  117. package/dist/scope/delivery-evidence.js +22 -27
  118. package/dist/scope/index.d.ts +1 -0
  119. package/dist/scope/index.js +1 -0
  120. package/dist/scope/parent-lineage.js +7 -6
  121. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  122. package/dist/scope/project-invariant-coverage.js +322 -0
  123. package/dist/scope/transition.js +1 -1
  124. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  125. package/dist/scope-provenance/compare-intent.js +233 -0
  126. package/dist/scope-provenance/digest.d.ts +18 -1
  127. package/dist/scope-provenance/digest.js +16 -11
  128. package/dist/scope-provenance/evaluate.d.ts +5 -1
  129. package/dist/scope-provenance/evaluate.js +64 -4
  130. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  131. package/dist/scope-provenance/extract-intent.js +262 -0
  132. package/dist/scope-provenance/index.d.ts +7 -1
  133. package/dist/scope-provenance/index.js +7 -1
  134. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  135. package/dist/scope-provenance/intent-digest.js +33 -0
  136. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  137. package/dist/scope-provenance/intent-evaluate.js +149 -0
  138. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  139. package/dist/scope-provenance/json-tokenizer.js +225 -0
  140. package/dist/scope-provenance/known-machine.d.ts +38 -0
  141. package/dist/scope-provenance/known-machine.js +157 -0
  142. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  143. package/dist/scope-provenance/mint-artifacts.js +425 -0
  144. package/dist/session/ac-pass-banking.d.ts +4 -36
  145. package/dist/session/ac-pass-banking.js +30 -77
  146. package/dist/session/ac-pass-reuse.d.ts +53 -0
  147. package/dist/session/ac-pass-reuse.js +95 -0
  148. package/dist/session/git.js +19 -4
  149. package/dist/session/index.d.ts +4 -0
  150. package/dist/session/index.js +4 -0
  151. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  152. package/dist/session/occupancy-stress-worker.js +36 -0
  153. package/dist/session/occupancy.d.ts +97 -0
  154. package/dist/session/occupancy.js +422 -0
  155. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  156. package/dist/session/product-state-hash.d.ts +26 -0
  157. package/dist/session/product-state-hash.js +683 -0
  158. package/dist/session/session-ready.d.ts +2 -0
  159. package/dist/session/session-ready.js +52 -30
  160. package/dist/session/session-start.d.ts +7 -0
  161. package/dist/session/session-start.js +71 -7
  162. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  163. package/dist/session/verify-ac-session-cache.js +126 -0
  164. package/dist/session/verify-session-ritual.d.ts +5 -0
  165. package/dist/session/verify-session-ritual.js +9 -0
  166. package/dist/slash/openclaw-deposit.js +13 -9
  167. package/dist/slice/constants.d.ts +2 -2
  168. package/dist/slice/constants.js +2 -2
  169. package/dist/slice/existing.js +9 -3
  170. package/dist/slice/lock.d.ts +16 -1
  171. package/dist/slice/lock.js +137 -11
  172. package/dist/story-ready/evaluate.d.ts +2 -0
  173. package/dist/story-ready/evaluate.js +13 -0
  174. package/dist/swarm/complete-cohort.d.ts +3 -0
  175. package/dist/swarm/complete-cohort.js +59 -11
  176. package/dist/swarm/finalize-cohort.js +33 -2
  177. package/dist/swarm/launch.d.ts +22 -0
  178. package/dist/swarm/launch.js +155 -0
  179. package/dist/swarm/routing-set-cli.d.ts +11 -1
  180. package/dist/swarm/routing-set-cli.js +13 -3
  181. package/dist/swarm/routing-verify.d.ts +12 -0
  182. package/dist/swarm/routing-verify.js +59 -20
  183. package/dist/swarm/routing.d.ts +14 -0
  184. package/dist/swarm/routing.js +53 -3
  185. package/dist/telemetry-coverage/fake-trial.js +10 -0
  186. package/dist/telemetry-coverage/kinds.js +2 -0
  187. package/dist/triage/actions/index.js +4 -1
  188. package/dist/ts-check-lane/index.d.ts +3 -0
  189. package/dist/ts-check-lane/index.js +2 -0
  190. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  191. package/dist/ts-check-lane/progress-reporter.js +38 -0
  192. package/dist/ts-check-lane/progress.d.ts +32 -0
  193. package/dist/ts-check-lane/progress.js +57 -0
  194. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  195. package/dist/ts-check-lane/run-lane.js +8 -6
  196. package/dist/validate-content/validate-links.d.ts +8 -0
  197. package/dist/validate-content/validate-links.js +9 -15
  198. package/dist/value/feedback-file.js +12 -2
  199. package/dist/vbrief-build/index.d.ts +1 -0
  200. package/dist/vbrief-build/index.js +1 -0
  201. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  202. package/dist/vbrief-build/project-invariants-io.js +41 -0
  203. package/dist/vbrief-reconcile/graph.js +18 -1
  204. package/dist/vbrief-reconcile/index.d.ts +1 -1
  205. package/dist/vbrief-reconcile/index.js +1 -1
  206. package/dist/vbrief-reconcile/types.d.ts +13 -0
  207. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  208. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  209. package/dist/vbrief-validate/index.d.ts +1 -1
  210. package/dist/vbrief-validate/index.js +1 -1
  211. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  212. package/dist/vbrief-validate/plan-hooks.js +68 -0
  213. package/dist/verify-ac/evaluate.d.ts +7 -1
  214. package/dist/verify-ac/evaluate.js +34 -26
  215. package/dist/verify-ac/flag.d.ts +4 -0
  216. package/dist/verify-ac/flag.js +16 -0
  217. package/dist/verify-ac/index.d.ts +2 -2
  218. package/dist/verify-ac/index.js +2 -2
  219. package/dist/verify-source/contained-writes.js +2 -0
  220. package/dist/verify-source/verify-stubs.d.ts +15 -1
  221. package/dist/verify-source/verify-stubs.js +20 -16
  222. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  223. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  224. package/dist/xbrief-migrate/migrate-project.js +2 -5
  225. package/package.json +3 -3
@@ -198,13 +198,16 @@ export function resolveDeliveryTip(projectRoot, tipOverride, runGit) {
198
198
  return { tip: candidate, error: null };
199
199
  }
200
200
  }
201
- // Last resort: HEAD of current checkout (still a real tip for fixtures).
202
- if (refExists(projectRoot, "HEAD", runGit)) {
203
- return { tip: "HEAD", error: null };
204
- }
201
+ // HEAD fallback (#3478 review). This gate exists to prove an artifact landed
202
+ // on the delivery tip rather than on feature-worktree HEAD; falling back to
203
+ // HEAD when the delivery ref is missing (shallow clone, unfetched worktree,
204
+ // fetch-depth:1 checkout) checks the very branch whose land is in question and
205
+ // silently passes. Unresolvable delivery tip must fail closed -- pass an
206
+ // explicit --tip (e.g. --tip HEAD for an in-flight land PR) to opt in.
205
207
  return {
206
208
  tip: null,
207
- error: `could not resolve delivery tip for branch '${branch}' (no origin/${branch}, ${branch}, or HEAD)`,
209
+ error: `could not resolve delivery tip for branch '${branch}' (no origin/${branch} or ${branch}); ` +
210
+ "fetch the delivery branch or pass an explicit --tip",
208
211
  };
209
212
  }
210
213
  function listTreePaths(projectRoot, tip, prefixes, runGit) {
@@ -270,6 +273,28 @@ function issuesFromBlobPaths(projectRoot, tip, paths, defaultRepo, runGit, origi
270
273
  }
271
274
  return hits;
272
275
  }
276
+ /**
277
+ * Narrow the origin map to the requested issue.
278
+ *
279
+ * Matching is repo-scoped when the caller's repo is known (#3478 review): a
280
+ * corpus entry for another repository's same-numbered issue must NOT survive
281
+ * this filter. Letting it through leaves `originMap` non-empty, which suppresses
282
+ * the synthesis below and makes the gate resolve the wrong `repo#number` --
283
+ * a foreign open issue then green-skips an unlanded local one.
284
+ */
285
+ function filterOriginsByIssue(originMap, issue, repo) {
286
+ const out = new Map();
287
+ for (const [key, entry] of originMap) {
288
+ if (entry.issue.number !== issue) {
289
+ continue;
290
+ }
291
+ if (repo !== null && entry.issue.repo !== repo) {
292
+ continue;
293
+ }
294
+ out.set(key, entry);
295
+ }
296
+ return out;
297
+ }
273
298
  function mergeOrigins(hits) {
274
299
  const map = new Map();
275
300
  for (const hit of hits) {
@@ -353,7 +378,47 @@ export function evaluateCompletedTracked(projectRoot, options = {}) {
353
378
  const localHits = scanLocalOrigins(root, defaultRepo);
354
379
  const tipNonterminalPaths = listTreePaths(root, tip, nonterminalPrefixes(), runGit);
355
380
  const tipNonterminalHits = issuesFromBlobPaths(root, tip, tipNonterminalPaths, defaultRepo, runGit, `tip:${tip}`);
356
- const originMap = mergeOrigins([...localHits, ...tipNonterminalHits]);
381
+ let originMap = mergeOrigins([...localHits, ...tipNonterminalHits]);
382
+ // Delivery tip only — the land invariant is post-merge tip truth (#3264 AC).
383
+ // Lifecycle PRs that commit completed/ on a feature branch are not expected
384
+ // to satisfy this gate until merge; run the verb with --tip HEAD when
385
+ // validating an in-flight land PR. The gate is a standalone verify verb
386
+ // (not wired into check:consumer) so product PRs are not deadlocked.
387
+ const tipTerminalPaths = listTreePaths(root, tip, terminalPrefixes(), runGit);
388
+ const tipTerminalHits = issuesFromBlobPaths(root, tip, tipTerminalPaths, defaultRepo, runGit, `tip:${tip}`);
389
+ const landedKeys = new Set(tipTerminalHits.map((h) => issueKey(h.issue)));
390
+ const issueFilter = options.issue ?? null;
391
+ if (issueFilter !== null) {
392
+ if (!Number.isInteger(issueFilter) || issueFilter <= 0) {
393
+ return {
394
+ code: 2,
395
+ message: `verify:completed-tracked: --issue must be a positive integer (got ${issueFilter})`,
396
+ stream: "stderr",
397
+ missing: [],
398
+ tip,
399
+ };
400
+ }
401
+ originMap = filterOriginsByIssue(originMap, issueFilter, defaultRepo);
402
+ // Drive-to DONE must not green-skip a named origin with no local brief
403
+ // (#3476). Synthesize the requested issue so closed+unlanded fails.
404
+ if (originMap.size === 0) {
405
+ if (defaultRepo === null) {
406
+ return {
407
+ code: 2,
408
+ message: `verify:completed-tracked: --issue ${issueFilter} requires --repo or a resolvable ` +
409
+ "origin remote when no scoped xBRIEF origin is present.",
410
+ stream: "stderr",
411
+ missing: [],
412
+ tip,
413
+ };
414
+ }
415
+ const synthetic = {
416
+ issue: { repo: defaultRepo, number: issueFilter },
417
+ origins: [`--issue ${issueFilter}`],
418
+ };
419
+ originMap.set(issueKey(synthetic.issue), synthetic);
420
+ }
421
+ }
357
422
  if (originMap.size === 0) {
358
423
  if (quiet) {
359
424
  return { code: 0, message: "", stream: "none", missing: [], tip };
@@ -366,14 +431,6 @@ export function evaluateCompletedTracked(projectRoot, options = {}) {
366
431
  tip,
367
432
  };
368
433
  }
369
- // Delivery tip only — the land invariant is post-merge tip truth (#3264 AC).
370
- // Lifecycle PRs that commit completed/ on a feature branch are not expected
371
- // to satisfy this gate until merge; run the verb with --tip HEAD when
372
- // validating an in-flight land PR. The gate is a standalone verify verb
373
- // (not wired into check:consumer) so product PRs are not deadlocked.
374
- const tipTerminalPaths = listTreePaths(root, tip, terminalPrefixes(), runGit);
375
- const tipTerminalHits = issuesFromBlobPaths(root, tip, tipTerminalPaths, defaultRepo, runGit, `tip:${tip}`);
376
- const landedKeys = new Set(tipTerminalHits.map((h) => issueKey(h.issue)));
377
434
  const missing = [];
378
435
  for (const [key, entry] of originMap) {
379
436
  if (landedKeys.has(key)) {
@@ -383,10 +440,20 @@ export function evaluateCompletedTracked(projectRoot, options = {}) {
383
440
  if (state === "open") {
384
441
  continue;
385
442
  }
386
- // Closed: always fail. Unknown with live expected (!skipGh): also fail —
387
- // cannot prove the issue is still open, so do not green-skip land debt
388
- // (#3264 Greptile residual on live lookup failure).
389
- if (state === "closed" || (state === null && !skipGh)) {
443
+ // Closed: always fail.
444
+ //
445
+ // Unknown: fail when live lookup was expected (!skipGh) -- cannot prove the
446
+ // issue is still open, so do not green-skip land debt (#3264 Greptile
447
+ // residual on live lookup failure).
448
+ //
449
+ // Unknown also fails for an explicitly named --issue even under --skip-gh
450
+ // (#3478 review): that is the drive-to DONE form, where the caller asserts
451
+ // this specific issue is done. An uncached issue must not exit 0 there --
452
+ // otherwise --skip-gh silently turns the DONE gate into a no-op. The
453
+ // unscoped corpus scan keeps the offline allowance, since a cold cache
454
+ // legitimately knows nothing about most scoped issues.
455
+ const unknownIsTerminal = !skipGh || issueFilter !== null;
456
+ if (state === "closed" || (state === null && unknownIsTerminal)) {
390
457
  missing.push(entry);
391
458
  }
392
459
  }
@@ -13,6 +13,8 @@ import type { LiteralAcceptanceCommand, RejectedLiteralCommand } from "./types.j
13
13
  export interface CaptureLiteralAcceptanceResult {
14
14
  readonly commands: readonly LiteralAcceptanceCommand[];
15
15
  readonly rejected: readonly RejectedLiteralCommand[];
16
+ /** Prompt lines skipped inside a transcript-shaped fence (#3511). */
17
+ readonly transcriptPromptSkipped: number;
16
18
  }
17
19
  /**
18
20
  * Capture literal acceptance commands from free-form task statement text.
@@ -47,6 +49,14 @@ export declare function captureAndAttachLiteralAcceptance(plan: Record<string, u
47
49
  readonly commands: readonly LiteralAcceptanceCommand[];
48
50
  readonly rejected: readonly RejectedLiteralCommand[];
49
51
  };
52
+ /** True when a rejected ledger entry was scraped from narrative prose (#3484). */
53
+ export declare function isProseDerivedRejection(rejected: RejectedLiteralCommand): boolean;
54
+ /**
55
+ * True when the author stated acceptance commands structurally — `swarm.verify_commands`
56
+ * or `plan.acceptance.commands` (#3484). Still used by intake/tests; demotion of
57
+ * prose-derived rejections no longer depends on this (#3511).
58
+ */
59
+ export declare function hasStructuredAcceptanceCommands(plan: Record<string, unknown> | null | undefined): boolean;
50
60
  /** Format rejected ledger for CLI / complete-gate messages. */
51
61
  export declare function formatRejectedLedger(rejected: readonly RejectedLiteralCommand[]): string;
52
62
  //# sourceMappingURL=capture.d.ts.map
@@ -167,22 +167,6 @@ function matchLabeledCommand(line) {
167
167
  return null;
168
168
  return line.slice(i).trimEnd();
169
169
  }
170
- /** Mid-line `verify: cmd` (e.g. "Also run: verify: task check"). */
171
- function matchMidlineLabeled(line) {
172
- const low = line.toLowerCase();
173
- for (const kw of LABELED_KEYWORDS) {
174
- if (kw === "run" || kw === "check")
175
- continue; // too ambiguous mid-line
176
- const needle = `${kw}:`;
177
- const idx = low.indexOf(needle);
178
- if (idx < 0)
179
- continue;
180
- const rest = line.slice(idx + needle.length).trim();
181
- if (rest.length > 0)
182
- return rest;
183
- }
184
- return null;
185
- }
186
170
  /** `$ cmd` or `> cmd` prompt lines. */
187
171
  function matchPromptCommand(line) {
188
172
  let i = skipWs(line, 0);
@@ -195,60 +179,144 @@ function matchPromptCommand(line) {
195
179
  return null;
196
180
  return line.slice(i).trimEnd();
197
181
  }
182
+ /** Box-drawing / block elements used in terminal UIs (━, │, …). */
183
+ const BOX_DRAWING_RE = /[\u2500-\u257F\u2580-\u259F]/;
184
+ /**
185
+ * Drop GNU-style long flags and their following value so `--filter fail`
186
+ * is not classified as FAIL transcript output (#3511).
187
+ * Covers `--flag`, `--flag=value`, and `--flag value` (value not another flag).
188
+ */
189
+ function stripLongFlags(line) {
190
+ return line.replace(/(^|\s)--[\w-]+(?:=[^\s]+|\s+(?!-)[^\s]+)?/g, "$1");
191
+ }
192
+ /**
193
+ * True when a fence line is log/transcript, not a stated command (#3511).
194
+ * `[1/13]` / `[ts:check-lane]`, box drawing, or FAIL/FAILED/Error:.
195
+ * Flag tokens (--fail-fast, --failOnError, --no-fail) and flag values
196
+ * (`--filter fail`, `--grep FAIL`) are not FAIL output: `\bFAIL` matches
197
+ * between `-` and `f`, and leftover values still match after name-only strip.
198
+ */
199
+ function isTranscriptOutputLine(line) {
200
+ const t = line.trim();
201
+ if (t.length === 0)
202
+ return false;
203
+ if (t.startsWith("#") || t.startsWith("//"))
204
+ return false;
205
+ if (/^\[[^\]]{1,80}\]/.test(t))
206
+ return true;
207
+ if (BOX_DRAWING_RE.test(t))
208
+ return true;
209
+ const withoutFlags = stripLongFlags(t);
210
+ if (/\b(?:FAILED|FAIL|Error:)/i.test(withoutFlags))
211
+ return true;
212
+ return false;
213
+ }
214
+ function isFenceCommentOrBlank(line) {
215
+ const t = line.trim();
216
+ return t.length === 0 || t.startsWith("#") || t.startsWith("//");
217
+ }
218
+ function isPromptLine(line) {
219
+ const t = line.trim();
220
+ return t.startsWith("$ ") || t.startsWith("> ");
221
+ }
222
+ /** Per-line mask: true for body lines inside ``` / ~~~ fences (not the markers). */
223
+ function fenceBodyMask(lines) {
224
+ const mask = Array.from({ length: lines.length }, () => false);
225
+ let open = null;
226
+ for (let i = 0; i < lines.length; i += 1) {
227
+ const line = lines[i] ?? "";
228
+ if (open === null) {
229
+ open = matchFenceOpen(line);
230
+ continue;
231
+ }
232
+ if (matchFenceClose(line, open)) {
233
+ open = null;
234
+ continue;
235
+ }
236
+ mask[i] = true;
237
+ }
238
+ return mask;
239
+ }
240
+ function captureFenceBodyLine(line, fenceLang, fenceStartLine, buckets) {
241
+ const trimmed = line.trim();
242
+ if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.startsWith("//")) {
243
+ return;
244
+ }
245
+ let body = trimmed;
246
+ if (body.startsWith("$ ") || body.startsWith("> ")) {
247
+ body = body.slice(2).trim();
248
+ }
249
+ const normalized = normalizeCommand(body);
250
+ if (normalized === null)
251
+ return;
252
+ const langOk = isShellFenceLang(fenceLang);
253
+ if (!looksLikeShellCommand(normalized) && (fenceLang === "" || !langOk))
254
+ return;
255
+ pushUnique(buckets, normalized, "task_statement", `fence@L${fenceStartLine}${fenceLang ? `:${fenceLang}` : ""}`);
256
+ }
257
+ function flushFenceBody(body, fenceLang, fenceStartLine, requireRegion, regionActive, buckets) {
258
+ if (requireRegion && !regionActive)
259
+ return;
260
+ if (!isShellFenceLang(fenceLang))
261
+ return;
262
+ // Transcript lines are skipped. `$`/`>` prompts inside a transcript fence are
263
+ // suggested-fixes (biome migrate), not stated AC (#3511). Comments do not
264
+ // mark a fence as transcript, so a genuine command next to `# Error:` still
265
+ // captures.
266
+ const transcript = body.some((line) => isTranscriptOutputLine(line));
267
+ for (const line of body) {
268
+ if (isFenceCommentOrBlank(line))
269
+ continue;
270
+ if (transcript && isPromptLine(line)) {
271
+ buckets.transcriptPromptSkipped += 1;
272
+ continue;
273
+ }
274
+ if (isTranscriptOutputLine(line))
275
+ continue;
276
+ captureFenceBodyLine(line, fenceLang, fenceStartLine, buckets);
277
+ }
278
+ }
198
279
  /**
199
280
  * Walk fenced ``` / ~~~ blocks; keep fence body lines that look like shell.
200
281
  * When `requireRegion` is true, only fences under an acceptance/verify heading.
282
+ * Output-shaped fences (progress tags, box drawing, FAIL/Error:) are skipped (#3511).
201
283
  */
202
284
  function extractFromFences(text, requireRegion, buckets) {
203
285
  const lines = text.split(/\r?\n/);
204
- let inFence = false;
205
- let fenceLang = "";
286
+ let open = null;
206
287
  let regionActive = !requireRegion;
207
288
  let fenceStartLine = 0;
289
+ let fenceBody = [];
290
+ const flush = () => {
291
+ if (open === null)
292
+ return;
293
+ flushFenceBody(fenceBody, open.lang, fenceStartLine, requireRegion, regionActive, buckets);
294
+ open = null;
295
+ fenceBody = [];
296
+ };
208
297
  for (let i = 0; i < lines.length; i += 1) {
209
298
  const line = lines[i] ?? "";
210
299
  const heading = matchMarkdownHeading(line);
211
- if (heading !== null && !inFence) {
300
+ if (heading !== null && open === null) {
212
301
  regionActive = requireRegion ? isRegionHeading(heading.text) : true;
213
302
  continue;
214
303
  }
215
304
  const fenceOpen = matchFenceOpen(line);
216
- if (fenceOpen !== null && !inFence) {
217
- inFence = true;
218
- fenceLang = fenceOpen.lang;
305
+ if (fenceOpen !== null && open === null) {
306
+ open = fenceOpen;
219
307
  fenceStartLine = i + 1;
308
+ fenceBody = [];
220
309
  continue;
221
310
  }
222
- if (inFence && matchFenceClose(line)) {
223
- inFence = false;
224
- fenceLang = "";
311
+ if (open !== null && matchFenceClose(line, open)) {
312
+ flush();
225
313
  continue;
226
314
  }
227
- if (!inFence)
315
+ if (open === null)
228
316
  continue;
229
- if (requireRegion && !regionActive)
230
- continue;
231
- // Skip comment-only fence lines.
232
- const trimmed = line.trim();
233
- if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.startsWith("//")) {
234
- continue;
235
- }
236
- // Shell-ish langs always accepted; unknown langs need CLI shape.
237
- const langOk = isShellFenceLang(fenceLang);
238
- if (!langOk && fenceLang !== "")
239
- continue;
240
- // Strip leading prompt markers inside fences.
241
- let body = trimmed;
242
- if (body.startsWith("$ ") || body.startsWith("> ")) {
243
- body = body.slice(2).trim();
244
- }
245
- const normalized = normalizeCommand(body);
246
- if (normalized === null)
247
- continue;
248
- if (!looksLikeShellCommand(normalized) && (fenceLang === "" || !langOk))
249
- continue;
250
- pushUnique(buckets, normalized, "task_statement", `fence@L${fenceStartLine}${fenceLang ? `:${fenceLang}` : ""}`);
317
+ fenceBody.push(line);
251
318
  }
319
+ flush();
252
320
  }
253
321
  function isShellFenceLang(lang) {
254
322
  return (lang === "" ||
@@ -262,26 +330,38 @@ function isShellFenceLang(lang) {
262
330
  lang === "cmd" ||
263
331
  lang === "text");
264
332
  }
265
- /** Linear fence open: ```lang or ~~~lang */
333
+ /**
334
+ * CommonMark fence open: 3+ backticks or tildes, then an optional info string.
335
+ * Longer openers (````) must close with the same marker at >= that length (#3511 P1).
336
+ */
266
337
  function matchFenceOpen(line) {
267
- const t = line.trimEnd();
268
- if (t.startsWith("```")) {
269
- const rest = t.slice(3).trim().toLowerCase();
270
- if (rest.includes(" ") || rest.includes("\t"))
271
- return null;
272
- return { lang: rest };
273
- }
274
- if (t.startsWith("~~~")) {
275
- const rest = t.slice(3).trim().toLowerCase();
276
- if (rest.includes(" ") || rest.includes("\t"))
277
- return null;
278
- return { lang: rest };
279
- }
280
- return null;
338
+ const body = line.trimEnd().trimStart();
339
+ if (body.length < 3)
340
+ return null;
341
+ const first = body[0];
342
+ if (first !== "`" && first !== "~")
343
+ return null;
344
+ const marker = first;
345
+ let length = 0;
346
+ while (length < body.length && body[length] === marker)
347
+ length += 1;
348
+ if (length < 3)
349
+ return null;
350
+ const rest = body.slice(length).trim().toLowerCase();
351
+ if (rest.includes(" ") || rest.includes("\t"))
352
+ return null;
353
+ if (rest.includes(marker))
354
+ return null;
355
+ return { lang: rest, marker, length };
281
356
  }
282
- function matchFenceClose(line) {
357
+ function matchFenceClose(line, open) {
283
358
  const t = line.trim();
284
- return t === "```" || t === "~~~";
359
+ let length = 0;
360
+ while (length < t.length && t[length] === open.marker)
361
+ length += 1;
362
+ if (length < open.length)
363
+ return false;
364
+ return t.slice(length).trim().length === 0;
285
365
  }
286
366
  function matchMarkdownHeading(line) {
287
367
  if (!line.startsWith("#"))
@@ -295,17 +375,25 @@ function matchMarkdownHeading(line) {
295
375
  return null;
296
376
  return { level, text: line.slice(level + 1).trim() };
297
377
  }
298
- /** Extract from labeled lines and `$` prompts anywhere in the statement. */
378
+ /**
379
+ * Extract from labeled lines and `$` prompts anywhere in the statement.
380
+ *
381
+ * A labeled command MUST start its own line (after an optional bullet / number
382
+ * marker). Mid-line label capture is deliberately not attempted (#3484): a
383
+ * `verify:<verb>` token inside ordinary prose has no terminator, so the capture
384
+ * 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.
387
+ */
299
388
  function extractFromLabeledLines(text, buckets) {
300
389
  const lines = text.split(/\r?\n/);
390
+ const inFence = fenceBodyMask(lines);
301
391
  for (let i = 0; i < lines.length; i += 1) {
392
+ if (inFence[i] === true)
393
+ continue;
302
394
  const line = lines[i] ?? "";
303
395
  let capturedLabeled = false;
304
- let labeledBody = matchLabeledCommand(line);
305
- if (labeledBody === null && matchPromptCommand(line) === null) {
306
- // Avoid treating `$ task verify:branch` as labeled "branch".
307
- labeledBody = matchMidlineLabeled(line);
308
- }
396
+ const labeledBody = matchLabeledCommand(line);
309
397
  if (labeledBody !== null) {
310
398
  const normalized = normalizeCommand(labeledBody);
311
399
  if (normalized !== null && looksLikeShellCommand(normalized)) {
@@ -330,7 +418,10 @@ function extractFromLabeledLines(text, buckets) {
330
418
  */
331
419
  function extractInlineVerifySpans(text, buckets) {
332
420
  const lines = text.split(/\r?\n/);
421
+ const inFence = fenceBodyMask(lines);
333
422
  for (let i = 0; i < lines.length; i += 1) {
423
+ if (inFence[i] === true)
424
+ continue;
334
425
  const line = lines[i] ?? "";
335
426
  if (!/\b(verify|run|execute|acceptance)\b/i.test(line))
336
427
  continue;
@@ -359,6 +450,7 @@ function emptyBuckets() {
359
450
  seen: new Set(),
360
451
  rejected: [],
361
452
  rejectedSeen: new Set(),
453
+ transcriptPromptSkipped: 0,
362
454
  };
363
455
  }
364
456
  /**
@@ -367,7 +459,7 @@ function emptyBuckets() {
367
459
  */
368
460
  export function captureLiteralAcceptanceCommandsDetailed(taskStatement) {
369
461
  if (!isNonEmptyString(taskStatement)) {
370
- return { commands: [], rejected: [] };
462
+ return { commands: [], rejected: [], transcriptPromptSkipped: 0 };
371
463
  }
372
464
  const buckets = emptyBuckets();
373
465
  // Prefer fences under acceptance/verify headings first.
@@ -376,7 +468,11 @@ export function captureLiteralAcceptanceCommandsDetailed(taskStatement) {
376
468
  extractFromFences(taskStatement, false, buckets);
377
469
  extractFromLabeledLines(taskStatement, buckets);
378
470
  extractInlineVerifySpans(taskStatement, buckets);
379
- return { commands: buckets.out, rejected: buckets.rejected };
471
+ return {
472
+ commands: buckets.out,
473
+ rejected: buckets.rejected,
474
+ transcriptPromptSkipped: buckets.transcriptPromptSkipped,
475
+ };
380
476
  }
381
477
  /**
382
478
  * Capture literal acceptance commands from free-form task statement text.
@@ -395,7 +491,7 @@ export function readStoredLiteralAcceptanceCommands(plan) {
395
491
  /** Read stored commands plus any persisted rejected ledger. */
396
492
  export function readStoredLiteralAcceptanceDetailed(plan) {
397
493
  if (plan === null || plan === undefined || typeof plan !== "object") {
398
- return { commands: [], rejected: [] };
494
+ return { commands: [], rejected: [], transcriptPromptSkipped: 0 };
399
495
  }
400
496
  const buckets = emptyBuckets();
401
497
  const metadata = asRecord(plan.metadata);
@@ -435,7 +531,11 @@ export function readStoredLiteralAcceptanceDetailed(plan) {
435
531
  }
436
532
  }
437
533
  walkPlanItems(plan.items, "items", buckets);
438
- return { commands: buckets.out, rejected: buckets.rejected };
534
+ return {
535
+ commands: buckets.out,
536
+ rejected: buckets.rejected,
537
+ transcriptPromptSkipped: buckets.transcriptPromptSkipped,
538
+ };
439
539
  }
440
540
  function walkPlanItems(items, pathPrefix, buckets) {
441
541
  if (!Array.isArray(items))
@@ -644,6 +744,55 @@ function toSerializable(cmd) {
644
744
  }
645
745
  return row;
646
746
  }
747
+ /**
748
+ * Provenance prefixes stamped by the free-text scrapers in this module.
749
+ * Anything carrying one of these spans came from prose, not from a structured
750
+ * acceptance field the author wrote on purpose (#3484).
751
+ */
752
+ const PROSE_SPAN_PREFIXES = ["labeled@", "prompt@", "inline@", "fence@"];
753
+ /** True when a rejected ledger entry was scraped from narrative prose (#3484). */
754
+ export function isProseDerivedRejection(rejected) {
755
+ const span = rejected.sourceSpan;
756
+ if (span === null || span === undefined)
757
+ return false;
758
+ return PROSE_SPAN_PREFIXES.some((prefix) => span.startsWith(prefix));
759
+ }
760
+ function hasNonEmptyCommandList(raw) {
761
+ if (typeof raw === "string")
762
+ return raw.trim().length > 0;
763
+ if (!Array.isArray(raw))
764
+ return false;
765
+ for (const entry of raw) {
766
+ if (typeof entry === "string" && entry.trim().length > 0)
767
+ return true;
768
+ const rec = asRecord(entry);
769
+ if (rec === null)
770
+ continue;
771
+ if (isNonEmptyString(rec.command ?? rec.cmd ?? rec.shell))
772
+ return true;
773
+ }
774
+ return false;
775
+ }
776
+ /**
777
+ * True when the author stated acceptance commands structurally — `swarm.verify_commands`
778
+ * or `plan.acceptance.commands` (#3484). Still used by intake/tests; demotion of
779
+ * prose-derived rejections no longer depends on this (#3511).
780
+ */
781
+ export function hasStructuredAcceptanceCommands(plan) {
782
+ const rec = asRecord(plan);
783
+ if (rec === null)
784
+ return false;
785
+ const acceptance = asRecord(rec.acceptance);
786
+ if (acceptance !== null && hasNonEmptyCommandList(acceptance.commands))
787
+ return true;
788
+ const metadata = asRecord(rec.metadata);
789
+ if (metadata === null)
790
+ return false;
791
+ const swarm = asRecord(metadata.swarm);
792
+ if (swarm === null)
793
+ return false;
794
+ return hasNonEmptyCommandList(swarm.verify_commands);
795
+ }
647
796
  /** Format rejected ledger for CLI / complete-gate messages. */
648
797
  export function formatRejectedLedger(rejected) {
649
798
  if (rejected.length === 0)
@@ -13,6 +13,19 @@ export interface EvaluateLiteralAcceptanceOptions {
13
13
  /** Quiet success (empty message on pass). */
14
14
  readonly quiet?: boolean;
15
15
  }
16
+ /** Resolved literal-AC view: executable commands plus the blocking / advisory ledgers. */
17
+ export interface ResolvedLiteralAcceptance {
18
+ readonly commands: readonly LiteralAcceptanceCommand[];
19
+ /** Safety-rejected entries that block completion. */
20
+ readonly rejected: readonly RejectedLiteralCommand[];
21
+ /**
22
+ * Prose/fence/inline-derived rejections demoted to advisory (#3484 / #3511).
23
+ * Reported, never blocking. Structured-field rejections stay on `rejected`.
24
+ */
25
+ readonly advisoryRejected: readonly RejectedLiteralCommand[];
26
+ /** Prompt lines skipped inside a transcript-shaped fence (#3511). */
27
+ readonly transcriptPromptSkipped: number;
28
+ }
16
29
  /**
17
30
  * Resolve commands + rejected ledger.
18
31
  * Stored first; when captureFromNarratives is true (default), also re-scan narratives
@@ -21,16 +34,28 @@ export interface EvaluateLiteralAcceptanceOptions {
21
34
  */
22
35
  export declare function resolveLiteralAcceptanceDetailed(plan: Record<string, unknown>, options?: {
23
36
  readonly captureFromNarratives?: boolean;
24
- }): {
25
- readonly commands: readonly LiteralAcceptanceCommand[];
26
- readonly rejected: readonly RejectedLiteralCommand[];
27
- };
37
+ }): ResolvedLiteralAcceptance;
28
38
  /**
29
39
  * Resolve commands: stored first; optional narrative re-capture when empty.
30
40
  */
31
41
  export declare function resolveLiteralAcceptanceCommands(plan: Record<string, unknown>, options?: {
32
42
  readonly captureFromNarratives?: boolean;
33
43
  }): LiteralAcceptanceCommand[];
44
+ /**
45
+ * Marker that opens the #3484 advisory block. Consumers strip from here to the end
46
+ * before any free-text inspection of a literal-AC message: the block quotes refusal
47
+ * reasons that were explicitly demoted and must not be read back as blocking (#3497).
48
+ */
49
+ export declare const LITERAL_ACCEPTANCE_ADVISORY_MARKER = "Literal acceptance advisory (#3484):";
50
+ /** Render the #3484 advisory block (reported, never blocking). */
51
+ export declare function formatLiteralAcceptanceAdvisory(advisory: readonly RejectedLiteralCommand[]): string;
52
+ /** Append the #3484 advisory block to a message tail. */
53
+ export declare function appendLiteralAcceptanceAdvisory(message: string, advisory: readonly RejectedLiteralCommand[]): string;
54
+ /**
55
+ * Drop the #3484 advisory block from a rendered message.
56
+ * The block is always the tail, so an index cut is exact.
57
+ */
58
+ export declare function stripLiteralAcceptanceAdvisory(message: string): string;
34
59
  /**
35
60
  * Evaluate from an in-memory plan object.
36
61
  */