@deftai/directive-core 0.106.0 → 0.108.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 (249) hide show
  1. package/dist/architecture/sor-preflight.js +2 -0
  2. package/dist/check/cached-orchestrator.js +28 -3
  3. package/dist/check/gate-lists.js +2 -0
  4. package/dist/check/named-cause.js +18 -3
  5. package/dist/codebase/provider.js +7 -1
  6. package/dist/delivery-attempt/handoff.js +1 -1
  7. package/dist/design-critique/citation-grammar.d.ts +53 -0
  8. package/dist/design-critique/citation-grammar.js +303 -0
  9. package/dist/design-critique/completed-arc-record.d.ts +62 -0
  10. package/dist/design-critique/completed-arc-record.js +232 -0
  11. package/dist/design-critique/exclusive-chip.d.ts +33 -0
  12. package/dist/design-critique/exclusive-chip.js +68 -0
  13. package/dist/design-critique/parent-audit.d.ts +59 -0
  14. package/dist/design-critique/parent-audit.js +121 -0
  15. package/dist/doctor/checks.d.ts +6 -0
  16. package/dist/doctor/checks.js +35 -0
  17. package/dist/freshness/bind.d.ts +32 -3
  18. package/dist/freshness/bind.js +58 -12
  19. package/dist/hooks/classify/host-session-identity.d.ts +76 -0
  20. package/dist/hooks/classify/host-session-identity.js +470 -0
  21. package/dist/hooks/classify/index.d.ts +3 -2
  22. package/dist/hooks/classify/index.js +3 -2
  23. package/dist/hooks/classify/paths.d.ts +7 -0
  24. package/dist/hooks/classify/paths.js +35 -0
  25. package/dist/hooks/classify/stdin.d.ts +5 -0
  26. package/dist/hooks/classify/stdin.js +27 -0
  27. package/dist/hooks/dest-form.d.ts +46 -0
  28. package/dist/hooks/dest-form.js +575 -0
  29. package/dist/hooks/dispatcher.d.ts +69 -2
  30. package/dist/hooks/dispatcher.js +609 -45
  31. package/dist/hooks/fixtures/cases.d.ts +16 -2
  32. package/dist/hooks/fixtures/cases.js +175 -0
  33. package/dist/hooks/index.d.ts +1 -0
  34. package/dist/hooks/index.js +1 -0
  35. package/dist/hooks/scope.js +3 -1
  36. package/dist/init-deposit/agent-hooks.d.ts +2 -1
  37. package/dist/init-deposit/agent-hooks.js +8 -1
  38. package/dist/init-deposit/hook-runtime-travel.d.ts +77 -0
  39. package/dist/init-deposit/hook-runtime-travel.js +269 -0
  40. package/dist/intake/clause-derivation.js +9 -3
  41. package/dist/intake/github-auth-modes-cli.js +14 -2
  42. package/dist/intake/github-auth-modes.d.ts +54 -9
  43. package/dist/intake/github-auth-modes.js +326 -123
  44. package/dist/intake/issue-ingest.js +62 -2
  45. package/dist/intake/platform-capabilities.d.ts +9 -2
  46. package/dist/intake/platform-capabilities.js +38 -9
  47. package/dist/lifecycle/completed-tracked-on-delivery.d.ts +26 -0
  48. package/dist/lifecycle/completed-tracked-on-delivery.js +37 -14
  49. package/dist/lifecycle/completed-write-guard.d.ts +48 -0
  50. package/dist/lifecycle/completed-write-guard.js +373 -0
  51. package/dist/lifecycle/index.d.ts +1 -0
  52. package/dist/lifecycle/index.js +1 -0
  53. package/dist/literal-acceptance/capture.d.ts +16 -0
  54. package/dist/literal-acceptance/capture.js +75 -7
  55. package/dist/literal-acceptance/evaluate.js +25 -3
  56. package/dist/literal-acceptance/index.d.ts +2 -2
  57. package/dist/literal-acceptance/index.js +2 -2
  58. package/dist/literal-acceptance/run.js +9 -3
  59. package/dist/literal-acceptance/types.d.ts +6 -0
  60. package/dist/literal-acceptance/types.js +6 -0
  61. package/dist/orphan-active/evaluate.d.ts +25 -0
  62. package/dist/orphan-active/evaluate.js +193 -128
  63. package/dist/orphan-active/index.d.ts +1 -0
  64. package/dist/orphan-active/index.js +1 -0
  65. package/dist/orphan-active/issue-state.d.ts +121 -0
  66. package/dist/orphan-active/issue-state.js +278 -0
  67. package/dist/platform/cursor-managed-runtime.d.ts +95 -0
  68. package/dist/platform/cursor-managed-runtime.js +241 -0
  69. package/dist/platform/index.d.ts +1 -0
  70. package/dist/platform/index.js +1 -0
  71. package/dist/platform/platform-capabilities.d.ts +23 -1
  72. package/dist/platform/platform-capabilities.js +55 -15
  73. package/dist/policy/ceremony-dial.js +5 -9
  74. package/dist/policy/host-hooks.js +4 -9
  75. package/dist/policy/org-force-on-migration.js +5 -10
  76. package/dist/policy/plan-extensions.d.ts +14 -2
  77. package/dist/policy/plan-extensions.js +24 -3
  78. package/dist/policy/product-signal.js +5 -10
  79. package/dist/policy/require-human-merge.js +5 -9
  80. package/dist/policy/resolve.js +77 -16
  81. package/dist/policy/runtime-authority.d.ts +18 -0
  82. package/dist/policy/runtime-authority.js +11 -0
  83. package/dist/policy/value-feedback.js +9 -18
  84. package/dist/policy/write-fence.js +3 -0
  85. package/dist/pr-closeout-attestable/evaluate.d.ts +80 -0
  86. package/dist/pr-closeout-attestable/evaluate.js +306 -0
  87. package/dist/pr-closeout-attestable/index.d.ts +2 -0
  88. package/dist/pr-closeout-attestable/index.js +2 -0
  89. package/dist/pr-protected-issues/gh.d.ts +6 -2
  90. package/dist/pr-protected-issues/gh.js +7 -3
  91. package/dist/pr-wait-mergeable/cascade.d.ts +9 -1
  92. package/dist/pr-wait-mergeable/cascade.js +21 -1
  93. package/dist/pr-wait-mergeable/types.d.ts +2 -0
  94. package/dist/pr-wait-mergeable/wrappers.d.ts +6 -0
  95. package/dist/pr-wait-mergeable/wrappers.js +11 -0
  96. package/dist/preflight/evaluate.d.ts +15 -0
  97. package/dist/preflight/evaluate.js +29 -3
  98. package/dist/preflight-cache/evaluate.js +9 -3
  99. package/dist/product-first-done-gate/acceptance-resolver.d.ts +14 -2
  100. package/dist/product-first-done-gate/acceptance-resolver.js +13 -2
  101. package/dist/product-first-done-gate/evaluate.js +53 -12
  102. package/dist/release/cli-drift-report.d.ts +68 -0
  103. package/dist/release/cli-drift-report.js +189 -0
  104. package/dist/release/index.d.ts +1 -0
  105. package/dist/release/index.js +1 -0
  106. package/dist/release/issue-state-fetch.d.ts +5 -3
  107. package/dist/release/issue-state-fetch.js +86 -21
  108. package/dist/release/native-steps.js +1 -0
  109. package/dist/release/pipeline.js +19 -0
  110. package/dist/release/types.d.ts +14 -0
  111. package/dist/render/constants.d.ts +4 -0
  112. package/dist/render/constants.js +11 -8
  113. package/dist/render/export-spec.js +31 -5
  114. package/dist/render/index.d.ts +1 -1
  115. package/dist/render/index.js +1 -1
  116. package/dist/render/prd-render.d.ts +4 -1
  117. package/dist/render/prd-render.js +82 -23
  118. package/dist/render/project-render.js +14 -7
  119. package/dist/render/scope-outlook.d.ts +2 -0
  120. package/dist/render/scope-outlook.js +3 -0
  121. package/dist/render/spec-render.js +2 -2
  122. package/dist/resolution/package-manager.d.ts +33 -4
  123. package/dist/resolution/package-manager.js +210 -9
  124. package/dist/review-monitor/constants.d.ts +7 -0
  125. package/dist/review-monitor/constants.js +7 -0
  126. package/dist/review-monitor/github-lease.d.ts +98 -1
  127. package/dist/review-monitor/github-lease.js +182 -1
  128. package/dist/review-monitor/lease-comment.d.ts +36 -0
  129. package/dist/review-monitor/lease-comment.js +103 -2
  130. package/dist/scm/call.js +12 -3
  131. package/dist/scm/design-critique-chip.d.ts +38 -0
  132. package/dist/scm/design-critique-chip.js +187 -0
  133. package/dist/scm/gh-rest.d.ts +9 -1
  134. package/dist/scm/gh-rest.js +103 -0
  135. package/dist/scm/index.d.ts +1 -0
  136. package/dist/scm/index.js +1 -0
  137. package/dist/scm/main.d.ts +3 -0
  138. package/dist/scm/main.js +15 -1
  139. package/dist/scm/readiness-cli.d.ts +2 -0
  140. package/dist/scm/readiness-cli.js +42 -1
  141. package/dist/scm/readiness.d.ts +4 -1
  142. package/dist/scm/readiness.js +35 -5
  143. package/dist/scope/decompose.js +2 -1
  144. package/dist/scope/delivery-evidence.d.ts +27 -1
  145. package/dist/scope/index.d.ts +1 -0
  146. package/dist/scope/index.js +1 -0
  147. package/dist/scope/lifecycle-write.d.ts +24 -0
  148. package/dist/scope/lifecycle-write.js +64 -0
  149. package/dist/scope/main.js +3 -1
  150. package/dist/scope/project-definition-sync.js +14 -7
  151. package/dist/scope/transition.js +90 -11
  152. package/dist/scope-provenance/evaluate.js +5 -1
  153. package/dist/session/git.d.ts +42 -0
  154. package/dist/session/git.js +219 -3
  155. package/dist/session/occupancy.d.ts +239 -12
  156. package/dist/session/occupancy.js +820 -27
  157. package/dist/session/ritual-sentinel.d.ts +5 -0
  158. package/dist/session/ritual-sentinel.js +5 -0
  159. package/dist/session/session-ready.d.ts +5 -1
  160. package/dist/session/session-ready.js +98 -8
  161. package/dist/session/session-start.d.ts +17 -1
  162. package/dist/session/session-start.js +49 -17
  163. package/dist/session/toolchain-preflight.d.ts +13 -3
  164. package/dist/session/toolchain-preflight.js +93 -18
  165. package/dist/session/verify-session-ritual.d.ts +33 -3
  166. package/dist/session/verify-session-ritual.js +89 -19
  167. package/dist/slice/constants.d.ts +1 -1
  168. package/dist/spec-authority/constants.d.ts +10 -2
  169. package/dist/spec-authority/constants.js +53 -8
  170. package/dist/spec-authority/resolver.d.ts +3 -0
  171. package/dist/spec-authority/resolver.js +55 -7
  172. package/dist/subprocess/max-buffer.d.ts +14 -0
  173. package/dist/subprocess/max-buffer.js +15 -0
  174. package/dist/swarm/complete-cohort.d.ts +2 -0
  175. package/dist/swarm/complete-cohort.js +21 -13
  176. package/dist/swarm/index.d.ts +1 -0
  177. package/dist/swarm/index.js +1 -0
  178. package/dist/swarm/launch-cli.js +53 -33
  179. package/dist/swarm/launch.d.ts +77 -0
  180. package/dist/swarm/launch.js +248 -38
  181. package/dist/swarm/pre-dispatch-cli.js +2 -1
  182. package/dist/swarm/pre-dispatch.js +10 -1
  183. package/dist/swarm/subagent-status-dir.d.ts +27 -0
  184. package/dist/swarm/subagent-status-dir.js +42 -0
  185. package/dist/swarm/worktrees.d.ts +17 -3
  186. package/dist/swarm/worktrees.js +48 -4
  187. package/dist/triage/actions/index.d.ts +4 -0
  188. package/dist/triage/actions/index.js +6 -0
  189. package/dist/triage/evaluate/evaluate.d.ts +11 -0
  190. package/dist/triage/evaluate/evaluate.js +169 -0
  191. package/dist/triage/evaluate/github.d.ts +16 -0
  192. package/dist/triage/evaluate/github.js +153 -0
  193. package/dist/triage/evaluate/index.d.ts +9 -0
  194. package/dist/triage/evaluate/index.js +8 -0
  195. package/dist/triage/evaluate/paths.d.ts +7 -0
  196. package/dist/triage/evaluate/paths.js +25 -0
  197. package/dist/triage/evaluate/sink.d.ts +5 -0
  198. package/dist/triage/evaluate/sink.js +52 -0
  199. package/dist/triage/evaluate/types.d.ts +93 -0
  200. package/dist/triage/evaluate/types.js +13 -0
  201. package/dist/triage/evaluate/validity.d.ts +8 -0
  202. package/dist/triage/evaluate/validity.js +87 -0
  203. package/dist/triage/evaluate/value.d.ts +12 -0
  204. package/dist/triage/evaluate/value.js +33 -0
  205. package/dist/triage/evaluate/wip-census.d.ts +5 -0
  206. package/dist/triage/evaluate/wip-census.js +39 -0
  207. package/dist/triage/evaluate/worktrees.d.ts +8 -0
  208. package/dist/triage/evaluate/worktrees.js +68 -0
  209. package/dist/triage/evaluate/xbrief-refs.d.ts +8 -0
  210. package/dist/triage/evaluate/xbrief-refs.js +60 -0
  211. package/dist/triage/help/registry-data.d.ts +21 -7
  212. package/dist/triage/help/registry-data.js +53 -5
  213. package/dist/triage/index.d.ts +1 -0
  214. package/dist/triage/index.js +1 -0
  215. package/dist/triage/scope/mutations-core.d.ts +2 -6
  216. package/dist/triage/scope/mutations-core.js +23 -55
  217. package/dist/triage/scope-drift/add-ignore.js +5 -39
  218. package/dist/triage/subscribe/index.d.ts +2 -6
  219. package/dist/triage/subscribe/index.js +15 -65
  220. package/dist/triage/welcome/writers.js +15 -74
  221. package/dist/value/feedback-file.d.ts +17 -2
  222. package/dist/value/feedback-file.js +80 -6
  223. package/dist/vbrief-build/index.d.ts +2 -1
  224. package/dist/vbrief-build/index.js +2 -1
  225. package/dist/vbrief-build/parity-scenarios.js +5 -5
  226. package/dist/vbrief-build/project-definition-io.d.ts +63 -3
  227. package/dist/vbrief-build/project-definition-io.js +357 -31
  228. package/dist/vbrief-build/project-definition-mutation.d.ts +32 -0
  229. package/dist/vbrief-build/project-definition-mutation.js +43 -0
  230. package/dist/vbrief-reconcile/labels.d.ts +1 -0
  231. package/dist/vbrief-reconcile/labels.js +30 -0
  232. package/dist/vbrief-validate/conformance.d.ts +1 -0
  233. package/dist/vbrief-validate/conformance.js +85 -8
  234. package/dist/vbrief-validate/main.js +2 -0
  235. package/dist/vbrief-validate/precutover.js +5 -10
  236. package/dist/vbrief-validation/story-quality.d.ts +7 -0
  237. package/dist/vbrief-validation/story-quality.js +8 -1
  238. package/dist/verify-ac/clauses.d.ts +62 -2
  239. package/dist/verify-ac/clauses.js +183 -111
  240. package/dist/verify-ac/index.d.ts +1 -1
  241. package/dist/verify-ac/index.js +1 -1
  242. package/dist/verify-env/node-runtime.d.ts +8 -4
  243. package/dist/verify-env/node-runtime.js +9 -6
  244. package/dist/verify-env/toolchain-check.d.ts +35 -3
  245. package/dist/verify-env/toolchain-check.js +155 -36
  246. package/dist/verify-source/project-definition-mutation-boundary.d.ts +104 -0
  247. package/dist/verify-source/project-definition-mutation-boundary.js +326 -0
  248. package/dist/xbrief-migrate/agents-header.js +69 -7
  249. package/package.json +7 -3
@@ -0,0 +1,326 @@
1
+ /**
2
+ * project-definition-mutation-boundary.ts -- mechanized production inventory for
3
+ * PROJECT-DEFINITION mutation identity (#3796).
4
+ *
5
+ * A convention that mutators "should" use the shared lock is not checkable: a
6
+ * caller can take the lock and then resolve, read, or write the artifact through
7
+ * its own path, and a helper-level test still passes while a production family
8
+ * split-brains. This scanner replaces that convention with a boundary.
9
+ *
10
+ * Two things are enforced:
11
+ *
12
+ * 1. **Boundary** -- outside the modules that own the protocol, no production
13
+ * source may call the raw lock, the raw write sink, or the raw loader, and no
14
+ * file that mutates PROJECT-DEFINITION may re-resolve the artifact path. The
15
+ * mutation capability is the only way in.
16
+ * 2. **Inventory** -- the per-file census of mutation call expressions is
17
+ * recorded. Adding or removing a mutation site is a deliberate edit to
18
+ * {@link PRODUCTION_MUTATION_INVENTORY}, not a silent drift.
19
+ *
20
+ * Consumed fail-closed by `project-definition-mutation-boundary.test.ts`.
21
+ */
22
+ import { readdirSync, readFileSync, statSync } from "node:fs";
23
+ import { join, relative, sep } from "node:path";
24
+ /** The capability that owns load/parse/persist against the captured path. */
25
+ export const MUTATION_CAPABILITY_MODULE = "packages/core/src/vbrief-build/project-definition-mutation.ts";
26
+ /** The module that owns the lock protocol and the raw primitives it wraps. */
27
+ export const MUTATION_PROTOCOL_MODULE = "packages/core/src/vbrief-build/project-definition-io.ts";
28
+ /**
29
+ * This scanner. Excluded from its own scan: it names every pattern it looks for
30
+ * in prose and regex literals, so scanning it would count the rules as call
31
+ * sites and let the inventory drift by editing the gate.
32
+ */
33
+ export const MUTATION_BOUNDARY_MODULE = "packages/core/src/verify-source/project-definition-mutation-boundary.ts";
34
+ /**
35
+ * Raw primitives and the only production files allowed to call them. Anything
36
+ * else reaching for these is a bypass of the captured-path identity.
37
+ */
38
+ export const RAW_MUTATION_CALL_RULES = [
39
+ {
40
+ label: "raw mutation lock",
41
+ pattern: /\bprojectDefinitionMutationLock\s*\(/,
42
+ allowedFiles: [MUTATION_CAPABILITY_MODULE],
43
+ },
44
+ {
45
+ label: "raw PROJECT-DEFINITION write sink",
46
+ pattern: /\batomicWriteProjectDefinition\s*\(/,
47
+ allowedFiles: [MUTATION_CAPABILITY_MODULE, MUTATION_PROTOCOL_MODULE],
48
+ },
49
+ {
50
+ label: "raw captured-path parser",
51
+ pattern: /\bparseProjectDefinitionAt\s*\(/,
52
+ allowedFiles: [MUTATION_CAPABILITY_MODULE, MUTATION_PROTOCOL_MODULE],
53
+ },
54
+ {
55
+ label: "raw unlocked loader",
56
+ pattern: /\bloadProjectDefinitionForMutation\s*\(/,
57
+ allowedFiles: [MUTATION_PROTOCOL_MODULE],
58
+ },
59
+ ];
60
+ /** Resolver calls that would give a mutation section a second artifact identity. */
61
+ export const RESOLVER_CALL_PATTERNS = [
62
+ /\bresolveProjectDefinitionPath\s*\(/,
63
+ /\bprojectDefinitionPath\s*\(/,
64
+ ];
65
+ /** Marks a file as performing a PROJECT-DEFINITION mutation. */
66
+ const MUTATION_CALL_PATTERN = /\bwithProjectDefinitionMutation\s*\(/g;
67
+ /**
68
+ * Character offsets of each `withProjectDefinitionMutation(...)` argument list.
69
+ *
70
+ * Scoping to the section is what keeps the resolver rule precise: resolving the
71
+ * path *before* acquiring the lock is ordinary pre-check work, while resolving it
72
+ * *inside* the critical section is the second identity the contract forbids.
73
+ * String, template, and comment spans are skipped so their braces and parens do
74
+ * not throw off the match.
75
+ */
76
+ export function extractMutationSections(text) {
77
+ const sections = [];
78
+ const call = /\bwithProjectDefinitionMutation\s*\(/g;
79
+ let match = call.exec(text);
80
+ while (match !== null) {
81
+ const open = match.index + match[0].length - 1;
82
+ const end = matchClosingParen(text, open);
83
+ if (end > open)
84
+ sections.push([open, end]);
85
+ call.lastIndex = end > open ? end : call.lastIndex;
86
+ match = call.exec(text);
87
+ }
88
+ return sections;
89
+ }
90
+ function matchClosingParen(text, openIndex) {
91
+ let depth = 0;
92
+ let i = openIndex;
93
+ while (i < text.length) {
94
+ const ch = text[i];
95
+ const next = text[i + 1];
96
+ if (ch === "/" && next === "/") {
97
+ while (i < text.length && text[i] !== "\n")
98
+ i += 1;
99
+ continue;
100
+ }
101
+ if (ch === "/" && next === "*") {
102
+ i += 2;
103
+ while (i < text.length && !(text[i] === "*" && text[i + 1] === "/"))
104
+ i += 1;
105
+ i += 2;
106
+ continue;
107
+ }
108
+ if (ch === '"' || ch === "'" || ch === "`") {
109
+ i = skipStringLiteral(text, i);
110
+ continue;
111
+ }
112
+ if (ch === "(")
113
+ depth += 1;
114
+ if (ch === ")") {
115
+ depth -= 1;
116
+ if (depth === 0)
117
+ return i;
118
+ }
119
+ i += 1;
120
+ }
121
+ return openIndex;
122
+ }
123
+ function skipStringLiteral(text, startIndex) {
124
+ const quote = text[startIndex];
125
+ let i = startIndex + 1;
126
+ while (i < text.length) {
127
+ if (text[i] === "\\") {
128
+ i += 2;
129
+ continue;
130
+ }
131
+ if (text[i] === quote)
132
+ return i + 1;
133
+ i += 1;
134
+ }
135
+ return i;
136
+ }
137
+ /**
138
+ * Production mutation call expressions, per file.
139
+ *
140
+ * The counts are the #3796 census: **18** call expressions across 13 core files
141
+ * that previously took the shared lock, plus the **2** `packages/cli` policy
142
+ * writers (`wipCap`, `swarmSubagentBackend`) that previously wrote
143
+ * PROJECT-DEFINITION with no lock at all. `parity-scenarios.ts` is the fixture
144
+ * harness for the same protocol and is counted separately below.
145
+ */
146
+ export const PRODUCTION_MUTATION_INVENTORY = Object.freeze({
147
+ "packages/cli/src/dispatch.ts": 2,
148
+ "packages/core/src/policy/ceremony-dial.ts": 1,
149
+ "packages/core/src/policy/host-hooks.ts": 1,
150
+ "packages/core/src/policy/org-force-on-migration.ts": 1,
151
+ "packages/core/src/policy/product-signal.ts": 1,
152
+ "packages/core/src/policy/require-human-merge.ts": 1,
153
+ "packages/core/src/policy/resolve.ts": 1,
154
+ "packages/core/src/policy/value-feedback.ts": 2,
155
+ "packages/core/src/render/project-render.ts": 2,
156
+ "packages/core/src/scope/project-definition-sync.ts": 1,
157
+ "packages/core/src/triage/scope-drift/add-ignore.ts": 1,
158
+ "packages/core/src/triage/scope/mutations-core.ts": 2,
159
+ "packages/core/src/triage/subscribe/index.ts": 1,
160
+ "packages/core/src/triage/welcome/writers.ts": 3,
161
+ "packages/core/src/vbrief-build/parity-scenarios.ts": 1,
162
+ });
163
+ /**
164
+ * The #3796 census, split by provenance so a drift failure says which family
165
+ * moved. 18 core call expressions across 13 files already took the shared lock;
166
+ * the 2 `packages/cli` policy writers did not; `parity-scenarios.ts` is the
167
+ * fixture harness for the same protocol.
168
+ */
169
+ export const PREVIOUSLY_LOCKED_CORE_CALL_EXPRESSIONS = 18;
170
+ export const PREVIOUSLY_LOCKED_CORE_FILES = 13;
171
+ export const PREVIOUSLY_UNLOCKED_CLI_WRITERS = 2;
172
+ export const PARITY_HARNESS_CALL_EXPRESSIONS = 1;
173
+ const SCAN_ROOTS = ["packages/core/src", "packages/cli/src"];
174
+ const TEST_PATH_MARKERS = [".test.ts", ".test.tsx", "/fixtures/"];
175
+ function toPosix(p) {
176
+ return p.split(sep).join("/");
177
+ }
178
+ function isProductionSource(relPosix) {
179
+ return !TEST_PATH_MARKERS.some((marker) => relPosix.includes(marker));
180
+ }
181
+ function walkTsFiles(dir, out) {
182
+ let entries;
183
+ try {
184
+ entries = readdirSync(dir);
185
+ }
186
+ catch {
187
+ return;
188
+ }
189
+ for (const name of entries) {
190
+ if (name === "node_modules" || name === "dist" || name === ".git")
191
+ continue;
192
+ const full = join(dir, name);
193
+ let st;
194
+ try {
195
+ st = statSync(full);
196
+ }
197
+ catch {
198
+ continue;
199
+ }
200
+ if (st.isDirectory()) {
201
+ walkTsFiles(full, out);
202
+ }
203
+ else if (st.isFile() && (name.endsWith(".ts") || name.endsWith(".tsx"))) {
204
+ out.push(full);
205
+ }
206
+ }
207
+ }
208
+ /**
209
+ * A re-export (`export { x } from "..."`) forwards a symbol, it does not call
210
+ * it, so it is not a bypass. Import lines are likewise declarations.
211
+ */
212
+ function isDeclarationLine(trimmed) {
213
+ return (trimmed.startsWith("import ") ||
214
+ trimmed.startsWith("export {") ||
215
+ trimmed.startsWith("export type {") ||
216
+ trimmed.startsWith("export *") ||
217
+ trimmed.startsWith("//") ||
218
+ trimmed.startsWith("*") ||
219
+ trimmed.startsWith("/*"));
220
+ }
221
+ /**
222
+ * Scan production TypeScript for mutation-identity bypasses and build the
223
+ * mutation call-expression inventory. The scan reads the filesystem, not the git
224
+ * index, so an unstaged or brand-new bypass is still caught.
225
+ */
226
+ export function scanProjectDefinitionMutationBoundary(projectRoot) {
227
+ const files = [];
228
+ for (const root of SCAN_ROOTS) {
229
+ walkTsFiles(join(projectRoot, root), files);
230
+ }
231
+ const findings = [];
232
+ const inventory = {};
233
+ let filesScanned = 0;
234
+ for (const abs of files) {
235
+ const relPosix = toPosix(relative(projectRoot, abs));
236
+ if (!isProductionSource(relPosix) || relPosix === MUTATION_BOUNDARY_MODULE)
237
+ continue;
238
+ filesScanned += 1;
239
+ let text;
240
+ try {
241
+ text = readFileSync(abs, "utf8");
242
+ }
243
+ catch {
244
+ continue;
245
+ }
246
+ MUTATION_CALL_PATTERN.lastIndex = 0;
247
+ const mutationCalls = text.match(MUTATION_CALL_PATTERN)?.length ?? 0;
248
+ if (mutationCalls > 0)
249
+ inventory[relPosix] = mutationCalls;
250
+ const sections = mutationCalls > 0 ? extractMutationSections(text) : [];
251
+ const lines = text.split(/\r?\n/);
252
+ let offset = 0;
253
+ for (let i = 0; i < lines.length; i += 1) {
254
+ const line = lines[i] ?? "";
255
+ const lineStart = offset;
256
+ offset += line.length + 1;
257
+ const trimmed = line.trim();
258
+ if (isDeclarationLine(trimmed))
259
+ continue;
260
+ for (const rule of RAW_MUTATION_CALL_RULES) {
261
+ if (rule.pattern.test(line) && !rule.allowedFiles.includes(relPosix)) {
262
+ findings.push({ path: relPosix, line: i + 1, rule: rule.label, text: trimmed });
263
+ }
264
+ }
265
+ // Inside a critical section the artifact identity is the captured one.
266
+ const insideSection = sections.some(([start, end]) => lineStart >= start && lineStart <= end);
267
+ if (insideSection && relPosix !== MUTATION_CAPABILITY_MODULE) {
268
+ for (const pattern of RESOLVER_CALL_PATTERNS) {
269
+ if (pattern.test(line)) {
270
+ findings.push({
271
+ path: relPosix,
272
+ line: i + 1,
273
+ rule: "artifact path re-resolved inside a mutation section",
274
+ text: trimmed,
275
+ });
276
+ }
277
+ }
278
+ }
279
+ }
280
+ }
281
+ return { findings, inventory, filesScanned };
282
+ }
283
+ /** Render findings as a stable, reviewable report. */
284
+ export function formatMutationBoundaryFindings(findings) {
285
+ return findings
286
+ .map((f) => ` ${f.path}:${f.line} [${f.rule}] ${f.text}`)
287
+ .sort()
288
+ .join("\n");
289
+ }
290
+ /**
291
+ * The fail-closed verdict: no bypass, and the mutation inventory still matches
292
+ * the recorded census. Adding or removing a mutation site is a deliberate edit
293
+ * to {@link PRODUCTION_MUTATION_INVENTORY} and the census constants above.
294
+ */
295
+ export function evaluateProjectDefinitionMutationBoundary(projectRoot) {
296
+ const scan = scanProjectDefinitionMutationBoundary(projectRoot);
297
+ const errors = [];
298
+ if (scan.findings.length > 0) {
299
+ errors.push(`${scan.findings.length} raw resolver/lock/write bypass(es) outside the mutation capability:\n` +
300
+ formatMutationBoundaryFindings(scan.findings));
301
+ }
302
+ for (const [path, expected] of Object.entries(PRODUCTION_MUTATION_INVENTORY)) {
303
+ const actual = scan.inventory[path] ?? 0;
304
+ if (actual !== expected) {
305
+ errors.push(`${path}: expected ${expected} mutation call(s), found ${actual}`);
306
+ }
307
+ }
308
+ for (const path of Object.keys(scan.inventory)) {
309
+ if (!(path in PRODUCTION_MUTATION_INVENTORY)) {
310
+ errors.push(`${path}: mutation call site not in the recorded inventory`);
311
+ }
312
+ }
313
+ const total = Object.values(scan.inventory).reduce((sum, n) => sum + n, 0);
314
+ const expectedTotal = PREVIOUSLY_LOCKED_CORE_CALL_EXPRESSIONS +
315
+ PREVIOUSLY_UNLOCKED_CLI_WRITERS +
316
+ PARITY_HARNESS_CALL_EXPRESSIONS;
317
+ if (total !== expectedTotal) {
318
+ errors.push(`census total: expected ${expectedTotal} mutation call(s), found ${total}`);
319
+ }
320
+ const coreFiles = Object.keys(scan.inventory).filter((path) => path.startsWith("packages/core/") && !path.includes("parity-scenarios"));
321
+ if (coreFiles.length !== PREVIOUSLY_LOCKED_CORE_FILES) {
322
+ errors.push(`census files: expected ${PREVIOUSLY_LOCKED_CORE_FILES} core file(s), found ${coreFiles.length}`);
323
+ }
324
+ return { ok: errors.length === 0, errors, scan };
325
+ }
326
+ //# sourceMappingURL=project-definition-mutation-boundary.js.map
@@ -6,14 +6,14 @@ import { MIGRATED_ARTIFACT_DIR } from "./constants.js";
6
6
  import { isDirectory } from "./fs-helpers.js";
7
7
  /**
8
8
  * Bounded, ordered set of legacy crossover tokens rewritten in the UNMANAGED
9
- * region of a consumer AGENTS.md after `migrate:xbrief` (#2154 / Option A).
9
+ * region of a consumer AGENTS.md after `migrate:xbrief` (#2154 / Option A / #3637).
10
10
  *
11
11
  * Each entry is a mechanical path / verb literal — NOT freeform prose. The
12
12
  * casing-only `vBRIEF format` product-description token from the issue table is
13
- * intentionally excluded so freeform prose survives untouched. The tokens are
14
- * disjoint substrings (`.vbrief.json` has no trailing slash, `vbrief:preflight`
15
- * uses a colon, `vbrief/` requires a slash), so replacement order does not
16
- * change the result and a second pass is a guaranteed no-op (idempotent).
13
+ * intentionally excluded so freeform prose survives untouched. `.vbrief.json`
14
+ * and `vbrief:preflight` are substring hits. `vbrief/` is left-bound and only
15
+ * a hard hit when a child segment follows (`vbrief/active/...`); bare
16
+ * `vbrief/` and `x-vbrief/` are not rewritten. A second pass is a no-op.
17
17
  */
18
18
  /** Refuse migrate header writes that escape via repo-controlled symlinks (#2668). */
19
19
  function projectionTarget(projectDir, ...relSegments) {
@@ -26,6 +26,8 @@ export const LEGACY_HEADER_TOKENS = [
26
26
  { legacy: "vbrief:preflight", migrated: "xbrief:preflight" },
27
27
  { legacy: "vbrief/", migrated: "xbrief/" },
28
28
  ];
29
+ const VBRIEF_DIR = "vbrief/";
30
+ const XBRIEF_DIR = "xbrief/";
29
31
  function countOccurrences(haystack, needle) {
30
32
  if (needle.length === 0)
31
33
  return 0;
@@ -37,10 +39,70 @@ function countOccurrences(haystack, needle) {
37
39
  }
38
40
  return count;
39
41
  }
42
+ /** `x-vbrief/` is not a `vbrief/` hit; ident/hyphen continue the token. */
43
+ function isLeftBoundAt(text, index) {
44
+ if (index === 0)
45
+ return true;
46
+ return !/[A-Za-z0-9_-]/.test(text[index - 1]);
47
+ }
48
+ /** Child path segment after `vbrief/` — not whitespace, fence close, or EOS. */
49
+ function hasVbriefChildSegment(text, index) {
50
+ const after = index + VBRIEF_DIR.length;
51
+ if (after >= text.length)
52
+ return false;
53
+ const ch = text[after];
54
+ return ch !== "/" && ch !== "`" && !/\s/.test(ch);
55
+ }
56
+ function isHardVbriefDirHit(text, index) {
57
+ return isLeftBoundAt(text, index) && hasVbriefChildSegment(text, index);
58
+ }
59
+ function hasHardVbriefDirHit(text) {
60
+ let index = text.indexOf(VBRIEF_DIR);
61
+ while (index !== -1) {
62
+ if (isHardVbriefDirHit(text, index))
63
+ return true;
64
+ index = text.indexOf(VBRIEF_DIR, index + VBRIEF_DIR.length);
65
+ }
66
+ return false;
67
+ }
68
+ /** Rewrite left-bound `vbrief/` + child; leave bare `vbrief/` and `x-vbrief/`. */
69
+ function rewriteLeftBoundVbriefDir(slice) {
70
+ let out = "";
71
+ let cursor = 0;
72
+ let count = 0;
73
+ let index = slice.indexOf(VBRIEF_DIR);
74
+ while (index !== -1) {
75
+ out += slice.slice(cursor, index);
76
+ if (isHardVbriefDirHit(slice, index)) {
77
+ out += XBRIEF_DIR;
78
+ count += 1;
79
+ }
80
+ else {
81
+ out += VBRIEF_DIR;
82
+ }
83
+ cursor = index + VBRIEF_DIR.length;
84
+ index = slice.indexOf(VBRIEF_DIR, cursor);
85
+ }
86
+ out += slice.slice(cursor);
87
+ return { next: out, count };
88
+ }
89
+ function unmanagedHitsLegacyToken(unmanaged, legacy) {
90
+ if (legacy === VBRIEF_DIR)
91
+ return hasHardVbriefDirHit(unmanaged);
92
+ return unmanaged.includes(legacy);
93
+ }
40
94
  /** Rewrite the bounded legacy tokens inside a single unmanaged text slice. */
41
95
  function rewriteSlice(slice, tally) {
42
96
  let next = slice;
43
97
  for (const { legacy, migrated } of LEGACY_HEADER_TOKENS) {
98
+ if (legacy === VBRIEF_DIR) {
99
+ const rewritten = rewriteLeftBoundVbriefDir(next);
100
+ if (rewritten.count === 0)
101
+ continue;
102
+ tally.set(legacy, (tally.get(legacy) ?? 0) + rewritten.count);
103
+ next = rewritten.next;
104
+ continue;
105
+ }
44
106
  const occurrences = countOccurrences(next, legacy);
45
107
  if (occurrences === 0)
46
108
  continue;
@@ -172,7 +234,7 @@ export function detectStaleUnmanagedHeader(projectRoot, readText = (path) => {
172
234
  cursor = end;
173
235
  }
174
236
  unmanaged += content.slice(cursor);
175
- const matches = LEGACY_HEADER_TOKENS.filter((t) => unmanaged.includes(t.legacy)).map((t) => t.legacy);
237
+ const matches = LEGACY_HEADER_TOKENS.filter((t) => unmanagedHitsLegacyToken(unmanaged, t.legacy)).map((t) => t.legacy);
176
238
  return { stale: matches.length > 0, matches };
177
239
  }
178
240
  /** One-line doctor / ritual signpost for the #2154 stale-header regression. */
@@ -183,6 +245,6 @@ export function renderStaleHeaderLine(projectRoot, readText) {
183
245
  }
184
246
  return (`AGENTS.md header drift: xbrief/ tree present but the unmanaged AGENTS.md header still ` +
185
247
  `references legacy token(s) ${matches.join(", ")}. ` +
186
- "Run `deft migrate:xbrief` (idempotent) to rewrite them, or hand-edit the header.");
248
+ "Hand-edit the unmanaged header path literals.");
187
249
  }
188
250
  //# sourceMappingURL=agents-header.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftai/directive-core",
3
- "version": "0.106.0",
3
+ "version": "0.108.0",
4
4
  "description": "TypeScript engine core for the Directive framework.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -195,6 +195,10 @@
195
195
  "types": "./dist/pr-closing-keywords/index.d.ts",
196
196
  "default": "./dist/pr-closing-keywords/index.js"
197
197
  },
198
+ "./pr-closeout-attestable": {
199
+ "types": "./dist/pr-closeout-attestable/index.d.ts",
200
+ "default": "./dist/pr-closeout-attestable/index.js"
201
+ },
198
202
  "./pr-monitor": {
199
203
  "types": "./dist/pr-monitor/index.d.ts",
200
204
  "default": "./dist/pr-monitor/index.js"
@@ -386,8 +390,8 @@
386
390
  "provenance": true
387
391
  },
388
392
  "dependencies": {
389
- "@deftai/directive-content": "^0.106.0",
390
- "@deftai/directive-types": "^0.106.0",
393
+ "@deftai/directive-content": "^0.108.0",
394
+ "@deftai/directive-types": "^0.108.0",
391
395
  "archiver": "^8.0.0"
392
396
  },
393
397
  "scripts": {