@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.
- package/dist/{branch-parity.d.ts → branch-fixtures.d.ts} +1 -3
- package/dist/{branch-parity.js → branch-fixtures.js} +3 -110
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +4 -1
- package/dist/framework-check-updates.d.ts +10 -0
- package/dist/framework-check-updates.js +68 -0
- package/dist/install-cli/coverage-map.js +3 -2
- package/dist/orchestration-cli/coverage-map.js +1 -1
- package/dist/{policy-parity.d.ts → policy-fixtures.d.ts} +1 -3
- package/dist/{policy-parity.js → policy-fixtures.js} +4 -100
- package/dist/{release-e2e-parity.d.ts → release-e2e-fixtures.d.ts} +1 -3
- package/dist/release-e2e-fixtures.js +38 -0
- package/dist/{story-ready-parity.d.ts → story-ready-fixtures.d.ts} +1 -3
- package/dist/{story-ready-parity.js → story-ready-fixtures.js} +4 -121
- package/dist/{triage-aux-a-parity.d.ts → triage-aux-a-fixtures.d.ts} +1 -3
- package/dist/{triage-aux-a-parity.js → triage-aux-a-fixtures.js} +3 -73
- package/dist/{triage-aux-b-parity.d.ts → triage-aux-b-fixtures.d.ts} +1 -3
- package/dist/triage-aux-b-fixtures.js +167 -0
- package/dist/{triage-bootstrap-parity.d.ts → triage-bootstrap-fixtures.d.ts} +1 -3
- package/dist/{triage-bootstrap-parity.js → triage-bootstrap-fixtures.js} +4 -91
- package/dist/{triage-classify-parity.d.ts → triage-classify-fixtures.d.ts} +1 -3
- package/dist/{triage-classify-parity.js → triage-classify-fixtures.js} +4 -94
- package/dist/{triage-queue-parity.d.ts → triage-queue-fixtures.d.ts} +1 -3
- package/dist/{triage-queue-parity.js → triage-queue-fixtures.js} +4 -86
- package/dist/{triage-scope-parity.d.ts → triage-scope-fixtures.d.ts} +1 -3
- package/dist/{triage-scope-parity.js → triage-scope-fixtures.js} +4 -91
- package/dist/umbrella-current-shape.d.ts +9 -0
- package/dist/umbrella-current-shape.js +56 -0
- package/dist/{vbrief-preflight-parity.d.ts → vbrief-preflight-fixtures.d.ts} +1 -3
- package/dist/vbrief-preflight-fixtures.js +79 -0
- package/dist/{wip-cap-parity.d.ts → wip-cap-fixtures.d.ts} +1 -3
- package/dist/{wip-cap-parity.js → wip-cap-fixtures.js} +4 -91
- package/package.json +4 -15
- package/dist/cache-parity.d.ts +0 -36
- package/dist/cache-parity.js +0 -165
- package/dist/codebase-parity.d.ts +0 -31
- package/dist/codebase-parity.js +0 -303
- package/dist/doc-cli-parity.d.ts +0 -29
- package/dist/doc-cli-parity.js +0 -159
- package/dist/doctor-parity.d.ts +0 -42
- package/dist/doctor-parity.js +0 -157
- package/dist/intake-parity.d.ts +0 -30
- package/dist/intake-parity.js +0 -203
- package/dist/lifecycle-packs-parity.d.ts +0 -30
- package/dist/lifecycle-packs-parity.js +0 -377
- package/dist/orchestration-parity.d.ts +0 -38
- package/dist/orchestration-parity.js +0 -364
- package/dist/parity.d.ts +0 -36
- package/dist/parity.js +0 -176
- package/dist/platform-parity.d.ts +0 -26
- package/dist/platform-parity.js +0 -309
- package/dist/pr-closing-keywords-parity.d.ts +0 -45
- package/dist/pr-closing-keywords-parity.js +0 -259
- package/dist/pr-merge-readiness-parity.d.ts +0 -44
- package/dist/pr-merge-readiness-parity.js +0 -296
- package/dist/pr-monitor-parity.d.ts +0 -44
- package/dist/pr-monitor-parity.js +0 -283
- package/dist/pr-protected-issues-parity.d.ts +0 -41
- package/dist/pr-protected-issues-parity.js +0 -220
- package/dist/pr-wait-mergeable-parity.d.ts +0 -45
- package/dist/pr-wait-mergeable-parity.js +0 -340
- package/dist/release-e2e-parity.js +0 -114
- package/dist/release-parity.d.ts +0 -40
- package/dist/release-parity.js +0 -226
- package/dist/release-publish-parity.d.ts +0 -36
- package/dist/release-publish-parity.js +0 -138
- package/dist/release-rollback-parity.d.ts +0 -37
- package/dist/release-rollback-parity.js +0 -161
- package/dist/render-parity.d.ts +0 -36
- package/dist/render-parity.js +0 -385
- package/dist/scm-parity.d.ts +0 -39
- package/dist/scm-parity.js +0 -181
- package/dist/scope-lifecycle-parity.d.ts +0 -35
- package/dist/scope-lifecycle-parity.js +0 -177
- package/dist/session-parity.d.ts +0 -39
- package/dist/session-parity.js +0 -262
- package/dist/slice-parity.d.ts +0 -36
- package/dist/slice-parity.js +0 -304
- package/dist/swarm-parity.d.ts +0 -28
- package/dist/swarm-parity.js +0 -327
- package/dist/triage-actions-parity.d.ts +0 -36
- package/dist/triage-actions-parity.js +0 -357
- package/dist/triage-aux-b-parity.js +0 -308
- package/dist/triage-summary-parity.d.ts +0 -50
- package/dist/triage-summary-parity.js +0 -306
- package/dist/validate-content-parity.d.ts +0 -33
- package/dist/validate-content-parity.js +0 -356
- package/dist/vbrief-activate-parity.d.ts +0 -39
- package/dist/vbrief-activate-parity.js +0 -216
- package/dist/vbrief-build-parity.d.ts +0 -28
- package/dist/vbrief-build-parity.js +0 -399
- package/dist/vbrief-preflight-parity.js +0 -163
- package/dist/vbrief-reconcile-parity.d.ts +0 -23
- package/dist/vbrief-reconcile-parity.js +0 -609
- package/dist/vbrief-validate-parity.d.ts +0 -27
- package/dist/vbrief-validate-parity.js +0 -122
- package/dist/vbrief-validation-parity.d.ts +0 -28
- package/dist/vbrief-validation-parity.js +0 -645
- package/dist/verify-env-parity.d.ts +0 -28
- package/dist/verify-env-parity.js +0 -272
- package/dist/verify-source-parity.d.ts +0 -26
- 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
|