@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,364 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Golden-output parity harness (#1788 s2): runs BOTH the Python oracles
|
|
4
|
-
* (subagent_monitor, probe_session, verify_investigation, verify_judgment_gates)
|
|
5
|
-
* and the ported TS CLI modules with cache-off, then diffs exit codes and output.
|
|
6
|
-
*
|
|
7
|
-
* Exit codes: 0 parity / 1 divergence / 2 harness setup error.
|
|
8
|
-
*/
|
|
9
|
-
import { spawnSync } from "node:child_process";
|
|
10
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { tmpdir } from "node:os";
|
|
12
|
-
import { dirname, join, resolve } from "node:path";
|
|
13
|
-
import { fileURLToPath } from "node:url";
|
|
14
|
-
function closeReadyLedger() {
|
|
15
|
-
return {
|
|
16
|
-
vBRIEFInfo: { version: "0.6" },
|
|
17
|
-
plan: {
|
|
18
|
-
id: "2026-06-14-example",
|
|
19
|
-
title: "Why did X happen?",
|
|
20
|
-
status: "completed",
|
|
21
|
-
items: [
|
|
22
|
-
{
|
|
23
|
-
id: "branch.slowness",
|
|
24
|
-
title: "Why slow",
|
|
25
|
-
status: "completed",
|
|
26
|
-
items: [
|
|
27
|
-
{
|
|
28
|
-
id: "claim.slowness.M1",
|
|
29
|
-
title: "embed contention",
|
|
30
|
-
status: "completed",
|
|
31
|
-
metadata: { "x-claim": { evidenceRefs: ["EV-001"] } },
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
id: "branch.queue",
|
|
37
|
-
title: "Queue wait",
|
|
38
|
-
status: "failed",
|
|
39
|
-
items: [
|
|
40
|
-
{
|
|
41
|
-
id: "claim.queue.B1",
|
|
42
|
-
title: "saturation",
|
|
43
|
-
status: "failed",
|
|
44
|
-
metadata: {
|
|
45
|
-
"x-claim": {
|
|
46
|
-
ruledOutReason: "active=2, cap=8",
|
|
47
|
-
evidenceRefs: ["EV-002"],
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
|
-
edges: [{ from: "claim.queue.B1", to: "branch.queue", type: "invalidates" }],
|
|
55
|
-
references: [
|
|
56
|
-
{ id: "EV-001", type: "log-excerpt" },
|
|
57
|
-
{ id: "EV-002", type: "metric-snapshot" },
|
|
58
|
-
],
|
|
59
|
-
metadata: {
|
|
60
|
-
"x-investigation": {
|
|
61
|
-
profile: "forensic-research-v1",
|
|
62
|
-
wavesCompleted: { "1": true, "2": true, "3": true, "4": true },
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function blockedLedger() {
|
|
69
|
-
const data = closeReadyLedger();
|
|
70
|
-
data.plan.status = "running";
|
|
71
|
-
return data;
|
|
72
|
-
}
|
|
73
|
-
function isoMinutesAgo(minutes) {
|
|
74
|
-
const d = new Date(Date.now() - minutes * 60 * 1000);
|
|
75
|
-
return d.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
76
|
-
}
|
|
77
|
-
function writeHeartbeat(scratch, agentId, minutesAgo, phase = "polling") {
|
|
78
|
-
mkdirSync(scratch, { recursive: true });
|
|
79
|
-
writeFileSync(join(scratch, `${agentId}.json`), JSON.stringify({
|
|
80
|
-
agent_id: agentId,
|
|
81
|
-
parent_id: "parent-test",
|
|
82
|
-
last_heartbeat_at: isoMinutesAgo(minutesAgo),
|
|
83
|
-
last_message: "polling Greptile",
|
|
84
|
-
phase,
|
|
85
|
-
terminal_state: null,
|
|
86
|
-
}), "utf8");
|
|
87
|
-
}
|
|
88
|
-
function setupProjectDefinition(root, policy = {}) {
|
|
89
|
-
mkdirSync(join(root, "vbrief", "proposed"), { recursive: true });
|
|
90
|
-
mkdirSync(join(root, "vbrief", "pending"), { recursive: true });
|
|
91
|
-
mkdirSync(join(root, "vbrief", "active"), { recursive: true });
|
|
92
|
-
mkdirSync(join(root, "vbrief", "completed"), { recursive: true });
|
|
93
|
-
mkdirSync(join(root, "vbrief", "cancelled"), { recursive: true });
|
|
94
|
-
writeFileSync(join(root, "vbrief", "PROJECT-DEFINITION.vbrief.json"), JSON.stringify({
|
|
95
|
-
vBRIEFInfo: { version: "0.6" },
|
|
96
|
-
plan: { title: "parity", status: "running", items: [], policy },
|
|
97
|
-
}), "utf8");
|
|
98
|
-
}
|
|
99
|
-
export const PARITY_SCENARIOS = [
|
|
100
|
-
{
|
|
101
|
-
name: "monitor-empty-scratch",
|
|
102
|
-
module: "subagent_monitor",
|
|
103
|
-
argv: ["--json"],
|
|
104
|
-
setup: (root) => {
|
|
105
|
-
mkdirSync(join(root, ".deft-scratch", "subagent-status"), { recursive: true });
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
name: "monitor-fresh-heartbeat",
|
|
110
|
-
module: "subagent_monitor",
|
|
111
|
-
argv: ["--json", "--threshold-minutes", "30"],
|
|
112
|
-
setup: (root) => {
|
|
113
|
-
writeHeartbeat(join(root, ".deft-scratch", "subagent-status"), "agent-fresh", 1);
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: "monitor-stale-heartbeat",
|
|
118
|
-
module: "subagent_monitor",
|
|
119
|
-
argv: ["--json", "--threshold-minutes", "30"],
|
|
120
|
-
setup: (root) => {
|
|
121
|
-
writeHeartbeat(join(root, ".deft-scratch", "subagent-status"), "agent-stale", 45);
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: "monitor-missing-scratch-dir",
|
|
126
|
-
module: "subagent_monitor",
|
|
127
|
-
argv: ["--scratch-dir", ".deft-scratch/missing-status", "--json"],
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
name: "probe-session-guard-artifact-blocked",
|
|
131
|
-
module: "probe_session",
|
|
132
|
-
argv: ["guard-artifact", "--path", "vbrief/proposed/auth-probe.vbrief.json"],
|
|
133
|
-
setup: (root) => {
|
|
134
|
-
mkdirSync(join(root, ".deft"), { recursive: true });
|
|
135
|
-
writeFileSync(join(root, ".deft", "probe-session.json"), `${JSON.stringify({
|
|
136
|
-
schemaVersion: 1,
|
|
137
|
-
state: "interrogate",
|
|
138
|
-
target: "auth-probe",
|
|
139
|
-
currentBranch: "tokens",
|
|
140
|
-
resolvedDecisions: [],
|
|
141
|
-
startedAt: "2026-06-19T12:00:00Z",
|
|
142
|
-
})}\n`, "utf8");
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: "probe-session-complete-then-guard",
|
|
147
|
-
module: "probe_session",
|
|
148
|
-
argv: ["guard-artifact", "--path", "vbrief/proposed/auth-probe.vbrief.json"],
|
|
149
|
-
setup: (root) => {
|
|
150
|
-
mkdirSync(join(root, ".deft"), { recursive: true });
|
|
151
|
-
writeFileSync(join(root, ".deft", "probe-session.json"), `${JSON.stringify({
|
|
152
|
-
schemaVersion: 1,
|
|
153
|
-
state: "complete",
|
|
154
|
-
target: "auth-probe",
|
|
155
|
-
currentBranch: "tokens",
|
|
156
|
-
resolvedDecisions: [{ question: "Q?", answer: "A.", status: "locked" }],
|
|
157
|
-
startedAt: "2026-06-19T12:00:00Z",
|
|
158
|
-
completedAt: "2026-06-19T13:00:00Z",
|
|
159
|
-
})}\n`, "utf8");
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: "investigation-close-clean",
|
|
164
|
-
module: "verify_investigation",
|
|
165
|
-
argv: ["--ledger", "ledger.json"],
|
|
166
|
-
setup: (root) => {
|
|
167
|
-
writeFileSync(join(root, "ledger.json"), `${JSON.stringify(closeReadyLedger())}\n`, "utf8");
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
name: "investigation-close-blocked",
|
|
172
|
-
module: "verify_investigation",
|
|
173
|
-
argv: ["--ledger", "ledger.json"],
|
|
174
|
-
setup: (root) => {
|
|
175
|
-
writeFileSync(join(root, "ledger.json"), `${JSON.stringify(blockedLedger())}\n`, "utf8");
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
name: "judgment-gate-advise-secrets",
|
|
180
|
-
module: "verify_judgment_gates",
|
|
181
|
-
argv: ["--path", "secrets/prod.env"],
|
|
182
|
-
setup: (root) => setupProjectDefinition(root),
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
name: "judgment-gate-enforce-secrets-blocked",
|
|
186
|
-
module: "verify_judgment_gates",
|
|
187
|
-
argv: ["--enforce", "--path", "secrets/prod.env", "--quiet"],
|
|
188
|
-
setup: (root) => setupProjectDefinition(root),
|
|
189
|
-
},
|
|
190
|
-
];
|
|
191
|
-
const TS_SCRIPT = {
|
|
192
|
-
subagent_monitor: "subagent-monitor.js",
|
|
193
|
-
probe_session: "probe-session.js",
|
|
194
|
-
verify_investigation: "verify-investigation.js",
|
|
195
|
-
verify_judgment_gates: "verify-judgment-gates.js",
|
|
196
|
-
};
|
|
197
|
-
const PY_SCRIPT = {
|
|
198
|
-
subagent_monitor: "subagent_monitor.py",
|
|
199
|
-
probe_session: "probe_session.py",
|
|
200
|
-
verify_investigation: "verify_investigation.py",
|
|
201
|
-
verify_judgment_gates: "verify_judgment_gates.py",
|
|
202
|
-
};
|
|
203
|
-
function runCapture(cmd, args, cwd, env) {
|
|
204
|
-
const result = spawnSync(cmd, args, {
|
|
205
|
-
cwd,
|
|
206
|
-
encoding: "utf8",
|
|
207
|
-
env: { ...process.env, ...env },
|
|
208
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
209
|
-
});
|
|
210
|
-
return {
|
|
211
|
-
status: result.status ?? 2,
|
|
212
|
-
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
213
|
-
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
function resolveDeftRoot() {
|
|
217
|
-
if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
|
|
218
|
-
return resolve(process.env.DEFT_ROOT);
|
|
219
|
-
}
|
|
220
|
-
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
221
|
-
}
|
|
222
|
-
export function normaliseHarnessNoise(text) {
|
|
223
|
-
let out = text
|
|
224
|
-
.split("\n")
|
|
225
|
-
.filter((line) => !line.startsWith("Using CPython") &&
|
|
226
|
-
!line.startsWith("Creating virtual environment") &&
|
|
227
|
-
!line.startsWith("Installed "))
|
|
228
|
-
.join("\n");
|
|
229
|
-
// Monitor JSON emits wall-clock `now` / age_seconds — normalise volatile fields.
|
|
230
|
-
if (out.trimStart().startsWith("{")) {
|
|
231
|
-
try {
|
|
232
|
-
const obj = JSON.parse(out);
|
|
233
|
-
if (typeof obj.now === "string") {
|
|
234
|
-
delete obj.now;
|
|
235
|
-
if (Array.isArray(obj.records)) {
|
|
236
|
-
for (const rec of obj.records) {
|
|
237
|
-
if (typeof rec === "object" && rec !== null && !Array.isArray(rec)) {
|
|
238
|
-
delete rec.age_seconds;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
out = JSON.stringify(obj, null, 2);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
// not JSON — leave as-is
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return out;
|
|
250
|
-
}
|
|
251
|
-
export function diffParity(python, ts) {
|
|
252
|
-
const pythonStdout = normaliseHarnessNoise(python.stdout);
|
|
253
|
-
const tsStdout = normaliseHarnessNoise(ts.stdout);
|
|
254
|
-
const pythonStderr = normaliseHarnessNoise(python.stderr);
|
|
255
|
-
const tsStderr = normaliseHarnessNoise(ts.stderr);
|
|
256
|
-
return {
|
|
257
|
-
exitMismatch: python.exitCode !== ts.exitCode,
|
|
258
|
-
stdoutMismatch: pythonStdout !== tsStdout,
|
|
259
|
-
stderrMismatch: pythonStderr !== tsStderr,
|
|
260
|
-
};
|
|
261
|
-
}
|
|
262
|
-
function runScenario(deftRoot, scenario) {
|
|
263
|
-
const root = mkdtempSync(join(tmpdir(), "deft-orchestration-parity-"));
|
|
264
|
-
try {
|
|
265
|
-
if (scenario.setup) {
|
|
266
|
-
scenario.setup(root);
|
|
267
|
-
}
|
|
268
|
-
const env = {
|
|
269
|
-
DEFT_CACHE_DISABLE: "1",
|
|
270
|
-
PYTHONUTF8: "1",
|
|
271
|
-
};
|
|
272
|
-
const pyScript = join(deftRoot, "scripts", PY_SCRIPT[scenario.module]);
|
|
273
|
-
const pyArgs = ["run", "python", pyScript];
|
|
274
|
-
if (scenario.module === "probe_session" || scenario.module === "verify_judgment_gates") {
|
|
275
|
-
pyArgs.push("--project-root", root, ...scenario.argv);
|
|
276
|
-
}
|
|
277
|
-
else if (scenario.module === "verify_investigation") {
|
|
278
|
-
pyArgs.push(...scenario.argv.map((a) => (a === "ledger.json" ? join(root, "ledger.json") : a)));
|
|
279
|
-
pyArgs.push("--project-root", root);
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
pyArgs.push(...scenario.argv);
|
|
283
|
-
}
|
|
284
|
-
const tsScript = join(deftRoot, "packages", "cli", "dist", TS_SCRIPT[scenario.module]);
|
|
285
|
-
const tsArgs = [tsScript];
|
|
286
|
-
if (scenario.module === "probe_session" || scenario.module === "verify_judgment_gates") {
|
|
287
|
-
tsArgs.push("--project-root", root, ...scenario.argv);
|
|
288
|
-
}
|
|
289
|
-
else if (scenario.module === "verify_investigation") {
|
|
290
|
-
tsArgs.push(...scenario.argv.map((a) => (a === "ledger.json" ? join(root, "ledger.json") : a)));
|
|
291
|
-
tsArgs.push("--project-root", root);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
tsArgs.push(...scenario.argv);
|
|
295
|
-
}
|
|
296
|
-
const py = runCapture("uv", pyArgs, scenario.module === "subagent_monitor" ? root : deftRoot, env);
|
|
297
|
-
const ts = runCapture("node", tsArgs, scenario.module === "subagent_monitor" ? root : deftRoot, env);
|
|
298
|
-
return {
|
|
299
|
-
python: { name: scenario.name, exitCode: py.status, stdout: py.stdout, stderr: py.stderr },
|
|
300
|
-
ts: { name: scenario.name, exitCode: ts.status, stdout: ts.stdout, stderr: ts.stderr },
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
finally {
|
|
304
|
-
rmSync(root, { recursive: true, force: true });
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
export function runParity() {
|
|
308
|
-
const deftRoot = resolveDeftRoot();
|
|
309
|
-
process.env.DEFT_ROOT = deftRoot;
|
|
310
|
-
const scenarios = [];
|
|
311
|
-
for (const scenario of PARITY_SCENARIOS) {
|
|
312
|
-
const ran = runScenario(deftRoot, scenario);
|
|
313
|
-
const diff = diffParity(ran.python, ran.ts);
|
|
314
|
-
scenarios.push({
|
|
315
|
-
name: scenario.name,
|
|
316
|
-
pythonExit: ran.python.exitCode,
|
|
317
|
-
tsExit: ran.ts.exitCode,
|
|
318
|
-
pythonStdout: normaliseHarnessNoise(ran.python.stdout),
|
|
319
|
-
tsStdout: normaliseHarnessNoise(ran.ts.stdout),
|
|
320
|
-
pythonStderr: normaliseHarnessNoise(ran.python.stderr),
|
|
321
|
-
tsStderr: normaliseHarnessNoise(ran.ts.stderr),
|
|
322
|
-
...diff,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
const ok = scenarios.every((s) => !s.exitMismatch && !s.stdoutMismatch && !s.stderrMismatch);
|
|
326
|
-
return { ok, scenarios };
|
|
327
|
-
}
|
|
328
|
-
export function renderReport(result) {
|
|
329
|
-
if (result.ok) {
|
|
330
|
-
return `orchestration parity: CLEAN -- Python and TS agree on ${result.scenarios.length} case(s).`;
|
|
331
|
-
}
|
|
332
|
-
const lines = ["orchestration parity: DIVERGENCE"];
|
|
333
|
-
for (const s of result.scenarios) {
|
|
334
|
-
if (s.exitMismatch || s.stdoutMismatch || s.stderrMismatch) {
|
|
335
|
-
lines.push(` scenario: ${s.name}`);
|
|
336
|
-
if (s.exitMismatch) {
|
|
337
|
-
lines.push(` exit mismatch: python=${s.pythonExit} ts=${s.tsExit}`);
|
|
338
|
-
}
|
|
339
|
-
if (s.stdoutMismatch) {
|
|
340
|
-
lines.push(` stdout mismatch (python ${s.pythonStdout.length} / ts ${s.tsStdout.length} bytes)`);
|
|
341
|
-
}
|
|
342
|
-
if (s.stderrMismatch) {
|
|
343
|
-
lines.push(` stderr mismatch (python ${s.pythonStderr.length} / ts ${s.tsStderr.length} bytes)`);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
return lines.join("\n");
|
|
348
|
-
}
|
|
349
|
-
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
350
|
-
try {
|
|
351
|
-
const result = runParity();
|
|
352
|
-
if (result.ok) {
|
|
353
|
-
process.stdout.write(`${renderReport(result)}\n`);
|
|
354
|
-
process.exit(0);
|
|
355
|
-
}
|
|
356
|
-
process.stderr.write(`${renderReport(result)}\n`);
|
|
357
|
-
process.exit(1);
|
|
358
|
-
}
|
|
359
|
-
catch (err) {
|
|
360
|
-
process.stderr.write(`orchestration parity: harness error -- ${String(err)}\n`);
|
|
361
|
-
process.exit(2);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
//# sourceMappingURL=orchestration-parity.js.map
|
package/dist/parity.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export interface ParityFinding {
|
|
3
|
-
readonly path: string;
|
|
4
|
-
readonly line: number;
|
|
5
|
-
readonly label: string;
|
|
6
|
-
}
|
|
7
|
-
export interface GateOutput {
|
|
8
|
-
readonly exitCode: number;
|
|
9
|
-
readonly findings: ParityFinding[];
|
|
10
|
-
}
|
|
11
|
-
export interface ParityResult {
|
|
12
|
-
readonly ok: boolean;
|
|
13
|
-
readonly pythonExit: number;
|
|
14
|
-
readonly tsExit: number;
|
|
15
|
-
readonly exitMismatch: boolean;
|
|
16
|
-
readonly onlyPython: string[];
|
|
17
|
-
readonly onlyTs: string[];
|
|
18
|
-
}
|
|
19
|
-
/** Parse the rendered finding lines out of a gate's stderr. */
|
|
20
|
-
export declare function parseFindings(stderr: string): ParityFinding[];
|
|
21
|
-
/** Stable key for a finding (path:line:label). */
|
|
22
|
-
export declare function findingKey(f: ParityFinding): string;
|
|
23
|
-
/** Diff two gate outputs into a structured parity result. */
|
|
24
|
-
export declare function diffGates(python: GateOutput, ts: GateOutput): ParityResult;
|
|
25
|
-
/**
|
|
26
|
-
* Fixture corpus exercised by the parity harness. Each entry maps a repo-rel
|
|
27
|
-
* path to its exact textual content. Covers: clean files, U+FFFD, cp437/cp1252
|
|
28
|
-
* mojibake, unexpected/ tolerated BOM, vBRIEF narrative control chars, markdown
|
|
29
|
-
* code-span false-positive guard, and the `-798-` allow-list carve-out.
|
|
30
|
-
*/
|
|
31
|
-
export declare const PARITY_FIXTURES: ReadonlyArray<readonly [string, string]>;
|
|
32
|
-
/** Build the throwaway git repo with all fixtures; return its root path. */
|
|
33
|
-
export declare function buildFixtureRepo(): string;
|
|
34
|
-
/** Run both gates against a fresh fixture repo and diff them. */
|
|
35
|
-
export declare function runParity(): ParityResult;
|
|
36
|
-
//# sourceMappingURL=parity.d.ts.map
|
package/dist/parity.js
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Golden-output parity harness (#1718): builds a throwaway git repo of
|
|
4
|
-
* known-corruption fixtures, runs BOTH the Python oracle
|
|
5
|
-
* (`scripts/verify_encoding.py`) and the ported TS gate against it with the
|
|
6
|
-
* cache off, and diffs structured findings + exit codes. A clean run proves
|
|
7
|
-
* the TS port detects identically to the Python implementation it replaces.
|
|
8
|
-
*
|
|
9
|
-
* Exit codes: 0 parity / 1 divergence / 2 harness setup error.
|
|
10
|
-
*/
|
|
11
|
-
import { execFileSync } from "node:child_process";
|
|
12
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
13
|
-
import { tmpdir } from "node:os";
|
|
14
|
-
import { dirname, join, resolve } from "node:path";
|
|
15
|
-
import { fileURLToPath } from "node:url";
|
|
16
|
-
// Finding render is ` path:line [label] context`; capture path / line / label.
|
|
17
|
-
const FINDING_RE = /^ {2}(.+?):(\d+) \[(.+?)\] /;
|
|
18
|
-
/** Parse the rendered finding lines out of a gate's stderr. */
|
|
19
|
-
export function parseFindings(stderr) {
|
|
20
|
-
const out = [];
|
|
21
|
-
for (const line of stderr.split(/\r?\n/)) {
|
|
22
|
-
const m = FINDING_RE.exec(line);
|
|
23
|
-
if (m !== null) {
|
|
24
|
-
out.push({ path: m[1], line: Number(m[2]), label: m[3] });
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
return out;
|
|
28
|
-
}
|
|
29
|
-
/** Stable key for a finding (path:line:label). */
|
|
30
|
-
export function findingKey(f) {
|
|
31
|
-
return `${f.path}:${f.line}:${f.label}`;
|
|
32
|
-
}
|
|
33
|
-
/** Diff two gate outputs into a structured parity result. */
|
|
34
|
-
export function diffGates(python, ts) {
|
|
35
|
-
const pyKeys = new Set(python.findings.map(findingKey));
|
|
36
|
-
const tsKeys = new Set(ts.findings.map(findingKey));
|
|
37
|
-
const onlyPython = [...pyKeys].filter((k) => !tsKeys.has(k)).sort();
|
|
38
|
-
const onlyTs = [...tsKeys].filter((k) => !pyKeys.has(k)).sort();
|
|
39
|
-
const exitMismatch = python.exitCode !== ts.exitCode;
|
|
40
|
-
return {
|
|
41
|
-
ok: !exitMismatch && onlyPython.length === 0 && onlyTs.length === 0,
|
|
42
|
-
pythonExit: python.exitCode,
|
|
43
|
-
tsExit: ts.exitCode,
|
|
44
|
-
exitMismatch,
|
|
45
|
-
onlyPython,
|
|
46
|
-
onlyTs,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
function runCapture(cmd, args, cwd) {
|
|
50
|
-
try {
|
|
51
|
-
const stdout = execFileSync(cmd, args, {
|
|
52
|
-
cwd,
|
|
53
|
-
encoding: "utf8",
|
|
54
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
55
|
-
});
|
|
56
|
-
return { status: 0, stdout, stderr: "" };
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
const e = err;
|
|
60
|
-
return {
|
|
61
|
-
status: typeof e.status === "number" ? e.status : 2,
|
|
62
|
-
stdout: typeof e.stdout === "string" ? e.stdout : "",
|
|
63
|
-
stderr: typeof e.stderr === "string" ? e.stderr : "",
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
const BOM = "\ufeff";
|
|
68
|
-
/**
|
|
69
|
-
* Fixture corpus exercised by the parity harness. Each entry maps a repo-rel
|
|
70
|
-
* path to its exact textual content. Covers: clean files, U+FFFD, cp437/cp1252
|
|
71
|
-
* mojibake, unexpected/ tolerated BOM, vBRIEF narrative control chars, markdown
|
|
72
|
-
* code-span false-positive guard, and the `-798-` allow-list carve-out.
|
|
73
|
-
*/
|
|
74
|
-
export const PARITY_FIXTURES = [
|
|
75
|
-
["clean.md", "# Title\n\nplain ascii prose\n"],
|
|
76
|
-
["ufffd.txt", "line one\nbroken \ufffd marker\n"],
|
|
77
|
-
["cp437.md", "a cp437 glyph \u0393\u00a3\u00f4 in prose\n"],
|
|
78
|
-
["cp1252.txt", "a smart \u00e2\u20ac\u2122 quote in prose\n"],
|
|
79
|
-
["bom.json", `${BOM}{"a": 1}\n`],
|
|
80
|
-
["tolerated-bom.ps1", `${BOM}Write-Host 'ok'\n`],
|
|
81
|
-
["md-quoted.md", "see the bigon `\u0393\u00a3\u00f4` inside a code span\n"],
|
|
82
|
-
[
|
|
83
|
-
"vbrief/active/2026-01-01-1-x.vbrief.json",
|
|
84
|
-
`${JSON.stringify({ plan: { narratives: { problem: "has a \u000b vtab" } } }, null, 2)}\n`,
|
|
85
|
-
],
|
|
86
|
-
[
|
|
87
|
-
"vbrief/active/2026-01-01-798-recurrence.vbrief.json",
|
|
88
|
-
`${JSON.stringify({ plan: { narratives: { problem: "catalogs \u0393\u00a3\u00f4" } } }, null, 2)}\n`,
|
|
89
|
-
],
|
|
90
|
-
];
|
|
91
|
-
/** Build the throwaway git repo with all fixtures; return its root path. */
|
|
92
|
-
export function buildFixtureRepo() {
|
|
93
|
-
const root = mkdtempSync(join(tmpdir(), "deft-encoding-parity-"));
|
|
94
|
-
// mkdtempSync already created the dir, so any failure below (a write error or
|
|
95
|
-
// git not being on PATH) must clean it up here -- the try/finally in
|
|
96
|
-
// runParity only fires once this function returns the path successfully.
|
|
97
|
-
try {
|
|
98
|
-
for (const [rel, content] of PARITY_FIXTURES) {
|
|
99
|
-
const full = join(root, rel);
|
|
100
|
-
mkdirSync(dirname(full), { recursive: true });
|
|
101
|
-
writeFileSync(full, content, { encoding: "utf8" });
|
|
102
|
-
}
|
|
103
|
-
execFileSync("git", ["init", "-q"], { cwd: root });
|
|
104
|
-
execFileSync("git", ["add", "-A"], { cwd: root });
|
|
105
|
-
}
|
|
106
|
-
catch (err) {
|
|
107
|
-
rmSync(root, { recursive: true, force: true });
|
|
108
|
-
throw err;
|
|
109
|
-
}
|
|
110
|
-
return root;
|
|
111
|
-
}
|
|
112
|
-
function resolveDeftRoot() {
|
|
113
|
-
if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
|
|
114
|
-
return resolve(process.env.DEFT_ROOT);
|
|
115
|
-
}
|
|
116
|
-
// dist layout: packages/cli/dist/parity.js -> repo root is three levels up.
|
|
117
|
-
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
118
|
-
}
|
|
119
|
-
/** Run both gates against a fresh fixture repo and diff them. */
|
|
120
|
-
export function runParity() {
|
|
121
|
-
const deftRoot = resolveDeftRoot();
|
|
122
|
-
const repo = buildFixtureRepo();
|
|
123
|
-
try {
|
|
124
|
-
const py = runCapture("uv", [
|
|
125
|
-
"run",
|
|
126
|
-
"python",
|
|
127
|
-
join(deftRoot, "scripts", "verify_encoding.py"),
|
|
128
|
-
"--all",
|
|
129
|
-
"--project-root",
|
|
130
|
-
repo,
|
|
131
|
-
], deftRoot);
|
|
132
|
-
const ts = runCapture("node", [
|
|
133
|
-
join(deftRoot, "packages", "cli", "dist", "verify-encoding.js"),
|
|
134
|
-
"--all",
|
|
135
|
-
"--project-root",
|
|
136
|
-
repo,
|
|
137
|
-
], deftRoot);
|
|
138
|
-
return diffGates({ exitCode: py.status, findings: parseFindings(py.stderr) }, { exitCode: ts.status, findings: parseFindings(ts.stderr) });
|
|
139
|
-
}
|
|
140
|
-
finally {
|
|
141
|
-
rmSync(repo, { recursive: true, force: true });
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function renderReport(result) {
|
|
145
|
-
if (result.ok) {
|
|
146
|
-
return `verify_encoding parity: CLEAN -- Python and TS agree (exit ${result.pythonExit}).`;
|
|
147
|
-
}
|
|
148
|
-
const lines = ["verify_encoding parity: DIVERGENCE"];
|
|
149
|
-
if (result.exitMismatch) {
|
|
150
|
-
lines.push(` exit mismatch: python=${result.pythonExit} ts=${result.tsExit}`);
|
|
151
|
-
}
|
|
152
|
-
for (const k of result.onlyPython) {
|
|
153
|
-
lines.push(` only python: ${k}`);
|
|
154
|
-
}
|
|
155
|
-
for (const k of result.onlyTs) {
|
|
156
|
-
lines.push(` only ts: ${k}`);
|
|
157
|
-
}
|
|
158
|
-
return lines.join("\n");
|
|
159
|
-
}
|
|
160
|
-
// Normalize via fileURLToPath so this fires on Windows too (see verify-encoding.ts).
|
|
161
|
-
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
162
|
-
try {
|
|
163
|
-
const result = runParity();
|
|
164
|
-
if (result.ok) {
|
|
165
|
-
process.stdout.write(`${renderReport(result)}\n`);
|
|
166
|
-
process.exit(0);
|
|
167
|
-
}
|
|
168
|
-
process.stderr.write(`${renderReport(result)}\n`);
|
|
169
|
-
process.exit(1);
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
process.stderr.write(`verify_encoding parity: harness error -- ${String(err)}\n`);
|
|
173
|
-
process.exit(2);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
//# sourceMappingURL=parity.js.map
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export interface ParityCase {
|
|
3
|
-
readonly name: string;
|
|
4
|
-
readonly runPython: (deftRoot: string, repo: string) => unknown;
|
|
5
|
-
readonly runTs: (deftRoot: string, repo: string) => unknown;
|
|
6
|
-
readonly setup?: (repo: string) => void;
|
|
7
|
-
}
|
|
8
|
-
export interface ParityDiff {
|
|
9
|
-
readonly caseName: string;
|
|
10
|
-
readonly mismatch: boolean;
|
|
11
|
-
readonly pythonJson: string;
|
|
12
|
-
readonly tsJson: string;
|
|
13
|
-
}
|
|
14
|
-
export interface ParityResult {
|
|
15
|
-
readonly ok: boolean;
|
|
16
|
-
readonly diffs: ParityDiff[];
|
|
17
|
-
}
|
|
18
|
-
/** Strip volatile filesystem paths from capability reports before compare. */
|
|
19
|
-
export declare function normalizeCapabilityReport(value: unknown): unknown;
|
|
20
|
-
/** Normalise volatile agents-refresh plan fields for comparison. */
|
|
21
|
-
export declare function normalizeAgentsPlan(plan: Record<string, unknown>): Record<string, unknown>;
|
|
22
|
-
export declare const PARITY_CASES: readonly ParityCase[];
|
|
23
|
-
export declare function diffCase(name: string, python: unknown, ts: unknown): ParityDiff;
|
|
24
|
-
export declare function runParity(): ParityResult;
|
|
25
|
-
export declare function renderReport(result: ParityResult): string;
|
|
26
|
-
//# sourceMappingURL=platform-parity.d.ts.map
|