@deftai/directive 0.62.0 → 0.64.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 (99) hide show
  1. package/dist/{branch-parity.d.ts → branch-fixtures.d.ts} +1 -3
  2. package/dist/{branch-parity.js → branch-fixtures.js} +3 -110
  3. package/dist/dispatch.d.ts +1 -1
  4. package/dist/dispatch.js +3 -1
  5. package/dist/orchestration-cli/coverage-map.js +1 -1
  6. package/dist/{policy-parity.d.ts → policy-fixtures.d.ts} +1 -3
  7. package/dist/{policy-parity.js → policy-fixtures.js} +4 -100
  8. package/dist/{release-e2e-parity.d.ts → release-e2e-fixtures.d.ts} +1 -3
  9. package/dist/release-e2e-fixtures.js +38 -0
  10. package/dist/{story-ready-parity.d.ts → story-ready-fixtures.d.ts} +1 -3
  11. package/dist/{story-ready-parity.js → story-ready-fixtures.js} +4 -121
  12. package/dist/{triage-aux-a-parity.d.ts → triage-aux-a-fixtures.d.ts} +1 -3
  13. package/dist/{triage-aux-a-parity.js → triage-aux-a-fixtures.js} +3 -73
  14. package/dist/{triage-aux-b-parity.d.ts → triage-aux-b-fixtures.d.ts} +1 -3
  15. package/dist/triage-aux-b-fixtures.js +167 -0
  16. package/dist/{triage-bootstrap-parity.d.ts → triage-bootstrap-fixtures.d.ts} +1 -3
  17. package/dist/{triage-bootstrap-parity.js → triage-bootstrap-fixtures.js} +4 -91
  18. package/dist/{triage-classify-parity.d.ts → triage-classify-fixtures.d.ts} +1 -3
  19. package/dist/{triage-classify-parity.js → triage-classify-fixtures.js} +4 -94
  20. package/dist/{triage-queue-parity.d.ts → triage-queue-fixtures.d.ts} +1 -3
  21. package/dist/{triage-queue-parity.js → triage-queue-fixtures.js} +4 -86
  22. package/dist/{triage-scope-parity.d.ts → triage-scope-fixtures.d.ts} +1 -3
  23. package/dist/{triage-scope-parity.js → triage-scope-fixtures.js} +4 -91
  24. package/dist/{vbrief-preflight-parity.d.ts → vbrief-preflight-fixtures.d.ts} +1 -3
  25. package/dist/vbrief-preflight-fixtures.js +79 -0
  26. package/dist/verify-source-cli/verify-cursor-tier1.d.ts +12 -0
  27. package/dist/verify-source-cli/verify-cursor-tier1.js +51 -0
  28. package/dist/{wip-cap-parity.d.ts → wip-cap-fixtures.d.ts} +1 -3
  29. package/dist/{wip-cap-parity.js → wip-cap-fixtures.js} +4 -91
  30. package/package.json +4 -15
  31. package/dist/cache-parity.d.ts +0 -36
  32. package/dist/cache-parity.js +0 -165
  33. package/dist/codebase-parity.d.ts +0 -31
  34. package/dist/codebase-parity.js +0 -303
  35. package/dist/doc-cli-parity.d.ts +0 -29
  36. package/dist/doc-cli-parity.js +0 -159
  37. package/dist/doctor-parity.d.ts +0 -42
  38. package/dist/doctor-parity.js +0 -157
  39. package/dist/intake-parity.d.ts +0 -30
  40. package/dist/intake-parity.js +0 -203
  41. package/dist/lifecycle-packs-parity.d.ts +0 -30
  42. package/dist/lifecycle-packs-parity.js +0 -377
  43. package/dist/orchestration-parity.d.ts +0 -38
  44. package/dist/orchestration-parity.js +0 -364
  45. package/dist/parity.d.ts +0 -36
  46. package/dist/parity.js +0 -176
  47. package/dist/platform-parity.d.ts +0 -26
  48. package/dist/platform-parity.js +0 -309
  49. package/dist/pr-closing-keywords-parity.d.ts +0 -45
  50. package/dist/pr-closing-keywords-parity.js +0 -259
  51. package/dist/pr-merge-readiness-parity.d.ts +0 -44
  52. package/dist/pr-merge-readiness-parity.js +0 -296
  53. package/dist/pr-monitor-parity.d.ts +0 -44
  54. package/dist/pr-monitor-parity.js +0 -283
  55. package/dist/pr-protected-issues-parity.d.ts +0 -41
  56. package/dist/pr-protected-issues-parity.js +0 -220
  57. package/dist/pr-wait-mergeable-parity.d.ts +0 -45
  58. package/dist/pr-wait-mergeable-parity.js +0 -340
  59. package/dist/release-e2e-parity.js +0 -114
  60. package/dist/release-parity.d.ts +0 -40
  61. package/dist/release-parity.js +0 -226
  62. package/dist/release-publish-parity.d.ts +0 -36
  63. package/dist/release-publish-parity.js +0 -138
  64. package/dist/release-rollback-parity.d.ts +0 -37
  65. package/dist/release-rollback-parity.js +0 -161
  66. package/dist/render-parity.d.ts +0 -36
  67. package/dist/render-parity.js +0 -385
  68. package/dist/scm-parity.d.ts +0 -39
  69. package/dist/scm-parity.js +0 -181
  70. package/dist/scope-lifecycle-parity.d.ts +0 -35
  71. package/dist/scope-lifecycle-parity.js +0 -177
  72. package/dist/session-parity.d.ts +0 -39
  73. package/dist/session-parity.js +0 -262
  74. package/dist/slice-parity.d.ts +0 -36
  75. package/dist/slice-parity.js +0 -304
  76. package/dist/swarm-parity.d.ts +0 -28
  77. package/dist/swarm-parity.js +0 -327
  78. package/dist/triage-actions-parity.d.ts +0 -36
  79. package/dist/triage-actions-parity.js +0 -357
  80. package/dist/triage-aux-b-parity.js +0 -308
  81. package/dist/triage-summary-parity.d.ts +0 -50
  82. package/dist/triage-summary-parity.js +0 -306
  83. package/dist/validate-content-parity.d.ts +0 -33
  84. package/dist/validate-content-parity.js +0 -356
  85. package/dist/vbrief-activate-parity.d.ts +0 -39
  86. package/dist/vbrief-activate-parity.js +0 -216
  87. package/dist/vbrief-build-parity.d.ts +0 -28
  88. package/dist/vbrief-build-parity.js +0 -399
  89. package/dist/vbrief-preflight-parity.js +0 -163
  90. package/dist/vbrief-reconcile-parity.d.ts +0 -23
  91. package/dist/vbrief-reconcile-parity.js +0 -609
  92. package/dist/vbrief-validate-parity.d.ts +0 -27
  93. package/dist/vbrief-validate-parity.js +0 -122
  94. package/dist/vbrief-validation-parity.d.ts +0 -28
  95. package/dist/vbrief-validation-parity.js +0 -645
  96. package/dist/verify-env-parity.d.ts +0 -28
  97. package/dist/verify-env-parity.js +0 -272
  98. package/dist/verify-source-parity.d.ts +0 -26
  99. package/dist/verify-source-parity.js +0 -178
@@ -1,356 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Golden-output parity harness (#1783 s3): runs BOTH the Python oracles
4
- * (`validate-links.py`, `validate_strategy_output.py`, `verify_capacity.py`)
5
- * and the ported TS validate-content gates over shared fixtures, then diffs
6
- * exit codes + stdout/stderr (cache-off).
7
- *
8
- * Exit codes: 0 parity / 1 divergence / 2 harness setup error.
9
- */
10
- import { execFileSync } from "node:child_process";
11
- import { mkdirSync, 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 { validateLinks, validateStrategyOutput, verifyCapacity, } from "@deftai/directive-core/validate-content";
16
- export function normalizeOutput(text) {
17
- return text.replace(/verify_capacity: --project-root is not a directory: [^\n]+/g, "verify_capacity: --project-root is not a directory: <ROOT>");
18
- }
19
- const NOW_ISO = "2026-06-04T12:00:00Z";
20
- function runPythonGate(deftRoot, gate, repo, argv, env) {
21
- const scriptsDir = join(deftRoot, "scripts").replace(/\\/g, "/");
22
- const repoPath = repo.replace(/\\/g, "/");
23
- let code;
24
- if (gate === "validate-links") {
25
- const strict = argv.includes("--strict") || env.LINK_CHECK_STRICT === "1";
26
- code = [
27
- "import sys,os,importlib.util",
28
- `sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
29
- `os.chdir(${JSON.stringify(repoPath)})`,
30
- strict ? "os.environ['LINK_CHECK_STRICT']='1'" : "os.environ.pop('LINK_CHECK_STRICT', None)",
31
- `spec=importlib.util.spec_from_file_location('validate_links', ${JSON.stringify(join(deftRoot, "scripts", "validate-links.py"))})`,
32
- "mod=importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)",
33
- `sys.argv = ['validate-links.py'] + ${JSON.stringify(argv)}`,
34
- "raise SystemExit(mod.main())",
35
- ].join(";");
36
- }
37
- else if (gate === "validate-strategy-output") {
38
- code = [
39
- "import sys",
40
- `sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
41
- "from validate_strategy_output import main as m",
42
- `raise SystemExit(m([${["--project-root", repoPath, ...argv].map((a) => JSON.stringify(a)).join(",")}]))`,
43
- ].join(";");
44
- }
45
- else {
46
- code = [
47
- "import sys",
48
- "from datetime import datetime",
49
- "from pathlib import Path",
50
- `sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
51
- "from verify_capacity import evaluate as ev",
52
- `root = Path(${JSON.stringify(repoPath)})`,
53
- `now = datetime.fromisoformat(${JSON.stringify(NOW_ISO)}.replace("Z", "+00:00"))`,
54
- "code, msg = ev(root, now=now)",
55
- "stream = sys.stdout if code == 0 else sys.stderr",
56
- "stream.write(msg)",
57
- 'if not msg.endswith("\\n"):',
58
- ' stream.write("\\n")',
59
- "raise SystemExit(code)",
60
- ].join("\n");
61
- }
62
- try {
63
- const stdout = execFileSync("uv", ["run", "python", "-c", code], {
64
- cwd: deftRoot,
65
- encoding: "utf8",
66
- env: { ...process.env, ...env, DEFT_CACHE_DISABLE: "1", PYTHONUTF8: "1" },
67
- stdio: ["ignore", "pipe", "pipe"],
68
- });
69
- return { exitCode: 0, stdout: typeof stdout === "string" ? stdout : "", stderr: "" };
70
- }
71
- catch (err) {
72
- const e = err;
73
- return {
74
- exitCode: e.status ?? 2,
75
- stdout: typeof e.stdout === "string" ? e.stdout : "",
76
- stderr: typeof e.stderr === "string" ? e.stderr : "",
77
- };
78
- }
79
- }
80
- function runTsGate(gate, repo, testCase) {
81
- const argv = testCase.argv ?? [];
82
- const env = testCase.env ?? {};
83
- const now = testCase.now ? new Date(testCase.now) : new Date(NOW_ISO);
84
- let result;
85
- if (gate === "validate-links") {
86
- result = validateLinks.evaluate({
87
- cwd: repo,
88
- argv,
89
- linkCheckStrict: env.LINK_CHECK_STRICT === "1",
90
- strict: argv.includes("--strict"),
91
- });
92
- }
93
- else if (gate === "validate-strategy-output") {
94
- result = validateStrategyOutput.evaluate({
95
- projectRoot: repo,
96
- strict: argv.includes("--strict"),
97
- quiet: argv.includes("--quiet"),
98
- });
99
- }
100
- else {
101
- result = verifyCapacity.evaluate({ projectRoot: repo, now });
102
- }
103
- const stream = result.stream;
104
- return {
105
- exitCode: result.code,
106
- stdout: stream === "stdout" ? `${result.message}\n` : "",
107
- stderr: stream === "stderr" ? `${result.message}\n` : "",
108
- };
109
- }
110
- function writeJson(path, data) {
111
- writeFileSync(path, `${JSON.stringify(data)}\n`, { encoding: "utf8" });
112
- }
113
- function makeLifecycle(root) {
114
- for (const folder of ["proposed", "pending", "active", "completed", "cancelled"]) {
115
- mkdirSync(join(root, "vbrief", folder), { recursive: true });
116
- }
117
- }
118
- export const PARITY_CASES = [
119
- {
120
- gate: "validate-links",
121
- name: "links-clean",
122
- setup: (root) => {
123
- writeFileSync(join(root, "README.md"), "See [guide](guide.md)\n", { encoding: "utf8" });
124
- writeFileSync(join(root, "guide.md"), "# Guide\n", { encoding: "utf8" });
125
- },
126
- },
127
- {
128
- gate: "validate-links",
129
- name: "links-broken-warning",
130
- setup: (root) => {
131
- writeFileSync(join(root, "README.md"), "See [missing](nope.md)\n", { encoding: "utf8" });
132
- },
133
- env: { LINK_CHECK_STRICT: "" },
134
- },
135
- {
136
- gate: "validate-links",
137
- name: "links-broken-strict",
138
- setup: (root) => {
139
- writeFileSync(join(root, "doc.md"), "See [nope](nope.md).\n", { encoding: "utf8" });
140
- },
141
- argv: ["--strict"],
142
- },
143
- {
144
- gate: "validate-strategy-output",
145
- name: "strategy-conformant",
146
- setup: (root) => {
147
- mkdirSync(join(root, "vbrief", "proposed"), { recursive: true });
148
- writeFileSync(join(root, "vbrief", "PROJECT-DEFINITION.vbrief.json"), "{}", {
149
- encoding: "utf8",
150
- });
151
- writeFileSync(join(root, "vbrief", "proposed", "2026-05-26-good.vbrief.json"), "{}", {
152
- encoding: "utf8",
153
- });
154
- },
155
- },
156
- {
157
- gate: "validate-strategy-output",
158
- name: "strategy-missing-projdef",
159
- setup: (root) => {
160
- mkdirSync(join(root, "vbrief", "proposed"), { recursive: true });
161
- writeFileSync(join(root, "vbrief", "proposed", "2026-05-26-good.vbrief.json"), "{}", {
162
- encoding: "utf8",
163
- });
164
- },
165
- },
166
- {
167
- gate: "validate-strategy-output",
168
- name: "strategy-strict-missing-vbrief",
169
- setup: () => {
170
- /* empty root */
171
- },
172
- argv: ["--strict"],
173
- },
174
- {
175
- gate: "verify-capacity",
176
- name: "capacity-unconfigured",
177
- setup: (root) => {
178
- makeLifecycle(root);
179
- writeJson(join(root, "vbrief", "PROJECT-DEFINITION.vbrief.json"), {
180
- vBRIEFInfo: { version: "0.6" },
181
- plan: { title: "T", status: "running", items: [] },
182
- });
183
- },
184
- },
185
- {
186
- gate: "verify-capacity",
187
- name: "capacity-advise-posture",
188
- setup: (root) => {
189
- makeLifecycle(root);
190
- writeJson(join(root, "vbrief", "PROJECT-DEFINITION.vbrief.json"), {
191
- vBRIEFInfo: { version: "0.6" },
192
- plan: {
193
- title: "T",
194
- status: "running",
195
- items: [],
196
- policy: {
197
- capacityAllocation: {
198
- unit: "vbrief-count",
199
- window: 30,
200
- enforcement: "advise",
201
- minSampleSize: 2,
202
- defaultBucket: "feature",
203
- buckets: [
204
- { id: "debt", target: 0.4 },
205
- { id: "feature", target: 0.6 },
206
- ],
207
- },
208
- },
209
- },
210
- });
211
- const completedAt = "2026-06-03T12:00:00Z";
212
- for (let i = 0; i < 4; i += 1) {
213
- writeJson(join(root, "vbrief", "completed", `feat-${i}.vbrief.json`), {
214
- vBRIEFInfo: { version: "0.6" },
215
- plan: {
216
- title: `feat-${i}`,
217
- status: "completed",
218
- items: [],
219
- metadata: { capacityBucket: "feature", completedAt },
220
- },
221
- });
222
- }
223
- },
224
- },
225
- {
226
- gate: "verify-capacity",
227
- name: "capacity-enforce-deficit",
228
- setup: (root) => {
229
- makeLifecycle(root);
230
- writeJson(join(root, "vbrief", "PROJECT-DEFINITION.vbrief.json"), {
231
- vBRIEFInfo: { version: "0.6" },
232
- plan: {
233
- title: "T",
234
- status: "running",
235
- items: [],
236
- policy: {
237
- capacityAllocation: {
238
- unit: "vbrief-count",
239
- window: 30,
240
- enforcement: "enforce",
241
- minSampleSize: 2,
242
- defaultBucket: "feature",
243
- buckets: [
244
- { id: "debt", target: 0.4 },
245
- { id: "feature", target: 0.6 },
246
- ],
247
- },
248
- },
249
- },
250
- });
251
- const completedAt = "2026-06-03T12:00:00Z";
252
- for (let i = 0; i < 4; i += 1) {
253
- writeJson(join(root, "vbrief", "completed", `feat-${i}.vbrief.json`), {
254
- vBRIEFInfo: { version: "0.6" },
255
- plan: {
256
- title: `feat-${i}`,
257
- status: "completed",
258
- items: [],
259
- metadata: { capacityBucket: "feature", completedAt },
260
- },
261
- });
262
- }
263
- },
264
- },
265
- {
266
- gate: "verify-capacity",
267
- name: "capacity-config-error",
268
- setup: (root) => {
269
- writeFileSync(join(root, "not-a-dir.txt"), "x", { encoding: "utf8" });
270
- },
271
- },
272
- ];
273
- export function diffCase(python, ts, gate, caseName) {
274
- const pyOut = normalizeOutput(python.stdout);
275
- const tsOut = normalizeOutput(ts.stdout);
276
- const pyErr = normalizeOutput(python.stderr);
277
- const tsErr = normalizeOutput(ts.stderr);
278
- return {
279
- gate,
280
- caseName,
281
- exitMismatch: python.exitCode !== ts.exitCode,
282
- stdoutMismatch: pyOut !== tsOut,
283
- stderrMismatch: pyErr !== tsErr,
284
- pythonExit: python.exitCode,
285
- tsExit: ts.exitCode,
286
- };
287
- }
288
- function resolveDeftRoot() {
289
- if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
290
- return resolve(process.env.DEFT_ROOT);
291
- }
292
- return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
293
- }
294
- export function runParity() {
295
- const deftRoot = resolveDeftRoot();
296
- const diffs = [];
297
- for (const testCase of PARITY_CASES) {
298
- const pyRepo = mkdtempSync(join(tmpdir(), "deft-vc-parity-py-"));
299
- const tsRepo = mkdtempSync(join(tmpdir(), "deft-vc-parity-ts-"));
300
- try {
301
- testCase.setup(pyRepo);
302
- testCase.setup(tsRepo);
303
- const argv = testCase.argv ?? [];
304
- const env = testCase.env ?? {};
305
- const configErrorRoot = testCase.name === "capacity-config-error" ? join(pyRepo, "not-a-dir.txt") : pyRepo;
306
- const tsConfigRoot = testCase.name === "capacity-config-error" ? join(tsRepo, "not-a-dir.txt") : tsRepo;
307
- const python = testCase.gate === "verify-capacity" && testCase.name === "capacity-config-error"
308
- ? runPythonGate(deftRoot, testCase.gate, configErrorRoot, argv, env)
309
- : runPythonGate(deftRoot, testCase.gate, pyRepo, argv, env);
310
- const ts = testCase.gate === "verify-capacity" && testCase.name === "capacity-config-error"
311
- ? runTsGate(testCase.gate, tsConfigRoot, testCase)
312
- : runTsGate(testCase.gate, tsRepo, testCase);
313
- diffs.push(diffCase(python, ts, testCase.gate, testCase.name));
314
- }
315
- finally {
316
- rmSync(pyRepo, { recursive: true, force: true });
317
- rmSync(tsRepo, { recursive: true, force: true });
318
- }
319
- }
320
- const ok = diffs.every((d) => !d.exitMismatch && !d.stdoutMismatch && !d.stderrMismatch);
321
- return { ok, diffs };
322
- }
323
- export function renderReport(result) {
324
- if (result.ok) {
325
- return `validate-content parity: CLEAN -- Python and TS agree on ${PARITY_CASES.length} cases.`;
326
- }
327
- const lines = ["validate-content parity: DIVERGENCE"];
328
- for (const d of result.diffs) {
329
- if (d.exitMismatch || d.stdoutMismatch || d.stderrMismatch) {
330
- lines.push(` gate: ${d.gate} case: ${d.caseName}`);
331
- if (d.exitMismatch)
332
- lines.push(` exit: python=${d.pythonExit} ts=${d.tsExit}`);
333
- if (d.stdoutMismatch)
334
- lines.push(" stdout mismatch");
335
- if (d.stderrMismatch)
336
- lines.push(" stderr mismatch");
337
- }
338
- }
339
- return lines.join("\n");
340
- }
341
- if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
342
- try {
343
- const result = runParity();
344
- if (result.ok) {
345
- process.stdout.write(`${renderReport(result)}\n`);
346
- process.exit(0);
347
- }
348
- process.stderr.write(`${renderReport(result)}\n`);
349
- process.exit(1);
350
- }
351
- catch (err) {
352
- process.stderr.write(`validate-content parity: harness error -- ${String(err)}\n`);
353
- process.exit(2);
354
- }
355
- }
356
- //# sourceMappingURL=validate-content-parity.js.map
@@ -1,39 +0,0 @@
1
- #!/usr/bin/env node
2
- export interface CommandCapture {
3
- readonly exitCode: number;
4
- readonly stdout: string;
5
- readonly stderr: string;
6
- }
7
- export interface ParityCase {
8
- readonly name: string;
9
- readonly folder: string;
10
- readonly status: string;
11
- readonly mutates?: boolean;
12
- readonly rawOverride?: string;
13
- readonly payloadOverride?: Record<string, unknown>;
14
- readonly preSeedActive?: boolean;
15
- readonly missingPath?: boolean;
16
- }
17
- export interface ParityDiff {
18
- readonly caseName: string;
19
- readonly exitMismatch: boolean;
20
- readonly stdoutMismatch: boolean;
21
- readonly stderrMismatch: boolean;
22
- readonly sideEffectMismatch: boolean;
23
- readonly pythonExit: number;
24
- readonly tsExit: number;
25
- readonly pythonStdout: string;
26
- readonly pythonStderr: string;
27
- readonly tsStdout: string;
28
- readonly tsStderr: string;
29
- }
30
- export interface ParityResult {
31
- readonly ok: boolean;
32
- readonly diffs: ParityDiff[];
33
- }
34
- export declare const PARITY_CASES: readonly ParityCase[];
35
- export declare function diffCase(python: CommandCapture, ts: CommandCapture, caseName: string, sideEffectMismatch: boolean): ParityDiff;
36
- export declare function runParity(): ParityResult;
37
- export declare function renderReport(result: ParityResult): string;
38
- export declare function runParityCli(): number;
39
- //# sourceMappingURL=vbrief-activate-parity.d.ts.map
@@ -1,216 +0,0 @@
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
@@ -1,28 +0,0 @@
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