@deftai/directive 0.62.0 → 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 +0 -1
- 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/{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
package/dist/platform-parity.js
DELETED
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Golden-output parity harness (#1787 s3): runs BOTH the frozen Python oracle
|
|
4
|
-
* modules and the ported TS @deftai/directive-core/platform helpers over shared fixtures,
|
|
5
|
-
* cache-off, and diffs JSON payloads + exit codes.
|
|
6
|
-
*
|
|
7
|
-
* Exit codes: 0 parity / 1 divergence / 2 harness setup error.
|
|
8
|
-
*/
|
|
9
|
-
import { execFileSync } from "node:child_process";
|
|
10
|
-
import { existsSync, mkdirSync, mkdtempSync, readFileSync, 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
|
-
import { agentsRefreshPlan, detectIpTerms, disambiguateSlug, normalizeSlug, probeRuntimeCapabilities, reportToDict, resolveChangelog, resolveVersion, toPep440, } from "@deftai/directive-core/platform";
|
|
15
|
-
const FIXED_ISO = "2026-06-19T12:00:00Z";
|
|
16
|
-
const FIXED_SESSION = "abcdef012345";
|
|
17
|
-
const FIXED_SHA = "deadbeefcafe";
|
|
18
|
-
const SLUG_FIXTURES = [
|
|
19
|
-
"Hello World",
|
|
20
|
-
"Add widget (v2)!",
|
|
21
|
-
"café latte",
|
|
22
|
-
"El Niño Año",
|
|
23
|
-
"日本語",
|
|
24
|
-
"[x] Fix login bug",
|
|
25
|
-
"con",
|
|
26
|
-
"foo bar---baz",
|
|
27
|
-
];
|
|
28
|
-
const PEP440_FIXTURES = ["v0.22.0", "v0.20.0-rc.3", "v0.20.0-beta.2", "0.20.0-alpha.1"];
|
|
29
|
-
function resolveDeftRoot() {
|
|
30
|
-
if (process.env.DEFT_ROOT)
|
|
31
|
-
return resolve(process.env.DEFT_ROOT);
|
|
32
|
-
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
33
|
-
}
|
|
34
|
-
function runPythonJson(deftRoot, code) {
|
|
35
|
-
const scriptsDir = join(deftRoot, "scripts").replace(/\\/g, "/");
|
|
36
|
-
const wrapped = [
|
|
37
|
-
"import json,sys",
|
|
38
|
-
`sys.path.insert(0, ${JSON.stringify(scriptsDir)})`,
|
|
39
|
-
code,
|
|
40
|
-
].join("\n");
|
|
41
|
-
const stdout = execFileSync("uv", ["run", "python", "-c", wrapped], {
|
|
42
|
-
cwd: deftRoot,
|
|
43
|
-
encoding: "utf8",
|
|
44
|
-
env: { ...process.env, DEFT_CACHE_DISABLE: "1", PYTHONUTF8: "1" },
|
|
45
|
-
});
|
|
46
|
-
return JSON.parse(stdout.trim());
|
|
47
|
-
}
|
|
48
|
-
function stableJson(value) {
|
|
49
|
-
return `${JSON.stringify(value, null, 2)}\n`;
|
|
50
|
-
}
|
|
51
|
-
/** Strip volatile filesystem paths from capability reports before compare. */
|
|
52
|
-
export function normalizeCapabilityReport(value) {
|
|
53
|
-
if (value === null || typeof value !== "object")
|
|
54
|
-
return value;
|
|
55
|
-
const record = value;
|
|
56
|
-
const out = { ...record };
|
|
57
|
-
if (record.ownership && typeof record.ownership === "object") {
|
|
58
|
-
out.ownership = { ...record.ownership, path: "<REPO>" };
|
|
59
|
-
}
|
|
60
|
-
return out;
|
|
61
|
-
}
|
|
62
|
-
/** Normalise volatile agents-refresh plan fields for comparison. */
|
|
63
|
-
export function normalizeAgentsPlan(plan) {
|
|
64
|
-
const out = { ...plan };
|
|
65
|
-
for (const key of ["sha", "refreshed", "session", "attributed_rendered", "new_content"]) {
|
|
66
|
-
if (key in out)
|
|
67
|
-
out[key] = "<NORMALIZED>";
|
|
68
|
-
}
|
|
69
|
-
return out;
|
|
70
|
-
}
|
|
71
|
-
const CHANGELOG_HEADER = "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n";
|
|
72
|
-
function buildConflictChangelog() {
|
|
73
|
-
return (`${CHANGELOG_HEADER}## [Unreleased]\n\n` +
|
|
74
|
-
"### Added\n\n" +
|
|
75
|
-
"<<<<<<< HEAD\n" +
|
|
76
|
-
"- **feat: head entry** -- landed (#100)\n" +
|
|
77
|
-
"=======\n" +
|
|
78
|
-
"- **feat: branch entry** -- new (#200)\n" +
|
|
79
|
-
">>>>>>> branch-sha\n\n" +
|
|
80
|
-
"## [0.1.0] - 2026-01-01\n");
|
|
81
|
-
}
|
|
82
|
-
function readTemplate(deftRoot) {
|
|
83
|
-
// #1875: the AGENTS.md template moved under content/ in the source repo
|
|
84
|
-
// (content/templates/agents-entry.md). Prefer that location and fall back to
|
|
85
|
-
// the flattened layout (templates/agents-entry.md) so the harness resolves in
|
|
86
|
-
// both a source checkout and a flattened consumer deposit.
|
|
87
|
-
const contentCandidate = join(deftRoot, "content", "templates", "agents-entry.md");
|
|
88
|
-
const flatCandidate = join(deftRoot, "templates", "agents-entry.md");
|
|
89
|
-
return readFileSync(existsSync(contentCandidate) ? contentCandidate : flatCandidate, "utf8");
|
|
90
|
-
}
|
|
91
|
-
export const PARITY_CASES = [
|
|
92
|
-
{
|
|
93
|
-
name: "slug-normalize-unicode",
|
|
94
|
-
runPython: (deftRoot) => runPythonJson(deftRoot, [
|
|
95
|
-
"from slug_normalize import normalize_slug, disambiguate_slug",
|
|
96
|
-
`fixtures = ${JSON.stringify(SLUG_FIXTURES)}`,
|
|
97
|
-
"out = {'slugs': [normalize_slug(t) for t in fixtures], 'collision': disambiguate_slug('hello-world', {'hello-world'})}",
|
|
98
|
-
"print(json.dumps(out))",
|
|
99
|
-
].join("\n")),
|
|
100
|
-
runTs: () => ({
|
|
101
|
-
slugs: SLUG_FIXTURES.map((t) => normalizeSlug(t)),
|
|
102
|
-
collision: disambiguateSlug("hello-world", new Set(["hello-world"])),
|
|
103
|
-
}),
|
|
104
|
-
},
|
|
105
|
-
{
|
|
106
|
-
name: "version-resolve-manifest",
|
|
107
|
-
setup: (repo) => {
|
|
108
|
-
writeFileSync(join(repo, "VERSION"), "tag: v9.8.7\nref: v9.8.7\n", "utf8");
|
|
109
|
-
},
|
|
110
|
-
runPython: (deftRoot, repo) => runPythonJson(deftRoot, [
|
|
111
|
-
"import json",
|
|
112
|
-
"from pathlib import Path",
|
|
113
|
-
"import resolve_version as rv",
|
|
114
|
-
`base = Path(${JSON.stringify(repo)})`,
|
|
115
|
-
"print(json.dumps({'version': rv._from_manifest(base), 'pep440': [rv.to_pep440(v) for v in " +
|
|
116
|
-
JSON.stringify(PEP440_FIXTURES) +
|
|
117
|
-
"]}))",
|
|
118
|
-
].join("\n")),
|
|
119
|
-
runTs: (_deftRoot, repo) => ({
|
|
120
|
-
version: resolveVersion({
|
|
121
|
-
frameworkRoot: repo,
|
|
122
|
-
fromEnv: () => null,
|
|
123
|
-
fromManifest: (base) => {
|
|
124
|
-
const text = readFileSync(join(base, "VERSION"), "utf8");
|
|
125
|
-
for (const line of text.split("\n")) {
|
|
126
|
-
const trimmed = line.trim();
|
|
127
|
-
if (trimmed.startsWith("tag:") || trimmed.startsWith("ref:")) {
|
|
128
|
-
let value = trimmed.slice(trimmed.indexOf(":") + 1).trim();
|
|
129
|
-
if (value.startsWith("v"))
|
|
130
|
-
value = value.slice(1);
|
|
131
|
-
return value;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
},
|
|
136
|
-
fromDeftVersion: () => null,
|
|
137
|
-
fromGit: () => null,
|
|
138
|
-
}),
|
|
139
|
-
pep440: PEP440_FIXTURES.map((v) => toPep440(v)),
|
|
140
|
-
}),
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: "changelog-union-merge",
|
|
144
|
-
runPython: (deftRoot) => runPythonJson(deftRoot, [
|
|
145
|
-
"import resolve_changelog_unreleased as r",
|
|
146
|
-
`content = ${JSON.stringify(buildConflictChangelog())}`,
|
|
147
|
-
"new_content, msg = r.resolve_changelog(content)",
|
|
148
|
-
"print(json.dumps({'message': msg, 'has_100': '(#100)' in new_content, 'has_200': '(#200)' in new_content, 'markers': '<<<<<<<' in new_content}))",
|
|
149
|
-
].join("\n")),
|
|
150
|
-
runTs: () => {
|
|
151
|
-
const { content, message } = resolveChangelog(buildConflictChangelog());
|
|
152
|
-
return {
|
|
153
|
-
message,
|
|
154
|
-
has_100: content?.includes("(#100)") ?? false,
|
|
155
|
-
has_200: content?.includes("(#200)") ?? false,
|
|
156
|
-
markers: content?.includes("<<<<<<<") ?? false,
|
|
157
|
-
};
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
name: "agents-refresh-managed-section",
|
|
162
|
-
setup: (repo) => {
|
|
163
|
-
const deftRoot = resolveDeftRoot();
|
|
164
|
-
const template = readTemplate(deftRoot);
|
|
165
|
-
writeFileSync(join(repo, "AGENTS.md"), `# Notes\n\n<!-- deft:managed-section v3 sha=old refreshed=2020-01-01T00:00:00Z session=oldsession12 -->\nlegacy body\n<!-- /deft:managed-section -->\n`, "utf8");
|
|
166
|
-
mkdirSync(join(repo, "templates"), { recursive: true });
|
|
167
|
-
writeFileSync(join(repo, "templates", "agents-entry.md"), template, "utf8");
|
|
168
|
-
},
|
|
169
|
-
runPython: (deftRoot, repo) => {
|
|
170
|
-
const template = readTemplate(deftRoot);
|
|
171
|
-
return runPythonJson(deftRoot, [
|
|
172
|
-
"import json",
|
|
173
|
-
"from pathlib import Path",
|
|
174
|
-
"import _agents_md as am",
|
|
175
|
-
`root = Path(${JSON.stringify(repo)})`,
|
|
176
|
-
`template = ${JSON.stringify(template)}`,
|
|
177
|
-
"plan = am._agents_refresh_plan(",
|
|
178
|
-
" root,",
|
|
179
|
-
" read_template=lambda: template,",
|
|
180
|
-
` resolve_sha=lambda: ${JSON.stringify(FIXED_SHA)},`,
|
|
181
|
-
` now_iso=lambda: ${JSON.stringify(FIXED_ISO)},`,
|
|
182
|
-
` new_session=lambda: ${JSON.stringify(FIXED_SESSION)},`,
|
|
183
|
-
")",
|
|
184
|
-
"print(json.dumps({'state': plan['state'], 'rendered_len': len(plan.get('rendered') or ''), 'has_v3': 'v3 sha=' in (plan.get('new_content') or '')}))",
|
|
185
|
-
].join("\n"));
|
|
186
|
-
},
|
|
187
|
-
runTs: (deftRoot, repo) => {
|
|
188
|
-
const template = readTemplate(deftRoot);
|
|
189
|
-
const plan = agentsRefreshPlan(repo, {
|
|
190
|
-
frameworkRoot: repo,
|
|
191
|
-
readTemplate: () => template,
|
|
192
|
-
resolveSha: () => FIXED_SHA,
|
|
193
|
-
nowIso: () => FIXED_ISO,
|
|
194
|
-
newSession: () => FIXED_SESSION,
|
|
195
|
-
});
|
|
196
|
-
return {
|
|
197
|
-
state: plan.state,
|
|
198
|
-
rendered_len: (plan.rendered ?? "").length,
|
|
199
|
-
has_v3: String(plan.new_content ?? "").includes("v3 sha="),
|
|
200
|
-
};
|
|
201
|
-
},
|
|
202
|
-
},
|
|
203
|
-
{
|
|
204
|
-
name: "platform-capabilities-sandbox",
|
|
205
|
-
setup: (repo) => {
|
|
206
|
-
writeFileSync(join(repo, "uid_map"), "0 1000 1\n1 100001 1\n", "utf8");
|
|
207
|
-
},
|
|
208
|
-
runPython: (deftRoot, repo) => runPythonJson(deftRoot, [
|
|
209
|
-
"import json",
|
|
210
|
-
"from pathlib import Path",
|
|
211
|
-
"import platform_capabilities as pc",
|
|
212
|
-
`env = {'CURSOR_ORIG_UID': '1000', 'CURSOR_SANDBOX': '1'}`,
|
|
213
|
-
`report = pc.probe_runtime_capabilities(environ=env, uid_map_path=Path(${JSON.stringify(join(repo, "uid_map"))}), cwd=Path(${JSON.stringify(repo)}), effective_uid_override=0)`,
|
|
214
|
-
"payload = report.to_dict()",
|
|
215
|
-
"if payload.get('ownership'): payload['ownership'] = {**payload['ownership'], 'path': '<REPO>'}",
|
|
216
|
-
"print(json.dumps(payload))",
|
|
217
|
-
].join("\n")),
|
|
218
|
-
runTs: (_deftRoot, repo) => normalizeCapabilityReport(reportToDict(probeRuntimeCapabilities({
|
|
219
|
-
environ: { CURSOR_ORIG_UID: "1000", CURSOR_SANDBOX: "1" },
|
|
220
|
-
uidMapPath: join(repo, "uid_map"),
|
|
221
|
-
cwd: repo,
|
|
222
|
-
effectiveUidOverride: 0,
|
|
223
|
-
}))),
|
|
224
|
-
},
|
|
225
|
-
{
|
|
226
|
-
name: "ip-risk-detect",
|
|
227
|
-
runPython: (deftRoot) => runPythonJson(deftRoot, [
|
|
228
|
-
"import json",
|
|
229
|
-
"import ip_risk",
|
|
230
|
-
"text = 'A Magic: The Gathering deck-builder with NFL stats'",
|
|
231
|
-
"hits = ip_risk.detect_ip_terms(text)",
|
|
232
|
-
"print(json.dumps([{'term': h.term, 'category': h.category} for h in hits]))",
|
|
233
|
-
].join("\n")),
|
|
234
|
-
runTs: () => detectIpTerms("A Magic: The Gathering deck-builder with NFL stats").map((h) => ({
|
|
235
|
-
term: h.term,
|
|
236
|
-
category: h.category,
|
|
237
|
-
})),
|
|
238
|
-
},
|
|
239
|
-
];
|
|
240
|
-
export function diffCase(name, python, ts) {
|
|
241
|
-
const pythonJson = stableJson(python);
|
|
242
|
-
const tsJson = stableJson(ts);
|
|
243
|
-
return {
|
|
244
|
-
caseName: name,
|
|
245
|
-
mismatch: pythonJson !== tsJson,
|
|
246
|
-
pythonJson,
|
|
247
|
-
tsJson,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
export function runParity() {
|
|
251
|
-
const deftRoot = resolveDeftRoot();
|
|
252
|
-
const diffs = [];
|
|
253
|
-
for (const testCase of PARITY_CASES) {
|
|
254
|
-
const pyRepo = mkdtempSync(join(tmpdir(), "deft-platform-parity-py-"));
|
|
255
|
-
const tsRepo = mkdtempSync(join(tmpdir(), "deft-platform-parity-ts-"));
|
|
256
|
-
try {
|
|
257
|
-
testCase.setup?.(pyRepo);
|
|
258
|
-
testCase.setup?.(tsRepo);
|
|
259
|
-
const python = testCase.runPython(deftRoot, pyRepo);
|
|
260
|
-
const ts = testCase.runTs(deftRoot, tsRepo);
|
|
261
|
-
diffs.push(diffCase(testCase.name, python, ts));
|
|
262
|
-
}
|
|
263
|
-
finally {
|
|
264
|
-
rmSync(pyRepo, { recursive: true, force: true });
|
|
265
|
-
rmSync(tsRepo, { recursive: true, force: true });
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return { ok: diffs.every((d) => !d.mismatch), diffs };
|
|
269
|
-
}
|
|
270
|
-
export function renderReport(result) {
|
|
271
|
-
if (result.ok) {
|
|
272
|
-
return `platform parity: CLEAN -- Python and TS agree on ${PARITY_CASES.length} case(s).`;
|
|
273
|
-
}
|
|
274
|
-
const lines = ["platform parity: DIVERGENCE"];
|
|
275
|
-
for (const d of result.diffs) {
|
|
276
|
-
if (d.mismatch) {
|
|
277
|
-
lines.push(` case: ${d.caseName}`);
|
|
278
|
-
lines.push(" --- python");
|
|
279
|
-
lines.push(d.pythonJson
|
|
280
|
-
.split("\n")
|
|
281
|
-
.slice(0, 12)
|
|
282
|
-
.map((l) => ` ${l}`)
|
|
283
|
-
.join("\n"));
|
|
284
|
-
lines.push(" --- ts");
|
|
285
|
-
lines.push(d.tsJson
|
|
286
|
-
.split("\n")
|
|
287
|
-
.slice(0, 12)
|
|
288
|
-
.map((l) => ` ${l}`)
|
|
289
|
-
.join("\n"));
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
return lines.join("\n");
|
|
293
|
-
}
|
|
294
|
-
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
295
|
-
try {
|
|
296
|
-
const result = runParity();
|
|
297
|
-
if (result.ok) {
|
|
298
|
-
process.stdout.write(`${renderReport(result)}\n`);
|
|
299
|
-
process.exit(0);
|
|
300
|
-
}
|
|
301
|
-
process.stderr.write(`${renderReport(result)}\n`);
|
|
302
|
-
process.exit(1);
|
|
303
|
-
}
|
|
304
|
-
catch (err) {
|
|
305
|
-
process.stderr.write(`platform parity: harness error -- ${String(err)}\n`);
|
|
306
|
-
process.exit(2);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
//# sourceMappingURL=platform-parity.js.map
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export interface FakeGhResponses {
|
|
3
|
-
readonly [label: string]: {
|
|
4
|
-
readonly returncode: number;
|
|
5
|
-
readonly stdout?: string;
|
|
6
|
-
readonly stderr?: string;
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
export interface ParityScenario {
|
|
10
|
-
readonly name: string;
|
|
11
|
-
readonly argv: readonly string[];
|
|
12
|
-
readonly responses?: FakeGhResponses;
|
|
13
|
-
readonly setupFiles?: (dir: string) => Record<string, string>;
|
|
14
|
-
readonly compareStream?: "stdout" | "stderr";
|
|
15
|
-
}
|
|
16
|
-
export interface ScenarioResult {
|
|
17
|
-
readonly name: string;
|
|
18
|
-
readonly exitCode: number;
|
|
19
|
-
readonly stdout: string;
|
|
20
|
-
readonly stderr: string;
|
|
21
|
-
}
|
|
22
|
-
export interface ParityResult {
|
|
23
|
-
readonly ok: boolean;
|
|
24
|
-
readonly scenarios: Array<{
|
|
25
|
-
readonly name: string;
|
|
26
|
-
readonly exitMismatch: boolean;
|
|
27
|
-
readonly pythonExit: number;
|
|
28
|
-
readonly tsExit: number;
|
|
29
|
-
readonly outputMismatch: boolean;
|
|
30
|
-
readonly pythonOutput: string;
|
|
31
|
-
readonly tsOutput: string;
|
|
32
|
-
readonly stream: "stdout" | "stderr";
|
|
33
|
-
}>;
|
|
34
|
-
}
|
|
35
|
-
export declare const PARITY_SCENARIOS: readonly ParityScenario[];
|
|
36
|
-
export declare function pickOutput(result: ScenarioResult, stream: "stdout" | "stderr"): string;
|
|
37
|
-
export declare function diffParity(python: ScenarioResult, ts: ScenarioResult, stream: "stdout" | "stderr"): {
|
|
38
|
-
exitMismatch: boolean;
|
|
39
|
-
outputMismatch: boolean;
|
|
40
|
-
pythonOutput: string;
|
|
41
|
-
tsOutput: string;
|
|
42
|
-
};
|
|
43
|
-
export declare function runParity(): ParityResult;
|
|
44
|
-
export declare function renderReport(result: ParityResult): string;
|
|
45
|
-
//# sourceMappingURL=pr-closing-keywords-parity.d.ts.map
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Golden-output parity harness (#1730): runs BOTH the Python oracle
|
|
4
|
-
* (`scripts/pr_check_closing_keywords.py`) and the ported TS CLI with a fake `gh`
|
|
5
|
-
* on PATH (cache-off), then diffs exit codes and byte-identical stderr.
|
|
6
|
-
*
|
|
7
|
-
* Exit codes: 0 parity / 1 divergence / 2 harness setup error.
|
|
8
|
-
*/
|
|
9
|
-
import { spawnSync } from "node:child_process";
|
|
10
|
-
import { chmodSync, 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
|
-
const FAKE_GH_PY = `import json
|
|
15
|
-
import os
|
|
16
|
-
import sys
|
|
17
|
-
|
|
18
|
-
def classify(cmd):
|
|
19
|
-
joined = " ".join(cmd)
|
|
20
|
-
if "--json" in cmd and "body" in cmd:
|
|
21
|
-
return "pr-body"
|
|
22
|
-
if "--json" in cmd and "commits" in cmd:
|
|
23
|
-
return "pr-commits"
|
|
24
|
-
return "unknown"
|
|
25
|
-
|
|
26
|
-
responses = json.loads(os.environ.get("DEFT_FAKE_GH_RESPONSES", "{}"))
|
|
27
|
-
label = classify(sys.argv[1:])
|
|
28
|
-
resp = responses.get(label, {"returncode": 1, "stderr": f"unexpected gh call: {label}", "stdout": ""})
|
|
29
|
-
stdout = resp.get("stdout", "")
|
|
30
|
-
stderr = resp.get("stderr", "")
|
|
31
|
-
if stdout:
|
|
32
|
-
sys.stdout.write(stdout)
|
|
33
|
-
if stderr:
|
|
34
|
-
sys.stderr.write(stderr)
|
|
35
|
-
sys.exit(int(resp.get("returncode", 0)))
|
|
36
|
-
`;
|
|
37
|
-
export const PARITY_SCENARIOS = [
|
|
38
|
-
{
|
|
39
|
-
name: "offline-no-keywords-clean",
|
|
40
|
-
argv: ["--body-file", "{body}"],
|
|
41
|
-
setupFiles: () => ({ body: "Refs #642 (umbrella; remains open)." }),
|
|
42
|
-
compareStream: "stderr",
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: "offline-negation-hit",
|
|
46
|
-
argv: ["--body-file", "{body}"],
|
|
47
|
-
setupFiles: () => ({
|
|
48
|
-
body: "This PR DOES NOT CLOSE #734 -- the issue stays open.",
|
|
49
|
-
}),
|
|
50
|
-
compareStream: "stderr",
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: "offline-true-positive-closes-clean",
|
|
54
|
-
argv: ["--body-file", "{body}"],
|
|
55
|
-
setupFiles: () => ({
|
|
56
|
-
body: "feat(core): land the gate.\n\nCloses #734\n\nDescription continues...",
|
|
57
|
-
}),
|
|
58
|
-
compareStream: "stderr",
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: "pr-mode-no-keywords-clean",
|
|
62
|
-
argv: ["--pr", "735", "--repo", "deftai/directive"],
|
|
63
|
-
responses: {
|
|
64
|
-
"pr-body": {
|
|
65
|
-
returncode: 0,
|
|
66
|
-
stdout: JSON.stringify({ body: "Refs #642 only." }),
|
|
67
|
-
},
|
|
68
|
-
"pr-commits": {
|
|
69
|
-
returncode: 0,
|
|
70
|
-
stdout: JSON.stringify({
|
|
71
|
-
commits: [{ messageHeadline: "feat: implement", messageBody: "Refs #1\n" }],
|
|
72
|
-
}),
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
compareStream: "stderr",
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: "pr-mode-negation-hit",
|
|
79
|
-
argv: ["--pr", "735", "--repo", "deftai/directive"],
|
|
80
|
-
responses: {
|
|
81
|
-
"pr-body": {
|
|
82
|
-
returncode: 0,
|
|
83
|
-
stdout: JSON.stringify({
|
|
84
|
-
body: "Body header. Intentionally NOT using `Closes #642` because umbrella.",
|
|
85
|
-
}),
|
|
86
|
-
},
|
|
87
|
-
"pr-commits": { returncode: 0, stdout: JSON.stringify({ commits: [] }) },
|
|
88
|
-
},
|
|
89
|
-
compareStream: "stderr",
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: "allow-list-suppressed",
|
|
93
|
-
argv: ["--body-file", "{body}", "--allow-known-false-positives", "999"],
|
|
94
|
-
setupFiles: () => ({
|
|
95
|
-
body: "Body. Intentionally not `Closes #999` (test fixture).\n",
|
|
96
|
-
}),
|
|
97
|
-
compareStream: "stderr",
|
|
98
|
-
},
|
|
99
|
-
];
|
|
100
|
-
function installFakeGh() {
|
|
101
|
-
const binDir = mkdtempSync(join(tmpdir(), "deft-pr-closing-keywords-fake-gh-"));
|
|
102
|
-
const ghBin = join(binDir, "gh");
|
|
103
|
-
const ghxBin = join(binDir, "ghx");
|
|
104
|
-
writeFileSync(ghBin, `#!/usr/bin/env python3\n${FAKE_GH_PY}`, "utf8");
|
|
105
|
-
writeFileSync(ghxBin, `#!/usr/bin/env python3\n${FAKE_GH_PY}`, "utf8");
|
|
106
|
-
chmodSync(ghBin, 0o755);
|
|
107
|
-
chmodSync(ghxBin, 0o755);
|
|
108
|
-
return {
|
|
109
|
-
binDir,
|
|
110
|
-
cleanup: () => {
|
|
111
|
-
rmSync(binDir, { recursive: true, force: true });
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
function runCapture(cmd, args, cwd, env) {
|
|
116
|
-
const result = spawnSync(cmd, args, {
|
|
117
|
-
cwd,
|
|
118
|
-
encoding: "utf8",
|
|
119
|
-
env: { ...process.env, ...env },
|
|
120
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
121
|
-
});
|
|
122
|
-
return {
|
|
123
|
-
status: result.status ?? 2,
|
|
124
|
-
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
125
|
-
stderr: typeof result.stderr === "string" ? result.stderr : "",
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
function resolveDeftRoot() {
|
|
129
|
-
if (process.env.DEFT_ROOT !== undefined && process.env.DEFT_ROOT.length > 0) {
|
|
130
|
-
return resolve(process.env.DEFT_ROOT);
|
|
131
|
-
}
|
|
132
|
-
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
133
|
-
}
|
|
134
|
-
function normaliseHarnessNoise(text) {
|
|
135
|
-
return text
|
|
136
|
-
.split("\n")
|
|
137
|
-
.filter((line) => !line.startsWith("Using CPython") &&
|
|
138
|
-
!line.startsWith("Creating virtual environment") &&
|
|
139
|
-
!line.startsWith("Installed "))
|
|
140
|
-
.join("\n");
|
|
141
|
-
}
|
|
142
|
-
export function pickOutput(result, stream) {
|
|
143
|
-
return stream === "stdout" ? result.stdout : result.stderr;
|
|
144
|
-
}
|
|
145
|
-
export function diffParity(python, ts, stream) {
|
|
146
|
-
const pythonOutput = normaliseHarnessNoise(pickOutput(python, stream));
|
|
147
|
-
const tsOutput = normaliseHarnessNoise(pickOutput(ts, stream));
|
|
148
|
-
return {
|
|
149
|
-
exitMismatch: python.exitCode !== ts.exitCode,
|
|
150
|
-
outputMismatch: pythonOutput !== tsOutput,
|
|
151
|
-
pythonOutput,
|
|
152
|
-
tsOutput,
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
function materializeArgv(scenario, fileDir) {
|
|
156
|
-
const files = scenario.setupFiles?.(fileDir) ?? {};
|
|
157
|
-
for (const [name, content] of Object.entries(files)) {
|
|
158
|
-
writeFileSync(join(fileDir, name), content, "utf8");
|
|
159
|
-
}
|
|
160
|
-
return scenario.argv.map((arg) => {
|
|
161
|
-
if (arg.startsWith("{") && arg.endsWith("}")) {
|
|
162
|
-
const key = arg.slice(1, -1);
|
|
163
|
-
return join(fileDir, key);
|
|
164
|
-
}
|
|
165
|
-
return arg;
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
function runScenario(deftRoot, scenario) {
|
|
169
|
-
const fake = installFakeGh();
|
|
170
|
-
const fileDir = mkdtempSync(join(tmpdir(), "deft-pr-closing-keywords-files-"));
|
|
171
|
-
try {
|
|
172
|
-
const argv = materializeArgv(scenario, fileDir);
|
|
173
|
-
const pathPrefix = `${fake.binDir}:${process.env.PATH ?? ""}`;
|
|
174
|
-
const env = {
|
|
175
|
-
DEFT_CACHE_DISABLE: "1",
|
|
176
|
-
PYTHONUTF8: "1",
|
|
177
|
-
PATH: pathPrefix,
|
|
178
|
-
};
|
|
179
|
-
if (scenario.responses !== undefined) {
|
|
180
|
-
env.DEFT_FAKE_GH_RESPONSES = JSON.stringify(scenario.responses);
|
|
181
|
-
}
|
|
182
|
-
const py = runCapture("uv", ["run", "python", join(deftRoot, "scripts", "pr_check_closing_keywords.py"), ...argv], deftRoot, env);
|
|
183
|
-
const ts = runCapture("node", [join(deftRoot, "packages", "cli", "dist", "pr-closing-keywords.js"), ...argv], deftRoot, env);
|
|
184
|
-
return {
|
|
185
|
-
python: {
|
|
186
|
-
name: scenario.name,
|
|
187
|
-
exitCode: py.status,
|
|
188
|
-
stdout: py.stdout,
|
|
189
|
-
stderr: py.stderr,
|
|
190
|
-
},
|
|
191
|
-
ts: {
|
|
192
|
-
name: scenario.name,
|
|
193
|
-
exitCode: ts.status,
|
|
194
|
-
stdout: ts.stdout,
|
|
195
|
-
stderr: ts.stderr,
|
|
196
|
-
},
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
finally {
|
|
200
|
-
fake.cleanup();
|
|
201
|
-
rmSync(fileDir, { recursive: true, force: true });
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
export function runParity() {
|
|
205
|
-
const deftRoot = resolveDeftRoot();
|
|
206
|
-
const scenarios = [];
|
|
207
|
-
for (const scenario of PARITY_SCENARIOS) {
|
|
208
|
-
const ran = runScenario(deftRoot, scenario);
|
|
209
|
-
const stream = scenario.compareStream ?? "stderr";
|
|
210
|
-
scenarios.push({
|
|
211
|
-
name: scenario.name,
|
|
212
|
-
pythonExit: ran.python.exitCode,
|
|
213
|
-
tsExit: ran.ts.exitCode,
|
|
214
|
-
stream,
|
|
215
|
-
...diffParity(ran.python, ran.ts, stream),
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
const ok = scenarios.every((s) => !s.exitMismatch && !s.outputMismatch);
|
|
219
|
-
return { ok, scenarios };
|
|
220
|
-
}
|
|
221
|
-
export function renderReport(result) {
|
|
222
|
-
if (result.ok) {
|
|
223
|
-
return `pr_check_closing_keywords parity: CLEAN -- Python and TS agree on ${result.scenarios.length} scenario(s).`;
|
|
224
|
-
}
|
|
225
|
-
const lines = ["pr_check_closing_keywords parity: DIVERGENCE"];
|
|
226
|
-
for (const s of result.scenarios) {
|
|
227
|
-
if (s.exitMismatch || s.outputMismatch) {
|
|
228
|
-
lines.push(` scenario: ${s.name}`);
|
|
229
|
-
if (s.exitMismatch) {
|
|
230
|
-
lines.push(` exit mismatch: python=${s.pythonExit} ts=${s.tsExit}`);
|
|
231
|
-
}
|
|
232
|
-
if (s.outputMismatch) {
|
|
233
|
-
lines.push(` stream: ${s.stream}`);
|
|
234
|
-
lines.push(` python (${s.pythonOutput.length} bytes):`);
|
|
235
|
-
lines.push(s.pythonOutput.slice(0, 500));
|
|
236
|
-
lines.push(` ts (${s.tsOutput.length} bytes):`);
|
|
237
|
-
lines.push(s.tsOutput.slice(0, 500));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return lines.join("\n");
|
|
242
|
-
}
|
|
243
|
-
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
244
|
-
try {
|
|
245
|
-
const result = runParity();
|
|
246
|
-
if (result.ok) {
|
|
247
|
-
process.stdout.write(`${renderReport(result)}\n`);
|
|
248
|
-
process.exit(0);
|
|
249
|
-
}
|
|
250
|
-
process.stderr.write(`${renderReport(result)}\n`);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
const msg = String(err).replace(/\r?\n/g, " ");
|
|
255
|
-
process.stderr.write(`pr_check_closing_keywords parity: harness error -- ${msg}\n`);
|
|
256
|
-
process.exit(2);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
//# sourceMappingURL=pr-closing-keywords-parity.js.map
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export interface FakeGhResponses {
|
|
3
|
-
readonly [label: string]: {
|
|
4
|
-
readonly returncode: number;
|
|
5
|
-
readonly stdout?: string;
|
|
6
|
-
readonly stderr?: string;
|
|
7
|
-
};
|
|
8
|
-
}
|
|
9
|
-
export interface ParityScenario {
|
|
10
|
-
readonly name: string;
|
|
11
|
-
readonly argv: readonly string[];
|
|
12
|
-
readonly responses: FakeGhResponses;
|
|
13
|
-
readonly compareStream?: "stdout" | "stderr";
|
|
14
|
-
}
|
|
15
|
-
export interface ScenarioResult {
|
|
16
|
-
readonly name: string;
|
|
17
|
-
readonly exitCode: number;
|
|
18
|
-
readonly stdout: string;
|
|
19
|
-
readonly stderr: string;
|
|
20
|
-
}
|
|
21
|
-
export interface ParityResult {
|
|
22
|
-
readonly ok: boolean;
|
|
23
|
-
readonly scenarios: Array<{
|
|
24
|
-
readonly name: string;
|
|
25
|
-
readonly exitMismatch: boolean;
|
|
26
|
-
readonly pythonExit: number;
|
|
27
|
-
readonly tsExit: number;
|
|
28
|
-
readonly outputMismatch: boolean;
|
|
29
|
-
readonly pythonOutput: string;
|
|
30
|
-
readonly tsOutput: string;
|
|
31
|
-
readonly stream: "stdout" | "stderr";
|
|
32
|
-
}>;
|
|
33
|
-
}
|
|
34
|
-
export declare const PARITY_SCENARIOS: readonly ParityScenario[];
|
|
35
|
-
export declare function pickOutput(result: ScenarioResult, stream: "stdout" | "stderr"): string;
|
|
36
|
-
export declare function diffParity(python: ScenarioResult, ts: ScenarioResult, stream: "stdout" | "stderr"): {
|
|
37
|
-
exitMismatch: boolean;
|
|
38
|
-
outputMismatch: boolean;
|
|
39
|
-
pythonOutput: string;
|
|
40
|
-
tsOutput: string;
|
|
41
|
-
};
|
|
42
|
-
export declare function runParity(): ParityResult;
|
|
43
|
-
export declare function renderReport(result: ParityResult): string;
|
|
44
|
-
//# sourceMappingURL=pr-merge-readiness-parity.d.ts.map
|