@deftai/directive 0.55.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 (261) hide show
  1. package/dist/bin.d.ts +3 -0
  2. package/dist/bin.js +5 -0
  3. package/dist/branch-parity.d.ts +47 -0
  4. package/dist/branch-parity.js +263 -0
  5. package/dist/cache-parity.d.ts +36 -0
  6. package/dist/cache-parity.js +165 -0
  7. package/dist/cache.d.ts +4 -0
  8. package/dist/cache.js +10 -0
  9. package/dist/capacity-backfill.d.ts +3 -0
  10. package/dist/capacity-backfill.js +9 -0
  11. package/dist/capacity-show.d.ts +3 -0
  12. package/dist/capacity-show.js +9 -0
  13. package/dist/check.d.ts +10 -0
  14. package/dist/check.js +60 -0
  15. package/dist/cli-router/index.d.ts +8 -0
  16. package/dist/cli-router/index.js +37 -0
  17. package/dist/cli-router/route-argv.d.ts +32 -0
  18. package/dist/cli-router/route-argv.js +226 -0
  19. package/dist/codebase-default-extractor.d.ts +3 -0
  20. package/dist/codebase-default-extractor.js +9 -0
  21. package/dist/codebase-map-fresh.d.ts +3 -0
  22. package/dist/codebase-map-fresh.js +9 -0
  23. package/dist/codebase-map.d.ts +3 -0
  24. package/dist/codebase-map.js +9 -0
  25. package/dist/codebase-parity.d.ts +30 -0
  26. package/dist/codebase-parity.js +294 -0
  27. package/dist/codebase-projection-registry.d.ts +3 -0
  28. package/dist/codebase-projection-registry.js +9 -0
  29. package/dist/codebase-provider.d.ts +3 -0
  30. package/dist/codebase-provider.js +9 -0
  31. package/dist/content-validate-cli/validate-links.d.ts +11 -0
  32. package/dist/content-validate-cli/validate-links.js +37 -0
  33. package/dist/content-validate-cli/validate-strategy-output.d.ts +13 -0
  34. package/dist/content-validate-cli/validate-strategy-output.js +56 -0
  35. package/dist/content-validate-cli/verify-capacity.d.ts +12 -0
  36. package/dist/content-validate-cli/verify-capacity.js +54 -0
  37. package/dist/dispatch.d.ts +26 -0
  38. package/dist/dispatch.js +569 -0
  39. package/dist/doctor-parity.d.ts +38 -0
  40. package/dist/doctor-parity.js +151 -0
  41. package/dist/doctor.d.ts +3 -0
  42. package/dist/doctor.js +10 -0
  43. package/dist/gates-cli/_helpers.d.ts +18 -0
  44. package/dist/gates-cli/_helpers.js +71 -0
  45. package/dist/index.d.ts +11 -0
  46. package/dist/index.js +15 -0
  47. package/dist/init-cli/constants.d.ts +5 -0
  48. package/dist/init-cli/constants.js +5 -0
  49. package/dist/init-cli/init.d.ts +3 -0
  50. package/dist/init-cli/init.js +11 -0
  51. package/dist/init-cli/resolve-binary.d.ts +34 -0
  52. package/dist/init-cli/resolve-binary.js +78 -0
  53. package/dist/init-cli/run-deft-install.d.ts +23 -0
  54. package/dist/init-cli/run-deft-install.js +71 -0
  55. package/dist/init-cli/update.d.ts +3 -0
  56. package/dist/init-cli/update.js +11 -0
  57. package/dist/install-cli/coverage-map.d.ts +14 -0
  58. package/dist/install-cli/coverage-map.js +111 -0
  59. package/dist/intake-parity.d.ts +30 -0
  60. package/dist/intake-parity.js +203 -0
  61. package/dist/lifecycle-cli/coverage-map.d.ts +16 -0
  62. package/dist/lifecycle-cli/coverage-map.js +254 -0
  63. package/dist/lifecycle-cli/helpers.d.ts +8 -0
  64. package/dist/lifecycle-cli/helpers.js +20 -0
  65. package/dist/lifecycle-packs-parity.d.ts +30 -0
  66. package/dist/lifecycle-packs-parity.js +377 -0
  67. package/dist/orchestration-cli/coverage-map.d.ts +21 -0
  68. package/dist/orchestration-cli/coverage-map.js +225 -0
  69. package/dist/orchestration-cli/helpers.d.ts +11 -0
  70. package/dist/orchestration-cli/helpers.js +35 -0
  71. package/dist/orchestration-parity.d.ts +38 -0
  72. package/dist/orchestration-parity.js +364 -0
  73. package/dist/parity.d.ts +36 -0
  74. package/dist/parity.js +176 -0
  75. package/dist/platform-parity.d.ts +26 -0
  76. package/dist/platform-parity.js +309 -0
  77. package/dist/policy-parity.d.ts +35 -0
  78. package/dist/policy-parity.js +214 -0
  79. package/dist/policy.d.ts +27 -0
  80. package/dist/policy.js +316 -0
  81. package/dist/pr-closing-keywords-parity.d.ts +45 -0
  82. package/dist/pr-closing-keywords-parity.js +259 -0
  83. package/dist/pr-closing-keywords.d.ts +3 -0
  84. package/dist/pr-closing-keywords.js +10 -0
  85. package/dist/pr-merge-readiness-parity.d.ts +44 -0
  86. package/dist/pr-merge-readiness-parity.js +296 -0
  87. package/dist/pr-merge-readiness.d.ts +3 -0
  88. package/dist/pr-merge-readiness.js +10 -0
  89. package/dist/pr-monitor-parity.d.ts +44 -0
  90. package/dist/pr-monitor-parity.js +283 -0
  91. package/dist/pr-monitor.d.ts +3 -0
  92. package/dist/pr-monitor.js +10 -0
  93. package/dist/pr-protected-issues-parity.d.ts +41 -0
  94. package/dist/pr-protected-issues-parity.js +220 -0
  95. package/dist/pr-protected-issues.d.ts +3 -0
  96. package/dist/pr-protected-issues.js +10 -0
  97. package/dist/pr-wait-mergeable-parity.d.ts +45 -0
  98. package/dist/pr-wait-mergeable-parity.js +340 -0
  99. package/dist/pr-wait-mergeable.d.ts +3 -0
  100. package/dist/pr-wait-mergeable.js +10 -0
  101. package/dist/preflight-cache.d.ts +16 -0
  102. package/dist/preflight-cache.js +106 -0
  103. package/dist/preflight-gh.d.ts +12 -0
  104. package/dist/preflight-gh.js +188 -0
  105. package/dist/probe-session.d.ts +3 -0
  106. package/dist/probe-session.js +10 -0
  107. package/dist/release-e2e-parity.d.ts +31 -0
  108. package/dist/release-e2e-parity.js +114 -0
  109. package/dist/release-e2e.d.ts +3 -0
  110. package/dist/release-e2e.js +10 -0
  111. package/dist/release-parity.d.ts +40 -0
  112. package/dist/release-parity.js +223 -0
  113. package/dist/release-publish-parity.d.ts +36 -0
  114. package/dist/release-publish-parity.js +138 -0
  115. package/dist/release-publish.d.ts +3 -0
  116. package/dist/release-publish.js +10 -0
  117. package/dist/release-rollback-parity.d.ts +37 -0
  118. package/dist/release-rollback-parity.js +161 -0
  119. package/dist/release-rollback.d.ts +3 -0
  120. package/dist/release-rollback.js +10 -0
  121. package/dist/release.d.ts +3 -0
  122. package/dist/release.js +10 -0
  123. package/dist/render-cli/deft-ts-runner.d.ts +20 -0
  124. package/dist/render-cli/deft-ts-runner.js +35 -0
  125. package/dist/render-cli/prd-render-cli.d.ts +2 -0
  126. package/dist/render-cli/prd-render-cli.js +7 -0
  127. package/dist/render-cli/project-render-cli.d.ts +2 -0
  128. package/dist/render-cli/project-render-cli.js +6 -0
  129. package/dist/render-cli/spec-render-cli.d.ts +2 -0
  130. package/dist/render-cli/spec-render-cli.js +6 -0
  131. package/dist/render-cli/spec-validate-cli.d.ts +2 -0
  132. package/dist/render-cli/spec-validate-cli.js +6 -0
  133. package/dist/render-parity.d.ts +35 -0
  134. package/dist/render-parity.js +383 -0
  135. package/dist/scm-parity.d.ts +39 -0
  136. package/dist/scm-parity.js +181 -0
  137. package/dist/scope-lifecycle-parity.d.ts +35 -0
  138. package/dist/scope-lifecycle-parity.js +177 -0
  139. package/dist/scope-lifecycle.d.ts +4 -0
  140. package/dist/scope-lifecycle.js +29 -0
  141. package/dist/session-parity.d.ts +39 -0
  142. package/dist/session-parity.js +260 -0
  143. package/dist/slice-parity.d.ts +36 -0
  144. package/dist/slice-parity.js +304 -0
  145. package/dist/slice.d.ts +4 -0
  146. package/dist/slice.js +29 -0
  147. package/dist/story-ready-parity.d.ts +49 -0
  148. package/dist/story-ready-parity.js +254 -0
  149. package/dist/subagent-monitor.d.ts +3 -0
  150. package/dist/subagent-monitor.js +10 -0
  151. package/dist/swarm-parity.d.ts +28 -0
  152. package/dist/swarm-parity.js +326 -0
  153. package/dist/toolchain-check.d.ts +3 -0
  154. package/dist/toolchain-check.js +14 -0
  155. package/dist/triage-actions-parity.d.ts +33 -0
  156. package/dist/triage-actions-parity.js +201 -0
  157. package/dist/triage-actions.d.ts +17 -0
  158. package/dist/triage-actions.js +144 -0
  159. package/dist/triage-aux-a-parity.d.ts +35 -0
  160. package/dist/triage-aux-a-parity.js +172 -0
  161. package/dist/triage-aux-b-parity.d.ts +32 -0
  162. package/dist/triage-aux-b-parity.js +308 -0
  163. package/dist/triage-bootstrap-parity.d.ts +44 -0
  164. package/dist/triage-bootstrap-parity.js +226 -0
  165. package/dist/triage-bootstrap.d.ts +24 -0
  166. package/dist/triage-bootstrap.js +172 -0
  167. package/dist/triage-bulk.d.ts +17 -0
  168. package/dist/triage-bulk.js +154 -0
  169. package/dist/triage-classify-parity.d.ts +40 -0
  170. package/dist/triage-classify-parity.js +227 -0
  171. package/dist/triage-classify.d.ts +13 -0
  172. package/dist/triage-classify.js +76 -0
  173. package/dist/triage-help.d.ts +3 -0
  174. package/dist/triage-help.js +10 -0
  175. package/dist/triage-queue-parity.d.ts +59 -0
  176. package/dist/triage-queue-parity.js +363 -0
  177. package/dist/triage-queue.d.ts +17 -0
  178. package/dist/triage-queue.js +174 -0
  179. package/dist/triage-reconcile.d.ts +11 -0
  180. package/dist/triage-reconcile.js +76 -0
  181. package/dist/triage-refresh.d.ts +7 -0
  182. package/dist/triage-refresh.js +37 -0
  183. package/dist/triage-scope-drift.d.ts +12 -0
  184. package/dist/triage-scope-drift.js +117 -0
  185. package/dist/triage-scope-parity.d.ts +34 -0
  186. package/dist/triage-scope-parity.js +218 -0
  187. package/dist/triage-scope.d.ts +4 -0
  188. package/dist/triage-scope.js +29 -0
  189. package/dist/triage-smoketest.d.ts +3 -0
  190. package/dist/triage-smoketest.js +43 -0
  191. package/dist/triage-subscribe.d.ts +15 -0
  192. package/dist/triage-subscribe.js +157 -0
  193. package/dist/triage-summary-parity.d.ts +50 -0
  194. package/dist/triage-summary-parity.js +306 -0
  195. package/dist/triage-summary.d.ts +14 -0
  196. package/dist/triage-summary.js +77 -0
  197. package/dist/triage-welcome.d.ts +12 -0
  198. package/dist/triage-welcome.js +78 -0
  199. package/dist/ts-check-lane.d.ts +9 -0
  200. package/dist/ts-check-lane.js +49 -0
  201. package/dist/validate-content-parity.d.ts +33 -0
  202. package/dist/validate-content-parity.js +356 -0
  203. package/dist/vbrief-activate-parity.d.ts +39 -0
  204. package/dist/vbrief-activate-parity.js +216 -0
  205. package/dist/vbrief-activate.d.ts +3 -0
  206. package/dist/vbrief-activate.js +7 -0
  207. package/dist/vbrief-build-parity.d.ts +28 -0
  208. package/dist/vbrief-build-parity.js +399 -0
  209. package/dist/vbrief-build.d.ts +3 -0
  210. package/dist/vbrief-build.js +8 -0
  211. package/dist/vbrief-preflight-parity.d.ts +34 -0
  212. package/dist/vbrief-preflight-parity.js +163 -0
  213. package/dist/vbrief-preflight.d.ts +12 -0
  214. package/dist/vbrief-preflight.js +58 -0
  215. package/dist/vbrief-reconcile-parity.d.ts +22 -0
  216. package/dist/vbrief-reconcile-parity.js +600 -0
  217. package/dist/vbrief-reconcile.d.ts +3 -0
  218. package/dist/vbrief-reconcile.js +7 -0
  219. package/dist/vbrief-validate-parity.d.ts +27 -0
  220. package/dist/vbrief-validate-parity.js +122 -0
  221. package/dist/vbrief-validate.d.ts +3 -0
  222. package/dist/vbrief-validate.js +8 -0
  223. package/dist/vbrief-validation-parity.d.ts +28 -0
  224. package/dist/vbrief-validation-parity.js +645 -0
  225. package/dist/vbrief-validation.d.ts +3 -0
  226. package/dist/vbrief-validation.js +8 -0
  227. package/dist/verify-branch.d.ts +14 -0
  228. package/dist/verify-branch.js +81 -0
  229. package/dist/verify-encoding.d.ts +15 -0
  230. package/dist/verify-encoding.js +86 -0
  231. package/dist/verify-env-parity.d.ts +28 -0
  232. package/dist/verify-env-parity.js +272 -0
  233. package/dist/verify-hooks-installed.d.ts +10 -0
  234. package/dist/verify-hooks-installed.js +48 -0
  235. package/dist/verify-investigation.d.ts +3 -0
  236. package/dist/verify-investigation.js +10 -0
  237. package/dist/verify-judgment-gates.d.ts +3 -0
  238. package/dist/verify-judgment-gates.js +10 -0
  239. package/dist/verify-no-task-runtime.d.ts +3 -0
  240. package/dist/verify-no-task-runtime.js +16 -0
  241. package/dist/verify-session-ritual.d.ts +13 -0
  242. package/dist/verify-session-ritual.js +78 -0
  243. package/dist/verify-source-cli/rule-ownership-lint.d.ts +12 -0
  244. package/dist/verify-source-cli/rule-ownership-lint.js +61 -0
  245. package/dist/verify-source-cli/verify-content-manifest.d.ts +12 -0
  246. package/dist/verify-source-cli/verify-content-manifest.js +61 -0
  247. package/dist/verify-source-cli/verify-scm-boundary.d.ts +13 -0
  248. package/dist/verify-source-cli/verify-scm-boundary.js +66 -0
  249. package/dist/verify-source-cli/verify-stubs.d.ts +11 -0
  250. package/dist/verify-source-cli/verify-stubs.js +27 -0
  251. package/dist/verify-source-parity.d.ts +26 -0
  252. package/dist/verify-source-parity.js +178 -0
  253. package/dist/verify-story-ready.d.ts +16 -0
  254. package/dist/verify-story-ready.js +138 -0
  255. package/dist/verify-tools.d.ts +13 -0
  256. package/dist/verify-tools.js +67 -0
  257. package/dist/verify-wip-cap.d.ts +13 -0
  258. package/dist/verify-wip-cap.js +62 -0
  259. package/dist/wip-cap-parity.d.ts +39 -0
  260. package/dist/wip-cap-parity.js +209 -0
  261. package/package.json +50 -0
@@ -0,0 +1,216 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Golden-output parity harness (#1782 s5): runs BOTH the Python oracle
4
+ * (`scripts/vbrief_activate.py`) and the ported TS vbrief-activate CLI over
5
+ * temp fixture vBRIEF trees, then diffs exit codes and byte-identical
6
+ * stdout/stderr (cache-off). Mutating scenarios also compare destination
7
+ * JSON with the volatile ``vBRIEFInfo.updated`` field stripped.
8
+ *
9
+ * Exit codes: 0 parity / 1 divergence / 2 harness setup error.
10
+ */
11
+ import { execFileSync } from "node:child_process";
12
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
13
+ import { tmpdir } from "node:os";
14
+ import { dirname, join, resolve } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+ const FIXTURE_NAME = "2026-05-01-test.vbrief.json";
17
+ export const PARITY_CASES = [
18
+ { name: "pending-to-active", folder: "pending", status: "pending", mutates: true },
19
+ { name: "approved-to-active", folder: "pending", status: "approved", mutates: true },
20
+ { name: "already-active-noop", folder: "active", status: "running" },
21
+ { name: "proposed-reject", folder: "proposed", status: "proposed" },
22
+ { name: "completed-reject", folder: "completed", status: "completed" },
23
+ { name: "active-blocked-reject", folder: "active", status: "blocked" },
24
+ { name: "pending-draft-reject", folder: "pending", status: "draft" },
25
+ { name: "nonexistent-reject", folder: "pending", status: "pending", missingPath: true },
26
+ { name: "malformed-json", folder: "pending", status: "pending", rawOverride: "{ not json" },
27
+ {
28
+ name: "missing-plan",
29
+ folder: "pending",
30
+ status: "pending",
31
+ payloadOverride: { vBRIEFInfo: { version: "0.6" } },
32
+ },
33
+ {
34
+ name: "missing-plan-status",
35
+ folder: "pending",
36
+ status: "pending",
37
+ payloadOverride: { vBRIEFInfo: { version: "0.6" }, plan: { title: "T" } },
38
+ },
39
+ {
40
+ name: "destination-collision",
41
+ folder: "pending",
42
+ status: "pending",
43
+ preSeedActive: true,
44
+ },
45
+ ];
46
+ function runCapture(cmd, args, cwd) {
47
+ try {
48
+ const stdout = execFileSync(cmd, args, {
49
+ cwd,
50
+ encoding: "utf8",
51
+ env: process.env,
52
+ stdio: ["ignore", "pipe", "pipe"],
53
+ });
54
+ return { exitCode: 0, stdout, stderr: "" };
55
+ }
56
+ catch (err) {
57
+ const e = err;
58
+ return {
59
+ exitCode: typeof e.status === "number" ? e.status : 2,
60
+ stdout: typeof e.stdout === "string" ? e.stdout : "",
61
+ stderr: typeof e.stderr === "string" ? e.stderr : "",
62
+ };
63
+ }
64
+ }
65
+ function resolveDeftRoot() {
66
+ if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
67
+ return resolve(process.env.DEFT_ROOT);
68
+ }
69
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
70
+ }
71
+ function writeFixture(repo, testCase) {
72
+ const full = join(repo, "vbrief", testCase.folder, FIXTURE_NAME);
73
+ mkdirSync(dirname(full), { recursive: true });
74
+ if (testCase.rawOverride !== undefined) {
75
+ writeFileSync(full, testCase.rawOverride, "utf8");
76
+ return full;
77
+ }
78
+ if (testCase.payloadOverride !== undefined) {
79
+ writeFileSync(full, JSON.stringify(testCase.payloadOverride), "utf8");
80
+ return full;
81
+ }
82
+ writeFileSync(full, JSON.stringify({
83
+ vBRIEFInfo: { version: "0.6", updated: "2026-04-30T00:00:00Z" },
84
+ plan: { title: "T", status: testCase.status, items: [] },
85
+ }), "utf8");
86
+ return full;
87
+ }
88
+ function setupRepo(testCase) {
89
+ const repo = mkdtempSync(join(tmpdir(), "deft-vbrief-activate-parity-"));
90
+ mkdirSync(join(repo, "vbrief"), { recursive: true });
91
+ if (testCase.missingPath === true) {
92
+ return { repo, vbriefPath: join(repo, "vbrief", "pending", "missing.vbrief.json") };
93
+ }
94
+ const vbriefPath = writeFixture(repo, testCase);
95
+ if (testCase.preSeedActive === true) {
96
+ const activePath = join(repo, "vbrief", "active", FIXTURE_NAME);
97
+ mkdirSync(dirname(activePath), { recursive: true });
98
+ writeFileSync(activePath, "{}", "utf8");
99
+ }
100
+ return { repo, vbriefPath };
101
+ }
102
+ function stripUpdatedField(repo) {
103
+ const dest = join(repo, "vbrief", "active", FIXTURE_NAME);
104
+ if (!existsSync(dest)) {
105
+ return null;
106
+ }
107
+ const payload = JSON.parse(readFileSync(dest, "utf8"));
108
+ const info = payload.vBRIEFInfo;
109
+ if (info !== null && typeof info === "object" && !Array.isArray(info)) {
110
+ delete info.updated;
111
+ }
112
+ return JSON.stringify(payload);
113
+ }
114
+ function runPython(deftRoot, vbriefPath) {
115
+ return runCapture("uv", ["run", "python", join(deftRoot, "scripts", "vbrief_activate.py"), vbriefPath], deftRoot);
116
+ }
117
+ function runTs(deftRoot, vbriefPath) {
118
+ return runCapture("node", [join(deftRoot, "packages", "cli", "dist", "vbrief-activate.js"), vbriefPath], deftRoot);
119
+ }
120
+ export function diffCase(python, ts, caseName, sideEffectMismatch) {
121
+ return {
122
+ caseName,
123
+ exitMismatch: python.exitCode !== ts.exitCode,
124
+ stdoutMismatch: python.stdout !== ts.stdout,
125
+ stderrMismatch: python.stderr !== ts.stderr,
126
+ sideEffectMismatch,
127
+ pythonExit: python.exitCode,
128
+ tsExit: ts.exitCode,
129
+ pythonStdout: python.stdout,
130
+ pythonStderr: python.stderr,
131
+ tsStdout: ts.stdout,
132
+ tsStderr: ts.stderr,
133
+ };
134
+ }
135
+ export function runParity() {
136
+ const deftRoot = resolveDeftRoot();
137
+ const diffs = [];
138
+ for (const testCase of PARITY_CASES) {
139
+ const pySetup = setupRepo(testCase);
140
+ const tsSetup = testCase.mutates === true ? setupRepo(testCase) : pySetup;
141
+ try {
142
+ const py = runPython(deftRoot, pySetup.vbriefPath);
143
+ const ts = runTs(deftRoot, tsSetup.vbriefPath);
144
+ let sideEffectMismatch = false;
145
+ if (testCase.mutates === true && py.exitCode === 0 && ts.exitCode === 0) {
146
+ const pyBody = stripUpdatedField(pySetup.repo);
147
+ const tsBody = stripUpdatedField(tsSetup.repo);
148
+ sideEffectMismatch = pyBody !== tsBody;
149
+ if (pyBody !== null && tsBody !== null) {
150
+ const pyPendingGone = !existsSync(pySetup.vbriefPath);
151
+ const tsPendingGone = !existsSync(tsSetup.vbriefPath);
152
+ sideEffectMismatch =
153
+ sideEffectMismatch ||
154
+ pyPendingGone !== tsPendingGone ||
155
+ !pyPendingGone ||
156
+ !tsPendingGone;
157
+ }
158
+ }
159
+ diffs.push(diffCase(py, ts, testCase.name, sideEffectMismatch));
160
+ }
161
+ finally {
162
+ rmSync(pySetup.repo, { recursive: true, force: true });
163
+ if (tsSetup.repo !== pySetup.repo) {
164
+ rmSync(tsSetup.repo, { recursive: true, force: true });
165
+ }
166
+ }
167
+ }
168
+ const ok = diffs.every((d) => !d.exitMismatch && !d.stdoutMismatch && !d.stderrMismatch && !d.sideEffectMismatch);
169
+ return { ok, diffs };
170
+ }
171
+ export function renderReport(result) {
172
+ if (result.ok) {
173
+ return `vbrief-activate parity: CLEAN -- Python and TS agree on ${result.diffs.length} scenario(s).`;
174
+ }
175
+ const lines = ["vbrief-activate parity: DIVERGENCE"];
176
+ for (const d of result.diffs) {
177
+ if (d.exitMismatch || d.stdoutMismatch || d.stderrMismatch || d.sideEffectMismatch) {
178
+ lines.push(` scenario: ${d.caseName}`);
179
+ if (d.exitMismatch) {
180
+ lines.push(` exit mismatch: python=${d.pythonExit} ts=${d.tsExit}`);
181
+ }
182
+ if (d.stdoutMismatch) {
183
+ lines.push(` python stdout: ${JSON.stringify(d.pythonStdout)}`);
184
+ lines.push(` ts stdout: ${JSON.stringify(d.tsStdout)}`);
185
+ }
186
+ if (d.stderrMismatch) {
187
+ lines.push(` python stderr: ${JSON.stringify(d.pythonStderr)}`);
188
+ lines.push(` ts stderr: ${JSON.stringify(d.tsStderr)}`);
189
+ }
190
+ if (d.sideEffectMismatch) {
191
+ lines.push(" side-effect mismatch (active/ payload or source removal)");
192
+ }
193
+ }
194
+ }
195
+ return lines.join("\n");
196
+ }
197
+ export function runParityCli() {
198
+ try {
199
+ const result = runParity();
200
+ if (result.ok) {
201
+ process.stdout.write(`${renderReport(result)}\n`);
202
+ return 0;
203
+ }
204
+ process.stderr.write(`${renderReport(result)}\n`);
205
+ return 1;
206
+ }
207
+ catch (err) {
208
+ const msg = String(err).replace(/\r?\n/g, " ");
209
+ process.stderr.write(`vbrief-activate parity: harness error -- ${msg}\n`);
210
+ return 2;
211
+ }
212
+ }
213
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
214
+ process.exit(runParityCli());
215
+ }
216
+ //# sourceMappingURL=vbrief-activate-parity.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=vbrief-activate.d.ts.map
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import { run } from "@deftai/directive-core/vbrief-activate";
4
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
5
+ process.exit(run(process.argv.slice(2)));
6
+ }
7
+ //# sourceMappingURL=vbrief-activate.js.map
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+ export interface ScenarioResult {
3
+ readonly name: string;
4
+ readonly exitCode: number;
5
+ readonly stdout: string;
6
+ readonly stderr: string;
7
+ }
8
+ export interface ParityResult {
9
+ readonly ok: boolean;
10
+ readonly scenarios: Array<{
11
+ readonly name: string;
12
+ readonly exitMismatch: boolean;
13
+ readonly pythonExit: number;
14
+ readonly tsExit: number;
15
+ readonly outputMismatch: boolean;
16
+ readonly pythonOutput: string;
17
+ readonly tsOutput: string;
18
+ }>;
19
+ }
20
+ export declare function diffParity(python: ScenarioResult, ts: ScenarioResult): {
21
+ exitMismatch: boolean;
22
+ outputMismatch: boolean;
23
+ pythonOutput: string;
24
+ tsOutput: string;
25
+ };
26
+ export declare function runParity(): ParityResult;
27
+ export declare function renderReport(result: ParityResult): string;
28
+ //# sourceMappingURL=vbrief-build-parity.d.ts.map
@@ -0,0 +1,399 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Golden-output parity harness (#1782 s1): runs BOTH the Python oracle
4
+ * (frozen ``scripts/_vbrief_*.py`` helpers via an inline driver) and the
5
+ * ported TS vbrief-build CLI over shared fixtures, then diffs exit codes and
6
+ * byte-identical stdout/stderr (cache-off).
7
+ *
8
+ * Exit codes: 0 parity / 1 divergence / 2 harness setup error.
9
+ */
10
+ import { spawnSync } from "node:child_process";
11
+ import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { dirname, join, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { PARITY_SCENARIO_NAMES } from "@deftai/directive-core/vbrief-build";
16
+ const PYTHON_DRIVER = String.raw `import json, os, sys
17
+ from pathlib import Path
18
+
19
+ ROOT = Path(os.environ["DEFT_ROOT"])
20
+ sys.path.insert(0, str(ROOT / "scripts"))
21
+
22
+ from _vbrief_build import create_scope_vbrief, reference_with_default_trust, slugify
23
+ from _vbrief_sources import (
24
+ derive_overview_narrative,
25
+ extract_tech_stack,
26
+ first_prose_paragraph,
27
+ parse_roadmap_items,
28
+ resolve_repo_url,
29
+ )
30
+ from _vbrief_routing import build_scope_vbrief_from_reconciled, folder_for_status, plan_status_matches_folder
31
+ from _vbrief_speckit import (
32
+ create_speckit_scope_vbrief,
33
+ dependencies_for_item,
34
+ edge_nodes,
35
+ migrate_speckit_plan,
36
+ speckit_ip_index,
37
+ speckit_ip_slug,
38
+ )
39
+ from _project_definition_io import (
40
+ atomic_write_project_definition,
41
+ load_project_definition_for_mutation,
42
+ project_definition_mutation_lock,
43
+ )
44
+
45
+ SAMPLE_ROADMAP_MD = """# Roadmap
46
+
47
+ ## Phase 1 -- Foundation
48
+
49
+ - **#100** -- Add widget support
50
+ - **#101** -- Fix login bug
51
+
52
+ ## Phase 2 -- Features
53
+
54
+ - **#200** -- Dashboard redesign
55
+
56
+ ## Completed
57
+
58
+ - ~~#50 -- Initial setup~~
59
+ """
60
+
61
+ SAMPLE_SPEC_VBRIEF = {
62
+ "vBRIEFInfo": {"version": "0.5", "description": "Test spec"},
63
+ "plan": {
64
+ "title": "Test Specification",
65
+ "status": "approved",
66
+ "narratives": {
67
+ "Overview": "A test project for migration.",
68
+ "Architecture": "Simple architecture.",
69
+ },
70
+ "items": [{"id": "t1.1", "title": "Task one", "status": "completed"}],
71
+ },
72
+ }
73
+
74
+ SAMPLE_PROJECT_MD = (
75
+ "# Test Project\n\n"
76
+ "## Project Configuration\n\n"
77
+ "**Tech Stack**: Python\n\n"
78
+ "**Generated by**: deft-setup skill\n"
79
+ "**Date**: 2026-04-01\n"
80
+ )
81
+
82
+ SAMPLE_SPEC_MD = (
83
+ "# Test Specification\n\n"
84
+ "A test specification.\n\n"
85
+ "**Generated by**: spec_render.py\n"
86
+ )
87
+
88
+ def reconciled_base(**overrides):
89
+ base = {
90
+ "task_id": "#99",
91
+ "number": "99",
92
+ "title": "Widget feature",
93
+ "description": "Add a widget.",
94
+ "description_source": "SPECIFICATION.md",
95
+ "status": "pending",
96
+ "status_source": "default",
97
+ "folder": "pending",
98
+ "phase": "Phase 1",
99
+ "phase_description": "",
100
+ "tier": "",
101
+ "spec_phase": "",
102
+ "roadmap_summary": "",
103
+ "source_conflict": "",
104
+ "title_source": "",
105
+ "override_applied": False,
106
+ "synthetic_id": "",
107
+ "original_task_id": "",
108
+ }
109
+ base.update(overrides)
110
+ return base
111
+
112
+ def dump(obj):
113
+ payload = json.dumps(obj, indent=2, ensure_ascii=False, sort_keys=False)
114
+ if not payload.endswith("\n"):
115
+ payload += "\n"
116
+ sys.stdout.write(payload)
117
+
118
+ def run_scenario(name, fixture_root):
119
+ fixture = Path(fixture_root)
120
+ if name == "slugify-basic":
121
+ return {"scenario": name, "ok": True, "payload": {
122
+ "hello": slugify("Hello World"),
123
+ "special": slugify("Add widget (v2)!"),
124
+ "underscores": slugify("task_id_foo"),
125
+ }}
126
+ if name == "create-scope-vbrief":
127
+ return {"scenario": name, "ok": True, "payload": create_scope_vbrief(
128
+ {"number": "99", "title": "Test feature", "phase": "Phase 1", "tier": "Tier 1"},
129
+ repo_url="https://github.com/owner/repo",
130
+ )}
131
+ if name == "reference-trust-levels":
132
+ return {"scenario": name, "ok": True, "payload": {
133
+ "internal": reference_with_default_trust({"type": "x-vbrief/plan", "uri": "specification.vbrief.json"}),
134
+ "external": reference_with_default_trust({"type": "x-vbrief/github-issue", "uri": "https://github.com/o/r/issues/1", "title": "Issue #1"}),
135
+ "preset": reference_with_default_trust({"type": "x-vbrief/github-issue", "uri": "https://github.com/o/r/issues/1", "title": "Issue #1", "TrustLevel": "internal"}),
136
+ }}
137
+ if name == "parse-roadmap":
138
+ roadmap = fixture / "ROADMAP.md"
139
+ roadmap.write_text(SAMPLE_ROADMAP_MD, encoding="utf-8")
140
+ items, phase_descs, completed = parse_roadmap_items(roadmap)
141
+ return {"scenario": name, "ok": True, "payload": {"items": items, "phaseDescriptions": phase_descs, "completedItems": completed}}
142
+ if name == "resolve-repo-url":
143
+ return {"scenario": name, "ok": True, "payload": {
144
+ "fromRepository": resolve_repo_url({"vBRIEFInfo": {"repository": "owner/repo"}}),
145
+ "fromRef": resolve_repo_url({"plan": {"references": [{"uri": "https://github.com/acme/widget/issues/1"}]}}),
146
+ "empty": resolve_repo_url(None),
147
+ }}
148
+ if name == "extract-tech-stack":
149
+ return {"scenario": name, "ok": True, "payload": extract_tech_stack(SAMPLE_PROJECT_MD)}
150
+ if name == "first-prose-paragraph":
151
+ return {"scenario": name, "ok": True, "payload": first_prose_paragraph(SAMPLE_SPEC_MD)}
152
+ if name == "derive-overview":
153
+ return {"scenario": name, "ok": True, "payload": {
154
+ "fromSpec": derive_overview_narrative(SAMPLE_SPEC_VBRIEF, None, None, 0),
155
+ "fromSpecMd": derive_overview_narrative(None, SAMPLE_SPEC_MD, None, 0),
156
+ "placeholder": derive_overview_narrative(None, None, None, 3),
157
+ }}
158
+ if name == "routing-map":
159
+ return {"scenario": name, "ok": True, "payload": {
160
+ "running": folder_for_status("running"),
161
+ "matches": plan_status_matches_folder("running", "active"),
162
+ }}
163
+ if name == "build-reconciled":
164
+ return {"scenario": name, "ok": True, "payload": build_scope_vbrief_from_reconciled(
165
+ reconciled_base(status="completed", folder="completed", source_section="ROADMAP Completed section"),
166
+ repo_url="https://github.com/acme/widget",
167
+ migration_timestamp="2026-04-23T00:00:00Z",
168
+ )}
169
+ if name == "speckit-edge-deps":
170
+ return {"scenario": name, "ok": True, "payload": {
171
+ "nodes": list(edge_nodes({"from": "a", "to": "b"})),
172
+ "legacy": list(edge_nodes({"source": "x", "target": "y"})),
173
+ "deps": dependencies_for_item("ip-2", [
174
+ {"type": "blocks", "from": "ip-1", "to": "ip-2"},
175
+ {"type": "blocks", "source": "ip-0", "target": "ip-2"},
176
+ ]),
177
+ "slug": speckit_ip_slug("IP-1: Widget phase", "ip-1"),
178
+ "index": speckit_ip_index({"id": "phase-ip-3", "title": "IP 3: Build"}, 9),
179
+ }}
180
+ if name == "create-speckit-scope":
181
+ return {"scenario": name, "ok": True, "payload": create_speckit_scope_vbrief(
182
+ {"title": "IP-1: Foundation", "narrative": {"Description": "Build the foundation.", "Acceptance": "Tests pass.", "Traces": "REQ-1"}},
183
+ ip_index=1,
184
+ dependencies=["ip-0"],
185
+ spec_ref="../specification.vbrief.json",
186
+ )}
187
+ if name == "project-definition-roundtrip":
188
+ vbrief = fixture / "vbrief"
189
+ vbrief.mkdir(parents=True, exist_ok=True)
190
+ seed = {
191
+ "vBRIEFInfo": {"version": "0.6"},
192
+ "plan": {"title": "Parity project", "status": "running", "policy": {"wipCap": 10}, "items": []},
193
+ }
194
+ pd = vbrief / "PROJECT-DEFINITION.vbrief.json"
195
+ pd.write_text(json.dumps(seed, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
196
+ roundtrip = {}
197
+ with project_definition_mutation_lock(fixture):
198
+ data, path = load_project_definition_for_mutation(fixture)
199
+ data["plan"]["policy"] = {"wipCap": 12}
200
+ atomic_write_project_definition(path, data)
201
+ roundtrip = json.loads(path.read_text(encoding="utf-8"))
202
+ return {"scenario": name, "ok": True, "payload": roundtrip}
203
+ if name == "migrate-speckit-plan":
204
+ vbrief = fixture / "vbrief"
205
+ vbrief.mkdir(parents=True, exist_ok=True)
206
+ plan_path = vbrief / "plan.vbrief.json"
207
+ plan_fixture = {
208
+ "vBRIEFInfo": {"version": "0.5", "description": "Speckit plan"},
209
+ "plan": {
210
+ "title": "Session",
211
+ "items": [{"id": "ip-1", "title": "IP-1: Foundation", "narrative": {"Description": "Build it."}}],
212
+ "edges": [],
213
+ },
214
+ }
215
+ plan_path.write_text(json.dumps(plan_fixture, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
216
+ ok, actions = migrate_speckit_plan(plan_path, pending_dir=vbrief / "pending", today="2026-04-23")
217
+ pending_content = None
218
+ pending_path = vbrief / "pending" / "2026-04-23-ip001-foundation.vbrief.json"
219
+ if pending_path.is_file():
220
+ pending_content = json.loads(pending_path.read_text(encoding="utf-8"))
221
+ rewritten = json.loads(plan_path.read_text(encoding="utf-8"))
222
+ return {"scenario": name, "ok": ok, "payload": {"actions": actions, "pendingContent": pending_content, "rewrittenPlan": rewritten, "pendingFiles": ["pending"]}}
223
+ if name == "extract-tech-stack-eos":
224
+ return {"scenario": name, "ok": True, "payload": {
225
+ "endOfString": extract_tech_stack("## Tech Stack\nRust + TypeScript"),
226
+ "trailingNewline": extract_tech_stack("## Tech Stack\nRust + TypeScript\n"),
227
+ "followedByHeading": extract_tech_stack("## Tech Stack\nRust\n## Next\nmore"),
228
+ "multiLine": extract_tech_stack("## Tech Stack\nRust\nVitest\n"),
229
+ }}
230
+ if name == "speckit-ip-index-edge":
231
+ return {"scenario": name, "ok": True, "payload": {
232
+ "longDigits": speckit_ip_index({"id": "12345678901234"}, 1),
233
+ "trailingWhitespace": speckit_ip_index({"id": "ip-7 "}, 1),
234
+ "trailingNewline": speckit_ip_index({"id": "ip-8\n"}, 1),
235
+ "titleFallback": speckit_ip_index({"id": "phase", "title": "IP-3: Build"}, 9),
236
+ "noDigits": speckit_ip_index({"id": "phase-x", "title": "no ip"}, 5),
237
+ }}
238
+ if name == "repo-url-trailing-slashes":
239
+ return {"scenario": name, "ok": True, "payload": {
240
+ "single": create_scope_vbrief({"number": "5", "title": "T"}, repo_url="https://github.com/o/r/"),
241
+ "multiple": create_scope_vbrief({"number": "6", "title": "U"}, repo_url="https://github.com/o/r///"),
242
+ "spaced": create_scope_vbrief({"number": "7", "title": "V"}, repo_url=" https://github.com/o/r// "),
243
+ }}
244
+ return {"scenario": name, "ok": False, "payload": {"error": f"unknown scenario: {name}"}}
245
+
246
+ def main():
247
+ mode = sys.argv[1] if len(sys.argv) > 1 else ""
248
+ fixture_root = os.environ.get("DEFT_VBRIEF_BUILD_FIXTURE", "")
249
+ if mode == "--all":
250
+ results = [run_scenario(name, fixture_root) for name in json.loads(os.environ["DEFT_VBRIEF_BUILD_SCENARIOS"])]
251
+ dump(results)
252
+ return 0
253
+ if mode == "--scenario":
254
+ name = sys.argv[2]
255
+ dump(run_scenario(name, fixture_root))
256
+ return 0
257
+ sys.stderr.write("usage: driver --all | --scenario NAME\n")
258
+ return 2
259
+
260
+ if __name__ == "__main__":
261
+ raise SystemExit(main())
262
+ `;
263
+ function runCapture(cmd, args, cwd, env) {
264
+ const result = spawnSync(cmd, args, {
265
+ cwd,
266
+ encoding: "utf8",
267
+ env: { ...process.env, ...env },
268
+ stdio: ["ignore", "pipe", "pipe"],
269
+ });
270
+ return {
271
+ status: result.status ?? 2,
272
+ stdout: typeof result.stdout === "string" ? result.stdout : "",
273
+ stderr: typeof result.stderr === "string" ? result.stderr : "",
274
+ };
275
+ }
276
+ function resolveDeftRoot() {
277
+ if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
278
+ return resolve(process.env.DEFT_ROOT);
279
+ }
280
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
281
+ }
282
+ function normaliseHarnessNoise(text) {
283
+ return text
284
+ .split("\n")
285
+ .filter((line) => !line.startsWith("Using CPython") &&
286
+ !line.startsWith("Creating virtual environment") &&
287
+ !line.startsWith("Installed "))
288
+ .join("\n");
289
+ }
290
+ export function diffParity(python, ts) {
291
+ const pythonOutput = normaliseHarnessNoise(python.stdout);
292
+ const tsOutput = normaliseHarnessNoise(ts.stdout);
293
+ return {
294
+ exitMismatch: python.exitCode !== ts.exitCode,
295
+ outputMismatch: pythonOutput !== tsOutput,
296
+ pythonOutput,
297
+ tsOutput,
298
+ };
299
+ }
300
+ function installPythonDriver(_deftRoot) {
301
+ const dir = mkdtempSync(join(tmpdir(), "deft-vbrief-build-py-driver-"));
302
+ const driverPath = join(dir, "vbrief_build_parity_driver.py");
303
+ writeFileSync(driverPath, PYTHON_DRIVER, "utf8");
304
+ chmodSync(driverPath, 0o755);
305
+ return {
306
+ driverPath,
307
+ cleanup: () => rmSync(dir, { recursive: true, force: true }),
308
+ };
309
+ }
310
+ function runScenario(deftRoot, driverPath, name) {
311
+ const pyFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-build-parity-py-"));
312
+ const tsFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-build-parity-ts-"));
313
+ const envBase = {
314
+ DEFT_CACHE_DISABLE: "1",
315
+ PYTHONUTF8: "1",
316
+ DEFT_ROOT: deftRoot,
317
+ DEFT_VBRIEF_BUILD_SCENARIOS: JSON.stringify(PARITY_SCENARIO_NAMES),
318
+ };
319
+ try {
320
+ const py = runCapture("uv", ["run", "python", driverPath, "--scenario", name], deftRoot, {
321
+ ...envBase,
322
+ DEFT_VBRIEF_BUILD_FIXTURE: pyFixture,
323
+ });
324
+ const ts = runCapture("node", [
325
+ join(deftRoot, "packages", "cli", "dist", "vbrief-build.js"),
326
+ "--scenario",
327
+ name,
328
+ "--fixture-root",
329
+ tsFixture,
330
+ ], deftRoot, envBase);
331
+ return {
332
+ python: { name, exitCode: py.status, stdout: py.stdout, stderr: py.stderr },
333
+ ts: { name, exitCode: ts.status, stdout: ts.stdout, stderr: ts.stderr },
334
+ };
335
+ }
336
+ finally {
337
+ rmSync(pyFixture, { recursive: true, force: true });
338
+ rmSync(tsFixture, { recursive: true, force: true });
339
+ }
340
+ }
341
+ export function runParity() {
342
+ const deftRoot = resolveDeftRoot();
343
+ const driver = installPythonDriver(deftRoot);
344
+ const scenarios = [];
345
+ try {
346
+ for (const name of PARITY_SCENARIO_NAMES) {
347
+ const ran = runScenario(deftRoot, driver.driverPath, name);
348
+ scenarios.push({
349
+ name,
350
+ pythonExit: ran.python.exitCode,
351
+ tsExit: ran.ts.exitCode,
352
+ ...diffParity(ran.python, ran.ts),
353
+ });
354
+ }
355
+ }
356
+ finally {
357
+ driver.cleanup();
358
+ }
359
+ const ok = scenarios.every((s) => !s.exitMismatch && !s.outputMismatch);
360
+ return { ok, scenarios };
361
+ }
362
+ export function renderReport(result) {
363
+ if (result.ok) {
364
+ return `vbrief_build parity: CLEAN -- Python and TS agree on ${result.scenarios.length} scenario(s).`;
365
+ }
366
+ const lines = ["vbrief_build parity: DIVERGENCE"];
367
+ for (const s of result.scenarios) {
368
+ if (s.exitMismatch || s.outputMismatch) {
369
+ lines.push(` scenario: ${s.name}`);
370
+ if (s.exitMismatch) {
371
+ lines.push(` exit mismatch: python=${s.pythonExit} ts=${s.tsExit}`);
372
+ }
373
+ if (s.outputMismatch) {
374
+ lines.push(` python (${s.pythonOutput.length} bytes):`);
375
+ lines.push(s.pythonOutput.slice(0, 500));
376
+ lines.push(` ts (${s.tsOutput.length} bytes):`);
377
+ lines.push(s.tsOutput.slice(0, 500));
378
+ }
379
+ }
380
+ }
381
+ return lines.join("\n");
382
+ }
383
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
384
+ try {
385
+ const result = runParity();
386
+ if (result.ok) {
387
+ process.stdout.write(`${renderReport(result)}\n`);
388
+ process.exit(0);
389
+ }
390
+ process.stderr.write(`${renderReport(result)}\n`);
391
+ process.exit(1);
392
+ }
393
+ catch (err) {
394
+ const msg = String(err).replace(/\r?\n/g, " ");
395
+ process.stderr.write(`vbrief_build parity: harness error -- ${msg}\n`);
396
+ process.exit(2);
397
+ }
398
+ }
399
+ //# sourceMappingURL=vbrief-build-parity.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=vbrief-build.d.ts.map
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /** Thin CLI seam for vbrief-build parity scenarios (#1782 s1). */
3
+ import { fileURLToPath } from "node:url";
4
+ import { cmdVbriefBuild } from "@deftai/directive-core/vbrief-build";
5
+ if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
6
+ process.exit(cmdVbriefBuild(process.argv.slice(2)));
7
+ }
8
+ //# sourceMappingURL=vbrief-build.js.map