@deftai/directive 0.62.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/{branch-parity.d.ts → branch-fixtures.d.ts} +1 -3
  2. package/dist/{branch-parity.js → branch-fixtures.js} +3 -110
  3. package/dist/dispatch.d.ts +1 -1
  4. package/dist/dispatch.js +3 -1
  5. package/dist/orchestration-cli/coverage-map.js +1 -1
  6. package/dist/{policy-parity.d.ts → policy-fixtures.d.ts} +1 -3
  7. package/dist/{policy-parity.js → policy-fixtures.js} +4 -100
  8. package/dist/{release-e2e-parity.d.ts → release-e2e-fixtures.d.ts} +1 -3
  9. package/dist/release-e2e-fixtures.js +38 -0
  10. package/dist/{story-ready-parity.d.ts → story-ready-fixtures.d.ts} +1 -3
  11. package/dist/{story-ready-parity.js → story-ready-fixtures.js} +4 -121
  12. package/dist/{triage-aux-a-parity.d.ts → triage-aux-a-fixtures.d.ts} +1 -3
  13. package/dist/{triage-aux-a-parity.js → triage-aux-a-fixtures.js} +3 -73
  14. package/dist/{triage-aux-b-parity.d.ts → triage-aux-b-fixtures.d.ts} +1 -3
  15. package/dist/triage-aux-b-fixtures.js +167 -0
  16. package/dist/{triage-bootstrap-parity.d.ts → triage-bootstrap-fixtures.d.ts} +1 -3
  17. package/dist/{triage-bootstrap-parity.js → triage-bootstrap-fixtures.js} +4 -91
  18. package/dist/{triage-classify-parity.d.ts → triage-classify-fixtures.d.ts} +1 -3
  19. package/dist/{triage-classify-parity.js → triage-classify-fixtures.js} +4 -94
  20. package/dist/{triage-queue-parity.d.ts → triage-queue-fixtures.d.ts} +1 -3
  21. package/dist/{triage-queue-parity.js → triage-queue-fixtures.js} +4 -86
  22. package/dist/{triage-scope-parity.d.ts → triage-scope-fixtures.d.ts} +1 -3
  23. package/dist/{triage-scope-parity.js → triage-scope-fixtures.js} +4 -91
  24. package/dist/{vbrief-preflight-parity.d.ts → vbrief-preflight-fixtures.d.ts} +1 -3
  25. package/dist/vbrief-preflight-fixtures.js +79 -0
  26. package/dist/verify-source-cli/verify-cursor-tier1.d.ts +12 -0
  27. package/dist/verify-source-cli/verify-cursor-tier1.js +51 -0
  28. package/dist/{wip-cap-parity.d.ts → wip-cap-fixtures.d.ts} +1 -3
  29. package/dist/{wip-cap-parity.js → wip-cap-fixtures.js} +4 -91
  30. package/package.json +4 -15
  31. package/dist/cache-parity.d.ts +0 -36
  32. package/dist/cache-parity.js +0 -165
  33. package/dist/codebase-parity.d.ts +0 -31
  34. package/dist/codebase-parity.js +0 -303
  35. package/dist/doc-cli-parity.d.ts +0 -29
  36. package/dist/doc-cli-parity.js +0 -159
  37. package/dist/doctor-parity.d.ts +0 -42
  38. package/dist/doctor-parity.js +0 -157
  39. package/dist/intake-parity.d.ts +0 -30
  40. package/dist/intake-parity.js +0 -203
  41. package/dist/lifecycle-packs-parity.d.ts +0 -30
  42. package/dist/lifecycle-packs-parity.js +0 -377
  43. package/dist/orchestration-parity.d.ts +0 -38
  44. package/dist/orchestration-parity.js +0 -364
  45. package/dist/parity.d.ts +0 -36
  46. package/dist/parity.js +0 -176
  47. package/dist/platform-parity.d.ts +0 -26
  48. package/dist/platform-parity.js +0 -309
  49. package/dist/pr-closing-keywords-parity.d.ts +0 -45
  50. package/dist/pr-closing-keywords-parity.js +0 -259
  51. package/dist/pr-merge-readiness-parity.d.ts +0 -44
  52. package/dist/pr-merge-readiness-parity.js +0 -296
  53. package/dist/pr-monitor-parity.d.ts +0 -44
  54. package/dist/pr-monitor-parity.js +0 -283
  55. package/dist/pr-protected-issues-parity.d.ts +0 -41
  56. package/dist/pr-protected-issues-parity.js +0 -220
  57. package/dist/pr-wait-mergeable-parity.d.ts +0 -45
  58. package/dist/pr-wait-mergeable-parity.js +0 -340
  59. package/dist/release-e2e-parity.js +0 -114
  60. package/dist/release-parity.d.ts +0 -40
  61. package/dist/release-parity.js +0 -226
  62. package/dist/release-publish-parity.d.ts +0 -36
  63. package/dist/release-publish-parity.js +0 -138
  64. package/dist/release-rollback-parity.d.ts +0 -37
  65. package/dist/release-rollback-parity.js +0 -161
  66. package/dist/render-parity.d.ts +0 -36
  67. package/dist/render-parity.js +0 -385
  68. package/dist/scm-parity.d.ts +0 -39
  69. package/dist/scm-parity.js +0 -181
  70. package/dist/scope-lifecycle-parity.d.ts +0 -35
  71. package/dist/scope-lifecycle-parity.js +0 -177
  72. package/dist/session-parity.d.ts +0 -39
  73. package/dist/session-parity.js +0 -262
  74. package/dist/slice-parity.d.ts +0 -36
  75. package/dist/slice-parity.js +0 -304
  76. package/dist/swarm-parity.d.ts +0 -28
  77. package/dist/swarm-parity.js +0 -327
  78. package/dist/triage-actions-parity.d.ts +0 -36
  79. package/dist/triage-actions-parity.js +0 -357
  80. package/dist/triage-aux-b-parity.js +0 -308
  81. package/dist/triage-summary-parity.d.ts +0 -50
  82. package/dist/triage-summary-parity.js +0 -306
  83. package/dist/validate-content-parity.d.ts +0 -33
  84. package/dist/validate-content-parity.js +0 -356
  85. package/dist/vbrief-activate-parity.d.ts +0 -39
  86. package/dist/vbrief-activate-parity.js +0 -216
  87. package/dist/vbrief-build-parity.d.ts +0 -28
  88. package/dist/vbrief-build-parity.js +0 -399
  89. package/dist/vbrief-preflight-parity.js +0 -163
  90. package/dist/vbrief-reconcile-parity.d.ts +0 -23
  91. package/dist/vbrief-reconcile-parity.js +0 -609
  92. package/dist/vbrief-validate-parity.d.ts +0 -27
  93. package/dist/vbrief-validate-parity.js +0 -122
  94. package/dist/vbrief-validation-parity.d.ts +0 -28
  95. package/dist/vbrief-validation-parity.js +0 -645
  96. package/dist/verify-env-parity.d.ts +0 -28
  97. package/dist/verify-env-parity.js +0 -272
  98. package/dist/verify-source-parity.d.ts +0 -26
  99. package/dist/verify-source-parity.js +0 -178
@@ -1,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