@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
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Public compare entry for extracted intent (#3376 R2 / R5 / #3385 F1–F2).
3
+ *
4
+ * Scalars are byte-equal after canonicalize. Decisions and references[] are
5
+ * guarded-append. Unclassified key only when unknown AND absent from base.
6
+ */
7
+ import { DECISION_FILE_SUFFIX, DECISIONS_DIR_REL } from "../decision/schema.js";
8
+ import { GITHUB_ISSUE_REF_TYPES } from "../intake/reconcile-issues.js";
9
+ import { slugFromGithubIssueUri } from "./extract-intent.js";
10
+ import { sortKeysDeep } from "./intent-digest.js";
11
+ import { classifyPlanPath } from "./known-machine.js";
12
+ const EM_DASH = "\u2014";
13
+ const DECISION_LINE = new RegExp(`^- (${DECISIONS_DIR_REL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/[^\\s]+${DECISION_FILE_SUFFIX.replace(".", "\\.")}) ${EM_DASH} (.+)$`);
14
+ function canon(value) {
15
+ return JSON.stringify(sortKeysDeep(value));
16
+ }
17
+ function asRecord(value) {
18
+ if (value === null || typeof value !== "object" || Array.isArray(value))
19
+ return null;
20
+ return value;
21
+ }
22
+ function splitDecisionLines(raw) {
23
+ if (typeof raw !== "string")
24
+ return [];
25
+ return raw
26
+ .split(/\r?\n/)
27
+ .map((l) => l.trim())
28
+ .filter((l) => l.length > 0);
29
+ }
30
+ function refKey(ref) {
31
+ return `${String(ref.type ?? "")}\n${String(ref.uri ?? "")}`;
32
+ }
33
+ function narrativesOf(plan) {
34
+ return asRecord(plan.narratives) ?? {};
35
+ }
36
+ function compareScalars(basePlan, livePlan, findings) {
37
+ const skip = new Set(["references", "narratives", "items", "parentId"]);
38
+ const keys = new Set([...Object.keys(basePlan), ...Object.keys(livePlan)]);
39
+ for (const key of keys) {
40
+ if (skip.has(key))
41
+ continue;
42
+ if (key === "metadata") {
43
+ if (canon(basePlan.metadata) !== canon(livePlan.metadata)) {
44
+ findings.push({
45
+ kind: "intent-drift",
46
+ path: "metadata",
47
+ detail: "extracted metadata drifted from the base preimage",
48
+ });
49
+ }
50
+ continue;
51
+ }
52
+ if (canon(basePlan[key]) !== canon(livePlan[key])) {
53
+ findings.push({
54
+ kind: "intent-drift",
55
+ path: key,
56
+ detail: `extracted ${key} drifted from the base preimage`,
57
+ });
58
+ }
59
+ }
60
+ }
61
+ function compareItems(baseItems, liveItems, findings) {
62
+ if (canon(baseItems ?? []) !== canon(liveItems ?? [])) {
63
+ findings.push({
64
+ kind: "intent-drift",
65
+ path: "items",
66
+ detail: "extracted items drifted from the base preimage",
67
+ });
68
+ }
69
+ }
70
+ function compareNarratives(basePlan, livePlan, options, findings) {
71
+ const baseN = narrativesOf(basePlan);
72
+ const liveN = narrativesOf(livePlan);
73
+ const keys = new Set([...Object.keys(baseN), ...Object.keys(liveN)]);
74
+ for (const key of keys) {
75
+ if (key === "Decisions")
76
+ continue;
77
+ if (canon(baseN[key]) !== canon(liveN[key])) {
78
+ findings.push({
79
+ kind: "intent-drift",
80
+ path: `narratives.${key}`,
81
+ detail: `extracted narratives.${key} drifted from the base preimage`,
82
+ });
83
+ }
84
+ }
85
+ const baseLines = splitDecisionLines(baseN.Decisions);
86
+ const liveLines = splitDecisionLines(liveN.Decisions);
87
+ if (liveLines.length < baseLines.length) {
88
+ findings.push({
89
+ kind: "decisions-mutation",
90
+ path: "narratives.Decisions",
91
+ detail: "Decisions lines were deleted; preimage lines are law",
92
+ });
93
+ return;
94
+ }
95
+ for (let i = 0; i < baseLines.length; i += 1) {
96
+ if (liveLines[i] !== baseLines[i]) {
97
+ findings.push({
98
+ kind: "decisions-mutation",
99
+ path: "narratives.Decisions",
100
+ detail: "existing Decisions lines were edited; only append is allowed",
101
+ });
102
+ return;
103
+ }
104
+ }
105
+ const changed = new Set((options.changedFiles ?? []).map((p) => p.replace(/\\/g, "/")));
106
+ for (const line of liveLines.slice(baseLines.length)) {
107
+ const m = DECISION_LINE.exec(line);
108
+ if (m === null) {
109
+ findings.push({
110
+ kind: "decisions-mutation",
111
+ path: "narratives.Decisions",
112
+ detail: `appended Decisions line is not '- <${DECISIONS_DIR_REL}>/…${DECISION_FILE_SUFFIX} ${EM_DASH} <summary>'`,
113
+ });
114
+ continue;
115
+ }
116
+ const rel = m[1] ?? "";
117
+ const onBaseOrChange = changed.has(rel) || Boolean(options.decisionExists?.(rel));
118
+ if (!onBaseOrChange) {
119
+ findings.push({
120
+ kind: "decision-pointer-missing",
121
+ path: "narratives.Decisions",
122
+ detail: `Decisions pointer ${rel} is not on base and not in the change set`,
123
+ });
124
+ }
125
+ }
126
+ }
127
+ function compareReferences(base, live, findings) {
128
+ const baseRefs = Array.isArray(base.plan.references)
129
+ ? base.plan.references
130
+ : [];
131
+ const liveRefs = Array.isArray(live.plan.references)
132
+ ? live.plan.references
133
+ : [];
134
+ const liveByKey = new Map();
135
+ for (const ref of liveRefs) {
136
+ liveByKey.set(refKey(ref), ref);
137
+ }
138
+ for (const ref of baseRefs) {
139
+ const key = refKey(ref);
140
+ const liveRef = liveByKey.get(key);
141
+ if (liveRef === undefined) {
142
+ findings.push({
143
+ kind: "reference-mutation",
144
+ path: "references",
145
+ detail: `origin reference removed: ${String(ref.uri ?? "")}`,
146
+ });
147
+ continue;
148
+ }
149
+ if (canon(ref) !== canon(liveRef)) {
150
+ findings.push({
151
+ kind: "reference-mutation",
152
+ path: "references",
153
+ detail: `origin reference edited: ${String(ref.uri ?? "")}`,
154
+ });
155
+ }
156
+ }
157
+ const baseKeys = new Set(baseRefs.map(refKey));
158
+ const approved = new Set(base.approvedRepos.map((s) => s.toLowerCase()));
159
+ for (const ref of liveRefs) {
160
+ if (baseKeys.has(refKey(ref)))
161
+ continue;
162
+ const type = String(ref.type ?? "");
163
+ if (!GITHUB_ISSUE_REF_TYPES.has(type)) {
164
+ findings.push({
165
+ kind: "ref-type-not-appendable",
166
+ path: "references",
167
+ detail: `appended reference type ${JSON.stringify(type)} is not in GITHUB_ISSUE_REF_TYPES`,
168
+ });
169
+ continue;
170
+ }
171
+ const uri = typeof ref.uri === "string" ? ref.uri : "";
172
+ const slug = slugFromGithubIssueUri(uri);
173
+ if (slug === null || !approved.has(slug)) {
174
+ findings.push({
175
+ kind: "repo-not-approved",
176
+ path: "references",
177
+ detail: `appended github-issue URL is not in the base preimage approvedRepos: ${uri}`,
178
+ });
179
+ }
180
+ }
181
+ }
182
+ function compareUnknownKeys(base, live, findings) {
183
+ const baseUnknown = new Set(base.unknownPaths);
184
+ const basePlan = asRecord(base.plan) ?? {};
185
+ for (const path of live.unknownPaths) {
186
+ if (baseUnknown.has(path))
187
+ continue;
188
+ const top = path.split(".")[0] ?? path;
189
+ if (top in basePlan && classifyPlanPath(top) === "unknown")
190
+ continue;
191
+ const parts = path.split(".");
192
+ let cursor = base.plan;
193
+ let present = true;
194
+ for (const part of parts) {
195
+ const rec = asRecord(cursor);
196
+ if (rec === null || !(part in rec)) {
197
+ present = false;
198
+ break;
199
+ }
200
+ cursor = rec[part];
201
+ }
202
+ if (present)
203
+ continue;
204
+ findings.push({
205
+ kind: "unclassified-key",
206
+ path,
207
+ detail: `unclassified key ${path} is absent from the base preimage`,
208
+ });
209
+ }
210
+ }
211
+ /**
212
+ * Compare a live extraction to the base-committed preimage.
213
+ * One entry point covers scalar equality and guarded append (R2 / R5).
214
+ */
215
+ export function compareExtractedIntent(base, live, options = {}) {
216
+ const findings = [];
217
+ const basePlan = asRecord(base.plan) ?? {};
218
+ const livePlan = asRecord(live.plan) ?? {};
219
+ if (canon(basePlan.parentId) !== canon(livePlan.parentId)) {
220
+ findings.push({
221
+ kind: "intent-drift",
222
+ path: "parentId",
223
+ detail: "parent identity changed (silent reparent)",
224
+ });
225
+ }
226
+ compareScalars(basePlan, livePlan, findings);
227
+ compareItems(basePlan.items, livePlan.items, findings);
228
+ compareNarratives(basePlan, livePlan, options, findings);
229
+ compareReferences(base, live, findings);
230
+ compareUnknownKeys(base, live, findings);
231
+ return { ok: findings.length === 0, findings };
232
+ }
233
+ //# sourceMappingURL=compare-intent.js.map
@@ -27,8 +27,15 @@ export interface ApprovedScopeRecord {
27
27
  readonly mintedAt: string;
28
28
  readonly mintedVia?: string;
29
29
  };
30
- /** Digest of the full xBRIEF canonical JSON body (optional stronger pin). */
30
+ /**
31
+ * Legacy optional body digest. Wave 1 (#3384 F4) does not write this on new
32
+ * records. Do not treat it as authority (Wave 2 #3385 / R6).
33
+ */
31
34
  readonly xbriefBodyDigest?: string;
35
+ /** Checksum of the base-committed `.intent.json` preimage (#3385). */
36
+ readonly intentDigest?: string;
37
+ /** Digest algorithm name; Wave 2 writes `intent-extract-v1`. */
38
+ readonly digestAlgo?: string;
32
39
  }
33
40
  export declare const APPROVED_SCOPE_DIR = ".deft/approved-scope";
34
41
  /** Normalize and sort scope paths for stable digests. */
@@ -42,14 +49,24 @@ export declare function extractFileScope(payload: unknown): string[];
42
49
  /** Extract plan.id from an xBRIEF payload. */
43
50
  export declare function extractPlanId(payload: unknown): string | null;
44
51
  export declare function approvedScopeDir(projectRoot: string): string;
52
+ export declare function approvedScopeSafePlanId(planId: string): string;
45
53
  export declare function approvedScopeRecordPath(projectRoot: string, planId: string): string;
54
+ /** Repo-relative preimage path. */
55
+ export declare function approvedScopeIntentRel(planId: string): string;
56
+ export declare function approvedScopeIntentPath(projectRoot: string, planId: string): string;
46
57
  /** Build an ApprovedScopeRecord from xBRIEF payload + metadata. */
47
58
  export declare function buildApprovedScopeRecord(input: {
48
59
  readonly xbriefRelPath: string;
49
60
  readonly payload: unknown;
50
61
  readonly approvedAt?: string;
51
62
  readonly humanApproval?: ApprovedScopeRecord["humanApproval"];
63
+ /**
64
+ * Ignored. Wave 1 (#3384 F4) no longer writes `xbriefBodyDigest` on new records.
65
+ * Kept so existing callers do not have to drop the field in the same change.
66
+ */
52
67
  readonly xbriefRawText?: string;
68
+ readonly intentDigest?: string;
69
+ readonly digestAlgo?: string;
53
70
  }): ApprovedScopeRecord;
54
71
  /** Persist approved-scope record (activation / renewed human approval). */
55
72
  export declare function writeApprovedScopeRecord(projectRoot: string, record: ApprovedScopeRecord): string;
@@ -69,9 +69,18 @@ export function extractPlanId(payload) {
69
69
  export function approvedScopeDir(projectRoot) {
70
70
  return join(resolve(projectRoot), ...APPROVED_SCOPE_DIR.split("/"));
71
71
  }
72
+ export function approvedScopeSafePlanId(planId) {
73
+ return planId.replace(/[^a-zA-Z0-9._-]/g, "_");
74
+ }
72
75
  export function approvedScopeRecordPath(projectRoot, planId) {
73
- const safe = planId.replace(/[^a-zA-Z0-9._-]/g, "_");
74
- return join(approvedScopeDir(projectRoot), `${safe}.json`);
76
+ return join(approvedScopeDir(projectRoot), `${approvedScopeSafePlanId(planId)}.json`);
77
+ }
78
+ /** Repo-relative preimage path. */
79
+ export function approvedScopeIntentRel(planId) {
80
+ return `.deft/approved-scope/${approvedScopeSafePlanId(planId)}.intent.json`;
81
+ }
82
+ export function approvedScopeIntentPath(projectRoot, planId) {
83
+ return join(approvedScopeDir(projectRoot), `${approvedScopeSafePlanId(planId)}.intent.json`);
75
84
  }
76
85
  /** Build an ApprovedScopeRecord from xBRIEF payload + metadata. */
77
86
  export function buildApprovedScopeRecord(input) {
@@ -87,17 +96,13 @@ export function buildApprovedScopeRecord(input) {
87
96
  fileScope,
88
97
  fileScopeDigest,
89
98
  };
99
+ const withIntent = input.intentDigest !== undefined
100
+ ? { ...record, intentDigest: input.intentDigest, digestAlgo: input.digestAlgo }
101
+ : record;
90
102
  if (input.humanApproval !== undefined) {
91
- return {
92
- ...record,
93
- humanApproval: input.humanApproval,
94
- xbriefBodyDigest: input.xbriefRawText !== undefined ? computeTextDigest(input.xbriefRawText) : undefined,
95
- };
96
- }
97
- if (input.xbriefRawText !== undefined) {
98
- return { ...record, xbriefBodyDigest: computeTextDigest(input.xbriefRawText) };
103
+ return { ...withIntent, humanApproval: input.humanApproval };
99
104
  }
100
- return record;
105
+ return withIntent;
101
106
  }
102
107
  /** Persist approved-scope record (activation / renewed human approval). */
103
108
  export function writeApprovedScopeRecord(projectRoot, record) {
@@ -9,7 +9,7 @@
9
9
  * Migration: missing approved-scope records → warn (exit 0) unless --enforce.
10
10
  */
11
11
  import { type ApprovedScopeRecord } from "./digest.js";
12
- export type ScopeProvenanceViolationKind = "self-authorizing-scope-expansion" | "active-xbrief-modified-without-digest" | "digest-mismatch-without-renewal";
12
+ export type ScopeProvenanceViolationKind = "self-authorizing-scope-expansion" | "active-xbrief-modified-without-digest" | "digest-mismatch-without-renewal" | "intent-drift" | "unclassified-key" | "intent-digest-mismatch" | "duplicate-key" | "duplicate-item-id" | "same-pr-intent-rewrite" | "first-activation-missing-intent-pin" | "legacy-intent-edit" | "intent-parse-error";
13
13
  export interface ScopeProvenanceFinding {
14
14
  readonly xbriefRelPath: string;
15
15
  readonly planId: string;
@@ -39,6 +39,10 @@ export interface ScopeProvenanceOptions {
39
39
  readonly approvedRecords?: readonly ApprovedScopeRecord[];
40
40
  /** Inject renewed-approval stamps keyed by planId (test seam). */
41
41
  readonly renewedApprovals?: ReadonlyMap<string, ApprovedScopeRecord["humanApproval"]>;
42
+ /** Inject `git show <base>:<rel>` (test seam; never working-tree). */
43
+ readonly readAtBase?: (relPath: string) => string | null;
44
+ /** Optional repo slug seed for live extract (mint uses resolveProjectRepo). */
45
+ readonly approvedReposSeed?: readonly string[];
42
46
  }
43
47
  /**
44
48
  * Resolve a PR-aware base ref. Bare `HEAD` only shows uncommitted changes, so
@@ -12,7 +12,9 @@ import { spawnSync } from "node:child_process";
12
12
  import { existsSync, readdirSync, readFileSync } from "node:fs";
13
13
  import { join, resolve } from "node:path";
14
14
  import { GitCommandError, GitNotFoundError } from "../encoding/git.js";
15
- import { computeFileScopeDigest, extractFileScope, extractPlanId, isHumanApprovalStamp, listApprovedScopeRecords, normalizeFileScope, readApprovedScopeRecord, scopeExpansion, } from "./digest.js";
15
+ import { approvedScopeIntentRel, computeFileScopeDigest, extractFileScope, extractPlanId, isHumanApprovalStamp, listApprovedScopeRecords, normalizeFileScope, readApprovedScopeRecord, scopeExpansion, } from "./digest.js";
16
+ import { bodyDigestIsAuthority, evaluateIntentForXbrief } from "./intent-evaluate.js";
17
+ import { recoverApprovedScopePairs } from "./mint-artifacts.js";
16
18
  function git(args, projectRoot) {
17
19
  const result = spawnSync("git", args, {
18
20
  cwd: projectRoot,
@@ -203,7 +205,7 @@ function listActiveXbriefPaths(projectRoot) {
203
205
  }
204
206
  function remediationForExpansion() {
205
207
  return ("Renew human approval: re-record the approved-scope digest after operator review " +
206
- "(`task scope:record-approved-scope -- <xbrief-path> --actor <you>` writes " +
208
+ "(`task scope:record-approved-scope -- <xbrief-path> --actor <you> --confirm` writes " +
207
209
  "`.deft/approved-scope/<plan-id>.json` with a humanApproval stamp). Commit that " +
208
210
  "approval on the merge base (or a prior PR) before expanding or activating the " +
209
211
  "scoped xBRIEF in the implementation change set. Editing the active xBRIEF alone " +
@@ -236,6 +238,8 @@ export function parseApprovedScopeRecordRaw(raw) {
236
238
  const expected = computeFileScopeDigest(scopePaths);
237
239
  if (rec.fileScopeDigest !== expected)
238
240
  return null;
241
+ // xbriefBodyDigest is never authority (#3385 F4 / R6) — ignored if present.
242
+ bodyDigestIsAuthority(data);
239
243
  return data;
240
244
  }
241
245
  catch {
@@ -340,7 +344,7 @@ export function evaluateOneScopeProvenance(input) {
340
344
  detail: "active xBRIEF modified with a non-human (agent/missing) approved-scope stamp; " +
341
345
  "only humanApproval stamps authorize non-empty file_scope",
342
346
  remediation: "Record a human-origin approval via `task scope:record-approved-scope -- " +
343
- "<xbrief-path> --actor <you>` (#3145 / #3205).",
347
+ "<xbrief-path> --actor <you> --confirm` (#3145 / #3205).",
344
348
  };
345
349
  }
346
350
  }
@@ -357,7 +361,7 @@ export function evaluateOneScopeProvenance(input) {
357
361
  detail: "active xBRIEF modified with a non-human approved-scope stamp (scope shrink/noise path); " +
358
362
  "only humanApproval stamps authorize non-empty file_scope",
359
363
  remediation: "Record a human-origin approval via `task scope:record-approved-scope -- " +
360
- "<xbrief-path> --actor <you>` (#3145 / #3205).",
364
+ "<xbrief-path> --actor <you> --confirm` (#3145 / #3205).",
361
365
  };
362
366
  }
363
367
  return null;
@@ -379,6 +383,7 @@ export function evaluateOneScopeProvenance(input) {
379
383
  export function evaluateScopeProvenance(projectRoot, options = {}) {
380
384
  const root = resolve(projectRoot);
381
385
  const enforce = options.enforce ?? false;
386
+ recoverApprovedScopePairs(root);
382
387
  let changed;
383
388
  /** Merge-base ref used for changed-file discovery (null when files injected). */
384
389
  let discoveryBaseRef = null;
@@ -523,6 +528,17 @@ export function evaluateScopeProvenance(projectRoot, options = {}) {
523
528
  return (n.includes("/approved-scope/") &&
524
529
  (n.endsWith(`/${safe}.json`) || n.endsWith(`${safe}.json`)));
525
530
  });
531
+ const preimageRel = planId !== null ? approvedScopeIntentRel(planId) : null;
532
+ const preimageInGitChange = preimageRel !== null &&
533
+ [...changedSet].some((p) => {
534
+ const n = normalizeRepoRelPath(p);
535
+ if (n === preimageRel || n.endsWith(`/${preimageRel}`))
536
+ return true;
537
+ if (planId === null)
538
+ return false;
539
+ const safe = planId.replace(/[^a-zA-Z0-9._-]/g, "_");
540
+ return n.includes("/approved-scope/") && n.endsWith(`/${safe}.intent.json`);
541
+ });
526
542
  // Disk-only / concurrent-rewrite inference (#3205):
527
543
  // Authority is the *approval record on the merge base*, not whether the
528
544
  // active xBRIEF path existed there. pending→active leaves the active path
@@ -555,7 +571,7 @@ export function evaluateScopeProvenance(projectRoot, options = {}) {
555
571
  }
556
572
  }
557
573
  }
558
- const approvalRecordRewritten = approvalInGitChange || approvalDiskOnly;
574
+ const approvalRecordRewritten = approvalInGitChange || approvalDiskOnly || preimageInGitChange;
559
575
  const recordMatchesCurrent = approved !== null &&
560
576
  !approvalRecordRewritten &&
561
577
  approved.fileScopeDigest ===
@@ -570,11 +586,11 @@ export function evaluateScopeProvenance(projectRoot, options = {}) {
570
586
  planId: planId ?? rel,
571
587
  kind: "self-authorizing-scope-expansion",
572
588
  expandedPaths: currentScope,
573
- detail: "approved-scope record rewritten in the same change set as the active xBRIEF; " +
589
+ detail: "approved-scope record or preimage rewritten in the same change set as the active xBRIEF; " +
574
590
  "cannot self-authorize via concurrent approval rewrite",
575
591
  remediation: "Commit human approval via `task scope:record-approved-scope` on the merge base " +
576
592
  "(or a prior PR), then activate/expand without rewriting the approval in this " +
577
- "change set. Same-PR approval rewrites do not authorize expansion (#3145 / #3205).",
593
+ "change set. Same-PR approval rewrites do not authorize expansion (#3145 / #3205 / #3385).",
578
594
  });
579
595
  continue;
580
596
  }
@@ -586,6 +602,50 @@ export function evaluateScopeProvenance(projectRoot, options = {}) {
586
602
  enforce,
587
603
  renewedHumanApproval: renewed ?? (recordMatchesCurrent ? approved?.humanApproval : null),
588
604
  });
605
+ const readAtBase = (baseRel) => {
606
+ if (options.readAtBase !== undefined)
607
+ return options.readAtBase(baseRel);
608
+ if (discoveryBaseRef === null || discoveryBaseRef === "")
609
+ return null;
610
+ try {
611
+ return readRepoFileAtRef(root, discoveryBaseRef, baseRel);
612
+ }
613
+ catch {
614
+ return null;
615
+ }
616
+ };
617
+ const intentHits = evaluateIntentForXbrief({
618
+ projectRoot: root,
619
+ xbriefRelPath: rel,
620
+ liveRaw: raw,
621
+ livePayload: payload,
622
+ planId: planId ?? rel,
623
+ approved,
624
+ xbriefModified: modified,
625
+ approvalRewritten: approvalRecordRewritten,
626
+ preimageRewritten: preimageInGitChange,
627
+ currentScopeNonEmpty: normalizeFileScope(extractFileScope(payload)).length > 0,
628
+ baseRef: discoveryBaseRef,
629
+ changedFiles: changed,
630
+ readAtBase,
631
+ approvedReposSeed: options.approvedReposSeed,
632
+ });
633
+ for (const hit of intentHits) {
634
+ const mapped = {
635
+ xbriefRelPath: hit.xbriefRelPath,
636
+ planId: hit.planId,
637
+ kind: hit.kind,
638
+ expandedPaths: [],
639
+ detail: hit.detail,
640
+ remediation: hit.remediation,
641
+ };
642
+ if (hit.warnOnly) {
643
+ softFindings.push(mapped);
644
+ }
645
+ else {
646
+ findings.push(mapped);
647
+ }
648
+ }
589
649
  if (finding === null)
590
650
  continue;
591
651
  // Soft migration: body-only edits (empty file_scope) without digest when !enforce.
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Extract intent preimage from a live xBRIEF (#3376 R1–R5 / #3385 F1–F2).
3
+ *
4
+ * Unknown plan.* keys are extracted (pinned), not rejected. Known-machine keys
5
+ * are omitted. plan.tags is machine; plan.edges is extracted.
6
+ */
7
+ import { GITHUB_ISSUE_REF_TYPES } from "../intake/reconcile-issues.js";
8
+ import { INTENT_DIGEST_ALGO } from "./intent-digest.js";
9
+ export declare const INTENT_PREIMAGE_SCHEMA: 1;
10
+ export interface IntentPreimage {
11
+ readonly schemaVersion: typeof INTENT_PREIMAGE_SCHEMA;
12
+ readonly algo: typeof INTENT_DIGEST_ALGO;
13
+ readonly plan: Record<string, unknown>;
14
+ readonly approvedRepos: readonly string[];
15
+ /** Paths classified unknown at extract (for verify unclassified-key). */
16
+ readonly unknownPaths: readonly string[];
17
+ }
18
+ export interface ExtractIntentOk {
19
+ readonly ok: true;
20
+ readonly preimage: IntentPreimage;
21
+ }
22
+ export interface ExtractIntentErr {
23
+ readonly ok: false;
24
+ readonly error: string;
25
+ }
26
+ export type ExtractIntentResult = ExtractIntentOk | ExtractIntentErr;
27
+ export interface ExtractIntentOptions {
28
+ readonly projectRoot?: string;
29
+ readonly approvedReposSeed?: readonly string[];
30
+ readonly readFile?: (absPath: string) => string | null;
31
+ }
32
+ export declare function slugFromGithubIssueUri(uri: string): string | null;
33
+ export declare function resolveParentPlanId(planRef: string, projectRoot: string, readFile?: (absPath: string) => string | null): {
34
+ id: string;
35
+ } | {
36
+ error: string;
37
+ };
38
+ /** Extract from a parsed payload (tokenizer already applied). */
39
+ export declare function extractIntentFromPayload(payload: unknown, options?: ExtractIntentOptions): ExtractIntentResult;
40
+ /** Parse raw text (duplicate-key reject) then extract. */
41
+ export declare function extractIntentFromRaw(raw: string, options?: ExtractIntentOptions): ExtractIntentResult;
42
+ export { GITHUB_ISSUE_REF_TYPES };
43
+ //# sourceMappingURL=extract-intent.d.ts.map