@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
@@ -8,5 +8,5 @@ export { pyRepr } from "./py-repr.js";
8
8
  export { buildSpecTaskIndex, detectStatusMarker, folderFromStatus, formatReconciliationMarkdown, hasDisagreement, loadOverrides, normalizeTaskId, OVERRIDES_FILENAME, parseOverridesYaml, reconcileScopeItems, writeReconciliationReport, } from "./reconciliation.js";
9
9
  export { allScopeIds, asStrList, candidateDepGraph, candidateFromPath, markCycles, } from "./swarm-deps.js";
10
10
  export type { Candidate, Child, ConflictEntry, ForgeIssueState, LabelChange, LabelClient, ReconciledItem, ReconcileGraphOutcome, ReconcileLabelsOutcome, ReconcileUmbrellasOutcome, ReconciliationReport, SpecTaskEntry, UmbrellaChange, UmbrellaClient, } from "./types.js";
11
- export { buildChildIndex, CHILD_REF_TYPE, CLOSED_FOLDERS, childFromData, classifyPassType, computeChildren, computeWaves, indexChildrenByIssueNumber, isChildOpen, LIFECYCLE_FOLDERS, nowIso, OPEN_FOLDERS, parseCurrentShape, reconcileBodyChecklist, reconcileUmbrellas, renderBody, renderUmbrellasReport, ScmUmbrellaClient, UmbrellaScmError, UNKNOWN_FOLDER, umbrellasOutcomeToJson, } from "./umbrellas.js";
11
+ export { buildChildIndex, CHILD_REF_TYPE, CLOSED_FOLDERS, childFromData, childrenFromSliceRecord, classifyPassType, computeChildren, computeWaves, formatCloseComment, indexChildrenByIssueNumber, isChildOpen, LIFECYCLE_FOLDERS, nowIso, OPEN_FOLDERS, parseCurrentShape, reconcileBodyChecklist, reconcileUmbrellas, renderBody, renderUmbrellasReport, ScmUmbrellaClient, shouldCloseOnAllChildrenMerged, UmbrellaScmError, UNKNOWN_FOLDER, umbrellasOutcomeToJson, } from "./umbrellas.js";
12
12
  //# sourceMappingURL=index.d.ts.map
@@ -6,5 +6,5 @@ export { PARITY_SCENARIO_NAMES, renderScenarioOutput, runParityScenario, } from
6
6
  export { pyRepr } from "./py-repr.js";
7
7
  export { buildSpecTaskIndex, detectStatusMarker, folderFromStatus, formatReconciliationMarkdown, hasDisagreement, loadOverrides, normalizeTaskId, OVERRIDES_FILENAME, parseOverridesYaml, reconcileScopeItems, writeReconciliationReport, } from "./reconciliation.js";
8
8
  export { allScopeIds, asStrList, candidateDepGraph, candidateFromPath, markCycles, } from "./swarm-deps.js";
9
- export { buildChildIndex, CHILD_REF_TYPE, CLOSED_FOLDERS, childFromData, classifyPassType, computeChildren, computeWaves, indexChildrenByIssueNumber, isChildOpen, LIFECYCLE_FOLDERS, nowIso, OPEN_FOLDERS, parseCurrentShape, reconcileBodyChecklist, reconcileUmbrellas, renderBody, renderUmbrellasReport, ScmUmbrellaClient, UmbrellaScmError, UNKNOWN_FOLDER, umbrellasOutcomeToJson, } from "./umbrellas.js";
9
+ export { buildChildIndex, CHILD_REF_TYPE, CLOSED_FOLDERS, childFromData, childrenFromSliceRecord, classifyPassType, computeChildren, computeWaves, formatCloseComment, indexChildrenByIssueNumber, isChildOpen, LIFECYCLE_FOLDERS, nowIso, OPEN_FOLDERS, parseCurrentShape, reconcileBodyChecklist, reconcileUmbrellas, renderBody, renderUmbrellasReport, ScmUmbrellaClient, shouldCloseOnAllChildrenMerged, UmbrellaScmError, UNKNOWN_FOLDER, umbrellasOutcomeToJson, } from "./umbrellas.js";
10
10
  //# sourceMappingURL=index.js.map
@@ -54,6 +54,11 @@ export interface Candidate {
54
54
  }
55
55
  export interface ReconcileGraphOutcome {
56
56
  promoted: string[];
57
+ /** Successful promote messages, including refused-stamp remediation (#3398). */
58
+ promotedNotices?: Array<{
59
+ story_id: string;
60
+ message: string;
61
+ }>;
57
62
  deferredWip: string[];
58
63
  waiting: Array<{
59
64
  story_id: string;
@@ -110,6 +115,12 @@ export interface UmbrellaChange {
110
115
  * `unchanged` when already correct, `skipped` when body APIs unavailable.
111
116
  */
112
117
  readonly checklist_action?: "edited" | "unchanged" | "skipped";
118
+ /**
119
+ * slices.jsonl expected_close_signal close (#3428): `closed` when the
120
+ * umbrella was closed this run, `unchanged` when already closed under
121
+ * all-children-merged, `skipped` when the signal is not a close.
122
+ */
123
+ readonly close_action?: "closed" | "unchanged" | "skipped";
113
124
  }
114
125
  export interface ReconcileUmbrellasOutcome {
115
126
  changed: UmbrellaChange[];
@@ -138,5 +149,7 @@ export interface UmbrellaClient {
138
149
  fetchIssueBody?(repo: string, issueNumber: number): string;
139
150
  /** Optional: write reconciled checklist body (#1649). */
140
151
  editIssueBody?(repo: string, issueNumber: number, body: string): void;
152
+ /** Optional: close an umbrella via REST (#3428). */
153
+ closeIssue?(repo: string, issueNumber: number): void;
141
154
  }
142
155
  //# sourceMappingURL=types.d.ts.map
@@ -63,8 +63,22 @@ export declare class ScmUmbrellaClient implements UmbrellaClient {
63
63
  fetchIssueStates(repo: string, issueNumbers: readonly number[]): ReadonlyMap<number, ForgeIssueState>;
64
64
  fetchIssueBody(repo: string, issueNumber: number): string;
65
65
  editIssueBody(repo: string, issueNumber: number, body: string): void;
66
+ closeIssue(repo: string, issueNumber: number): void;
66
67
  }
67
68
  export declare function nowIso(date?: Date): string;
69
+ /** Children listed on a slices.jsonl row (#3428). Last write of a duplicate `n` wins. */
70
+ export declare function childrenFromSliceRecord(record: Record<string, unknown>): Child[];
71
+ /**
72
+ * Close only when the slice signal is all-children-merged, the umbrella is
73
+ * still open, and every child has forge state closed (#3428).
74
+ */
75
+ export declare function shouldCloseOnAllChildrenMerged(options: {
76
+ readonly signal: unknown;
77
+ readonly children: readonly Child[];
78
+ readonly forgeStates: ReadonlyMap<number, ForgeIssueState> | null | undefined;
79
+ readonly umbrellaState: ForgeIssueState | null | undefined;
80
+ }): boolean;
81
+ export declare function formatCloseComment(signal: string, children: readonly Child[]): string;
68
82
  export interface ReconcileUmbrellasOptions {
69
83
  readonly repo?: string | null;
70
84
  readonly dryRun?: boolean;
@@ -5,6 +5,8 @@ import { createIssueComment, editIssueBody, editIssueCommentBody, fetchIssueBody
5
5
  import { fetchIssueStates, IssueState } from "../intake/reconcile-issues.js";
6
6
  import { hasArtifactSuffix, resolveLifecycleRoot, stripArtifactSuffix } from "../layout/resolve.js";
7
7
  import { call } from "../scm/call.js";
8
+ import { restCloseIssue } from "../scm/gh-rest.js";
9
+ import { readAll } from "../slice/record.js";
8
10
  import { extractIssueRef, parseGithubIssueUri } from "../triage/reconcile/parse-uri.js";
9
11
  import { isRepoMutationAllowed } from "./repo-guard.js";
10
12
  export const OPEN_FOLDERS = ["proposed", "pending", "active"];
@@ -307,33 +309,43 @@ export function classifyPassType(prevTotal, total) {
307
309
  function hasCurrentShape(body) {
308
310
  return HEADER_RE.test(body);
309
311
  }
312
+ function parseCommentPage(data) {
313
+ if (!Array.isArray(data))
314
+ return [];
315
+ const comments = [];
316
+ for (const entry of data) {
317
+ if (typeof entry === "object" &&
318
+ entry !== null &&
319
+ !Array.isArray(entry) &&
320
+ typeof entry.id === "number" &&
321
+ typeof entry.body === "string") {
322
+ const rec = entry;
323
+ comments.push({ id: rec.id, body: rec.body });
324
+ }
325
+ }
326
+ return comments;
327
+ }
310
328
  export class ScmUmbrellaClient {
311
329
  fetchComments(repo, issueNumber) {
312
- const proc = call(SCM_SOURCE, "api", [
313
- `repos/${repo}/issues/${issueNumber}/comments?per_page=100`,
314
- ]);
315
- if (proc.returncode !== 0) {
316
- throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) failed: ${(proc.stderr || "").trim()}`);
317
- }
318
- let data;
319
- try {
320
- data = JSON.parse(proc.stdout || "[]");
321
- }
322
- catch (exc) {
323
- throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) returned non-JSON: ${String(exc)}`);
324
- }
325
- if (!Array.isArray(data))
326
- return [];
327
330
  const comments = [];
328
- for (const entry of data) {
329
- if (typeof entry === "object" &&
330
- entry !== null &&
331
- !Array.isArray(entry) &&
332
- typeof entry.id === "number" &&
333
- typeof entry.body === "string") {
334
- const rec = entry;
335
- comments.push({ id: rec.id, body: rec.body });
331
+ for (let page = 1; page <= 50; page += 1) {
332
+ const proc = call(SCM_SOURCE, "api", [
333
+ `repos/${repo}/issues/${issueNumber}/comments?per_page=100&page=${page}`,
334
+ ]);
335
+ if (proc.returncode !== 0) {
336
+ throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) failed: ${(proc.stderr || "").trim()}`);
337
+ }
338
+ let data;
339
+ try {
340
+ data = JSON.parse(proc.stdout || "[]");
341
+ }
342
+ catch (exc) {
343
+ throw new UmbrellaScmError(`list comments #${issueNumber} (${repo}) returned non-JSON: ${String(exc)}`);
336
344
  }
345
+ const batch = parseCommentPage(data);
346
+ comments.push(...batch);
347
+ if (batch.length < 100)
348
+ break;
337
349
  }
338
350
  return comments;
339
351
  }
@@ -394,6 +406,15 @@ export class ScmUmbrellaClient {
394
406
  throw new UmbrellaScmError(`edit issue body #${issueNumber} (${repo}) failed: ${message}`);
395
407
  }
396
408
  }
409
+ closeIssue(repo, issueNumber) {
410
+ try {
411
+ restCloseIssue(repo, issueNumber, "completed");
412
+ }
413
+ catch (exc) {
414
+ const message = exc instanceof Error ? exc.message : String(exc);
415
+ throw new UmbrellaScmError(`close issue #${issueNumber} (${repo}) failed: ${message}`);
416
+ }
417
+ }
397
418
  }
398
419
  function planShape(epicData, index, options = {}) {
399
420
  const children = computeChildren(epicData, index, {
@@ -545,6 +566,147 @@ function reconcileOneEpic(epicData, index, options) {
545
566
  export function nowIso(date = new Date()) {
546
567
  return date.toISOString().replace(/\.\d{3}Z$/, "Z");
547
568
  }
569
+ const ALL_CHILDREN_MERGED = "all-children-merged";
570
+ /** Children listed on a slices.jsonl row (#3428). Last write of a duplicate `n` wins. */
571
+ export function childrenFromSliceRecord(record) {
572
+ const raw = record.children;
573
+ if (!Array.isArray(raw))
574
+ return [];
575
+ const byNumber = new Map();
576
+ for (const entry of raw) {
577
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry))
578
+ continue;
579
+ const rec = entry;
580
+ const n = rec.n;
581
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1)
582
+ continue;
583
+ byNumber.set(n, {
584
+ story_id: `#${n}`,
585
+ title: `#${n}`,
586
+ kind: "story",
587
+ folder: UNKNOWN_FOLDER,
588
+ depends_on: [],
589
+ issue_number: n,
590
+ });
591
+ }
592
+ return [...byNumber.values()];
593
+ }
594
+ /**
595
+ * Close only when the slice signal is all-children-merged, the umbrella is
596
+ * still open, and every child has forge state closed (#3428).
597
+ */
598
+ export function shouldCloseOnAllChildrenMerged(options) {
599
+ if (options.signal !== ALL_CHILDREN_MERGED)
600
+ return false;
601
+ if (options.umbrellaState !== "open")
602
+ return false;
603
+ if (options.children.length === 0)
604
+ return false;
605
+ if (options.forgeStates == null)
606
+ return false;
607
+ return options.children.every((c) => typeof c.issue_number === "number" && options.forgeStates?.get(c.issue_number) === "closed");
608
+ }
609
+ export function formatCloseComment(signal, children) {
610
+ const nums = children
611
+ .map((c) => c.issue_number)
612
+ .filter((n) => typeof n === "number")
613
+ .sort((a, b) => a - b)
614
+ .map((n) => `#${n}`);
615
+ return `expected_close_signal=${signal}; children: ${nums.join(", ")}`;
616
+ }
617
+ function loadLatestSliceRecords(vbriefDir) {
618
+ const logPath = join(vbriefDir, ".triage-cache", "slices.jsonl");
619
+ if (!existsSync(logPath))
620
+ return [];
621
+ const latest = new Map();
622
+ for (const rec of readAll({ path: logPath })) {
623
+ const n = rec.umbrella;
624
+ if (typeof n !== "number" || !Number.isInteger(n) || n < 1)
625
+ continue;
626
+ const [urlRepo] = parseGithubIssueUri(rec.umbrella_url);
627
+ latest.set(`${urlRepo ?? ""}:${n}`, rec);
628
+ }
629
+ return [...latest.values()];
630
+ }
631
+ function epicDataFromSlice(repo, number, children) {
632
+ const refs = [
633
+ { type: "x-xbrief/github-issue", uri: `https://github.com/${repo}/issues/${number}` },
634
+ ...children.map((c) => ({
635
+ type: "x-xbrief/github-issue",
636
+ uri: `https://github.com/${repo}/issues/${c.issue_number}`,
637
+ title: c.title,
638
+ })),
639
+ ];
640
+ return {
641
+ plan: {
642
+ id: `slice-umbrella-${number}`,
643
+ metadata: { kind: "epic" },
644
+ references: refs,
645
+ },
646
+ };
647
+ }
648
+ function maybeCloseSliceUmbrella(options) {
649
+ if (typeof options.client.closeIssue !== "function")
650
+ return "skipped";
651
+ if (typeof options.client.fetchIssueStates !== "function")
652
+ return "skipped";
653
+ const mutateGate = isRepoMutationAllowed(options.repo, options.projectRoot, {
654
+ allowCrossRepo: options.allowCrossRepo,
655
+ allowlist: options.repoAllowlist,
656
+ explicitRepo: options.explicitRepo ?? null,
657
+ });
658
+ if (!mutateGate.allowed) {
659
+ throw new Error(mutateGate.reason ?? `refusing cross-repo mutation on ${options.repo}`);
660
+ }
661
+ const childNumbers = options.children
662
+ .map((c) => c.issue_number)
663
+ .filter((n) => typeof n === "number");
664
+ const states = options.client.fetchIssueStates(options.repo, [...childNumbers, options.number]);
665
+ const umbrellaState = states.get(options.number) ?? null;
666
+ if (!shouldCloseOnAllChildrenMerged({
667
+ signal: options.signal,
668
+ children: options.children,
669
+ forgeStates: states,
670
+ umbrellaState,
671
+ })) {
672
+ return options.signal === ALL_CHILDREN_MERGED && umbrellaState === "closed"
673
+ ? "unchanged"
674
+ : "skipped";
675
+ }
676
+ const closeBody = formatCloseComment(ALL_CHILDREN_MERGED, options.children);
677
+ if (!options.dryRun) {
678
+ const alreadyCommented = options.client
679
+ .fetchComments(options.repo, options.number)
680
+ .some((c) => c.body.trim() === closeBody);
681
+ if (!alreadyCommented) {
682
+ options.client.createComment(options.repo, options.number, closeBody);
683
+ }
684
+ options.client.closeIssue(options.repo, options.number);
685
+ }
686
+ return "closed";
687
+ }
688
+ function pushUmbrellaChange(outcome, change) {
689
+ if (change.action === "unchanged" && change.close_action !== "closed") {
690
+ outcome.unchanged.push(change);
691
+ }
692
+ else {
693
+ outcome.changed.push(change);
694
+ }
695
+ }
696
+ function replaceCloseAction(outcome, repo, issueNumber, closeAction) {
697
+ const match = (c) => c.repo === repo && c.issue_number === issueNumber;
698
+ for (const list of [outcome.changed, outcome.unchanged]) {
699
+ const idx = list.findIndex(match);
700
+ if (idx < 0)
701
+ continue;
702
+ const prev = list[idx];
703
+ if (prev === undefined)
704
+ return;
705
+ list.splice(idx, 1);
706
+ pushUmbrellaChange(outcome, { ...prev, close_action: closeAction });
707
+ return;
708
+ }
709
+ }
548
710
  export function reconcileUmbrellas(projectRoot, options = {}) {
549
711
  const root = resolve(projectRoot);
550
712
  let vbriefDir;
@@ -616,7 +778,6 @@ export function reconcileUmbrellas(projectRoot, options = {}) {
616
778
  const key = `${effectiveRepo}:${number}`;
617
779
  if (seenIssues.has(key))
618
780
  continue;
619
- seenIssues.add(key);
620
781
  try {
621
782
  const change = reconcileOneEpic(data, index, {
622
783
  storyId,
@@ -630,6 +791,7 @@ export function reconcileUmbrellas(projectRoot, options = {}) {
630
791
  repoAllowlist: options.repoAllowlist,
631
792
  explicitRepo: options.repo ?? null,
632
793
  });
794
+ seenIssues.add(key);
633
795
  if (change.action === "unchanged")
634
796
  outcome.unchanged.push(change);
635
797
  else
@@ -643,6 +805,71 @@ export function reconcileUmbrellas(projectRoot, options = {}) {
643
805
  }
644
806
  }
645
807
  }
808
+ // slices.jsonl umbrellas have no epic xBRIEF (#3428). Last row per umbrella wins.
809
+ for (const record of loadLatestSliceRecords(vbriefDir)) {
810
+ const number = record.umbrella;
811
+ if (typeof number !== "number")
812
+ continue;
813
+ const [urlRepo] = parseGithubIssueUri(record.umbrella_url);
814
+ const effectiveRepo = urlRepo ?? options.repo ?? null;
815
+ if (effectiveRepo === null) {
816
+ outcome.skipped_no_ref.push(`slice-umbrella-${number}`);
817
+ continue;
818
+ }
819
+ const key = `${effectiveRepo}:${number}`;
820
+ const children = childrenFromSliceRecord(record);
821
+ const storyId = `slice-umbrella-${number}`;
822
+ try {
823
+ if (seenIssues.has(key)) {
824
+ const closeAction = maybeCloseSliceUmbrella({
825
+ signal: record.expected_close_signal,
826
+ children,
827
+ repo: effectiveRepo,
828
+ number,
829
+ client,
830
+ dryRun: options.dryRun ?? false,
831
+ projectRoot: root,
832
+ allowCrossRepo: options.allowCrossRepo ?? false,
833
+ repoAllowlist: options.repoAllowlist,
834
+ explicitRepo: options.repo ?? null,
835
+ });
836
+ replaceCloseAction(outcome, effectiveRepo, number, closeAction);
837
+ continue;
838
+ }
839
+ const change = reconcileOneEpic(epicDataFromSlice(effectiveRepo, number, children), index, {
840
+ storyId,
841
+ repo: effectiveRepo,
842
+ number,
843
+ client,
844
+ dryRun: options.dryRun ?? false,
845
+ now,
846
+ projectRoot: root,
847
+ allowCrossRepo: options.allowCrossRepo ?? false,
848
+ repoAllowlist: options.repoAllowlist,
849
+ explicitRepo: options.repo ?? null,
850
+ });
851
+ seenIssues.add(key);
852
+ const closeAction = maybeCloseSliceUmbrella({
853
+ signal: record.expected_close_signal,
854
+ children,
855
+ repo: effectiveRepo,
856
+ number,
857
+ client,
858
+ dryRun: options.dryRun ?? false,
859
+ projectRoot: root,
860
+ allowCrossRepo: options.allowCrossRepo ?? false,
861
+ repoAllowlist: options.repoAllowlist,
862
+ explicitRepo: options.repo ?? null,
863
+ });
864
+ pushUmbrellaChange(outcome, { ...change, close_action: closeAction });
865
+ }
866
+ catch (exc) {
867
+ outcome.errors.push({
868
+ story_id: storyId,
869
+ message: exc instanceof Error ? exc.message : String(exc),
870
+ });
871
+ }
872
+ }
646
873
  return [outcome.errors.length > 0 ? 1 : 0, outcome];
647
874
  }
648
875
  function formatChecklistSuffix(action) {
@@ -650,6 +877,11 @@ function formatChecklistSuffix(action) {
650
877
  return "";
651
878
  return `; checklist=${action}`;
652
879
  }
880
+ function formatCloseSuffix(action) {
881
+ if (action === undefined || action === "skipped")
882
+ return "";
883
+ return `; close=${action}`;
884
+ }
653
885
  export function renderUmbrellasReport(outcome) {
654
886
  const lines = ["vBRIEF reconcile umbrellas", ""];
655
887
  const suffix = outcome.dry_run ? " (dry-run)" : "";
@@ -657,7 +889,8 @@ export function renderUmbrellasReport(outcome) {
657
889
  if (outcome.changed.length > 0) {
658
890
  for (const c of outcome.changed) {
659
891
  lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: ${c.action} -> pass-${c.pass_n}` +
660
- formatChecklistSuffix(c.checklist_action));
892
+ formatChecklistSuffix(c.checklist_action) +
893
+ formatCloseSuffix(c.close_action));
661
894
  }
662
895
  }
663
896
  else {
@@ -668,7 +901,8 @@ export function renderUmbrellasReport(outcome) {
668
901
  if (outcome.unchanged.length > 0) {
669
902
  for (const c of outcome.unchanged) {
670
903
  lines.push(`- #${c.issue_number} (${c.repo}) [${c.story_id}]: pass-${c.pass_n}` +
671
- formatChecklistSuffix(c.checklist_action));
904
+ formatChecklistSuffix(c.checklist_action) +
905
+ formatCloseSuffix(c.close_action));
672
906
  }
673
907
  }
674
908
  else {
@@ -696,6 +930,7 @@ export function umbrellasOutcomeToJson(outcome) {
696
930
  action: c.action,
697
931
  pass_n: c.pass_n,
698
932
  checklist_action: c.checklist_action ?? "skipped",
933
+ close_action: c.close_action ?? "skipped",
699
934
  });
700
935
  return {
701
936
  changed: outcome.changed.map(toChange),
@@ -6,7 +6,7 @@ export { validateFolderStatus } from "./folder-status.js";
6
6
  export type { ConformanceCliOptions, ValidateCliOptions } from "./main.js";
7
7
  export { cmdVbriefValidate, runConformance, runValidate } from "./main.js";
8
8
  export { validateOriginProvenance } from "./origin.js";
9
- export { validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
9
+ export { validateForgeOutageRetryMinutesOnPlan, validateProjectInvariantsOnPlan, validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
10
10
  export { validateProjectDefinition } from "./project-definition.js";
11
11
  export type { ExtensionEntry, ExtensionRoundtripEvaluateResult, ExtensionRoundtripFinding, } from "./roundtrip.js";
12
12
  export { collectExtensionEntries, EXTENSION_CONFORMANCE_FIXTURES_DIR, evaluateExtensionRoundtrip, findExtensionPreservationViolations, reEmitVbriefArtifact, renderExtensionRoundtripFinding, VbriefSchemaValidationError, } from "./roundtrip.js";
@@ -4,7 +4,7 @@ export { matchesFilenameConvention, validateFilename } from "./filename.js";
4
4
  export { validateFolderStatus } from "./folder-status.js";
5
5
  export { cmdVbriefValidate, runConformance, runValidate } from "./main.js";
6
6
  export { validateOriginProvenance } from "./origin.js";
7
- export { validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
7
+ export { validateForgeOutageRetryMinutesOnPlan, validateProjectInvariantsOnPlan, validateSessionRitualStalenessHoursOnPlan, validateTriageRankingLabelsOnPlan, validateWipCapOnPlan, } from "./plan-hooks.js";
8
8
  export { validateProjectDefinition } from "./project-definition.js";
9
9
  export { collectExtensionEntries, EXTENSION_CONFORMANCE_FIXTURES_DIR, evaluateExtensionRoundtrip, findExtensionPreservationViolations, reEmitVbriefArtifact, renderExtensionRoundtripFinding, VbriefSchemaValidationError, } from "./roundtrip.js";
10
10
  export { normalizeNarrativeKey, validateVbriefSchema } from "./schema.js";
@@ -2,6 +2,10 @@
2
2
  export declare function validateWipCapOnPlan(plan: unknown, filepath: string): string[];
3
3
  /** vbrief_validate hook for ``sessionRitualStalenessHours`` (#1348). */
4
4
  export declare function validateSessionRitualStalenessHoursOnPlan(plan: unknown, filepath: string): string[];
5
+ /** vbrief_validate hook for ``projectInvariants`` (#3425). */
6
+ export declare function validateProjectInvariantsOnPlan(plan: unknown, filepath: string): string[];
7
+ /** vbrief_validate hook for ``forgeOutageRetryMinutes`` (#3422). */
8
+ export declare function validateForgeOutageRetryMinutesOnPlan(plan: unknown, filepath: string): string[];
5
9
  /** vbrief_validate hook: validate ``plan.policy.triageRankingLabels`` (#1128). */
6
10
  export declare function validateTriageRankingLabelsOnPlan(plan: unknown, filepath: string): string[];
7
11
  /** vbrief_validate hook: validate ``plan.policy.runtimeAuthority`` (#1394). */
@@ -3,6 +3,7 @@ import { validateCoverageDebt } from "../policy/coverage-debt.js";
3
3
  import { validateHostHooks } from "../policy/host-hooks.js";
4
4
  import { validateHostSlashCommands } from "../policy/host-slash-commands.js";
5
5
  import { readPlanPolicy } from "../policy/plan-extensions.js";
6
+ import { parseProjectInvariants } from "../policy/project-invariants.js";
6
7
  import { validateRuntimeAuthority } from "../policy/runtime-authority.js";
7
8
  import { validateStalenessTickler } from "../policy/staleness-tickler.js";
8
9
  import { validateOpenClawProductCommands } from "../slash/openclaw-deposit.js";
@@ -93,6 +94,61 @@ export function validateSessionRitualStalenessHoursOnPlan(plan, filepath) {
93
94
  }
94
95
  return out;
95
96
  }
97
+ function validateForgeOutageRetryMinutes(value) {
98
+ if (value === null || value === undefined) {
99
+ return [];
100
+ }
101
+ if (typeof value !== "number" || !Number.isInteger(value)) {
102
+ const repr = typeof value === "string"
103
+ ? pyStrRepr(value)
104
+ : typeof value === "boolean"
105
+ ? value
106
+ ? "True"
107
+ : "False"
108
+ : String(value);
109
+ return [
110
+ "plan.policy.forgeOutageRetryMinutes must be an integer; got " +
111
+ `${pythonTypeName(value)} (${repr})`,
112
+ ];
113
+ }
114
+ if (value < 5) {
115
+ return [`plan.policy.forgeOutageRetryMinutes must be >= 5; got ${value}`];
116
+ }
117
+ return [];
118
+ }
119
+ /** vbrief_validate hook for ``projectInvariants`` (#3425). */
120
+ export function validateProjectInvariantsOnPlan(plan, filepath) {
121
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
122
+ return [];
123
+ }
124
+ const policy = readPlanPolicy(plan);
125
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
126
+ return [];
127
+ }
128
+ if (!("projectInvariants" in policy)) {
129
+ return [];
130
+ }
131
+ const { errors } = parseProjectInvariants(policy.projectInvariants);
132
+ return errors.map((err) => `${filepath}: ${err} (#3425)`);
133
+ }
134
+ /** vbrief_validate hook for ``forgeOutageRetryMinutes`` (#3422). */
135
+ export function validateForgeOutageRetryMinutesOnPlan(plan, filepath) {
136
+ if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
137
+ return [];
138
+ }
139
+ const policy = readPlanPolicy(plan);
140
+ if (typeof policy !== "object" || policy === null || Array.isArray(policy)) {
141
+ return [];
142
+ }
143
+ if (!("forgeOutageRetryMinutes" in policy)) {
144
+ return [];
145
+ }
146
+ const out = [];
147
+ for (const err of validateForgeOutageRetryMinutes(policy.forgeOutageRetryMinutes)) {
148
+ out.push(`${filepath}: ${err} (#3422)`);
149
+ }
150
+ return out;
151
+ }
96
152
  /** vbrief_validate hook: validate ``plan.policy.triageRankingLabels`` (#1128). */
97
153
  export function validateTriageRankingLabelsOnPlan(plan, filepath) {
98
154
  if (typeof plan !== "object" || plan === null || Array.isArray(plan)) {
@@ -283,6 +339,18 @@ export function runProjectDefinitionHooks(plan, filepath) {
283
339
  catch {
284
340
  /* hook must not break validation */
285
341
  }
342
+ try {
343
+ errors.push(...validateForgeOutageRetryMinutesOnPlan(plan, filepath));
344
+ }
345
+ catch {
346
+ /* hook must not break validation */
347
+ }
348
+ try {
349
+ errors.push(...validateProjectInvariantsOnPlan(plan, filepath));
350
+ }
351
+ catch {
352
+ /* hook must not break validation */
353
+ }
286
354
  try {
287
355
  errors.push(...validateStalenessTicklerOnPlan(plan, filepath));
288
356
  }
@@ -1,4 +1,8 @@
1
1
  export type JsonObject = Record<string, unknown>;
2
+ export declare function resolveInfoBlock(data: JsonObject): {
3
+ key: "vBRIEFInfo" | "xBRIEFInfo";
4
+ info: JsonObject;
5
+ } | null;
2
6
  /** Validate vBRIEF/xBRIEF structural requirements (v0.6 + v0.8 additive). */
3
7
  export declare function validateVbriefSchema(data: JsonObject, filepath: string): string[];
4
8
  /** Normalize a narrative key for D3 comparison. */
@@ -22,7 +22,7 @@ function validatePlanRefs(planRefs, path, errors) {
22
22
  }
23
23
  }
24
24
  }
25
- function resolveInfoBlock(data) {
25
+ export function resolveInfoBlock(data) {
26
26
  for (const key of VALID_INFO_ROOT_KEYS) {
27
27
  if (!(key in data)) {
28
28
  continue;
@@ -27,6 +27,11 @@ export interface OracleIntegrityResultFields {
27
27
  readonly code: number;
28
28
  readonly message: string;
29
29
  }
30
+ /**
31
+ * Walk-level method fingerprint: command list + cwd hash (#3397 / #3322).
32
+ * Same shape for fail and pass so an honest product fix does not flag.
33
+ */
34
+ export declare function methodFingerprintForWalk(commands: readonly string[], cwd: string): string;
30
35
  /** Stable prefix for product-oracle check ids emitted by verify:ac (#3322 / #3337). */
31
36
  export declare const VERIFY_AC_CHECK_ID_PREFIX = "verify:ac";
32
37
  /**
@@ -40,7 +45,8 @@ export declare function verifyAcCheckId(scopeKey?: string | null): string;
40
45
  /** Test seam: isolate stdout-dest buffer across cases. */
41
46
  export declare function resetInProcessVerificationBuffer(): void;
42
47
  /**
43
- * Record each executed acceptance command as a verification event (#3322).
48
+ * Record one walk-level verification event for the executed command set (#3322 / #3397).
49
+ * Fail and pass use the same method_fingerprint shape (command list + cwd hash).
44
50
  * Fail-open: missing dest / write errors never change the AC result.
45
51
  * Stdout dest also appends to the same-process buffer so evaluate can
46
52
  * inspect this invocation without re-reading a file.