@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,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