@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,645 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Golden-output parity harness (#1782 s2): runs BOTH the Python oracle
4
- * (frozen ``scripts/_vbrief_*.py`` helpers via an inline driver) and the
5
- * ported TS vbrief-validation CLI over shared fixtures, then diffs exit codes
6
- * and 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-validation";
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_validation import (
23
- HASH_SUFFIX_LENGTH,
24
- ID_MAX_LENGTH,
25
- RECOVERY_HINT,
26
- finalize_migration,
27
- isolate_invalid_output,
28
- slug_fallback_id,
29
- slugify_id,
30
- validate_migration_output,
31
- )
32
- from _vbrief_fidelity import (
33
- align_spec_narratives,
34
- build_edges_from_tasks,
35
- build_requirements_narrative,
36
- format_migration_log_entry,
37
- ingest_spec_narratives,
38
- map_spec_status,
39
- parse_requirement_definitions,
40
- parse_spec_tasks,
41
- task_scope_narratives,
42
- )
43
- from _vbrief_legacy import (
44
- lookup_canonical,
45
- normalize_title,
46
- parse_top_level_sections,
47
- partition_sections,
48
- SPEC_KNOWN_MAPPINGS,
49
- )
50
- from _vbrief_story_quality import story_quality_issues
51
- from _vbrief_safety import (
52
- RenameRecord,
53
- SafetyManifest,
54
- dirty_tree_refusal_message,
55
- is_tree_dirty,
56
- plan_backups,
57
- premigrate_sibling,
58
- sha256_of,
59
- write_backups,
60
- )
61
-
62
- DEPRECATION_SENTINEL = "<!-- deft:deprecated-redirect -->"
63
-
64
- SAMPLE_SPEC_TASKS = """## Overview
65
-
66
- Intro.
67
-
68
- ### t1.1.1 -- Widget support [done]
69
-
70
- Build the widget layer.
71
-
72
- Depends on: t1.0.1, t1.0.2
73
-
74
- **Traces**: FR-1, NFR-2
75
-
76
- Acceptance criteria:
77
-
78
- - Given a user, when they open the widget, then it renders.
79
-
80
- - The widget persists state across reloads.
81
-
82
- ## Requirements
83
-
84
- FR-1: Users can open widgets.
85
- NFR-2: Widget state persists.
86
- """
87
-
88
- STORY_QUALITY_BASE = {
89
- "title": "Auth model",
90
- "description": (
91
- "Auth model persistence stores user identity and session state. The story covers focused "
92
- "model changes plus matching unit tests for save and load behavior."
93
- ),
94
- "implementation_plan": (
95
- "- Update the src/auth model persistence code so valid payloads are saved through the model boundary.\n"
96
- "- Add focused tests for successful persistence and a missing-record fixture in tests/auth/model."
97
- ),
98
- "user_story": "As an auth maintainer, I want persisted user records, so that login state survives requests.",
99
- "acceptance_texts": [
100
- "Given a valid user payload, when the auth model saves it, then the user record persists.",
101
- "Given an existing user, when the auth model loads it, then the saved identity returns.",
102
- ],
103
- "acceptance_count_justification": "",
104
- "swarm": {
105
- "file_scope": ["src/auth/model.ts", "tests/auth/model.test.ts"],
106
- "verify_commands": ["npm test -- auth/model"],
107
- "expected_outputs": ["Updated auth model tests pass"],
108
- "depends_on": [],
109
- "conflict_group": "auth",
110
- "size": "M",
111
- "file_scope_confidence": "high",
112
- "model_tier": "medium",
113
- "parallel_safe": True,
114
- },
115
- "concurrent_ready": True,
116
- }
117
-
118
- EDGE_SPEC = """### t1.1.1 -- Title A [done]
119
-
120
- Body line.
121
-
122
- Depends on: t1.0.1, t1.0.2
123
-
124
- **Traces**: FR-1, NFR-2
125
-
126
- Acceptance criteria:
127
-
128
- - crit one
129
-
130
- #### \x60t2.2\x60 Backtick title
131
-
132
- **Depends on** : none
133
-
134
- Traces: FR-9
135
-
136
- Acceptance:
137
-
138
- - crit two
139
-
140
- ### t3.3.3: colon title [pending]
141
-
142
- Dependson: t1.0.1
143
-
144
- ##### t6.6.6 five hashes not a task
145
-
146
- ### t4.4.4 spaced [wip]
147
-
148
- ### t5.5.5 title with [notend] tail
149
- """
150
-
151
- EDGE_HEADINGS = """## Title one
152
-
153
- body1
154
-
155
- ## Spaced Title
156
-
157
- body2
158
-
159
- ##
160
-
161
- still body
162
-
163
- ### h3 not top
164
-
165
- ## Final
166
-
167
- last
168
- """
169
-
170
- EDGE_SLUGS = ["---Hello---World---", "!!!", " spaced ", "Mix-Of_Things 42", "a" * 90 + "----"]
171
-
172
- EDGE_STORIES = [
173
- "As a maintainer , I want x , so that y .",
174
- "As an engineer, I want feature, so that benefit.",
175
- "as a x, i want y, so that z.",
176
- "As a role, I want cap, so that out",
177
- "As a, I want y, so that z.",
178
- "As a role, I want cap, so that done.",
179
- "As a dev, I want\nmulti line, so that\noutcome.\n",
180
- "As a dev, I want a, b, c, so that x, y, z.",
181
- "As a role, I want cap, so that out.",
182
- " As a role, I want cap, so that out. \n",
183
- "As a role, I want cap, so that out",
184
- "As a role, I want cap.",
185
- "As a role, so that out.",
186
- "I want cap, As a role, so that out.",
187
- "As animal, I want cap, so that out.",
188
- "As a role, I want , so that out.",
189
- "As a role, I want cap, so that .",
190
- "As a role, I want cap, so that v1.2 ships.",
191
- "As a role, I want cap, so that out.x",
192
- ]
193
-
194
-
195
- def sorted_diag(errors, warnings):
196
- return {"errors": sorted(errors), "warnings": sorted(warnings)}
197
-
198
-
199
- def sort_failure_actions(actions):
200
- prefix, errors, suffix = [], [], []
201
- seen = False
202
- for line in actions:
203
- if line.startswith(" ") and ".vbrief.json:" in line:
204
- errors.append(line)
205
- seen = True
206
- elif seen:
207
- suffix.append(line)
208
- else:
209
- prefix.append(line)
210
- return prefix + sorted(errors) + suffix
211
-
212
-
213
- def sort_failure_stderr(stderr):
214
- lines = stderr.split("\n")
215
- prefix, errors, suffix = [], [], []
216
- seen = False
217
- for line in lines:
218
- if line.startswith(" ") and ".vbrief.json:" in line:
219
- errors.append(line)
220
- seen = True
221
- elif seen:
222
- suffix.append(line)
223
- else:
224
- prefix.append(line)
225
- return "\n".join(prefix + sorted(errors) + suffix)
226
-
227
-
228
- def normalize_fixture(obj, fixture_root):
229
- token = "<FIXTURE>"
230
- root = fixture_root.replace("\\", "/")
231
- if isinstance(obj, str):
232
- return obj.replace(root, token)
233
- if isinstance(obj, list):
234
- return [normalize_fixture(item, fixture_root) for item in obj]
235
- if isinstance(obj, dict):
236
- return {k: normalize_fixture(v, fixture_root) for k, v in obj.items()}
237
- return obj
238
-
239
-
240
- def dump(obj, fixture_root=""):
241
- if fixture_root:
242
- obj = normalize_fixture(obj, fixture_root)
243
- payload = json.dumps(obj, indent=2, ensure_ascii=False, sort_keys=False)
244
- if not payload.endswith("\n"):
245
- payload += "\n"
246
- sys.stdout.write(payload)
247
-
248
-
249
- def write_valid_pd(vbrief_dir: Path):
250
- data = {
251
- "vBRIEFInfo": {"version": "0.6"},
252
- "plan": {
253
- "title": "PROJECT-DEFINITION",
254
- "status": "running",
255
- "narratives": {
256
- "Overview": "Test overview narrative.",
257
- "tech stack": "Python 3.12",
258
- },
259
- "items": [],
260
- },
261
- }
262
- (vbrief_dir / "PROJECT-DEFINITION.vbrief.json").write_text(json.dumps(data), encoding="utf-8")
263
-
264
-
265
- def run_scenario(name, fixture_root):
266
- fixture = Path(fixture_root)
267
- if name == "slugify-basic":
268
- return {"scenario": name, "ok": True, "payload": {
269
- "hello": slugify_id("Hello World"),
270
- "special": slugify_id("Add widget (v2)!"),
271
- "untitled": slugify_id(""),
272
- "constants": {"ID_MAX_LENGTH": ID_MAX_LENGTH, "HASH_SUFFIX_LENGTH": HASH_SUFFIX_LENGTH, "RECOVERY_HINT": RECOVERY_HINT},
273
- }}
274
- if name == "slugify-collision":
275
- existing = {"hello"}
276
- first = slugify_id("hello world", existing)
277
- second = slugify_id("hello world", existing)
278
- return {"scenario": name, "ok": True, "payload": {"first": first, "second": second, "size": len(existing)}}
279
- if name == "slug-fallback-id":
280
- return {"scenario": name, "ok": True, "payload": {
281
- "number": slug_fallback_id({"number": "42", "task_id": "1.1", "title": "foo"}),
282
- "taskId": slug_fallback_id({"number": "", "task_id": "1.1.2", "title": "foo"}),
283
- "synthetic": slug_fallback_id({"number": "", "task_id": "", "synthetic_id": "roadmap-3", "title": "foo"}),
284
- "title": slug_fallback_id({"title": "Fix login bug"}),
285
- "untitled": slug_fallback_id({}),
286
- }}
287
- if name == "validate-migration-missing-dir":
288
- missing = fixture / "nonexistent"
289
- errors, warnings = validate_migration_output(missing)
290
- return {"scenario": name, "ok": True, "payload": sorted_diag(errors, warnings)}
291
- if name == "validate-migration-empty-dir":
292
- vbrief = fixture / "vbrief"
293
- vbrief.mkdir(parents=True, exist_ok=True)
294
- errors, warnings = validate_migration_output(vbrief)
295
- return {"scenario": name, "ok": True, "payload": sorted_diag(errors, warnings)}
296
- if name == "validate-migration-valid-pd":
297
- vbrief = fixture / "vbrief-valid"
298
- vbrief.mkdir(parents=True, exist_ok=True)
299
- write_valid_pd(vbrief)
300
- errors, warnings = validate_migration_output(vbrief)
301
- return {"scenario": name, "ok": True, "payload": sorted_diag(errors, warnings)}
302
- if name == "validate-migration-invalid-status":
303
- vbrief = fixture / "vbrief-bad"
304
- vbrief.mkdir(parents=True, exist_ok=True)
305
- (vbrief / "PROJECT-DEFINITION.vbrief.json").write_text(
306
- json.dumps({"vBRIEFInfo": {"version": "0.6"}, "plan": {"title": "Bad", "status": "in_progress", "items": []}}),
307
- encoding="utf-8",
308
- )
309
- errors, warnings = validate_migration_output(vbrief)
310
- return {"scenario": name, "ok": True, "payload": sorted_diag(errors, warnings)}
311
- if name == "isolate-invalid-output":
312
- project_root = fixture / "isolate"
313
- project_root.mkdir(parents=True, exist_ok=True)
314
- vbrief = project_root / "vbrief"
315
- vbrief.mkdir(parents=True, exist_ok=True)
316
- (vbrief / "sentinel.txt").write_text("marker", encoding="utf-8")
317
- (project_root / "vbrief.invalid").mkdir(exist_ok=True)
318
- (project_root / "vbrief.invalid.2").mkdir(exist_ok=True)
319
- target = isolate_invalid_output(project_root, vbrief)
320
- return {"scenario": name, "ok": True, "payload": {
321
- "target": target.as_posix().replace(fixture.as_posix(), "<FIXTURE>") if target else None,
322
- "sentinel": (project_root / "vbrief.invalid.3" / "sentinel.txt").read_text(encoding="utf-8"),
323
- }}
324
- if name == "finalize-migration-success":
325
- project_root = fixture / "finalize-ok"
326
- vbrief = project_root / "vbrief"
327
- vbrief.mkdir(parents=True, exist_ok=True)
328
- write_valid_pd(vbrief)
329
- import io
330
- buf = io.StringIO()
331
- class _Writer:
332
- def write(self, s):
333
- buf.write(s)
334
- import _vbrief_validation as mod
335
- old = sys.stderr
336
- sys.stderr = _Writer()
337
- try:
338
- ok, actions = finalize_migration(project_root, vbrief, ["CREATE ok"])
339
- finally:
340
- sys.stderr = old
341
- return {"scenario": name, "ok": True, "payload": {"ok": ok, "actions": actions, "stderr": buf.getvalue()}}
342
- if name == "finalize-migration-failure":
343
- project_root = fixture / "finalize-fail"
344
- vbrief = project_root / "vbrief"
345
- vbrief.mkdir(parents=True, exist_ok=True)
346
- (vbrief / "PROJECT-DEFINITION.vbrief.json").write_text(
347
- json.dumps({"vBRIEFInfo": {"version": "0.6"}, "plan": {}}),
348
- encoding="utf-8",
349
- )
350
- import io
351
- buf = io.StringIO()
352
- class _Writer:
353
- def write(self, s):
354
- buf.write(s)
355
- old = sys.stderr
356
- sys.stderr = _Writer()
357
- try:
358
- ok, actions = finalize_migration(project_root, vbrief, ["CREATE bad"])
359
- finally:
360
- sys.stderr = old
361
- return {"scenario": name, "ok": True, "payload": {
362
- "ok": ok,
363
- "actions": sort_failure_actions(actions),
364
- "stderr": sort_failure_stderr(buf.getvalue()),
365
- }}
366
- if name == "legacy-normalize-title":
367
- return {"scenario": name, "ok": True, "payload": {
368
- "techStack": normalize_title("Tech Stack"),
369
- "camel": normalize_title("ProblemStatement"),
370
- "trailingNewline": normalize_title("Branching Strategy\n"),
371
- "empty": normalize_title(""),
372
- }}
373
- if name == "legacy-lookup-canonical":
374
- return {"scenario": name, "ok": True, "payload": {
375
- "overview": lookup_canonical("Summary", SPEC_KNOWN_MAPPINGS),
376
- "unknown": lookup_canonical("Mystery Section", SPEC_KNOWN_MAPPINGS),
377
- }}
378
- if name == "legacy-parse-sections":
379
- content = "## Overview\n\nAn overview.\n\n### Sub-section\n\ninside overview\n\n## Goals\n\nSome goals.\n"
380
- sections = parse_top_level_sections(content)
381
- return {"scenario": name, "ok": True, "payload": {
382
- "count": len(sections),
383
- "firstTitle": sections[0][0],
384
- "hasSubsection": "### Sub-section" in sections[0][1],
385
- "trailingEmpty": parse_top_level_sections("## Only\n\nBody\n"),
386
- }}
387
- if name == "legacy-partition-sections":
388
- sections = parse_top_level_sections("## Summary\n\nOverview body.\n\n## Mystery\n\nLegacy body.\n")
389
- canonical, legacy = partition_sections(sections, SPEC_KNOWN_MAPPINGS)
390
- return {"scenario": name, "ok": True, "payload": {"canonical": canonical, "legacyCount": len(legacy)}}
391
- if name == "fidelity-map-spec-status":
392
- return {"scenario": name, "ok": True, "payload": {
393
- "done": map_spec_status("done"),
394
- "unknown": map_spec_status("weird"),
395
- "empty": map_spec_status(""),
396
- "trailing": map_spec_status("completed\n"),
397
- }}
398
- if name == "fidelity-parse-spec-tasks":
399
- tasks = parse_spec_tasks(SAMPLE_SPEC_TASKS)
400
- return {"scenario": name, "ok": True, "payload": {
401
- "tasks": tasks,
402
- "empty": parse_spec_tasks(""),
403
- "trailingNewline": parse_spec_tasks(SAMPLE_SPEC_TASKS + "\n"),
404
- }}
405
- if name == "fidelity-requirements":
406
- reqs = parse_requirement_definitions(SAMPLE_SPEC_TASKS)
407
- return {"scenario": name, "ok": True, "payload": {
408
- "requirements": reqs,
409
- "narrative": build_requirements_narrative(reqs),
410
- "empty": build_requirements_narrative({}),
411
- }}
412
- if name == "fidelity-edges-and-narratives":
413
- tasks = parse_spec_tasks(SAMPLE_SPEC_TASKS)
414
- return {"scenario": name, "ok": True, "payload": {
415
- "edges": build_edges_from_tasks(tasks),
416
- "scope": task_scope_narratives(tasks[0] if tasks else {}),
417
- "aligned": align_spec_narratives({"tech stack": "Rust", "Overview": "Hi"}),
418
- }}
419
- if name == "fidelity-ingest-spec":
420
- canonical, log_entries, legacy = ingest_spec_narratives(SAMPLE_SPEC_TASKS)
421
- return {"scenario": name, "ok": True, "payload": {
422
- "canonicalKeys": list(canonical.keys()),
423
- "firstLog": format_migration_log_entry(log_entries[0]) if log_entries else None,
424
- "legacyCount": len(legacy),
425
- }}
426
- if name == "story-quality-happy":
427
- return {"scenario": name, "ok": True, "payload": {"issues": story_quality_issues(**STORY_QUALITY_BASE)}}
428
- if name == "story-quality-failures":
429
- return {"scenario": name, "ok": True, "payload": {
430
- "userStory": story_quality_issues(**{**STORY_QUALITY_BASE, "user_story": "Just build it."}),
431
- "broadScope": story_quality_issues(**{**STORY_QUALITY_BASE, "swarm": {**STORY_QUALITY_BASE["swarm"], "file_scope": ["backend"]}}),
432
- "genericVerify": story_quality_issues(**{**STORY_QUALITY_BASE, "swarm": {**STORY_QUALITY_BASE["swarm"], "verify_commands": ["task check"]}}),
433
- "endOfStringObservable": story_quality_issues(**{**STORY_QUALITY_BASE, "acceptance_texts": [
434
- "A user with valid credentials logs into the system successfully today.",
435
- "Given an existing user, when the auth model loads it, then the saved identity returns.",
436
- ]}),
437
- }}
438
- if name == "safety-premigrate-sibling":
439
- return {"scenario": name, "ok": True, "payload": {
440
- "md": premigrate_sibling(Path("/tmp/SPECIFICATION.md")).as_posix(),
441
- "json": premigrate_sibling(Path("/tmp/specification.vbrief.json")).as_posix(),
442
- "noExt": premigrate_sibling(Path("/tmp/README")).as_posix(),
443
- }}
444
- if name == "safety-plan-backups":
445
- project_root = fixture / "safety-backups"
446
- project_root.mkdir(parents=True, exist_ok=True)
447
- (project_root / "SPECIFICATION.md").write_text("spec", encoding="utf-8")
448
- (project_root / "PROJECT.md").write_text(DEPRECATION_SENTINEL, encoding="utf-8")
449
- pairs = plan_backups(project_root)
450
- return {"scenario": name, "ok": True, "payload": {
451
- "pairs": [[src.name, dst.name] for src, dst in pairs],
452
- "dirtyMessage": dirty_tree_refusal_message(),
453
- "isDirty": is_tree_dirty(project_root),
454
- }}
455
- if name == "safety-manifest-roundtrip":
456
- manifest = SafetyManifest(
457
- version="1",
458
- migration_timestamp="2026-04-22T00:00:00Z",
459
- created_files=["vbrief/migration/LEGACY-REPORT.md"],
460
- renames=[
461
- RenameRecord(
462
- original="vbrief/migration/LEGACY-REPORT.md",
463
- current="vbrief/migration/LEGACY-REPORT.reviewed.md",
464
- renamed_by="deft-directive-sync Phase 6c",
465
- renamed_at="2026-04-22T00:45:00Z",
466
- )
467
- ],
468
- )
469
- clone = SafetyManifest.from_json(manifest.to_json())
470
- return {"scenario": name, "ok": True, "payload": {
471
- "resolved": clone.current_path_for("vbrief/migration/LEGACY-REPORT.md"),
472
- "shaEmpty": sha256_of(fixture / "missing-file"),
473
- }}
474
- if name == "safety-write-backups-dryrun":
475
- project_root = fixture / "safety-write"
476
- project_root.mkdir(parents=True, exist_ok=True)
477
- src = project_root / "SPECIFICATION.md"
478
- src.write_text("hello", encoding="utf-8")
479
- dst = premigrate_sibling(src)
480
- records, actions = write_backups(project_root, [(src, dst)], dry_run=True)
481
- return {"scenario": name, "ok": True, "payload": {"records": [
482
- {"source": r.source, "backup": r.backup, "source_sha256": r.source_sha256, "size_bytes": r.size_bytes}
483
- for r in records
484
- ], "actions": actions}}
485
- if name == "regex-edge-cases":
486
- return {"scenario": name, "ok": True, "payload": {
487
- "tasks": parse_spec_tasks(EDGE_SPEC),
488
- "headings": parse_top_level_sections(EDGE_HEADINGS),
489
- "slugs": [slugify_id(s) for s in EDGE_SLUGS],
490
- "stories": [
491
- any("UserStory must match" in i for i in story_quality_issues(**{**STORY_QUALITY_BASE, "user_story": s}))
492
- for s in EDGE_STORIES
493
- ],
494
- }}
495
- return {"scenario": name, "ok": False, "payload": {"error": f"unknown scenario: {name}"}}
496
-
497
-
498
- def main():
499
- mode = sys.argv[1] if len(sys.argv) > 1 else ""
500
- fixture_root = os.environ.get("DEFT_VBRIEF_VALIDATION_FIXTURE", "")
501
- if mode == "--scenario":
502
- dump(run_scenario(sys.argv[2], fixture_root), fixture_root)
503
- return 0
504
- sys.stderr.write("usage: driver --scenario NAME\n")
505
- return 2
506
-
507
- if __name__ == "__main__":
508
- raise SystemExit(main())
509
- `;
510
- function runCapture(cmd, args, cwd, env) {
511
- const result = spawnSync(cmd, args, {
512
- cwd,
513
- encoding: "utf8",
514
- env: { ...process.env, ...env },
515
- stdio: ["ignore", "pipe", "pipe"],
516
- });
517
- return {
518
- status: result.status ?? 2,
519
- stdout: typeof result.stdout === "string" ? result.stdout : "",
520
- stderr: typeof result.stderr === "string" ? result.stderr : "",
521
- };
522
- }
523
- function resolveDeftRoot() {
524
- if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
525
- return resolve(process.env.DEFT_ROOT);
526
- }
527
- return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
528
- }
529
- function normaliseHarnessNoise(text) {
530
- return text
531
- .split("\n")
532
- .filter((line) => !line.startsWith("Using CPython") &&
533
- !line.startsWith("Creating virtual environment") &&
534
- !line.startsWith("Installed "))
535
- .join("\n");
536
- }
537
- export function diffParity(python, ts) {
538
- const pythonOutput = normaliseHarnessNoise(python.stdout);
539
- const tsOutput = normaliseHarnessNoise(ts.stdout);
540
- return {
541
- exitMismatch: python.exitCode !== ts.exitCode,
542
- outputMismatch: pythonOutput !== tsOutput,
543
- pythonOutput,
544
- tsOutput,
545
- };
546
- }
547
- function installPythonDriver(_deftRoot) {
548
- const dir = mkdtempSync(join(tmpdir(), "deft-vbrief-validation-py-driver-"));
549
- const driverPath = join(dir, "vbrief_validation_parity_driver.py");
550
- writeFileSync(driverPath, PYTHON_DRIVER, "utf8");
551
- chmodSync(driverPath, 0o755);
552
- return {
553
- driverPath,
554
- cleanup: () => rmSync(dir, { recursive: true, force: true }),
555
- };
556
- }
557
- function runScenario(deftRoot, driverPath, name) {
558
- const pyFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-validation-parity-py-"));
559
- const tsFixture = mkdtempSync(join(tmpdir(), "deft-vbrief-validation-parity-ts-"));
560
- const envBase = {
561
- DEFT_CACHE_DISABLE: "1",
562
- PYTHONUTF8: "1",
563
- DEFT_ROOT: deftRoot,
564
- };
565
- try {
566
- const py = runCapture("uv", ["run", "python", driverPath, "--scenario", name], deftRoot, {
567
- ...envBase,
568
- DEFT_VBRIEF_VALIDATION_FIXTURE: pyFixture,
569
- });
570
- const ts = runCapture("node", [
571
- join(deftRoot, "packages", "cli", "dist", "vbrief-validation.js"),
572
- "--scenario",
573
- name,
574
- "--fixture-root",
575
- tsFixture,
576
- ], deftRoot, envBase);
577
- return {
578
- python: { name, exitCode: py.status, stdout: py.stdout, stderr: py.stderr },
579
- ts: { name, exitCode: ts.status, stdout: ts.stdout, stderr: ts.stderr },
580
- };
581
- }
582
- finally {
583
- rmSync(pyFixture, { recursive: true, force: true });
584
- rmSync(tsFixture, { recursive: true, force: true });
585
- }
586
- }
587
- export function runParity() {
588
- const deftRoot = resolveDeftRoot();
589
- const driver = installPythonDriver(deftRoot);
590
- const scenarios = [];
591
- try {
592
- for (const name of PARITY_SCENARIO_NAMES) {
593
- const ran = runScenario(deftRoot, driver.driverPath, name);
594
- scenarios.push({
595
- name,
596
- pythonExit: ran.python.exitCode,
597
- tsExit: ran.ts.exitCode,
598
- ...diffParity(ran.python, ran.ts),
599
- });
600
- }
601
- }
602
- finally {
603
- driver.cleanup();
604
- }
605
- const ok = scenarios.every((s) => !s.exitMismatch && !s.outputMismatch);
606
- return { ok, scenarios };
607
- }
608
- export function renderReport(result) {
609
- if (result.ok) {
610
- return `vbrief_validation parity: CLEAN -- Python and TS agree on ${result.scenarios.length} scenario(s).`;
611
- }
612
- const lines = ["vbrief_validation parity: DIVERGENCE"];
613
- for (const s of result.scenarios) {
614
- if (s.exitMismatch || s.outputMismatch) {
615
- lines.push(` scenario: ${s.name}`);
616
- if (s.exitMismatch) {
617
- lines.push(` exit mismatch: python=${s.pythonExit} ts=${s.tsExit}`);
618
- }
619
- if (s.outputMismatch) {
620
- lines.push(` python (${s.pythonOutput.length} bytes):`);
621
- lines.push(s.pythonOutput.slice(0, 500));
622
- lines.push(` ts (${s.tsOutput.length} bytes):`);
623
- lines.push(s.tsOutput.slice(0, 500));
624
- }
625
- }
626
- }
627
- return lines.join("\n");
628
- }
629
- if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
630
- try {
631
- const result = runParity();
632
- if (result.ok) {
633
- process.stdout.write(`${renderReport(result)}\n`);
634
- process.exit(0);
635
- }
636
- process.stderr.write(`${renderReport(result)}\n`);
637
- process.exit(1);
638
- }
639
- catch (err) {
640
- const msg = String(err).replace(/\r?\n/g, " ");
641
- process.stderr.write(`vbrief_validation parity: harness error -- ${msg}\n`);
642
- process.exit(2);
643
- }
644
- }
645
- //# sourceMappingURL=vbrief-validation-parity.js.map
@@ -1,28 +0,0 @@
1
- #!/usr/bin/env node
2
- export interface Capture {
3
- readonly exitCode: number;
4
- readonly stdout: string;
5
- readonly stderr: string;
6
- }
7
- export interface ParityCase {
8
- readonly name: string;
9
- readonly runPython: (deftRoot: string) => Capture;
10
- readonly runTs: (deftRoot: string) => Capture;
11
- }
12
- export interface ParityDiff {
13
- readonly name: string;
14
- readonly exitMismatch: boolean;
15
- readonly stdoutMismatch: boolean;
16
- readonly stderrMismatch: boolean;
17
- readonly pythonExit: number;
18
- readonly tsExit: number;
19
- }
20
- export interface ParityResult {
21
- readonly ok: boolean;
22
- readonly diffs: ParityDiff[];
23
- }
24
- export declare const PARITY_CASES: readonly ParityCase[];
25
- export declare function diffCase(python: Capture, ts: Capture, name: string): ParityDiff;
26
- export declare function runParity(deftRoot?: string): ParityResult;
27
- export declare function renderReport(result: ParityResult): string;
28
- //# sourceMappingURL=verify-env-parity.d.ts.map