@deftai/directive-core 0.104.0 → 0.105.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/authz/classify.js +526 -42
  2. package/dist/check/cached-orchestrator.d.ts +1 -1
  3. package/dist/check/cached-orchestrator.js +2 -2
  4. package/dist/check/named-cause.d.ts +1 -1
  5. package/dist/check/named-cause.js +33 -3
  6. package/dist/codebase/default-extractor.d.ts +5 -0
  7. package/dist/codebase/default-extractor.js +7 -15
  8. package/dist/deposit/copy-tree.js +60 -0
  9. package/dist/deposit/python-free.js +26 -18
  10. package/dist/doctor/checks.d.ts +8 -0
  11. package/dist/doctor/checks.js +37 -25
  12. package/dist/doctor/doctor-state.d.ts +4 -0
  13. package/dist/doctor/doctor-state.js +7 -1
  14. package/dist/doctor/main.js +9 -19
  15. package/dist/doctor/types.d.ts +9 -0
  16. package/dist/fs/contained-write.d.ts +115 -1
  17. package/dist/fs/contained-write.js +241 -5
  18. package/dist/fs/mutation-ledger.d.ts +54 -0
  19. package/dist/fs/mutation-ledger.js +133 -0
  20. package/dist/fs/non-product-dirs.d.ts +79 -0
  21. package/dist/fs/non-product-dirs.js +96 -0
  22. package/dist/hooks/dispatcher.d.ts +3 -1
  23. package/dist/hooks/dispatcher.js +14 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +2 -0
  26. package/dist/init-deposit/agent-hooks.js +17 -15
  27. package/dist/init-deposit/core-guard-pin-content.d.ts +7 -0
  28. package/dist/init-deposit/core-guard-pin-content.js +393 -0
  29. package/dist/init-deposit/gitignore.js +9 -0
  30. package/dist/init-deposit/hygiene.d.ts +47 -11
  31. package/dist/init-deposit/hygiene.js +172 -37
  32. package/dist/init-deposit/legacy-detect.js +6 -2
  33. package/dist/init-deposit/refresh.d.ts +21 -1
  34. package/dist/init-deposit/refresh.js +106 -9
  35. package/dist/init-deposit/scaffold.js +28 -279
  36. package/dist/init-deposit/slash-deposit.js +13 -7
  37. package/dist/init-deposit/xbrief-projections.d.ts +8 -1
  38. package/dist/init-deposit/xbrief-projections.js +24 -11
  39. package/dist/intake/clause-derivation.d.ts +38 -1
  40. package/dist/intake/clause-derivation.js +211 -9
  41. package/dist/intake/issue-ingest.d.ts +3 -1
  42. package/dist/intake/issue-ingest.js +35 -6
  43. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +5 -0
  44. package/dist/lifecycle/completed-tracked-on-delivery.js +85 -18
  45. package/dist/literal-acceptance/capture.d.ts +10 -0
  46. package/dist/literal-acceptance/capture.js +224 -75
  47. package/dist/literal-acceptance/evaluate.d.ts +29 -4
  48. package/dist/literal-acceptance/evaluate.js +70 -2
  49. package/dist/literal-acceptance/index.d.ts +3 -3
  50. package/dist/literal-acceptance/index.js +3 -3
  51. package/dist/literal-acceptance/safety.d.ts +41 -0
  52. package/dist/literal-acceptance/safety.js +126 -2
  53. package/dist/literal-acceptance/types.d.ts +8 -0
  54. package/dist/orphan-active/evaluate.d.ts +7 -1
  55. package/dist/orphan-active/evaluate.js +102 -23
  56. package/dist/platform/platform-capabilities.js +2 -3
  57. package/dist/policy/base-branch.d.ts +37 -0
  58. package/dist/policy/base-branch.js +114 -0
  59. package/dist/policy/branch-sync.d.ts +80 -0
  60. package/dist/policy/branch-sync.js +211 -0
  61. package/dist/policy/ceremony-dial-escalation.js +1 -0
  62. package/dist/policy/ceremony-dial.js +4 -14
  63. package/dist/policy/delivery-branch.d.ts +5 -3
  64. package/dist/policy/delivery-branch.js +7 -3
  65. package/dist/policy/file-size-thresholds.d.ts +24 -0
  66. package/dist/policy/file-size-thresholds.js +23 -0
  67. package/dist/policy/forge-outage-retry.d.ts +47 -0
  68. package/dist/policy/forge-outage-retry.js +157 -0
  69. package/dist/policy/index.d.ts +7 -0
  70. package/dist/policy/index.js +66 -7
  71. package/dist/policy/project-invariants.d.ts +50 -0
  72. package/dist/policy/project-invariants.js +205 -0
  73. package/dist/policy/sync-default.d.ts +129 -0
  74. package/dist/policy/sync-default.js +452 -0
  75. package/dist/policy/sync-max-files.d.ts +93 -0
  76. package/dist/policy/sync-max-files.js +192 -0
  77. package/dist/pr-monitor/absent-required.d.ts +11 -0
  78. package/dist/pr-monitor/absent-required.js +61 -0
  79. package/dist/pr-monitor/constants.d.ts +7 -0
  80. package/dist/pr-monitor/constants.js +7 -0
  81. package/dist/pr-monitor/index.d.ts +2 -1
  82. package/dist/pr-monitor/index.js +2 -1
  83. package/dist/pr-monitor/main.js +4 -0
  84. package/dist/pr-monitor/monitor.js +24 -1
  85. package/dist/pr-wait-mergeable/classify.js +6 -0
  86. package/dist/preflight/evaluate.d.ts +4 -0
  87. package/dist/preflight/evaluate.js +35 -1
  88. package/dist/preflight/index.d.ts +4 -0
  89. package/dist/preflight/index.js +2 -0
  90. package/dist/preflight/intended-placement.d.ts +62 -0
  91. package/dist/preflight/intended-placement.js +275 -0
  92. package/dist/preflight/project-invariants-gate.d.ts +29 -0
  93. package/dist/preflight/project-invariants-gate.js +82 -0
  94. package/dist/product-first-done-gate/acceptance-resolver.d.ts +128 -0
  95. package/dist/product-first-done-gate/acceptance-resolver.js +144 -0
  96. package/dist/product-first-done-gate/acceptance.d.ts +7 -4
  97. package/dist/product-first-done-gate/acceptance.js +79 -8
  98. package/dist/product-first-done-gate/empty-resolution.d.ts +4 -1
  99. package/dist/product-first-done-gate/empty-resolution.js +12 -1
  100. package/dist/product-first-done-gate/evaluate.d.ts +12 -0
  101. package/dist/product-first-done-gate/evaluate.js +254 -31
  102. package/dist/product-first-done-gate/index.d.ts +2 -0
  103. package/dist/product-first-done-gate/index.js +1 -0
  104. package/dist/release/build-dist.d.ts +101 -0
  105. package/dist/release/build-dist.js +428 -46
  106. package/dist/run-summary/emit.d.ts +16 -10
  107. package/dist/run-summary/emit.js +26 -15
  108. package/dist/run-summary/index.d.ts +3 -2
  109. package/dist/run-summary/index.js +2 -1
  110. package/dist/run-summary/session-id.d.ts +21 -0
  111. package/dist/run-summary/session-id.js +92 -0
  112. package/dist/run-summary/types.d.ts +29 -4
  113. package/dist/run-summary/types.js +1 -0
  114. package/dist/scope/acceptance-evidence.d.ts +19 -7
  115. package/dist/scope/acceptance-evidence.js +38 -11
  116. package/dist/scope/delivery-evidence.d.ts +3 -3
  117. package/dist/scope/delivery-evidence.js +22 -27
  118. package/dist/scope/index.d.ts +1 -0
  119. package/dist/scope/index.js +1 -0
  120. package/dist/scope/parent-lineage.js +7 -6
  121. package/dist/scope/project-invariant-coverage.d.ts +54 -0
  122. package/dist/scope/project-invariant-coverage.js +322 -0
  123. package/dist/scope/transition.js +1 -1
  124. package/dist/scope-provenance/compare-intent.d.ts +27 -0
  125. package/dist/scope-provenance/compare-intent.js +233 -0
  126. package/dist/scope-provenance/digest.d.ts +18 -1
  127. package/dist/scope-provenance/digest.js +16 -11
  128. package/dist/scope-provenance/evaluate.d.ts +5 -1
  129. package/dist/scope-provenance/evaluate.js +64 -4
  130. package/dist/scope-provenance/extract-intent.d.ts +43 -0
  131. package/dist/scope-provenance/extract-intent.js +262 -0
  132. package/dist/scope-provenance/index.d.ts +7 -1
  133. package/dist/scope-provenance/index.js +7 -1
  134. package/dist/scope-provenance/intent-digest.d.ts +12 -0
  135. package/dist/scope-provenance/intent-digest.js +33 -0
  136. package/dist/scope-provenance/intent-evaluate.d.ts +43 -0
  137. package/dist/scope-provenance/intent-evaluate.js +149 -0
  138. package/dist/scope-provenance/json-tokenizer.d.ts +19 -0
  139. package/dist/scope-provenance/json-tokenizer.js +225 -0
  140. package/dist/scope-provenance/known-machine.d.ts +38 -0
  141. package/dist/scope-provenance/known-machine.js +157 -0
  142. package/dist/scope-provenance/mint-artifacts.d.ts +102 -0
  143. package/dist/scope-provenance/mint-artifacts.js +425 -0
  144. package/dist/session/ac-pass-banking.d.ts +4 -36
  145. package/dist/session/ac-pass-banking.js +30 -77
  146. package/dist/session/ac-pass-reuse.d.ts +53 -0
  147. package/dist/session/ac-pass-reuse.js +95 -0
  148. package/dist/session/git.js +19 -4
  149. package/dist/session/index.d.ts +4 -0
  150. package/dist/session/index.js +4 -0
  151. package/dist/session/occupancy-stress-worker.d.ts +7 -0
  152. package/dist/session/occupancy-stress-worker.js +36 -0
  153. package/dist/session/occupancy.d.ts +97 -0
  154. package/dist/session/occupancy.js +422 -0
  155. package/dist/session/openclaw-soft-rebind-deposit.js +6 -6
  156. package/dist/session/product-state-hash.d.ts +26 -0
  157. package/dist/session/product-state-hash.js +683 -0
  158. package/dist/session/session-ready.d.ts +2 -0
  159. package/dist/session/session-ready.js +52 -30
  160. package/dist/session/session-start.d.ts +7 -0
  161. package/dist/session/session-start.js +71 -7
  162. package/dist/session/verify-ac-session-cache.d.ts +42 -0
  163. package/dist/session/verify-ac-session-cache.js +126 -0
  164. package/dist/session/verify-session-ritual.d.ts +5 -0
  165. package/dist/session/verify-session-ritual.js +9 -0
  166. package/dist/slash/openclaw-deposit.js +13 -9
  167. package/dist/slice/constants.d.ts +2 -2
  168. package/dist/slice/constants.js +2 -2
  169. package/dist/slice/existing.js +9 -3
  170. package/dist/slice/lock.d.ts +16 -1
  171. package/dist/slice/lock.js +137 -11
  172. package/dist/story-ready/evaluate.d.ts +2 -0
  173. package/dist/story-ready/evaluate.js +13 -0
  174. package/dist/swarm/complete-cohort.d.ts +3 -0
  175. package/dist/swarm/complete-cohort.js +59 -11
  176. package/dist/swarm/finalize-cohort.js +33 -2
  177. package/dist/swarm/launch.d.ts +22 -0
  178. package/dist/swarm/launch.js +155 -0
  179. package/dist/swarm/routing-set-cli.d.ts +11 -1
  180. package/dist/swarm/routing-set-cli.js +13 -3
  181. package/dist/swarm/routing-verify.d.ts +12 -0
  182. package/dist/swarm/routing-verify.js +59 -20
  183. package/dist/swarm/routing.d.ts +14 -0
  184. package/dist/swarm/routing.js +53 -3
  185. package/dist/telemetry-coverage/fake-trial.js +10 -0
  186. package/dist/telemetry-coverage/kinds.js +2 -0
  187. package/dist/triage/actions/index.js +4 -1
  188. package/dist/ts-check-lane/index.d.ts +3 -0
  189. package/dist/ts-check-lane/index.js +2 -0
  190. package/dist/ts-check-lane/progress-reporter.d.ts +17 -0
  191. package/dist/ts-check-lane/progress-reporter.js +38 -0
  192. package/dist/ts-check-lane/progress.d.ts +32 -0
  193. package/dist/ts-check-lane/progress.js +57 -0
  194. package/dist/ts-check-lane/run-lane.d.ts +3 -1
  195. package/dist/ts-check-lane/run-lane.js +8 -6
  196. package/dist/validate-content/validate-links.d.ts +8 -0
  197. package/dist/validate-content/validate-links.js +9 -15
  198. package/dist/value/feedback-file.js +12 -2
  199. package/dist/vbrief-build/index.d.ts +1 -0
  200. package/dist/vbrief-build/index.js +1 -0
  201. package/dist/vbrief-build/project-invariants-io.d.ts +16 -0
  202. package/dist/vbrief-build/project-invariants-io.js +41 -0
  203. package/dist/vbrief-reconcile/graph.js +18 -1
  204. package/dist/vbrief-reconcile/index.d.ts +1 -1
  205. package/dist/vbrief-reconcile/index.js +1 -1
  206. package/dist/vbrief-reconcile/types.d.ts +13 -0
  207. package/dist/vbrief-reconcile/umbrellas.d.ts +14 -0
  208. package/dist/vbrief-reconcile/umbrellas.js +261 -26
  209. package/dist/vbrief-validate/index.d.ts +1 -1
  210. package/dist/vbrief-validate/index.js +1 -1
  211. package/dist/vbrief-validate/plan-hooks.d.ts +4 -0
  212. package/dist/vbrief-validate/plan-hooks.js +68 -0
  213. package/dist/verify-ac/evaluate.d.ts +7 -1
  214. package/dist/verify-ac/evaluate.js +34 -26
  215. package/dist/verify-ac/flag.d.ts +4 -0
  216. package/dist/verify-ac/flag.js +16 -0
  217. package/dist/verify-ac/index.d.ts +2 -2
  218. package/dist/verify-ac/index.js +2 -2
  219. package/dist/verify-source/contained-writes.js +2 -0
  220. package/dist/verify-source/verify-stubs.d.ts +15 -1
  221. package/dist/verify-source/verify-stubs.js +20 -16
  222. package/dist/vitest-runner/win32-coverage-tmp-setup.d.ts +5 -6
  223. package/dist/vitest-runner/win32-coverage-tmp-setup.js +5 -6
  224. package/dist/xbrief-migrate/migrate-project.js +2 -5
  225. package/package.json +3 -3
@@ -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,
@@ -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 {
@@ -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
@@ -0,0 +1,262 @@
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 { existsSync, readFileSync } from "node:fs";
8
+ import { basename, join } from "node:path";
9
+ import { GITHUB_ISSUE_REF_TYPES } from "../intake/reconcile-issues.js";
10
+ import { stripArtifactSuffix } from "../layout/resolve.js";
11
+ import { resolveParentPathFromRef } from "../scope/parent-lineage.js";
12
+ import { extractPlanId } from "./digest.js";
13
+ import { INTENT_DIGEST_ALGO } from "./intent-digest.js";
14
+ import { parseJsonRejectingDuplicateKeys } from "./json-tokenizer.js";
15
+ import { classifyItemKey, classifyPlanPath, classifyReferenceKey, ITEM_EXTRACT_KEYS, } from "./known-machine.js";
16
+ export const INTENT_PREIMAGE_SCHEMA = 1;
17
+ const ISSUE_URL_RE = /https?:\/\/(?:www\.)?github\.com\/([^/\s]+)\/([^/\s]+)\/issues\/(\d+)/i;
18
+ export function slugFromGithubIssueUri(uri) {
19
+ const m = ISSUE_URL_RE.exec(uri);
20
+ if (m === null)
21
+ return null;
22
+ return `${m[1]}/${m[2]}`.toLowerCase();
23
+ }
24
+ function asRecord(value) {
25
+ if (value === null || typeof value !== "object" || Array.isArray(value))
26
+ return null;
27
+ return value;
28
+ }
29
+ function itemId(item) {
30
+ return typeof item.id === "string" ? item.id : "";
31
+ }
32
+ function readText(abs, reader) {
33
+ if (reader !== undefined)
34
+ return reader(abs);
35
+ if (!existsSync(abs))
36
+ return null;
37
+ try {
38
+ return readFileSync(abs, "utf8");
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ export function resolveParentPlanId(planRef, projectRoot, readFile) {
45
+ const roots = [join(projectRoot, "xbrief"), join(projectRoot, "vbrief")];
46
+ for (const lifecycleRoot of roots) {
47
+ const resolved = resolveParentPathFromRef(planRef, lifecycleRoot);
48
+ if (resolved.path === null)
49
+ continue;
50
+ const raw = readText(resolved.path, readFile);
51
+ if (raw === null)
52
+ continue;
53
+ let payload;
54
+ try {
55
+ payload = JSON.parse(raw);
56
+ }
57
+ catch {
58
+ return { error: `unresolvable parent at mint: ${planRef} is not valid JSON` };
59
+ }
60
+ const id = extractPlanId(payload) ?? stripArtifactSuffix(basename(resolved.path));
61
+ if (id.trim().length === 0) {
62
+ return { error: `unresolvable parent at mint: ${planRef} has no plan.id` };
63
+ }
64
+ return { id: id.trim() };
65
+ }
66
+ return { error: `unresolvable parent at mint: ${planRef}` };
67
+ }
68
+ function extractReferences(refs) {
69
+ if (!Array.isArray(refs))
70
+ return { refs: [] };
71
+ const out = [];
72
+ for (const ref of refs) {
73
+ const rec = asRecord(ref);
74
+ if (rec === null)
75
+ continue;
76
+ const extracted = {};
77
+ for (const [key, value] of Object.entries(rec)) {
78
+ const cls = classifyReferenceKey(key);
79
+ if (cls === "machine")
80
+ continue;
81
+ extracted[key] = value;
82
+ }
83
+ out.push(extracted);
84
+ }
85
+ return { refs: out };
86
+ }
87
+ function extractItems(items, projectRoot, readFile, unknownPaths) {
88
+ if (!Array.isArray(items))
89
+ return { items: [] };
90
+ const out = [];
91
+ const ids = new Set();
92
+ for (const item of items) {
93
+ const rec = asRecord(item);
94
+ if (rec === null)
95
+ continue;
96
+ const extracted = {};
97
+ for (const key of ITEM_EXTRACT_KEYS) {
98
+ if (rec[key] !== undefined)
99
+ extracted[key] = rec[key];
100
+ }
101
+ for (const [key, value] of Object.entries(rec)) {
102
+ if (ITEM_EXTRACT_KEYS.includes(key))
103
+ continue;
104
+ const cls = classifyItemKey(key);
105
+ if (cls === "machine")
106
+ continue;
107
+ extracted[key] = value;
108
+ unknownPaths.push(`items[].${key}`);
109
+ }
110
+ if (typeof rec.planRef === "string" && rec.planRef.trim().length > 0) {
111
+ if (projectRoot === undefined) {
112
+ return { items: [], error: `unresolvable parent at mint: ${rec.planRef}` };
113
+ }
114
+ const parent = resolveParentPlanId(rec.planRef, projectRoot, readFile);
115
+ if ("error" in parent)
116
+ return { items: [], error: parent.error };
117
+ extracted.parentId = parent.id;
118
+ }
119
+ const id = itemId(extracted);
120
+ if (id.length > 0) {
121
+ if (ids.has(id)) {
122
+ return { items: [], error: `duplicate items[].id ${JSON.stringify(id)}` };
123
+ }
124
+ ids.add(id);
125
+ }
126
+ out.push(extracted);
127
+ }
128
+ out.sort((a, b) => itemId(a).localeCompare(itemId(b)));
129
+ return { items: out };
130
+ }
131
+ function copyUnknownSubtree(value) {
132
+ return value;
133
+ }
134
+ function extractMetadata(metadata, unknownPaths) {
135
+ const out = {};
136
+ for (const [key, value] of Object.entries(metadata)) {
137
+ const path = `metadata.${key}`;
138
+ const cls = classifyPlanPath(path);
139
+ if (cls === "machine")
140
+ continue;
141
+ if (key === "swarm") {
142
+ const swarm = asRecord(value);
143
+ if (swarm === null)
144
+ continue;
145
+ const swarmOut = {};
146
+ for (const [sKey, sVal] of Object.entries(swarm)) {
147
+ const sPath = `metadata.swarm.${sKey}`;
148
+ const sCls = classifyPlanPath(sPath);
149
+ if (sCls === "machine")
150
+ continue;
151
+ swarmOut[sKey] = sVal;
152
+ if (sCls === "unknown")
153
+ unknownPaths.push(sPath);
154
+ }
155
+ if (Object.keys(swarmOut).length > 0)
156
+ out.swarm = swarmOut;
157
+ continue;
158
+ }
159
+ if (cls === "unknown") {
160
+ out[key] = copyUnknownSubtree(value);
161
+ unknownPaths.push(path);
162
+ continue;
163
+ }
164
+ out[key] = value;
165
+ }
166
+ return out;
167
+ }
168
+ function seedApprovedRepos(seed, refs) {
169
+ const out = new Set();
170
+ for (const s of seed ?? []) {
171
+ const t = s.trim().toLowerCase();
172
+ if (t.includes("/"))
173
+ out.add(t);
174
+ }
175
+ for (const ref of refs) {
176
+ if (typeof ref.uri !== "string")
177
+ continue;
178
+ const slug = slugFromGithubIssueUri(ref.uri);
179
+ if (slug !== null)
180
+ out.add(slug);
181
+ }
182
+ return [...out].sort((a, b) => a.localeCompare(b));
183
+ }
184
+ /** Extract from a parsed payload (tokenizer already applied). */
185
+ export function extractIntentFromPayload(payload, options = {}) {
186
+ const root = asRecord(payload);
187
+ if (root === null)
188
+ return { ok: false, error: "xBRIEF is not an object" };
189
+ const plan = asRecord(root.plan);
190
+ if (plan === null)
191
+ return { ok: false, error: "xBRIEF plan is missing" };
192
+ const unknownPaths = [];
193
+ const extracted = {};
194
+ if (typeof plan.planRef === "string" && plan.planRef.trim().length > 0) {
195
+ if (options.projectRoot === undefined) {
196
+ return { ok: false, error: `unresolvable parent at mint: ${plan.planRef}` };
197
+ }
198
+ const parent = resolveParentPlanId(plan.planRef, options.projectRoot, options.readFile);
199
+ if ("error" in parent)
200
+ return { ok: false, error: parent.error };
201
+ extracted.parentId = parent.id;
202
+ }
203
+ for (const [key, value] of Object.entries(plan)) {
204
+ const cls = classifyPlanPath(key);
205
+ if (cls === "machine")
206
+ continue;
207
+ if (key === "items") {
208
+ const items = extractItems(value, options.projectRoot, options.readFile, unknownPaths);
209
+ if (items.error !== undefined)
210
+ return { ok: false, error: items.error };
211
+ extracted.items = items.items;
212
+ continue;
213
+ }
214
+ if (key === "references") {
215
+ const refs = extractReferences(value);
216
+ if (refs.error !== undefined)
217
+ return { ok: false, error: refs.error };
218
+ extracted.references = refs.refs;
219
+ continue;
220
+ }
221
+ if (key === "metadata") {
222
+ const meta = asRecord(value);
223
+ if (meta === null)
224
+ continue;
225
+ const extractedMeta = extractMetadata(meta, unknownPaths);
226
+ if (Object.keys(extractedMeta).length > 0)
227
+ extracted.metadata = extractedMeta;
228
+ continue;
229
+ }
230
+ if (cls === "unknown") {
231
+ extracted[key] = copyUnknownSubtree(value);
232
+ unknownPaths.push(key);
233
+ continue;
234
+ }
235
+ extracted[key] = value;
236
+ }
237
+ const refs = Array.isArray(extracted.references)
238
+ ? extracted.references
239
+ : [];
240
+ const approvedRepos = seedApprovedRepos(options.approvedReposSeed, refs);
241
+ const uniqueUnknown = [...new Set(unknownPaths)].sort((a, b) => a.localeCompare(b));
242
+ return {
243
+ ok: true,
244
+ preimage: {
245
+ schemaVersion: INTENT_PREIMAGE_SCHEMA,
246
+ algo: INTENT_DIGEST_ALGO,
247
+ plan: extracted,
248
+ approvedRepos,
249
+ unknownPaths: uniqueUnknown,
250
+ },
251
+ };
252
+ }
253
+ /** Parse raw text (duplicate-key reject) then extract. */
254
+ export function extractIntentFromRaw(raw, options = {}) {
255
+ const parsed = parseJsonRejectingDuplicateKeys(raw);
256
+ if (!parsed.ok) {
257
+ return { ok: false, error: parsed.error };
258
+ }
259
+ return extractIntentFromPayload(parsed.value, options);
260
+ }
261
+ export { GITHUB_ISSUE_REF_TYPES };
262
+ //# sourceMappingURL=extract-intent.js.map
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * scope-provenance package surface (#3145).
3
3
  */
4
- export { APPROVED_SCOPE_DIR, type ApprovedScopeRecord, approvedScopeDir, approvedScopeRecordPath, buildApprovedScopeRecord, computeFileScopeDigest, computeTextDigest, extractFileScope, extractPlanId, isHumanApprovalStamp, listApprovedScopeRecords, normalizeFileScope, readApprovedScopeRecord, scopeExpansion, writeApprovedScopeRecord, } from "./digest.js";
4
+ export { compareExtractedIntent } from "./compare-intent.js";
5
+ export { APPROVED_SCOPE_DIR, type ApprovedScopeRecord, approvedScopeDir, approvedScopeIntentPath, approvedScopeIntentRel, approvedScopeRecordPath, buildApprovedScopeRecord, computeFileScopeDigest, computeTextDigest, extractFileScope, extractPlanId, isHumanApprovalStamp, listApprovedScopeRecords, normalizeFileScope, readApprovedScopeRecord, scopeExpansion, writeApprovedScopeRecord, } from "./digest.js";
5
6
  export { baseApprovalAuthorizesCurrent, evaluateOneScopeProvenance, evaluateScopeProvenance, parseApprovedScopeRecordRaw, resolveDefaultBaseRef, type ScopeProvenanceFinding, type ScopeProvenanceOptions, type ScopeProvenanceResult, type ScopeProvenanceViolationKind, } from "./evaluate.js";
7
+ export { extractIntentFromPayload, extractIntentFromRaw, GITHUB_ISSUE_REF_TYPES, type IntentPreimage, } from "./extract-intent.js";
8
+ export { computeIntentDigest, INTENT_DIGEST_ALGO } from "./intent-digest.js";
9
+ export { parseJsonRejectingDuplicateKeys } from "./json-tokenizer.js";
10
+ export { allKnownMachineLeaves, KNOWN_MACHINE_WRITERS } from "./known-machine.js";
11
+ export { mintApprovedScopeArtifacts, recoverApprovedScopePairs, recoverIncompleteApprovedScopePair, } from "./mint-artifacts.js";
6
12
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,12 @@
1
1
  /**
2
2
  * scope-provenance package surface (#3145).
3
3
  */
4
- export { APPROVED_SCOPE_DIR, approvedScopeDir, approvedScopeRecordPath, buildApprovedScopeRecord, computeFileScopeDigest, computeTextDigest, extractFileScope, extractPlanId, isHumanApprovalStamp, listApprovedScopeRecords, normalizeFileScope, readApprovedScopeRecord, scopeExpansion, writeApprovedScopeRecord, } from "./digest.js";
4
+ export { compareExtractedIntent } from "./compare-intent.js";
5
+ export { APPROVED_SCOPE_DIR, approvedScopeDir, approvedScopeIntentPath, approvedScopeIntentRel, approvedScopeRecordPath, buildApprovedScopeRecord, computeFileScopeDigest, computeTextDigest, extractFileScope, extractPlanId, isHumanApprovalStamp, listApprovedScopeRecords, normalizeFileScope, readApprovedScopeRecord, scopeExpansion, writeApprovedScopeRecord, } from "./digest.js";
5
6
  export { baseApprovalAuthorizesCurrent, evaluateOneScopeProvenance, evaluateScopeProvenance, parseApprovedScopeRecordRaw, resolveDefaultBaseRef, } from "./evaluate.js";
7
+ export { extractIntentFromPayload, extractIntentFromRaw, GITHUB_ISSUE_REF_TYPES, } from "./extract-intent.js";
8
+ export { computeIntentDigest, INTENT_DIGEST_ALGO } from "./intent-digest.js";
9
+ export { parseJsonRejectingDuplicateKeys } from "./json-tokenizer.js";
10
+ export { allKnownMachineLeaves, KNOWN_MACHINE_WRITERS } from "./known-machine.js";
11
+ export { mintApprovedScopeArtifacts, recoverApprovedScopePairs, recoverIncompleteApprovedScopePair, } from "./mint-artifacts.js";
6
12
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,12 @@
1
+ /**
2
+ * intent-extract-v1 digest (#3376 R2 / #3385).
3
+ *
4
+ * sortKeysDeep, items[] by id (done at extract), NFC, compact stringify, sha256.
5
+ */
6
+ export declare const INTENT_DIGEST_ALGO: "intent-extract-v1";
7
+ export declare function nfcString(value: string): string;
8
+ /** Deep-sort object keys and NFC-normalize strings. Arrays keep order. */
9
+ export declare function sortKeysDeep(value: unknown): unknown;
10
+ /** sha256 hex of the canonical preimage. */
11
+ export declare function computeIntentDigest(preimage: unknown): string;
12
+ //# sourceMappingURL=intent-digest.d.ts.map
@@ -0,0 +1,33 @@
1
+ /**
2
+ * intent-extract-v1 digest (#3376 R2 / #3385).
3
+ *
4
+ * sortKeysDeep, items[] by id (done at extract), NFC, compact stringify, sha256.
5
+ */
6
+ import { createHash } from "node:crypto";
7
+ export const INTENT_DIGEST_ALGO = "intent-extract-v1";
8
+ export function nfcString(value) {
9
+ return value.normalize("NFC");
10
+ }
11
+ /** Deep-sort object keys and NFC-normalize strings. Arrays keep order. */
12
+ export function sortKeysDeep(value) {
13
+ if (typeof value === "string")
14
+ return nfcString(value);
15
+ if (Array.isArray(value))
16
+ return value.map(sortKeysDeep);
17
+ if (value !== null && typeof value === "object") {
18
+ const rec = value;
19
+ const out = {};
20
+ for (const key of Object.keys(rec).sort((a, b) => a.localeCompare(b))) {
21
+ out[key] = sortKeysDeep(rec[key]);
22
+ }
23
+ return out;
24
+ }
25
+ return value;
26
+ }
27
+ /** sha256 hex of the canonical preimage. */
28
+ export function computeIntentDigest(preimage) {
29
+ const canonical = sortKeysDeep(preimage);
30
+ const payload = JSON.stringify(canonical);
31
+ return createHash("sha256").update(payload, "utf8").digest("hex");
32
+ }
33
+ //# sourceMappingURL=intent-digest.js.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Verify-time intent pin evaluation (#3376 R2–R6 / #3385).
3
+ *
4
+ * Reads base-committed record + preimage (never working-tree copies).
5
+ * Legacy records (no intentDigest) authorize paths only; intent edits warn.
6
+ * xbriefBodyDigest is never authority.
7
+ */
8
+ import { type ApprovedScopeRecord } from "./digest.js";
9
+ import { type IntentPreimage } from "./extract-intent.js";
10
+ export type IntentViolationKind = "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";
11
+ export interface IntentFinding {
12
+ readonly kind: IntentViolationKind;
13
+ readonly planId: string;
14
+ readonly xbriefRelPath: string;
15
+ readonly detail: string;
16
+ readonly remediation: string;
17
+ readonly warnOnly: boolean;
18
+ }
19
+ export interface IntentEvaluateInput {
20
+ readonly projectRoot: string;
21
+ readonly xbriefRelPath: string;
22
+ readonly liveRaw: string;
23
+ readonly livePayload: unknown;
24
+ readonly planId: string;
25
+ readonly approved: ApprovedScopeRecord | null;
26
+ readonly xbriefModified: boolean;
27
+ readonly approvalRewritten: boolean;
28
+ readonly preimageRewritten: boolean;
29
+ readonly currentScopeNonEmpty: boolean;
30
+ readonly baseRef: string | null;
31
+ readonly changedFiles: readonly string[];
32
+ readonly readAtBase?: (relPath: string) => string | null;
33
+ readonly approvedReposSeed?: readonly string[];
34
+ }
35
+ export declare function parseIntentPreimageRaw(raw: string): IntentPreimage | null;
36
+ /**
37
+ * Evaluate intent pin for one modified active xBRIEF.
38
+ * Returns [] when intent is not in play (unmodified, or nothing to check).
39
+ */
40
+ export declare function evaluateIntentForXbrief(input: IntentEvaluateInput): IntentFinding[];
41
+ /** Explicit: never treat xbriefBodyDigest as authority. */
42
+ export declare function bodyDigestIsAuthority(_rec: ApprovedScopeRecord | null): false;
43
+ //# sourceMappingURL=intent-evaluate.d.ts.map