@deftai/directive 0.61.2 → 0.63.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 (102) 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 +4 -1
  5. package/dist/framework-check-updates.d.ts +10 -0
  6. package/dist/framework-check-updates.js +68 -0
  7. package/dist/install-cli/coverage-map.js +3 -2
  8. package/dist/orchestration-cli/coverage-map.js +1 -1
  9. package/dist/{policy-parity.d.ts → policy-fixtures.d.ts} +1 -3
  10. package/dist/{policy-parity.js → policy-fixtures.js} +4 -100
  11. package/dist/{release-e2e-parity.d.ts → release-e2e-fixtures.d.ts} +1 -3
  12. package/dist/release-e2e-fixtures.js +38 -0
  13. package/dist/{story-ready-parity.d.ts → story-ready-fixtures.d.ts} +1 -3
  14. package/dist/{story-ready-parity.js → story-ready-fixtures.js} +4 -121
  15. package/dist/{triage-aux-a-parity.d.ts → triage-aux-a-fixtures.d.ts} +1 -3
  16. package/dist/{triage-aux-a-parity.js → triage-aux-a-fixtures.js} +3 -73
  17. package/dist/{triage-aux-b-parity.d.ts → triage-aux-b-fixtures.d.ts} +1 -3
  18. package/dist/triage-aux-b-fixtures.js +167 -0
  19. package/dist/{triage-bootstrap-parity.d.ts → triage-bootstrap-fixtures.d.ts} +1 -3
  20. package/dist/{triage-bootstrap-parity.js → triage-bootstrap-fixtures.js} +4 -91
  21. package/dist/{triage-classify-parity.d.ts → triage-classify-fixtures.d.ts} +1 -3
  22. package/dist/{triage-classify-parity.js → triage-classify-fixtures.js} +4 -94
  23. package/dist/{triage-queue-parity.d.ts → triage-queue-fixtures.d.ts} +1 -3
  24. package/dist/{triage-queue-parity.js → triage-queue-fixtures.js} +4 -86
  25. package/dist/{triage-scope-parity.d.ts → triage-scope-fixtures.d.ts} +1 -3
  26. package/dist/{triage-scope-parity.js → triage-scope-fixtures.js} +4 -91
  27. package/dist/umbrella-current-shape.d.ts +9 -0
  28. package/dist/umbrella-current-shape.js +56 -0
  29. package/dist/{vbrief-preflight-parity.d.ts → vbrief-preflight-fixtures.d.ts} +1 -3
  30. package/dist/vbrief-preflight-fixtures.js +79 -0
  31. package/dist/{wip-cap-parity.d.ts → wip-cap-fixtures.d.ts} +1 -3
  32. package/dist/{wip-cap-parity.js → wip-cap-fixtures.js} +4 -91
  33. package/package.json +4 -15
  34. package/dist/cache-parity.d.ts +0 -36
  35. package/dist/cache-parity.js +0 -165
  36. package/dist/codebase-parity.d.ts +0 -31
  37. package/dist/codebase-parity.js +0 -303
  38. package/dist/doc-cli-parity.d.ts +0 -29
  39. package/dist/doc-cli-parity.js +0 -159
  40. package/dist/doctor-parity.d.ts +0 -42
  41. package/dist/doctor-parity.js +0 -157
  42. package/dist/intake-parity.d.ts +0 -30
  43. package/dist/intake-parity.js +0 -203
  44. package/dist/lifecycle-packs-parity.d.ts +0 -30
  45. package/dist/lifecycle-packs-parity.js +0 -377
  46. package/dist/orchestration-parity.d.ts +0 -38
  47. package/dist/orchestration-parity.js +0 -364
  48. package/dist/parity.d.ts +0 -36
  49. package/dist/parity.js +0 -176
  50. package/dist/platform-parity.d.ts +0 -26
  51. package/dist/platform-parity.js +0 -309
  52. package/dist/pr-closing-keywords-parity.d.ts +0 -45
  53. package/dist/pr-closing-keywords-parity.js +0 -259
  54. package/dist/pr-merge-readiness-parity.d.ts +0 -44
  55. package/dist/pr-merge-readiness-parity.js +0 -296
  56. package/dist/pr-monitor-parity.d.ts +0 -44
  57. package/dist/pr-monitor-parity.js +0 -283
  58. package/dist/pr-protected-issues-parity.d.ts +0 -41
  59. package/dist/pr-protected-issues-parity.js +0 -220
  60. package/dist/pr-wait-mergeable-parity.d.ts +0 -45
  61. package/dist/pr-wait-mergeable-parity.js +0 -340
  62. package/dist/release-e2e-parity.js +0 -114
  63. package/dist/release-parity.d.ts +0 -40
  64. package/dist/release-parity.js +0 -226
  65. package/dist/release-publish-parity.d.ts +0 -36
  66. package/dist/release-publish-parity.js +0 -138
  67. package/dist/release-rollback-parity.d.ts +0 -37
  68. package/dist/release-rollback-parity.js +0 -161
  69. package/dist/render-parity.d.ts +0 -36
  70. package/dist/render-parity.js +0 -385
  71. package/dist/scm-parity.d.ts +0 -39
  72. package/dist/scm-parity.js +0 -181
  73. package/dist/scope-lifecycle-parity.d.ts +0 -35
  74. package/dist/scope-lifecycle-parity.js +0 -177
  75. package/dist/session-parity.d.ts +0 -39
  76. package/dist/session-parity.js +0 -262
  77. package/dist/slice-parity.d.ts +0 -36
  78. package/dist/slice-parity.js +0 -304
  79. package/dist/swarm-parity.d.ts +0 -28
  80. package/dist/swarm-parity.js +0 -327
  81. package/dist/triage-actions-parity.d.ts +0 -36
  82. package/dist/triage-actions-parity.js +0 -357
  83. package/dist/triage-aux-b-parity.js +0 -308
  84. package/dist/triage-summary-parity.d.ts +0 -50
  85. package/dist/triage-summary-parity.js +0 -306
  86. package/dist/validate-content-parity.d.ts +0 -33
  87. package/dist/validate-content-parity.js +0 -356
  88. package/dist/vbrief-activate-parity.d.ts +0 -39
  89. package/dist/vbrief-activate-parity.js +0 -216
  90. package/dist/vbrief-build-parity.d.ts +0 -28
  91. package/dist/vbrief-build-parity.js +0 -399
  92. package/dist/vbrief-preflight-parity.js +0 -163
  93. package/dist/vbrief-reconcile-parity.d.ts +0 -23
  94. package/dist/vbrief-reconcile-parity.js +0 -609
  95. package/dist/vbrief-validate-parity.d.ts +0 -27
  96. package/dist/vbrief-validate-parity.js +0 -122
  97. package/dist/vbrief-validation-parity.d.ts +0 -28
  98. package/dist/vbrief-validation-parity.js +0 -645
  99. package/dist/verify-env-parity.d.ts +0 -28
  100. package/dist/verify-env-parity.js +0 -272
  101. package/dist/verify-source-parity.d.ts +0 -26
  102. package/dist/verify-source-parity.js +0 -178
@@ -1,609 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Golden-output parity harness (#1782 s4): runs BOTH the frozen Python oracles
4
- * and the ported TS vbrief-reconcile CLI over shared fixtures (fake gh on PATH,
5
- * cache-off), then diffs exit codes and byte-identical stdout/stderr.
6
- */
7
- import { spawnSync } from "node:child_process";
8
- import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
9
- import { tmpdir } from "node:os";
10
- import { dirname, join, resolve } from "node:path";
11
- import { fileURLToPath } from "node:url";
12
- import { PARITY_SCENARIO_NAMES } from "@deftai/directive-core/vbrief-reconcile";
13
- const FAKE_GH_PY = `import json
14
- import os
15
- import sys
16
-
17
- STATE = {"labels": {}, "comments": {}, "next_id": 1000}
18
-
19
- def classify(cmd):
20
- joined = " ".join(cmd)
21
- if "issue" in cmd and "view" in cmd and "--json" in cmd and "labels" in joined:
22
- return "issue-labels"
23
- if "issue" in cmd and "edit" in cmd:
24
- return "issue-edit"
25
- if "/issues/" in joined and "/comments" in joined and "-X" not in cmd:
26
- return "list-comments"
27
- if "-X" in cmd and "PATCH" in cmd and "/issues/comments/" in joined:
28
- return "patch-comment"
29
- if "-X" in cmd and "POST" in joined and "/issues/" in joined and "/comments" in joined:
30
- return "post-comment"
31
- return "unknown"
32
-
33
- def handle(label, cmd):
34
- responses = json.loads(os.environ.get("DEFT_FAKE_GH_RESPONSES", "{}"))
35
- if label in responses:
36
- return responses[label]
37
- if label == "issue-labels":
38
- repo = cmd[cmd.index("--repo") + 1]
39
- num = int(cmd[cmd.index("view") + 1])
40
- labels = STATE["labels"].get((repo, num), [])
41
- return {"returncode": 0, "stdout": json.dumps({"labels": [{"name": n} for n in labels]})}
42
- if label == "issue-edit":
43
- repo = cmd[cmd.index("--repo") + 1]
44
- num = int(cmd[cmd.index("edit") + 1])
45
- key = (repo, num)
46
- cur = set(STATE["labels"].get(key, []))
47
- i = 0
48
- while i < len(cmd):
49
- if cmd[i] == "--add-label":
50
- cur.add(cmd[i + 1]); i += 2
51
- elif cmd[i] == "--remove-label":
52
- cur.discard(cmd[i + 1]); i += 2
53
- else:
54
- i += 1
55
- STATE["labels"][key] = sorted(cur)
56
- return {"returncode": 0, "stdout": ""}
57
- if label == "list-comments":
58
- parts = [p for p in cmd[-1].split("/") if p]
59
- repo = "/".join(parts[1:3])
60
- num = int(parts[3])
61
- comments = STATE["comments"].get((repo, num), [])
62
- return {"returncode": 0, "stdout": json.dumps(comments)}
63
- if label == "post-comment":
64
- parts = [p for p in cmd if "/issues/" in p][0].split("/")
65
- repo = "/".join(parts[1:3])
66
- num = int(parts[3])
67
- body = json.loads(sys.stdin.read())["body"]
68
- cid = STATE["next_id"]; STATE["next_id"] += 1
69
- STATE["comments"].setdefault((repo, num), []).append({"id": cid, "body": body})
70
- return {"returncode": 0, "stdout": json.dumps({"id": cid})}
71
- if label == "patch-comment":
72
- cid = int([p for p in cmd if "/issues/comments/" in p][0].split("/")[-1])
73
- body = json.loads(sys.stdin.read())["body"]
74
- for bucket in STATE["comments"].values():
75
- for c in bucket:
76
- if c["id"] == cid:
77
- c["body"] = body
78
- return {"returncode": 0, "stdout": ""}
79
- return {"returncode": 1, "stderr": f"unexpected gh call: {label}", "stdout": ""}
80
-
81
- label = classify(sys.argv[1:])
82
- resp = handle(label, sys.argv[1:])
83
- stdout = resp.get("stdout", "")
84
- stderr = resp.get("stderr", "")
85
- if stdout:
86
- sys.stdout.write(stdout if stdout.endswith("\\n") else stdout)
87
- if stderr:
88
- sys.stderr.write(stderr)
89
- sys.exit(int(resp.get("returncode", 0)))
90
- `;
91
- const PYTHON_DRIVER = `import json, os, sys
92
- from datetime import datetime, timezone
93
- from pathlib import Path
94
-
95
- ROOT = Path(os.environ["DEFT_ROOT"])
96
- sys.path.insert(0, str(ROOT / "scripts"))
97
-
98
- from _vbrief_reconciliation import (
99
- build_spec_task_index,
100
- format_reconciliation_markdown,
101
- parse_overrides_yaml,
102
- reconcile_scope_items,
103
- )
104
- from vbrief_reconcile_umbrellas import parse_current_shape
105
- import _vbrief_reconciliation as _vr_mod
106
-
107
- FIXED_REPORT_NOW = datetime(2026, 6, 19, 12, 0, 0, tzinfo=timezone.utc)
108
-
109
- OVERRIDES_SAMPLE = """overrides:
110
- t2.4.1:
111
- status: completed
112
- body_source: spec
113
- roadmap-9:
114
- drop: true
115
- """
116
-
117
- PARSE_SHAPE_BODY = (
118
- "## Current shape (as of pass-4)\\n"
119
- "Last updated: 2026-06-19T00:00:00Z \\n"
120
- "Last pass type:\\tverify\\t\\n"
121
- "Child-count history: pass-1: 2, pass-2: 3, pass-3: 5\\n"
122
- "Trailing field with empty value: \\n"
123
- "Child-count history: pass-9: 9"
124
- )
125
-
126
- def spec_with(items):
127
- return {
128
- "vBRIEFInfo": {"version": "0.5", "description": "spec"},
129
- "plan": {"title": "Spec", "status": "approved", "narratives": {}, "items": items},
130
- }
131
-
132
- def run_scenario(name):
133
- if name == "reconcile-overrides":
134
- return {"scenario": name, "ok": True, "payload": parse_overrides_yaml(OVERRIDES_SAMPLE)}
135
- if name == "reconcile-spec-index":
136
- spec = spec_with([
137
- {"id": "t1.1", "title": "One", "status": "pending"},
138
- {
139
- "id": "phase-1",
140
- "title": "Phase 1: Foundation",
141
- "status": "pending",
142
- "subItems": [{"id": "t1.1.1", "title": "Deep task", "status": "pending"}],
143
- },
144
- ])
145
- index = build_spec_task_index(spec)
146
- return {
147
- "scenario": name,
148
- "ok": True,
149
- "payload": {"keys": sorted(index.keys()), "deepPhase": index["t1.1.1"].spec_phase},
150
- }
151
- if name == "reconcile-scope-clean":
152
- spec = spec_with([{"id": "t1", "title": "Task one", "status": "pending"}])
153
- items, report = reconcile_scope_items(
154
- roadmap_active=[{"number": "", "task_id": "t1", "title": "Task one", "phase": "Phase 1"}],
155
- roadmap_completed=[],
156
- spec_vbrief=spec,
157
- )
158
- return {"scenario": name, "ok": True, "payload": {"items": items, "hasDisagreement": report.has_disagreement()}}
159
- if name == "reconcile-scope-orphan":
160
- spec = spec_with([{"id": "t1", "title": "One", "status": "pending"}])
161
- items, report = reconcile_scope_items(
162
- roadmap_active=[{"number": "9", "title": "Orphan task", "phase": "Phase 1", "synthetic_id": "roadmap-9"}],
163
- roadmap_completed=[],
164
- spec_vbrief=spec,
165
- )
166
- return {"scenario": name, "ok": True, "payload": {"items": items, "orphans": report.orphans}}
167
- if name == "reconcile-report":
168
- spec = spec_with([{"id": "t2.4.1", "title": "Repo indexer", "status": "pending"}])
169
- _, report = reconcile_scope_items(
170
- roadmap_active=[],
171
- roadmap_completed=[{"number": "", "task_id": "t2.4.1", "title": "Repo indexer", "phase": "Completed"}],
172
- spec_vbrief=spec,
173
- )
174
- class _FixedDatetime:
175
- UTC = timezone.utc
176
-
177
- @staticmethod
178
- def now(tz=None):
179
- return FIXED_REPORT_NOW
180
-
181
- prior = _vr_mod.datetime
182
- _vr_mod.datetime = _FixedDatetime
183
- try:
184
- payload = format_reconciliation_markdown(report)
185
- finally:
186
- _vr_mod.datetime = prior
187
- return {"scenario": name, "ok": True, "payload": payload}
188
- if name == "reconcile-parse-shape":
189
- shape = parse_current_shape(PARSE_SHAPE_BODY)
190
- return {
191
- "scenario": name,
192
- "ok": True,
193
- "payload": {
194
- "passN": shape.pass_n,
195
- "history": [[n, c] for n, c in shape.history],
196
- "lastUpdated": shape.last_updated,
197
- "lastPassType": shape.last_pass_type,
198
- },
199
- }
200
- raise SystemExit(f"unknown library scenario: {name}")
201
-
202
- if __name__ == "__main__":
203
- name = sys.argv[sys.argv.index("--scenario") + 1] if "--scenario" in sys.argv else None
204
- if not name:
205
- raise SystemExit("usage: driver --scenario NAME")
206
- print(json.dumps(run_scenario(name), indent=2))
207
- `;
208
- const LIBRARY_SCENARIOS = new Set([
209
- "reconcile-overrides",
210
- "reconcile-spec-index",
211
- "reconcile-scope-clean",
212
- "reconcile-scope-orphan",
213
- "reconcile-report",
214
- "reconcile-parse-shape",
215
- ]);
216
- function installFakeGh() {
217
- const binDir = mkdtempSync(join(tmpdir(), "deft-vbrief-reconcile-fake-gh-"));
218
- for (const name of ["gh", "ghx"]) {
219
- const bin = join(binDir, name);
220
- writeFileSync(bin, `#!/usr/bin/env python3\n${FAKE_GH_PY}`, "utf8");
221
- chmodSync(bin, 0o755);
222
- }
223
- return { binDir, cleanup: () => rmSync(binDir, { recursive: true, force: true }) };
224
- }
225
- function runCapture(cmd, args, cwd, env) {
226
- const result = spawnSync(cmd, args, {
227
- cwd,
228
- encoding: "utf8",
229
- env: { ...process.env, ...env },
230
- stdio: ["ignore", "pipe", "pipe"],
231
- });
232
- return {
233
- name: "",
234
- exitCode: result.status ?? 2,
235
- stdout: typeof result.stdout === "string" ? result.stdout : "",
236
- stderr: typeof result.stderr === "string" ? result.stderr : "",
237
- };
238
- }
239
- function resolveDeftRoot() {
240
- if (process.env.DEFT_ROOT)
241
- return resolve(process.env.DEFT_ROOT);
242
- return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
243
- }
244
- export function normalizeOutput(text) {
245
- return text
246
- .replace(/(?:\/private)?\/var\/folders\/[^\s"')]+\/deft-vbrief-reconcile-[^\s"')]+/g, "<TMP>")
247
- .replace(/\/tmp\/deft-vbrief-reconcile-[^\s"')]+/g, "<TMP>");
248
- }
249
- function installPythonDriver() {
250
- const dir = mkdtempSync(join(tmpdir(), "deft-vbrief-reconcile-driver-"));
251
- const driverPath = join(dir, "vbrief_reconcile_parity_driver.py");
252
- writeFileSync(driverPath, PYTHON_DRIVER, "utf8");
253
- chmodSync(driverPath, 0o755);
254
- return { driverPath, cleanup: () => rmSync(dir, { recursive: true, force: true }) };
255
- }
256
- function fakeGhResponsesForScenario(name) {
257
- if (name === "labels-blocked-dry-run" || name === "labels-utf8-dry-run") {
258
- return {
259
- "issue-labels": { returncode: 0, stdout: "[]\n" },
260
- };
261
- }
262
- if (name === "umbrellas-create-dry-run") {
263
- return { "list-comments": { returncode: 0, stdout: "[]\n" } };
264
- }
265
- if (name === "umbrellas-unchanged") {
266
- return { "list-comments": { returncode: 0, stdout: "[]\n" } };
267
- }
268
- return {};
269
- }
270
- function writeGraphFixtures(root, name) {
271
- const writeBrief = (storyId, folder, dependsOn = []) => {
272
- const dir = join(root, "vbrief", folder);
273
- mkdirSync(dir, { recursive: true });
274
- const statusMap = {
275
- proposed: "proposed",
276
- completed: "completed",
277
- };
278
- writeFileSync(join(dir, `2026-05-21-${storyId}.vbrief.json`), `${JSON.stringify({
279
- vBRIEFInfo: { version: "0.6" },
280
- plan: {
281
- id: storyId,
282
- title: storyId,
283
- status: statusMap[folder] ?? "pending",
284
- narratives: {
285
- Description: `${storyId} description.`,
286
- ImplementationPlan: `1. Do ${storyId}.`,
287
- UserStory: `As a user, I want ${storyId}.`,
288
- Traces: "FR-1",
289
- },
290
- items: [
291
- {
292
- id: `${storyId}-a1`,
293
- title: "Acceptance item 1",
294
- status: "pending",
295
- narrative: { Acceptance: `Given X when ${storyId} then Y.` },
296
- },
297
- ],
298
- metadata: {
299
- kind: "story",
300
- swarm: {
301
- readiness: "ready",
302
- parallel_safe: true,
303
- file_scope: [`src/${storyId}.py`],
304
- verify_commands: [`pytest ${storyId}`],
305
- expected_outputs: ["tests pass"],
306
- depends_on: dependsOn,
307
- conflict_group: "reconcile-suite",
308
- size: "small",
309
- file_scope_confidence: "high",
310
- model_tier: "standard",
311
- },
312
- },
313
- },
314
- }, null, 2)}\n`, "utf8");
315
- };
316
- if (name === "graph-dry-run") {
317
- mkdirSync(join(root, "vbrief", "proposed"), { recursive: true });
318
- writeBrief("dep-done", "completed");
319
- writeBrief("child", "proposed", ["dep-done"]);
320
- }
321
- else if (name === "graph-cycle") {
322
- mkdirSync(join(root, "vbrief", "proposed"), { recursive: true });
323
- writeBrief("a", "proposed", ["b"]);
324
- writeBrief("b", "proposed", ["a"]);
325
- }
326
- else if (name === "graph-missing-proposed") {
327
- mkdirSync(join(root, "vbrief"), { recursive: true });
328
- }
329
- }
330
- function runGraphCliParity(deftRoot, name, fixture, env) {
331
- writeGraphFixtures(fixture, name);
332
- const py = runCapture("uv", [
333
- "run",
334
- "python",
335
- join(deftRoot, "scripts", "vbrief_reconcile_graph.py"),
336
- "--project-root",
337
- fixture,
338
- "--dry-run",
339
- ], deftRoot, env);
340
- const ts = runCapture("node", [
341
- join(deftRoot, "packages", "cli", "dist", "vbrief-reconcile.js"),
342
- "graph",
343
- "--project-root",
344
- fixture,
345
- "--dry-run",
346
- ], deftRoot, env);
347
- return {
348
- python: { name, exitCode: py.exitCode, stdout: py.stdout, stderr: py.stderr },
349
- ts: { name, exitCode: ts.exitCode, stdout: ts.stdout, stderr: ts.stderr },
350
- };
351
- }
352
- function writeLabelsUmbrellasFixtures(root, name) {
353
- const writeBrief = (storyId, folder, extra = {}) => {
354
- const dir = join(root, "vbrief", folder);
355
- mkdirSync(dir, { recursive: true });
356
- const statusMap = {
357
- proposed: "proposed",
358
- pending: "pending",
359
- active: "running",
360
- completed: "completed",
361
- cancelled: "cancelled",
362
- };
363
- writeFileSync(join(dir, `2026-05-21-${storyId}.vbrief.json`), `${JSON.stringify({
364
- vBRIEFInfo: { version: "0.6" },
365
- plan: {
366
- id: storyId,
367
- title: storyId,
368
- status: statusMap[folder] ?? "pending",
369
- narratives: {
370
- Description: `${storyId} description.`,
371
- ImplementationPlan: `1. Do ${storyId}.`,
372
- UserStory: `As a user, I want ${storyId}.`,
373
- Traces: "FR-1",
374
- },
375
- items: [
376
- {
377
- id: `${storyId}-a1`,
378
- title: "Acceptance item 1",
379
- status: "pending",
380
- narrative: { Acceptance: `Given X when ${storyId} then Y.` },
381
- },
382
- ],
383
- metadata: {
384
- kind: "story",
385
- swarm: {
386
- readiness: "ready",
387
- parallel_safe: true,
388
- file_scope: [`src/${storyId}.py`],
389
- verify_commands: [`pytest ${storyId}`],
390
- expected_outputs: ["tests pass"],
391
- depends_on: [],
392
- conflict_group: "reconcile-suite",
393
- size: "small",
394
- file_scope_confidence: "high",
395
- model_tier: "standard",
396
- },
397
- },
398
- references: [],
399
- ...extra,
400
- },
401
- }, null, 2)}\n`, "utf8");
402
- };
403
- if (name === "labels-blocked-dry-run") {
404
- writeBrief("blk", "active", {
405
- status: "blocked",
406
- references: [
407
- {
408
- uri: "https://github.com/deftai/directive/issues/10",
409
- type: "x-vbrief/github-issue",
410
- title: "Issue #10",
411
- },
412
- ],
413
- });
414
- }
415
- else if (name === "labels-utf8-dry-run") {
416
- writeBrief("utf8", "active", {
417
- references: [
418
- {
419
- uri: "https://github.com/deftai/directive/issues/11",
420
- type: "x-vbrief/github-issue",
421
- title: "Issue #11 — smart “quotes”",
422
- },
423
- ],
424
- });
425
- }
426
- else if (name === "umbrellas-create-dry-run") {
427
- writeBrief("child-a", "active", { metadata: { kind: "story", swarm: { depends_on: [] } } });
428
- writeBrief("epic-1", "active", {
429
- metadata: { kind: "epic", swarm: { depends_on: [] } },
430
- references: [
431
- { uri: "active/2026-05-21-child-a.vbrief.json", type: "x-vbrief/plan", title: "child-a" },
432
- {
433
- uri: "https://github.com/deftai/directive/issues/1284",
434
- type: "x-vbrief/github-issue",
435
- title: "Issue #1284",
436
- },
437
- ],
438
- });
439
- }
440
- else if (name === "umbrellas-unchanged") {
441
- writeBrief("child-b", "active", { metadata: { kind: "story", swarm: { depends_on: [] } } });
442
- writeBrief("epic-2", "active", {
443
- metadata: { kind: "epic", swarm: { depends_on: [] } },
444
- references: [
445
- { uri: "active/2026-05-21-child-b.vbrief.json", type: "x-vbrief/plan", title: "child-b" },
446
- {
447
- uri: "https://github.com/deftai/directive/issues/1285",
448
- type: "x-vbrief/github-issue",
449
- title: "Issue #1285",
450
- },
451
- ],
452
- });
453
- }
454
- }
455
- function runLabelsUmbrellasParity(deftRoot, name, env) {
456
- const pyFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-reconcile-py-"));
457
- const tsFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-reconcile-ts-"));
458
- const script = name.startsWith("labels-")
459
- ? "vbrief_reconcile_labels.py"
460
- : "vbrief_reconcile_umbrellas.py";
461
- const verb = name.startsWith("labels-") ? "labels" : "umbrellas";
462
- try {
463
- writeLabelsUmbrellasFixtures(pyFixture, name);
464
- writeLabelsUmbrellasFixtures(tsFixture, name);
465
- if (name === "umbrellas-unchanged") {
466
- runCapture("uv", ["run", "python", join(deftRoot, "scripts", script), "--project-root", pyFixture], deftRoot, env);
467
- runCapture("node", [
468
- join(deftRoot, "packages", "cli", "dist", "vbrief-reconcile.js"),
469
- verb,
470
- "--project-root",
471
- tsFixture,
472
- ], deftRoot, env);
473
- }
474
- const pyArgs = [
475
- "run",
476
- "python",
477
- join(deftRoot, "scripts", script),
478
- "--project-root",
479
- pyFixture,
480
- "--dry-run",
481
- ];
482
- const tsArgs = [
483
- join(deftRoot, "packages", "cli", "dist", "vbrief-reconcile.js"),
484
- verb,
485
- "--project-root",
486
- tsFixture,
487
- "--dry-run",
488
- ];
489
- const py = runCapture("uv", pyArgs, deftRoot, env);
490
- const ts = runCapture("node", tsArgs, deftRoot, env);
491
- return {
492
- python: { name, exitCode: py.exitCode, stdout: py.stdout, stderr: py.stderr },
493
- ts: { name, exitCode: ts.exitCode, stdout: ts.stdout, stderr: ts.stderr },
494
- };
495
- }
496
- finally {
497
- rmSync(pyFixture, { recursive: true, force: true });
498
- rmSync(tsFixture, { recursive: true, force: true });
499
- }
500
- }
501
- export function runParity() {
502
- const deftRoot = resolveDeftRoot();
503
- const driver = installPythonDriver();
504
- const fake = installFakeGh();
505
- const scenarios = [];
506
- try {
507
- for (const name of PARITY_SCENARIO_NAMES) {
508
- const env = {
509
- DEFT_CACHE_DISABLE: "1",
510
- PYTHONUTF8: "1",
511
- PATH: `${fake.binDir}:${process.env.PATH ?? ""}`,
512
- DEFT_ROOT: deftRoot,
513
- DEFT_FAKE_GH_RESPONSES: JSON.stringify(fakeGhResponsesForScenario(name)),
514
- };
515
- let ran;
516
- if (LIBRARY_SCENARIOS.has(name)) {
517
- const pyFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-reconcile-lib-py-"));
518
- const tsFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-reconcile-lib-ts-"));
519
- try {
520
- const py = runCapture("uv", ["run", "python", driver.driverPath, "--scenario", name], deftRoot, env);
521
- const ts = runCapture("node", [
522
- join(deftRoot, "packages", "cli", "dist", "vbrief-reconcile.js"),
523
- "parity",
524
- "--scenario",
525
- name,
526
- "--fixture-root",
527
- tsFixture,
528
- ], deftRoot, env);
529
- ran = {
530
- python: { name, exitCode: py.exitCode, stdout: py.stdout, stderr: py.stderr },
531
- ts: { name, exitCode: ts.exitCode, stdout: ts.stdout, stderr: ts.stderr },
532
- };
533
- }
534
- finally {
535
- rmSync(pyFixture, { recursive: true, force: true });
536
- rmSync(tsFixture, { recursive: true, force: true });
537
- }
538
- }
539
- else if (name.startsWith("graph-")) {
540
- const fixture = mkdtempSync(join(tmpdir(), "deft-vbrief-reconcile-graph-"));
541
- try {
542
- ran = runGraphCliParity(deftRoot, name, fixture, env);
543
- }
544
- finally {
545
- rmSync(fixture, { recursive: true, force: true });
546
- }
547
- }
548
- else {
549
- ran = runLabelsUmbrellasParity(deftRoot, name, env);
550
- }
551
- const pythonStdout = normalizeOutput(ran.python.stdout);
552
- const pythonStderr = normalizeOutput(ran.python.stderr);
553
- const tsStdout = normalizeOutput(ran.ts.stdout);
554
- const tsStderr = normalizeOutput(ran.ts.stderr);
555
- scenarios.push({
556
- name,
557
- pythonExit: ran.python.exitCode,
558
- tsExit: ran.ts.exitCode,
559
- exitMismatch: ran.python.exitCode !== ran.ts.exitCode,
560
- outputMismatch: pythonStdout !== tsStdout || pythonStderr !== tsStderr,
561
- pythonOutput: pythonStdout || pythonStderr,
562
- tsOutput: tsStdout || tsStderr,
563
- });
564
- }
565
- }
566
- finally {
567
- driver.cleanup();
568
- fake.cleanup();
569
- }
570
- const ok = scenarios.every((s) => !s.exitMismatch && !s.outputMismatch);
571
- return { ok, scenarios };
572
- }
573
- export function renderReport(result) {
574
- if (result.ok) {
575
- return `vbrief_reconcile parity: CLEAN -- Python and TS agree on ${result.scenarios.length} scenario(s).`;
576
- }
577
- const lines = ["vbrief_reconcile parity: DIVERGENCE"];
578
- for (const s of result.scenarios) {
579
- if (s.exitMismatch || s.outputMismatch) {
580
- lines.push(` scenario: ${s.name}`);
581
- if (s.exitMismatch)
582
- lines.push(` exit mismatch: python=${s.pythonExit} ts=${s.tsExit}`);
583
- if (s.outputMismatch) {
584
- lines.push(` python (${s.pythonOutput.length} bytes):`);
585
- lines.push(s.pythonOutput.slice(0, 500));
586
- lines.push(` ts (${s.tsOutput.length} bytes):`);
587
- lines.push(s.tsOutput.slice(0, 500));
588
- }
589
- }
590
- }
591
- return lines.join("\n");
592
- }
593
- if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
594
- try {
595
- const result = runParity();
596
- if (result.ok) {
597
- process.stdout.write(`${renderReport(result)}\n`);
598
- process.exit(0);
599
- }
600
- process.stderr.write(`${renderReport(result)}\n`);
601
- process.exit(1);
602
- }
603
- catch (err) {
604
- const msg = String(err).replace(/\r?\n/g, " ");
605
- process.stderr.write(`vbrief_reconcile parity: harness error -- ${msg}\n`);
606
- process.exit(2);
607
- }
608
- }
609
- //# sourceMappingURL=vbrief-reconcile-parity.js.map
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env node
2
- export interface GateCapture {
3
- readonly name: string;
4
- readonly exitCode: number;
5
- readonly stdout: string;
6
- readonly stderr: string;
7
- }
8
- export interface GateParity {
9
- readonly name: string;
10
- readonly exitMismatch: boolean;
11
- readonly stdoutMismatch: boolean;
12
- readonly stderrMismatch: boolean;
13
- readonly pythonExit: number;
14
- readonly tsExit: number;
15
- readonly pythonStdout: string;
16
- readonly tsStdout: string;
17
- readonly pythonStderr: string;
18
- readonly tsStderr: string;
19
- }
20
- export interface ParityResult {
21
- readonly ok: boolean;
22
- readonly gates: readonly GateParity[];
23
- }
24
- export declare function diffGate(python: GateCapture, ts: GateCapture): GateParity;
25
- export declare function runParity(deftRoot?: string): ParityResult;
26
- export declare function renderReport(result: ParityResult): string;
27
- //# sourceMappingURL=vbrief-validate-parity.d.ts.map