@deftai/directive-core 0.104.0 → 0.106.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 (287) hide show
  1. package/dist/authz/classify.js +1108 -67
  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 +34 -4
  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/forward-coverage/diff-coverage.d.ts +39 -0
  17. package/dist/forward-coverage/diff-coverage.js +112 -0
  18. package/dist/forward-coverage/diff-lines.d.ts +19 -0
  19. package/dist/forward-coverage/diff-lines.js +173 -0
  20. package/dist/forward-coverage/evaluate.d.ts +24 -9
  21. package/dist/forward-coverage/evaluate.js +125 -19
  22. package/dist/fs/contained-write.d.ts +115 -1
  23. package/dist/fs/contained-write.js +241 -5
  24. package/dist/fs/mutation-ledger.d.ts +54 -0
  25. package/dist/fs/mutation-ledger.js +133 -0
  26. package/dist/fs/non-product-dirs.d.ts +79 -0
  27. package/dist/fs/non-product-dirs.js +96 -0
  28. package/dist/hooks/dispatcher.d.ts +3 -1
  29. package/dist/hooks/dispatcher.js +14 -0
  30. package/dist/index.d.ts +1 -0
  31. package/dist/index.js +2 -0
  32. package/dist/init-deposit/agent-hooks.d.ts +1 -1
  33. package/dist/init-deposit/agent-hooks.js +18 -16
  34. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  35. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  36. package/dist/init-deposit/gitignore.js +9 -0
  37. package/dist/init-deposit/hygiene.d.ts +47 -11
  38. package/dist/init-deposit/hygiene.js +172 -37
  39. package/dist/init-deposit/legacy-detect.js +6 -2
  40. package/dist/init-deposit/refresh.d.ts +21 -1
  41. package/dist/init-deposit/refresh.js +106 -9
  42. package/dist/init-deposit/scaffold.js +28 -279
  43. package/dist/init-deposit/slash-deposit.js +13 -7
  44. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  45. package/dist/init-deposit/xbrief-projections.js +24 -11
  46. package/dist/intake/clause-derivation.d.ts +47 -1
  47. package/dist/intake/clause-derivation.js +238 -9
  48. package/dist/intake/issue-ingest.d.ts +3 -1
  49. package/dist/intake/issue-ingest.js +35 -6
  50. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  51. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  52. package/dist/lifecycle-visible/evaluate.d.ts +98 -0
  53. package/dist/lifecycle-visible/evaluate.js +710 -0
  54. package/dist/lifecycle-visible/index.d.ts +2 -0
  55. package/dist/lifecycle-visible/index.js +2 -0
  56. package/dist/literal-acceptance/capture.d.ts +10 -0
  57. package/dist/literal-acceptance/capture.js +226 -77
  58. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  59. package/dist/literal-acceptance/evaluate.js +70 -2
  60. package/dist/literal-acceptance/index.d.ts +3 -3
  61. package/dist/literal-acceptance/index.js +3 -3
  62. package/dist/literal-acceptance/safety.d.ts +41 -0
  63. package/dist/literal-acceptance/safety.js +126 -2
  64. package/dist/literal-acceptance/types.d.ts +8 -0
  65. package/dist/orchestration/judgment-policy.d.ts +13 -0
  66. package/dist/orchestration/judgment-policy.js +15 -0
  67. package/dist/orchestration/probe-session.js +13 -8
  68. package/dist/orchestration/verify-judgment-gates.d.ts +4 -0
  69. package/dist/orchestration/verify-judgment-gates.js +43 -11
  70. package/dist/orphan-active/evaluate.d.ts +7 -1
  71. package/dist/orphan-active/evaluate.js +102 -23
  72. package/dist/platform/platform-capabilities.js +2 -3
  73. package/dist/policy/base-branch.d.ts +37 -0
  74. package/dist/policy/base-branch.js +114 -0
  75. package/dist/policy/branch-sync.d.ts +80 -0
  76. package/dist/policy/branch-sync.js +211 -0
  77. package/dist/policy/ceremony-dial-escalation.js +1 -0
  78. package/dist/policy/ceremony-dial.js +11 -21
  79. package/dist/policy/delivery-branch.d.ts +5 -3
  80. package/dist/policy/delivery-branch.js +7 -3
  81. package/dist/policy/file-size-thresholds.d.ts +24 -0
  82. package/dist/policy/file-size-thresholds.js +23 -0
  83. package/dist/policy/forge-outage-retry.d.ts +47 -0
  84. package/dist/policy/forge-outage-retry.js +157 -0
  85. package/dist/policy/host-hooks.d.ts +20 -0
  86. package/dist/policy/host-hooks.js +121 -2
  87. package/dist/policy/index.d.ts +7 -0
  88. package/dist/policy/index.js +66 -7
  89. package/dist/policy/org-force-on-migration.js +2 -1
  90. package/dist/policy/plan-extensions.d.ts +1 -1
  91. package/dist/policy/plan-extensions.js +2 -0
  92. package/dist/policy/product-signal.js +6 -7
  93. package/dist/policy/project-invariants.d.ts +50 -0
  94. package/dist/policy/project-invariants.js +205 -0
  95. package/dist/policy/require-human-merge.js +8 -6
  96. package/dist/policy/resolve.d.ts +7 -2
  97. package/dist/policy/resolve.js +25 -8
  98. package/dist/policy/sync-default.d.ts +129 -0
  99. package/dist/policy/sync-default.js +452 -0
  100. package/dist/policy/sync-max-files.d.ts +93 -0
  101. package/dist/policy/sync-max-files.js +192 -0
  102. package/dist/policy/value-feedback.js +7 -8
  103. package/dist/pr-monitor/absent-required.d.ts +11 -0
  104. package/dist/pr-monitor/absent-required.js +61 -0
  105. package/dist/pr-monitor/constants.d.ts +7 -0
  106. package/dist/pr-monitor/constants.js +7 -0
  107. package/dist/pr-monitor/index.d.ts +2 -1
  108. package/dist/pr-monitor/index.js +2 -1
  109. package/dist/pr-monitor/main.js +4 -0
  110. package/dist/pr-monitor/monitor.js +24 -1
  111. package/dist/pr-wait-mergeable/classify.js +6 -0
  112. package/dist/preflight/evaluate.d.ts +4 -0
  113. package/dist/preflight/evaluate.js +35 -1
  114. package/dist/preflight/index.d.ts +4 -0
  115. package/dist/preflight/index.js +2 -0
  116. package/dist/preflight/intended-placement.d.ts +62 -0
  117. package/dist/preflight/intended-placement.js +275 -0
  118. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  119. package/dist/preflight/project-invariants-gate.js +82 -0
  120. package/dist/preflight-cache/evaluate.d.ts +12 -0
  121. package/dist/preflight-cache/evaluate.js +15 -1
  122. package/dist/preflight-cache/index.d.ts +1 -1
  123. package/dist/preflight-cache/index.js +1 -1
  124. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  125. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  126. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  127. package/dist/product-first-done-gate/acceptance.js +79 -8
  128. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  129. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  130. package/dist/product-first-done-gate/evaluate.d.ts +16 -0
  131. package/dist/product-first-done-gate/evaluate.js +298 -34
  132. package/dist/product-first-done-gate/index.d.ts +2 -0
  133. package/dist/product-first-done-gate/index.js +1 -0
  134. package/dist/release/build-dist.d.ts +101 -0
  135. package/dist/release/build-dist.js +428 -46
  136. package/dist/release/closed-verb-gate.d.ts +12 -0
  137. package/dist/release/closed-verb-gate.js +51 -0
  138. package/dist/release/index.d.ts +1 -0
  139. package/dist/release/index.js +1 -0
  140. package/dist/release/pipeline.js +7 -0
  141. package/dist/release/types.d.ts +12 -0
  142. package/dist/render/constants.d.ts +2 -1
  143. package/dist/render/constants.js +2 -1
  144. package/dist/render/framework-commands.js +4 -0
  145. package/dist/render/spec-validate.js +23 -12
  146. package/dist/run-summary/emit.d.ts +16 -10
  147. package/dist/run-summary/emit.js +26 -15
  148. package/dist/run-summary/index.d.ts +3 -2
  149. package/dist/run-summary/index.js +2 -1
  150. package/dist/run-summary/session-id.d.ts +21 -0
  151. package/dist/run-summary/session-id.js +92 -0
  152. package/dist/run-summary/types.d.ts +33 -4
  153. package/dist/run-summary/types.js +1 -0
  154. package/dist/scope/acceptance-evidence.d.ts +19 -7
  155. package/dist/scope/acceptance-evidence.js +38 -11
  156. package/dist/scope/delivery-evidence.d.ts +3 -3
  157. package/dist/scope/delivery-evidence.js +22 -27
  158. package/dist/scope/index.d.ts +1 -0
  159. package/dist/scope/index.js +1 -0
  160. package/dist/scope/main.js +15 -5
  161. package/dist/scope/parent-lineage.js +7 -6
  162. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  163. package/dist/scope/project-invariant-coverage.js +322 -0
  164. package/dist/scope/promote-from-issue.js +5 -5
  165. package/dist/scope/transition.js +1 -1
  166. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  167. package/dist/scope-provenance/compare-intent.js +233 -0
  168. package/dist/scope-provenance/digest.d.ts +18 -1
  169. package/dist/scope-provenance/digest.js +16 -11
  170. package/dist/scope-provenance/evaluate.d.ts +5 -1
  171. package/dist/scope-provenance/evaluate.js +67 -7
  172. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  173. package/dist/scope-provenance/extract-intent.js +262 -0
  174. package/dist/scope-provenance/index.d.ts +7 -1
  175. package/dist/scope-provenance/index.js +7 -1
  176. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  177. package/dist/scope-provenance/intent-digest.js +33 -0
  178. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  179. package/dist/scope-provenance/intent-evaluate.js +149 -0
  180. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  181. package/dist/scope-provenance/json-tokenizer.js +225 -0
  182. package/dist/scope-provenance/known-machine.d.ts +38 -0
  183. package/dist/scope-provenance/known-machine.js +157 -0
  184. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  185. package/dist/scope-provenance/mint-artifacts.js +425 -0
  186. package/dist/session/ac-pass-banking.d.ts +9 -36
  187. package/dist/session/ac-pass-banking.js +33 -79
  188. package/dist/session/ac-pass-reuse.d.ts +53 -0
  189. package/dist/session/ac-pass-reuse.js +95 -0
  190. package/dist/session/git.js +19 -4
  191. package/dist/session/index.d.ts +4 -0
  192. package/dist/session/index.js +4 -0
  193. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  194. package/dist/session/occupancy-stress-worker.js +36 -0
  195. package/dist/session/occupancy.d.ts +97 -0
  196. package/dist/session/occupancy.js +422 -0
  197. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  198. package/dist/session/process-cost.d.ts +32 -10
  199. package/dist/session/process-cost.js +189 -1
  200. package/dist/session/product-state-hash.d.ts +26 -0
  201. package/dist/session/product-state-hash.js +700 -0
  202. package/dist/session/ritual-entrypoint.js +16 -1
  203. package/dist/session/session-ready.d.ts +2 -0
  204. package/dist/session/session-ready.js +52 -30
  205. package/dist/session/session-start.d.ts +13 -0
  206. package/dist/session/session-start.js +103 -9
  207. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  208. package/dist/session/verify-ac-session-cache.js +126 -0
  209. package/dist/session/verify-session-ritual.d.ts +28 -0
  210. package/dist/session/verify-session-ritual.js +97 -5
  211. package/dist/slash/openclaw-deposit.js +13 -9
  212. package/dist/slice/constants.d.ts +2 -2
  213. package/dist/slice/constants.js +2 -2
  214. package/dist/slice/existing.js +9 -3
  215. package/dist/slice/lock.d.ts +16 -1
  216. package/dist/slice/lock.js +137 -11
  217. package/dist/story-ready/evaluate.d.ts +2 -0
  218. package/dist/story-ready/evaluate.js +13 -0
  219. package/dist/swarm/complete-cohort.d.ts +3 -0
  220. package/dist/swarm/complete-cohort.js +59 -11
  221. package/dist/swarm/finalize-cohort-cli.d.ts +7 -2
  222. package/dist/swarm/finalize-cohort-cli.js +165 -35
  223. package/dist/swarm/finalize-cohort.d.ts +1 -0
  224. package/dist/swarm/finalize-cohort.js +47 -4
  225. package/dist/swarm/launch.d.ts +22 -0
  226. package/dist/swarm/launch.js +155 -0
  227. package/dist/swarm/routing-set-cli.d.ts +11 -1
  228. package/dist/swarm/routing-set-cli.js +15 -3
  229. package/dist/swarm/routing-verify.d.ts +13 -1
  230. package/dist/swarm/routing-verify.js +60 -21
  231. package/dist/swarm/routing.d.ts +14 -0
  232. package/dist/swarm/routing.js +53 -3
  233. package/dist/telemetry-coverage/fake-trial.js +10 -0
  234. package/dist/telemetry-coverage/kinds.js +2 -0
  235. package/dist/triage/actions/index.js +4 -1
  236. package/dist/triage/bootstrap/index.js +6 -6
  237. package/dist/triage/help/registry-data.d.ts +1 -1
  238. package/dist/triage/help/registry-data.js +1 -1
  239. package/dist/triage/welcome/writers.d.ts +1 -1
  240. package/dist/triage/welcome/writers.js +10 -5
  241. package/dist/ts-check-lane/index.d.ts +3 -0
  242. package/dist/ts-check-lane/index.js +2 -0
  243. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  244. package/dist/ts-check-lane/progress-reporter.js +38 -0
  245. package/dist/ts-check-lane/progress.d.ts +32 -0
  246. package/dist/ts-check-lane/progress.js +57 -0
  247. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  248. package/dist/ts-check-lane/run-lane.js +8 -6
  249. package/dist/validate-content/validate-links.d.ts +8 -0
  250. package/dist/validate-content/validate-links.js +9 -15
  251. package/dist/value/feedback-file.js +12 -2
  252. package/dist/value/readback.d.ts +4 -1
  253. package/dist/value/readback.js +51 -12
  254. package/dist/vbrief-build/index.d.ts +1 -0
  255. package/dist/vbrief-build/index.js +1 -0
  256. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  257. package/dist/vbrief-build/project-invariants-io.js +41 -0
  258. package/dist/vbrief-reconcile/graph.js +18 -1
  259. package/dist/vbrief-reconcile/index.d.ts +1 -1
  260. package/dist/vbrief-reconcile/index.js +1 -1
  261. package/dist/vbrief-reconcile/types.d.ts +13 -0
  262. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  263. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  264. package/dist/vbrief-validate/index.d.ts +1 -1
  265. package/dist/vbrief-validate/index.js +1 -1
  266. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  267. package/dist/vbrief-validate/plan-hooks.js +68 -0
  268. package/dist/vbrief-validate/schema.d.ts +4 -0
  269. package/dist/vbrief-validate/schema.js +1 -1
  270. package/dist/verify-ac/evaluate.d.ts +7 -1
  271. package/dist/verify-ac/evaluate.js +34 -26
  272. package/dist/verify-ac/flag.d.ts +4 -0
  273. package/dist/verify-ac/flag.js +16 -0
  274. package/dist/verify-ac/index.d.ts +2 -2
  275. package/dist/verify-ac/index.js +2 -2
  276. package/dist/verify-env/agent-hook-readiness.d.ts +2 -2
  277. package/dist/verify-env/agent-hook-readiness.js +12 -8
  278. package/dist/verify-env/agent-hooks-live-probe.d.ts +5 -1
  279. package/dist/verify-env/agent-hooks-live-probe.js +26 -5
  280. package/dist/verify-env/agent-hooks.js +3 -4
  281. package/dist/verify-source/contained-writes.js +2 -0
  282. package/dist/verify-source/verify-stubs.d.ts +15 -1
  283. package/dist/verify-source/verify-stubs.js +20 -16
  284. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  285. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  286. package/dist/xbrief-migrate/migrate-project.js +2 -5
  287. package/package.json +7 -3
@@ -13,10 +13,10 @@
13
13
  * Directive thin wrappers (`isThinWrapperMarkdown`). Consumer-customized content
14
14
  * at a product filename is left untouched.
15
15
  */
16
- import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from "node:fs";
16
+ import { existsSync, readFileSync } from "node:fs";
17
17
  import { basename, dirname, join, resolve } from "node:path";
18
18
  import { assertDepositContained } from "../deposit/contain.js";
19
- import { containedWrite } from "../fs/contained-write.js";
19
+ import { containedMkdir, containedRemove, containedRename, containedWrite, } from "../fs/contained-write.js";
20
20
  import { isHostSlashCommandDepositEnabled, loadHostSlashCommandsPolicyFromProject, } from "../policy/host-slash-commands.js";
21
21
  import { emitHostCommandFiles, getHostCommandLayout, isThinWrapperMarkdown, listSlashEmitterHosts, } from "../slash/index.js";
22
22
  import { listProductCommands, logicalIdToFilename } from "../slash/product-set.js";
@@ -115,7 +115,7 @@ function writeHostCommandFileIfChanged(projectRoot, file) {
115
115
  }
116
116
  }
117
117
  const parent = dirname(absolute);
118
- mkdirSync(parent, { recursive: true });
118
+ containedMkdir({ root: projectRoot, target: parent });
119
119
  // Atomic replace via temp under project root (#2951).
120
120
  const tmpName = `${basename(absolute)}.deft-${process.pid}.tmp`;
121
121
  const temporary = join(parent, tmpName);
@@ -126,11 +126,16 @@ function writeHostCommandFileIfChanged(projectRoot, file) {
126
126
  data: file.contents,
127
127
  mode: "replace",
128
128
  });
129
- renameSync(temporary, absolute);
129
+ containedRename({
130
+ root: projectRoot,
131
+ from: temporary,
132
+ to: absolute,
133
+ mutation: false,
134
+ });
130
135
  }
131
136
  catch (err) {
132
137
  try {
133
- rmSync(temporary, { force: true });
138
+ containedRemove({ root: projectRoot, target: temporary, mutation: false });
134
139
  }
135
140
  catch {
136
141
  /* best-effort cleanup */
@@ -162,8 +167,9 @@ function stripManagedHostCommandFiles(projectRoot, hostId) {
162
167
  continue;
163
168
  }
164
169
  try {
165
- rmSync(absolute, { force: true });
166
- removed.push(file.relativePath);
170
+ if (containedRemove({ root: projectRoot, target: absolute }).removed) {
171
+ removed.push(file.relativePath);
172
+ }
167
173
  }
168
174
  catch {
169
175
  /* best-effort */
@@ -7,11 +7,18 @@
7
7
  */
8
8
  /** Rewrite legacy vbrief/.eval description paths for xbrief/schemas projection (#2670). */
9
9
  export declare function rewriteProjectedSchemaContent(content: string): string;
10
+ /** Fail closed when planned (source-rewritten) schema bytes still cite vbrief/.eval/. */
11
+ export declare function assertPlannedSchemaDescriptionRooted(rel: string, text: string): void;
10
12
  /**
11
13
  * Fail closed when a projected xbrief/schemas file still cites vbrief/.eval/.
12
14
  * Upstream vbrief/schemas/ may keep legacy paths; consumer copies must not (#2670).
15
+ *
16
+ * `skipRel` is dest files the collect record plans to replace (#3456 plan-note):
17
+ * never validate stale dest bytes a skipped write would have replaced.
13
18
  */
14
- export declare function assertProjectedSchemaDescriptionsRooted(projectDir: string, destinationDir: string): void;
19
+ export declare function assertProjectedSchemaDescriptionsRooted(projectDir: string, destinationDir: string, options?: {
20
+ readonly skipRel?: ReadonlySet<string>;
21
+ }): void;
15
22
  /**
16
23
  * Synchronize framework-owned xBRIEF schemas while preserving unknown consumer
17
24
  * files. The obsolete v0.6 root schema is the only destination-only file this
@@ -5,9 +5,9 @@
5
5
  * freshness. A current `.deft/core/VERSION` therefore cannot short-circuit
6
6
  * these repairs.
7
7
  */
8
- import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs";
8
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
9
9
  import { join, relative, resolve } from "node:path";
10
- import { containedWrite } from "../fs/contained-write.js";
10
+ import { containedMkdir, containedRemove, containedWrite } from "../fs/contained-write.js";
11
11
  import { assertDestinationNotSymlink } from "../fs/projection-containment.js";
12
12
  import { resolveLifecycleRoot } from "../layout/resolve.js";
13
13
  import { DEV_FALLBACK } from "../platform/constants.js";
@@ -50,20 +50,30 @@ export function rewriteProjectedSchemaContent(content) {
50
50
  ? content.replaceAll(LEGACY_EVAL_PATH_PREFIX, XBRIEF_EVAL_PATH_PREFIX)
51
51
  : content;
52
52
  }
53
+ /** Fail closed when planned (source-rewritten) schema bytes still cite vbrief/.eval/. */
54
+ export function assertPlannedSchemaDescriptionRooted(rel, text) {
55
+ if (text.includes(LEGACY_EVAL_PATH_PREFIX)) {
56
+ throw new Error(`projected xbrief schema still cites ${LEGACY_EVAL_PATH_PREFIX}: ${join(MIGRATED_ARTIFACT_DIR, "schemas", rel)}`);
57
+ }
58
+ }
53
59
  /**
54
60
  * Fail closed when a projected xbrief/schemas file still cites vbrief/.eval/.
55
61
  * Upstream vbrief/schemas/ may keep legacy paths; consumer copies must not (#2670).
62
+ *
63
+ * `skipRel` is dest files the collect record plans to replace (#3456 plan-note):
64
+ * never validate stale dest bytes a skipped write would have replaced.
56
65
  */
57
- export function assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir) {
66
+ export function assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir, options = {}) {
58
67
  assertDestinationNotSymlink(projectDir, destinationDir);
59
68
  if (!isDirectory(destinationDir))
60
69
  return;
70
+ const skipRel = options.skipRel;
61
71
  for (const rel of collectSchemaFiles(destinationDir)) {
72
+ if (skipRel?.has(rel))
73
+ continue;
62
74
  const full = join(destinationDir, rel);
63
75
  const text = readFileSync(full, "utf8");
64
- if (text.includes(LEGACY_EVAL_PATH_PREFIX)) {
65
- throw new Error(`projected xbrief schema still cites ${LEGACY_EVAL_PATH_PREFIX}: ${join(MIGRATED_ARTIFACT_DIR, "schemas", rel)}`);
66
- }
76
+ assertPlannedSchemaDescriptionRooted(rel, text);
67
77
  }
68
78
  }
69
79
  function writeFileIfChanged(projectDir, target, content) {
@@ -99,23 +109,26 @@ export function syncConsumerXbriefSchemas(projectDir, deftDir) {
99
109
  }
100
110
  const destinationDir = join(projectDir, MIGRATED_ARTIFACT_DIR, "schemas");
101
111
  assertDestinationNotSymlink(projectDir, destinationDir);
102
- mkdirSync(destinationDir, { recursive: true });
112
+ containedMkdir({ root: resolve(projectDir), target: destinationDir });
103
113
  let changed = false;
114
+ const plannedRel = new Set();
104
115
  for (const rel of collectSchemaFiles(sourceDir)) {
105
116
  if (rel === OBSOLETE_CORE_SCHEMA)
106
117
  continue;
107
118
  const source = join(sourceDir, rel);
108
119
  const destination = join(destinationDir, rel);
109
120
  const projected = rewriteProjectedSchemaContent(readFileSync(source, "utf8"));
121
+ // Planned content is the oracle. Dest bytes a skipped write would replace
122
+ // are not validated (#3456 plan-note).
123
+ assertPlannedSchemaDescriptionRooted(rel, projected);
124
+ plannedRel.add(rel);
110
125
  changed = writeFileIfChanged(projectDir, destination, projected) || changed;
111
126
  }
112
127
  const obsoleteDestination = join(destinationDir, OBSOLETE_CORE_SCHEMA);
113
- assertDestinationNotSymlink(projectDir, obsoleteDestination);
114
- if (existsSync(obsoleteDestination)) {
115
- rmSync(obsoleteDestination, { force: true });
128
+ if (containedRemove({ root: projectDir, target: obsoleteDestination }).removed) {
116
129
  changed = true;
117
130
  }
118
- assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir);
131
+ assertProjectedSchemaDescriptionsRooted(projectDir, destinationDir, { skipRel: plannedRel });
119
132
  return changed;
120
133
  }
121
134
  function syncBareVersionMarkerWithPolicy(projectDir, version, allowRootFallback) {
@@ -6,15 +6,61 @@
6
6
  * the missing derive step.
7
7
  */
8
8
  import { type AcceptanceClause } from "../verify-ac/clauses.js";
9
+ /** One-line remediation when a stamp has no statement-traceable clause (#3398). */
10
+ export declare const CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION = "derive clauses from the statement's testable constraints";
11
+ export type ClauseProvenance = "statement" | "implementation";
12
+ export interface ProvenanceCounts {
13
+ readonly statement: number;
14
+ readonly implementation: number;
15
+ }
16
+ export interface TracedAcceptanceClause extends AcceptanceClause {
17
+ readonly provenance: ClauseProvenance;
18
+ }
19
+ export interface ClauseStampPreparation {
20
+ readonly ok: boolean;
21
+ readonly clauses: readonly TracedAcceptanceClause[];
22
+ readonly provenance_counts: ProvenanceCounts;
23
+ readonly ambiguity_attestation?: "none_found";
24
+ readonly remediation?: string;
25
+ }
26
+ export declare const MISSING_AMBIGUITY_ATTESTATION_CAUSE: "missing_ambiguity_attestation";
27
+ export declare const AMBIGUITY_ATTESTATION_REMEDIATION = "record ambiguity readings or attest none_found";
28
+ export interface AmbiguityAttestationCheck {
29
+ readonly ok: boolean;
30
+ readonly attested: boolean;
31
+ readonly kind: "ambiguous-clause" | "none_found" | "missing";
32
+ readonly message: string;
33
+ readonly cause?: typeof MISSING_AMBIGUITY_ATTESTATION_CAUSE;
34
+ readonly remediation?: string;
35
+ }
9
36
  /** True when activate/promote must run #3323 (absent, empty none_stated, or command-only). */
10
37
  export declare function needsClauseDerivation(acceptance: unknown): boolean;
11
38
  /** Task-statement text from title, narratives, and item Acceptance fields. */
12
39
  export declare function collectTaskStatementFromPlan(plan: Record<string, unknown>): string;
13
40
  export interface ClauseDerivationResult {
14
41
  readonly applied: boolean;
15
- readonly clauses: readonly AcceptanceClause[];
42
+ readonly clauses: readonly (AcceptanceClause | TracedAcceptanceClause)[];
16
43
  readonly notice: string;
17
44
  }
45
+ /** Statement-traceable when the clause text or its identifiers appear in the statement. */
46
+ export declare function traceClauseProvenance(clause: AcceptanceClause, statement: string): ClauseProvenance;
47
+ export declare function countClauseProvenance(clauses: readonly TracedAcceptanceClause[]): ProvenanceCounts;
48
+ /** Fail closed when every clause is implementation-provenance; else attest ambiguity. */
49
+ export declare function prepareClauseStamp(clauses: readonly AcceptanceClause[], statement: string): ClauseStampPreparation;
50
+ /** Config-error surface for verify:ac: attestation or an ambiguous clause with readings. */
51
+ export declare function evaluateAmbiguityAttestation(acceptance: unknown): AmbiguityAttestationCheck;
52
+ /**
53
+ * Fail-closed check for a *stamped* plan.acceptance (#3559).
54
+ * Command-only / empty floor blocks are not stamps; they keep the empty-resolution path.
55
+ */
56
+ export declare function stampedAmbiguityAttestationError(acceptance: unknown): AmbiguityAttestationCheck | null;
57
+ /**
58
+ * Annotate stamped clauses with provenance and an ambiguity attestation.
59
+ * An all-implementation stamp is stripped (never sufficient alone).
60
+ */
61
+ export declare function applyClauseQualityToPlan(plan: Record<string, unknown>): ClauseDerivationResult;
62
+ /** Ingest path: strip an implementation-only stamp and record the remediation. */
63
+ export declare function applyClauseQualityForIngest(plan: Record<string, unknown>): ClauseDerivationResult;
18
64
  /**
19
65
  * Stamp derived clauses onto plan.acceptance in place. Headless: records
20
66
  * chosen_reading, never blocks on a question.
@@ -5,8 +5,12 @@
5
5
  * brief by hand; #3334 only required plan.acceptance to exist. This module is
6
6
  * the missing derive step.
7
7
  */
8
- import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter } from "../run-summary/index.js";
8
+ import { ENV_RUN_SUMMARY_PATH, RunSummaryEmitter, } from "../run-summary/index.js";
9
9
  import { deriveAcceptanceClauses, readAcceptanceClauses, serializeAcceptanceClauses, } from "../verify-ac/clauses.js";
10
+ /** One-line remediation when a stamp has no statement-traceable clause (#3398). */
11
+ export const CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION = "derive clauses from the statement's testable constraints";
12
+ export const MISSING_AMBIGUITY_ATTESTATION_CAUSE = "missing_ambiguity_attestation";
13
+ export const AMBIGUITY_ATTESTATION_REMEDIATION = "record ambiguity readings or attest none_found";
10
14
  const NARRATIVE_KEYS = [
11
15
  "Overview",
12
16
  "Description",
@@ -76,6 +80,194 @@ export function collectTaskStatementFromPlan(plan) {
76
80
  }
77
81
  return parts.join("\n\n");
78
82
  }
83
+ function hasAmbiguousReadings(clause) {
84
+ return clause.ambiguous === true && (clause.readings?.length ?? 0) > 0;
85
+ }
86
+ function extractClauseTokens(text) {
87
+ const found = [];
88
+ const seen = new Set();
89
+ const push = (raw) => {
90
+ const token = raw.trim();
91
+ if (token.length < 2 || seen.has(token)) {
92
+ return;
93
+ }
94
+ seen.add(token);
95
+ found.push(token);
96
+ };
97
+ const quoted = /["'`]([^"'`\n]{2,80})["'`]/g;
98
+ let match = quoted.exec(text);
99
+ while (match !== null) {
100
+ push(match[1] ?? "");
101
+ match = quoted.exec(text);
102
+ }
103
+ const pathRe = /(?<![A-Za-z0-9_])((?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\.[A-Za-z][A-Za-z0-9]+)(?![A-Za-z0-9_])/g;
104
+ match = pathRe.exec(text);
105
+ while (match !== null) {
106
+ push(match[1] ?? "");
107
+ match = pathRe.exec(text);
108
+ }
109
+ const ident = /(?<![A-Za-z0-9_])([A-Za-z_][A-Za-z0-9_]*)(?![A-Za-z0-9_])/g;
110
+ match = ident.exec(text);
111
+ while (match !== null) {
112
+ const token = match[1] ?? "";
113
+ const camelOrPascal = /[a-z][A-Z]/.test(token) || /[A-Z][a-z]+[A-Z]/.test(token);
114
+ const codeShaped = camelOrPascal || token.includes("_");
115
+ if (codeShaped) {
116
+ push(token);
117
+ }
118
+ match = ident.exec(text);
119
+ }
120
+ return found;
121
+ }
122
+ /** Statement-traceable when the clause text or its identifiers appear in the statement. */
123
+ export function traceClauseProvenance(clause, statement) {
124
+ const normClause = clause.text.replace(/\s+/g, " ").trim();
125
+ const normStatement = statement.replace(/\s+/g, " ").trim();
126
+ if (normClause.length > 0 && normStatement.includes(normClause)) {
127
+ return "statement";
128
+ }
129
+ const tokens = extractClauseTokens(clause.text);
130
+ if (tokens.length === 0) {
131
+ return "implementation";
132
+ }
133
+ return tokens.every((token) => statement.includes(token)) ? "statement" : "implementation";
134
+ }
135
+ export function countClauseProvenance(clauses) {
136
+ let statement = 0;
137
+ let implementation = 0;
138
+ for (const clause of clauses) {
139
+ if (clause.provenance === "statement") {
140
+ statement += 1;
141
+ }
142
+ else {
143
+ implementation += 1;
144
+ }
145
+ }
146
+ return { statement, implementation };
147
+ }
148
+ /** Fail closed when every clause is implementation-provenance; else attest ambiguity. */
149
+ export function prepareClauseStamp(clauses, statement) {
150
+ const traced = clauses.map((clause) => ({
151
+ ...clause,
152
+ provenance: traceClauseProvenance(clause, statement),
153
+ }));
154
+ const provenance_counts = countClauseProvenance(traced);
155
+ if (traced.length > 0 && provenance_counts.statement === 0) {
156
+ return {
157
+ ok: false,
158
+ clauses: traced,
159
+ provenance_counts,
160
+ remediation: CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION,
161
+ };
162
+ }
163
+ return {
164
+ ok: true,
165
+ clauses: traced,
166
+ provenance_counts,
167
+ ambiguity_attestation: traced.some(hasAmbiguousReadings) ? undefined : "none_found",
168
+ };
169
+ }
170
+ const AMBIGUITY_ATTESTATION_MISSING = `verify:ac config error: missing ambiguity attestation; ${AMBIGUITY_ATTESTATION_REMEDIATION}`;
171
+ function missingAttestationCheck() {
172
+ return {
173
+ ok: false,
174
+ attested: false,
175
+ kind: "missing",
176
+ message: AMBIGUITY_ATTESTATION_MISSING,
177
+ cause: MISSING_AMBIGUITY_ATTESTATION_CAUSE,
178
+ remediation: AMBIGUITY_ATTESTATION_REMEDIATION,
179
+ };
180
+ }
181
+ /** Config-error surface for verify:ac: attestation or an ambiguous clause with readings. */
182
+ export function evaluateAmbiguityAttestation(acceptance) {
183
+ const rec = asRecord(acceptance);
184
+ if (rec === null) {
185
+ return missingAttestationCheck();
186
+ }
187
+ const clauses = readAcceptanceClauses(rec);
188
+ if (clauses.some(hasAmbiguousReadings)) {
189
+ return { ok: true, attested: true, kind: "ambiguous-clause", message: "" };
190
+ }
191
+ if (rec.ambiguity_attestation === "none_found") {
192
+ return { ok: true, attested: true, kind: "none_found", message: "" };
193
+ }
194
+ return missingAttestationCheck();
195
+ }
196
+ /**
197
+ * Fail-closed check for a *stamped* plan.acceptance (#3559).
198
+ * Command-only / empty floor blocks are not stamps; they keep the empty-resolution path.
199
+ */
200
+ export function stampedAmbiguityAttestationError(acceptance) {
201
+ const rec = asRecord(acceptance);
202
+ if (rec === null) {
203
+ return null;
204
+ }
205
+ if (readAcceptanceClauses(rec).length === 0) {
206
+ return null;
207
+ }
208
+ const check = evaluateAmbiguityAttestation(rec);
209
+ return check.ok ? null : check;
210
+ }
211
+ function serializeTracedClauses(clauses) {
212
+ return serializeAcceptanceClauses(clauses).map((row, index) => ({
213
+ ...row,
214
+ provenance: clauses[index]?.provenance ?? "implementation",
215
+ }));
216
+ }
217
+ /**
218
+ * Annotate stamped clauses with provenance and an ambiguity attestation.
219
+ * An all-implementation stamp is stripped (never sufficient alone).
220
+ */
221
+ export function applyClauseQualityToPlan(plan) {
222
+ const existing = asRecord(plan.acceptance);
223
+ if (existing === null) {
224
+ return { applied: false, clauses: [], notice: "" };
225
+ }
226
+ const clauses = readAcceptanceClauses(existing);
227
+ if (clauses.length === 0) {
228
+ return { applied: false, clauses: [], notice: "" };
229
+ }
230
+ const prepared = prepareClauseStamp(clauses, collectTaskStatementFromPlan(plan));
231
+ if (!prepared.ok) {
232
+ const next = { ...existing };
233
+ delete next.clauses;
234
+ plan.acceptance = next;
235
+ return {
236
+ applied: false,
237
+ clauses: prepared.clauses,
238
+ notice: prepared.remediation ?? CLAUSE_STAMP_IMPLEMENTATION_ONLY_REMEDIATION,
239
+ };
240
+ }
241
+ const next = {
242
+ ...existing,
243
+ clauses: serializeTracedClauses(prepared.clauses),
244
+ };
245
+ if (prepared.ambiguity_attestation !== undefined &&
246
+ existing.ambiguity_attestation === undefined) {
247
+ next.ambiguity_attestation = prepared.ambiguity_attestation;
248
+ }
249
+ plan.acceptance = next;
250
+ return {
251
+ applied: true,
252
+ clauses: prepared.clauses,
253
+ notice: formatAmbiguousClauseNotice(prepared.clauses),
254
+ };
255
+ }
256
+ /** Ingest path: strip an implementation-only stamp and record the remediation. */
257
+ export function applyClauseQualityForIngest(plan) {
258
+ const quality = applyClauseQualityToPlan(plan);
259
+ if (!quality.applied && quality.notice.length > 0) {
260
+ const existing = asRecord(plan.acceptance);
261
+ if (existing !== null) {
262
+ plan.acceptance = {
263
+ ...existing,
264
+ derived_reason: quality.notice,
265
+ quality_notice: quality.notice,
266
+ };
267
+ }
268
+ }
269
+ return quality;
270
+ }
79
271
  function formatAmbiguousClauseNotice(clauses) {
80
272
  const flagged = clauses.filter((clause) => clause.ambiguous);
81
273
  const lines = [
@@ -117,6 +309,7 @@ export function applyClauseDerivationToPlan(plan, options = {}) {
117
309
  ? existing.source_rung
118
310
  : "stated"
119
311
  : "derived";
312
+ const previousAcceptance = plan.acceptance;
120
313
  plan.acceptance = {
121
314
  ...(existing ?? {}),
122
315
  commands: hasCommands ? commands : [],
@@ -125,13 +318,32 @@ export function applyClauseDerivationToPlan(plan, options = {}) {
125
318
  derived_reason: `derived ${clauses.length} independently testable clauses from the task statement before product edit (#3323)`,
126
319
  clauses: serializeAcceptanceClauses(clauses),
127
320
  };
321
+ const quality = applyClauseQualityToPlan(plan);
322
+ if (!quality.applied) {
323
+ const existing = asRecord(previousAcceptance);
324
+ if (existing !== null && quality.notice.length > 0) {
325
+ plan.acceptance = {
326
+ ...existing,
327
+ quality_notice: quality.notice,
328
+ derived_reason: quality.notice,
329
+ };
330
+ }
331
+ else {
332
+ plan.acceptance = previousAcceptance;
333
+ }
334
+ return {
335
+ applied: false,
336
+ clauses: quality.clauses,
337
+ notice: quality.notice,
338
+ };
339
+ }
128
340
  if (options.emitStamp !== false && options.projectRoot !== undefined) {
129
341
  emitAcceptanceStampFromPlan(options.projectRoot, plan);
130
342
  }
131
343
  return {
132
344
  applied: true,
133
- clauses,
134
- notice: formatAmbiguousClauseNotice(clauses),
345
+ clauses: quality.clauses,
346
+ notice: quality.notice,
135
347
  };
136
348
  }
137
349
  /** Stable fingerprint of plan.acceptance for first-write / material-change detection (#3355). */
@@ -176,18 +388,35 @@ export function emitAcceptanceStampFromPlan(projectRoot, plan, env = process.env
176
388
  return;
177
389
  }
178
390
  try {
179
- const sessionId = typeof env.DEFT_SESSION_ID === "string" && env.DEFT_SESSION_ID.trim().length > 0
180
- ? env.DEFT_SESSION_ID.trim()
181
- : "clause-derivation";
182
- const emitter = new RunSummaryEmitter({ projectRoot, sessionId, env });
391
+ const emitter = new RunSummaryEmitter({
392
+ projectRoot,
393
+ env,
394
+ component: "clause-derivation",
395
+ });
183
396
  const commands = Array.isArray(acceptance.commands) ? acceptance.commands.length : 0;
184
397
  const clauses = Array.isArray(acceptance.clauses) ? acceptance.clauses.length : 0;
185
- emitter.emitAcceptanceStamp({
398
+ const statement = collectTaskStatementFromPlan(rec);
399
+ const rawRows = Array.isArray(acceptance.clauses) ? acceptance.clauses : [];
400
+ const traced = readAcceptanceClauses(acceptance).map((clause, index) => {
401
+ const row = asRecord(rawRows[index]);
402
+ const stamped = row?.provenance === "statement" || row?.provenance === "implementation"
403
+ ? row.provenance
404
+ : undefined;
405
+ return {
406
+ ...clause,
407
+ provenance: stamped ??
408
+ (statement.trim().length > 0 ? traceClauseProvenance(clause, statement) : "statement"),
409
+ };
410
+ });
411
+ const payload = {
186
412
  rung: typeof acceptance.source_rung === "string" ? acceptance.source_rung : "project_floor",
187
413
  none_stated: acceptance.none_stated === true,
188
414
  command_count: commands,
189
415
  clause_count: clauses,
190
- });
416
+ provenance_counts: countClauseProvenance(traced),
417
+ ambiguity_attested: evaluateAmbiguityAttestation(acceptance).attested,
418
+ };
419
+ emitter.emitAcceptanceStamp(payload);
191
420
  }
192
421
  catch {
193
422
  // fail-open
@@ -58,6 +58,8 @@ export declare function buildIssueVbrief(issue: Record<string, unknown>, status:
58
58
  infoRootKey?: typeof LEGACY_INFO_ROOT_KEY | typeof MIGRATED_INFO_ROOT_KEY;
59
59
  infoVersion?: string;
60
60
  }): [Record<string, unknown>, string];
61
+ /** Include a refused-stamp remediation on the ingest result so it is not silent. */
62
+ export declare function formatIngestCreatedMessage(folder: string, filename: string, plan: unknown, dryRun?: boolean): string;
61
63
  export declare function targetFilename(number: number, title: string, artifactSuffix?: typeof LEGACY_ARTIFACT_SUFFIX | typeof MIGRATED_ARTIFACT_SUFFIX): string;
62
64
  export interface FetchIssueOptions {
63
65
  readonly cwd?: string | null;
@@ -116,5 +118,5 @@ export declare function ingestSingleForAccept(n: number, repo: string, options?:
116
118
  projectRoot?: string | null;
117
119
  status?: IngestStatus;
118
120
  cacheRoot?: string | null;
119
- }): [IngestResult, string | null];
121
+ }): [IngestResult, string | null, string];
120
122
  //# sourceMappingURL=issue-ingest.d.ts.map
@@ -5,6 +5,7 @@ import { scan } from "../cache/scanner.js";
5
5
  import { assertWriteTargetSafe, ProjectionContainmentError } from "../fs/projection-containment.js";
6
6
  import { hasArtifactSuffix, resolveLifecycleRoot } from "../layout/resolve.js";
7
7
  import { captureAndAttachLiteralAcceptance } from "../literal-acceptance/index.js";
8
+ import { stampIntendedPlacement } from "../preflight/intended-placement.js";
8
9
  import { stampAcceptanceFromLiteralCapture } from "../product-first-done-gate/index.js";
9
10
  import { call } from "../scm/call.js";
10
11
  import { resolveProjectRoot } from "../scope/project-context.js";
@@ -14,7 +15,7 @@ import { slugify, TODAY } from "../vbrief-build/build.js";
14
15
  import { EMITTED_VBRIEF_VERSION } from "../vbrief-build/constants.js";
15
16
  import { stampDerivedClausesOnAcceptance } from "../verify-ac/clauses.js";
16
17
  import { LEGACY_ARTIFACT_SUFFIX, LEGACY_INFO_ROOT_KEY, MIGRATED_ARTIFACT_DIR, MIGRATED_ARTIFACT_SUFFIX, MIGRATED_INFO_ROOT_KEY, VBRIEF_VERSION, } from "../xbrief-migrate/constants.js";
17
- import { emitAcceptanceStampFromPlan } from "./clause-derivation.js";
18
+ import { applyClauseQualityForIngest, emitAcceptanceStampFromPlan } from "./clause-derivation.js";
18
19
  import { findAcHeading, parseCheckboxItems, parseListItems, sliceAcSection, stripCodeBlocks, stripFencedCodeBlocks, } from "./markdown-scanners.js";
19
20
  import { detectRepo, extractReferencesFromVbrief, fetchOpenIssues, GITHUB_ISSUE_REF_TYPES, LIFECYCLE_FOLDERS, parseIssueNumber, } from "./reconcile-issues.js";
20
21
  /** Reference type pointing at the canonical current-shape comment permalink (#1870). */
@@ -480,11 +481,13 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
480
481
  // #3323: when no commands were stated, derive numbered clauses before product edit.
481
482
  const derived = stampDerivedClausesOnAcceptance(plan, [title, overviewSource].filter((s) => s.length > 0).join("\n\n"));
482
483
  Object.assign(plan, derived.plan);
484
+ applyClauseQualityForIngest(plan);
483
485
  }
484
486
  else {
485
487
  // No body: still record none_stated acceptance so absence is a decision.
486
488
  Object.assign(plan, stampAcceptanceFromLiteralCapture(plan));
487
489
  }
490
+ stampIntendedPlacement(plan);
488
491
  const infoRootKey = options.infoRootKey ?? LEGACY_INFO_ROOT_KEY;
489
492
  const infoVersion = options.infoVersion ?? EMITTED_VBRIEF_VERSION;
490
493
  const briefLabel = infoRootKey === MIGRATED_INFO_ROOT_KEY ? "xBRIEF" : "vBRIEF";
@@ -499,6 +502,24 @@ export function buildIssueVbrief(issue, status, repoUrl, options = {}) {
499
502
  folder,
500
503
  ];
501
504
  }
505
+ /** Include a refused-stamp remediation on the ingest result so it is not silent. */
506
+ export function formatIngestCreatedMessage(folder, filename, plan, dryRun = false) {
507
+ const lead = dryRun
508
+ ? `DRY-RUN would write ${folder}/${filename}`
509
+ : `CREATED ${folder}/${filename}`;
510
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
511
+ return lead;
512
+ }
513
+ const acceptance = plan.acceptance;
514
+ if (typeof acceptance !== "object" || acceptance === null || Array.isArray(acceptance)) {
515
+ return lead;
516
+ }
517
+ const notice = acceptance.quality_notice;
518
+ if (typeof notice !== "string" || notice.trim().length === 0) {
519
+ return lead;
520
+ }
521
+ return `${lead}\n${notice.trim()}`;
522
+ }
502
523
  export function targetFilename(number, title, artifactSuffix = LEGACY_ARTIFACT_SUFFIX) {
503
524
  const slug = slugify(title) || `issue-${number}`;
504
525
  return `${TODAY}-${number}-${slug}${artifactSuffix}`;
@@ -707,7 +728,7 @@ export function ingestOne(issue, options) {
707
728
  const folderPath = join(options.vbriefDir, folder);
708
729
  const target = join(folderPath, filename);
709
730
  if (options.dryRun) {
710
- return ["dryrun", target, `DRY-RUN would write ${folder}/${filename}`];
731
+ return ["dryrun", target, formatIngestCreatedMessage(folder, filename, vbrief.plan, true)];
711
732
  }
712
733
  // Gate lifecycle folder + leaf before mkdir/write so folder/parent symlinks
713
734
  // cannot divert issue:ingest / triage:accept outside the project (#2869).
@@ -724,7 +745,7 @@ export function ingestOne(issue, options) {
724
745
  mkdirSync(folderPath, { recursive: true });
725
746
  writeFileSync(target, `${JSON.stringify(vbrief, null, 2)}\n`, "utf8");
726
747
  emitAcceptanceStampFromPlan(projectRoot, vbrief.plan);
727
- return ["created", target, `CREATED ${folder}/${filename}`];
748
+ return ["created", target, formatIngestCreatedMessage(folder, filename, vbrief.plan)];
728
749
  }
729
750
  export function ingestBulk(issues, options) {
730
751
  let filtered = issues;
@@ -753,6 +774,7 @@ export function ingestBulk(issues, options) {
753
774
  duplicate: [],
754
775
  dryrun: [],
755
776
  failed: [],
777
+ notices: [],
756
778
  };
757
779
  for (const issue of filtered) {
758
780
  let ingested;
@@ -770,9 +792,12 @@ export function ingestBulk(issues, options) {
770
792
  }
771
793
  throw exc;
772
794
  }
773
- const [result, path, _msg] = ingested;
795
+ const [result, path, msg] = ingested;
774
796
  const rel = path !== null ? path.replace(`${options.vbriefDir}/`, "").replace(/\\/g, "/") : "";
775
797
  summary[result].push(rel);
798
+ if (msg.includes("\n")) {
799
+ summary.notices.push(msg);
800
+ }
776
801
  if (result === "created" && path !== null) {
777
802
  const num = Number(issue.number);
778
803
  const existing = refs.get(num) ?? [];
@@ -840,6 +865,9 @@ export function issueIngestMain(args) {
840
865
  for (const entry of created) {
841
866
  process.stdout.write(` CREATED ${entry}\n`);
842
867
  }
868
+ for (const notice of summary.notices ?? []) {
869
+ process.stdout.write(`${notice}\n`);
870
+ }
843
871
  for (const entry of dryrun) {
844
872
  process.stdout.write(` DRY-RUN ${entry}\n`);
845
873
  }
@@ -890,12 +918,13 @@ export function ingestSingleForAccept(n, repo, options = {}) {
890
918
  if (issue === null) {
891
919
  throw new Error(`failed to fetch GitHub issue #${n} from ${repo} (unified cache miss + live gh api fetch failed; see stderr)`);
892
920
  }
893
- const [result, path] = ingestOne(issue, {
921
+ // Forward ingestOne's notice-bearing third return so triage:accept can
922
+ // print a refused-stamp remediation instead of only the decision id (#3398).
923
+ return ingestOne(issue, {
894
924
  vbriefDir,
895
925
  status: options.status ?? "proposed",
896
926
  repoUrl,
897
927
  cwd: root,
898
928
  });
899
- return [result, path];
900
929
  }
901
930
  //# sourceMappingURL=issue-ingest.js.map
@@ -25,6 +25,11 @@ export interface EvaluateCompletedTrackedOptions {
25
25
  readonly repo?: string | null;
26
26
  /** Explicit delivery tip ref (e.g. origin/master). Overrides policy/git default. */
27
27
  readonly tip?: string | null;
28
+ /**
29
+ * When set, check only this issue number (#3476 drive-to DONE).
30
+ * Sibling unlanded closed issues do not fail the per-issue scan.
31
+ */
32
+ readonly issue?: number | null;
28
33
  readonly runGh?: RunGhFn;
29
34
  readonly skipGh?: boolean;
30
35
  readonly runGit?: GitRunner;