@moda-ai/cli 1.55.61 → 1.55.63
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/{agent-3hdkbyep.js → agent-y53qz08m.js} +18 -17
- package/dist/{cli-rtkjjf0a.js → cli-1fcx3eyn.js} +82 -1241
- package/dist/{cli-94r5x9bg.js → cli-27kkk5z4.js} +1 -1
- package/dist/{cli-ehf0f84r.js → cli-8b5yqyg3.js} +5 -5
- package/dist/{cli-9g8kp42c.js → cli-9rmx2zzy.js} +2 -2
- package/dist/{cli-hawwf30b.js → cli-bpnt0z0y.js} +1 -1
- package/dist/{cli-2jwxdhh5.js → cli-c7mj168q.js} +1 -1
- package/dist/cli-fsgh8mq5.js +1522 -0
- package/dist/{cli-y5d33a1y.js → cli-gmvbapx7.js} +2 -1517
- package/dist/{cli-sr3tnzfr.js → cli-jfb2fnfh.js} +11 -11
- package/dist/{cli-v2h21cgf.js → cli-m702wdtv.js} +1 -1
- package/dist/cli-mfvwvh84.js +1178 -0
- package/dist/{cli-b918vs2m.js → cli-p2tfx93b.js} +4 -4
- package/dist/{cli-m25c0rke.js → cli-rdwy3y4f.js} +3 -3
- package/dist/{cli-31ybq5f2.js → cli-wcax07xh.js} +2 -2
- package/dist/{cli-04q6xpn6.js → cli-xmv0fark.js} +46 -34
- package/dist/cli.js +27 -20
- package/dist/{doctor-3x4cnx7p.js → doctor-34yw1shj.js} +8 -8
- package/dist/{harness-esdbkwwh.js → harness-5e3qnand.js} +13 -11
- package/dist/{harness-github-actions-xbfvjpva.js → harness-github-actions-0mxwmp6g.js} +11 -9
- package/dist/{harness-report-gz5d3y48.js → harness-report-wbrrfzny.js} +2 -1
- package/dist/index-gtqxj0fp.js +2956 -0
- package/dist/{index-10ye3htt.js → index-tap398h2.js} +31 -29
- package/dist/{production-intelligence-fqvhzq6s.js → production-intelligence-40teyxyv.js} +4 -4
- package/dist/{profile-commands-yvpe4d8y.js → profile-commands-fa2858ez.js} +5 -5
- package/dist/{prompt-attribution-abms6d21.js → prompt-attribution-s7ec7bd3.js} +13 -11
- package/dist/prompt-source-mcp.js +11 -9
- package/dist/{prompt-sources-1hycgx6r.js → prompt-sources-r2xbxr0r.js} +2 -1
- package/dist/{prompts-sk27qc29.js → prompts-h59g3egz.js} +6 -6
- package/dist/{provision-aqhfbnez.js → provision-2bb73ywd.js} +6 -6
- package/dist/{registry-wv1zzyce.js → registry-ztn727mn.js} +4 -4
- package/dist/{skills-etzcqhp8.js → skills-c25dmy28.js} +6 -6
- package/dist/{sync-4fdqer3h.js → sync-jgk7dfjd.js} +16 -14
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/dist/{cli-pnarsb96.js → cli-15pzedv3.js} +3 -3
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CliInputError
|
|
3
3
|
} from "./cli-k4tqwah4.js";
|
|
4
|
+
import {
|
|
5
|
+
HARNESS_COMPONENT_STATUSES,
|
|
6
|
+
normalizeComponentStatus
|
|
7
|
+
} from "./cli-mfvwvh84.js";
|
|
4
8
|
import {
|
|
5
9
|
__require
|
|
6
10
|
} from "./cli-0v6na3yp.js";
|
|
@@ -9,1196 +13,19 @@ import {
|
|
|
9
13
|
import { createHash } from "node:crypto";
|
|
10
14
|
import {
|
|
11
15
|
existsSync,
|
|
12
|
-
lstatSync
|
|
16
|
+
lstatSync,
|
|
13
17
|
mkdirSync,
|
|
14
|
-
readFileSync
|
|
18
|
+
readFileSync,
|
|
15
19
|
writeFileSync
|
|
16
20
|
} from "node:fs";
|
|
17
|
-
import { basename, dirname, join
|
|
18
|
-
|
|
19
|
-
// src/harness-component-status.ts
|
|
20
|
-
import { closeSync, openSync, readdirSync as readdirSync2, readFileSync as readFileSync2, readSync } from "node:fs";
|
|
21
|
-
import { isAbsolute as isAbsolute2, join as join2, posix as posix2, relative as relative2 } from "node:path";
|
|
22
|
-
|
|
23
|
-
// src/harness-wrangler-bindings.ts
|
|
24
|
-
import { lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
25
|
-
import { isAbsolute, join, posix, relative } from "node:path";
|
|
26
|
-
var WRANGLER_FILE = /^wrangler(\.[\w-]+)?\.jsonc?$/;
|
|
27
|
-
var ANY_WRANGLER_FILE = /^wrangler(?:\.[\w-]+)?\.(?:jsonc?|toml)$/;
|
|
28
|
-
var SKIPPED_DIRS = new Set(["node_modules", ".git", ".wrangler", "dist", "build", ".next", ".turbo", "vendor"]);
|
|
29
|
-
var MAX_DEPTH = 4;
|
|
30
|
-
var AGENT_CLASS = /Agent(?:V\d+)?$/;
|
|
31
|
-
var TEST_SEGMENT = /^(?:test|tests|__tests__|test-fixtures|fixtures?|__fixtures__|e2e|mocks?)$/i;
|
|
32
|
-
var TEST_TOKEN = /(?:^|[._-])(?:test|tests|spec|e2e|fixtures?|mocks?)(?:$|[._-])/i;
|
|
33
|
-
function isTestWorkerConfig(input) {
|
|
34
|
-
const segments = input.path.split("/");
|
|
35
|
-
const file = segments[segments.length - 1] ?? "";
|
|
36
|
-
const variant = file.replace(/^wrangler\.?/, "").replace(/\.(?:jsonc?|toml)$/, "");
|
|
37
|
-
if (segments.slice(0, -1).some((segment) => TEST_SEGMENT.test(segment)))
|
|
38
|
-
return true;
|
|
39
|
-
if (variant && TEST_TOKEN.test(variant))
|
|
40
|
-
return true;
|
|
41
|
-
if (typeof input.name === "string" && TEST_TOKEN.test(input.name))
|
|
42
|
-
return true;
|
|
43
|
-
return (input.mains ?? []).some((main) => typeof main === "string" && main.split("/").slice(0, -1).some((segment) => TEST_SEGMENT.test(segment)));
|
|
44
|
-
}
|
|
45
|
-
function stripJsonc(text) {
|
|
46
|
-
let out = "";
|
|
47
|
-
let inString = false;
|
|
48
|
-
for (let i = 0;i < text.length; i += 1) {
|
|
49
|
-
const ch = text[i];
|
|
50
|
-
if (inString) {
|
|
51
|
-
out += ch;
|
|
52
|
-
if (ch === "\\") {
|
|
53
|
-
out += text[i + 1] ?? "";
|
|
54
|
-
i += 1;
|
|
55
|
-
} else if (ch === '"') {
|
|
56
|
-
inString = false;
|
|
57
|
-
}
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (ch === '"') {
|
|
61
|
-
inString = true;
|
|
62
|
-
out += ch;
|
|
63
|
-
} else if (ch === "/" && text[i + 1] === "/") {
|
|
64
|
-
while (i < text.length && text[i] !== `
|
|
65
|
-
`)
|
|
66
|
-
i += 1;
|
|
67
|
-
out += `
|
|
68
|
-
`;
|
|
69
|
-
} else if (ch === "/" && text[i + 1] === "*") {
|
|
70
|
-
i += 2;
|
|
71
|
-
while (i < text.length && !(text[i] === "*" && text[i + 1] === "/")) {
|
|
72
|
-
if (text[i] === `
|
|
73
|
-
`)
|
|
74
|
-
out += `
|
|
75
|
-
`;
|
|
76
|
-
i += 1;
|
|
77
|
-
}
|
|
78
|
-
i += 1;
|
|
79
|
-
} else {
|
|
80
|
-
out += ch;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return out.replace(/,(\s*[}\]])/g, "$1");
|
|
84
|
-
}
|
|
85
|
-
function findWranglerFiles(rootDir, pattern = WRANGLER_FILE) {
|
|
86
|
-
const found = [];
|
|
87
|
-
const walk = (dir, depth) => {
|
|
88
|
-
let entries;
|
|
89
|
-
try {
|
|
90
|
-
entries = readdirSync(dir);
|
|
91
|
-
} catch {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
for (const name of entries) {
|
|
95
|
-
const abs = join(dir, name);
|
|
96
|
-
let stat;
|
|
97
|
-
try {
|
|
98
|
-
stat = lstatSync(abs);
|
|
99
|
-
} catch {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (stat.isDirectory()) {
|
|
103
|
-
if (depth < MAX_DEPTH && !SKIPPED_DIRS.has(name) && !name.startsWith("."))
|
|
104
|
-
walk(abs, depth + 1);
|
|
105
|
-
} else if (stat.isFile() && pattern.test(name)) {
|
|
106
|
-
found.push(abs);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
walk(rootDir, 0);
|
|
111
|
-
return found.sort();
|
|
112
|
-
}
|
|
113
|
-
function lineOf(text, className) {
|
|
114
|
-
const index = text.search(new RegExp(`"class_name"\\s*:\\s*"${className.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"`));
|
|
115
|
-
return index < 0 ? undefined : text.slice(0, index).split(`
|
|
116
|
-
`).length;
|
|
117
|
-
}
|
|
118
|
-
function workerDir(path) {
|
|
119
|
-
const slash = path.lastIndexOf("/");
|
|
120
|
-
return slash < 0 ? "" : path.slice(0, slash);
|
|
121
|
-
}
|
|
122
|
-
function detectBoundRuntimeClasses(rootDir) {
|
|
123
|
-
const byClass = new Map;
|
|
124
|
-
for (const abs of findWranglerFiles(rootDir)) {
|
|
125
|
-
let text;
|
|
126
|
-
let config;
|
|
127
|
-
try {
|
|
128
|
-
text = readFileSync(abs, "utf8");
|
|
129
|
-
config = JSON.parse(stripJsonc(text));
|
|
130
|
-
} catch {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
if (!config || typeof config !== "object")
|
|
134
|
-
continue;
|
|
135
|
-
const path = relative(rootDir, abs).split("\\").join("/");
|
|
136
|
-
const root = config;
|
|
137
|
-
if (isTestWorkerConfig({ path, name: root.name, mains: [root.main] }))
|
|
138
|
-
continue;
|
|
139
|
-
const sections = [root, ...Object.values(root.env ?? {})];
|
|
140
|
-
for (const section of sections) {
|
|
141
|
-
if (!section || typeof section !== "object")
|
|
142
|
-
continue;
|
|
143
|
-
const s = section;
|
|
144
|
-
const durable = s.durable_objects?.bindings;
|
|
145
|
-
const entries = [
|
|
146
|
-
...Array.isArray(durable) ? durable.map((b) => ["durable_object", b]) : [],
|
|
147
|
-
...Array.isArray(s.workflows) ? s.workflows.map((w) => ["workflow", w]) : []
|
|
148
|
-
];
|
|
149
|
-
for (const [kind, raw] of entries) {
|
|
150
|
-
if (!raw || typeof raw !== "object")
|
|
151
|
-
continue;
|
|
152
|
-
const entry = raw;
|
|
153
|
-
const className = typeof entry.class_name === "string" ? entry.class_name : "";
|
|
154
|
-
const scriptName = typeof entry.script_name === "string" && entry.script_name ? entry.script_name : undefined;
|
|
155
|
-
const key = `${className}|${scriptName ? "external" : `config:${path}`}`;
|
|
156
|
-
if (!className || byClass.has(key))
|
|
157
|
-
continue;
|
|
158
|
-
const binding = typeof (kind === "workflow" ? entry.binding : entry.name) === "string" ? String(kind === "workflow" ? entry.binding : entry.name) : className;
|
|
159
|
-
byClass.set(key, {
|
|
160
|
-
className,
|
|
161
|
-
kind,
|
|
162
|
-
binding,
|
|
163
|
-
...scriptName ? { scriptName } : {},
|
|
164
|
-
path,
|
|
165
|
-
line: lineOf(text, className)
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return [...byClass.values()];
|
|
171
|
-
}
|
|
172
|
-
var RUNTIME_BASES = {
|
|
173
|
-
DurableObject: "durable_object",
|
|
174
|
-
WorkflowEntrypoint: "workflow",
|
|
175
|
-
AgentWorkflow: "workflow",
|
|
176
|
-
Agent: "agent",
|
|
177
|
-
AIChatAgent: "agent",
|
|
178
|
-
Think: "agent",
|
|
179
|
-
McpAgent: "agent"
|
|
180
|
-
};
|
|
181
|
-
var SOURCE_FILE = /\.tsx?$/;
|
|
182
|
-
var SKIPPED_SOURCE_FILE = /\.(?:test|spec)\.tsx?$|\.d\.ts$/;
|
|
183
|
-
var TEST_DIR = /^(?:tests?|__tests__|__mocks__|e2e|(?:.*[-_.])?fixtures?|__fixtures__)$/i;
|
|
184
|
-
var MAX_SOURCE_DEPTH = 12;
|
|
185
|
-
var MAX_SOURCE_FILES = 20000;
|
|
186
|
-
var MAX_SOURCE_BYTES = 1e6;
|
|
187
|
-
var CLASS_DECLARATION = /^[ \t]*(export[ \t]+(?:default[ \t]+)?)?(abstract[ \t]+)?class[ \t]+([A-Za-z_$][\w$]*)(?:[ \t]*<[^\n{]*?>)?[ \t]+extends[ \t]+([A-Za-z_$][\w$]*)/gm;
|
|
188
|
-
function findSourceFiles(rootDir) {
|
|
189
|
-
const found = [];
|
|
190
|
-
const walk = (dir, depth) => {
|
|
191
|
-
let entries;
|
|
192
|
-
try {
|
|
193
|
-
entries = readdirSync(dir).sort();
|
|
194
|
-
} catch {
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
for (const name of entries) {
|
|
198
|
-
if (found.length >= MAX_SOURCE_FILES)
|
|
199
|
-
return;
|
|
200
|
-
const abs = join(dir, name);
|
|
201
|
-
let stat;
|
|
202
|
-
try {
|
|
203
|
-
stat = lstatSync(abs);
|
|
204
|
-
} catch {
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (stat.isDirectory()) {
|
|
208
|
-
if (depth < MAX_SOURCE_DEPTH && !SKIPPED_DIRS.has(name) && !name.startsWith(".") && !TEST_DIR.test(name))
|
|
209
|
-
walk(abs, depth + 1);
|
|
210
|
-
} else if (stat.isFile() && SOURCE_FILE.test(name) && !SKIPPED_SOURCE_FILE.test(name) && stat.size <= MAX_SOURCE_BYTES) {
|
|
211
|
-
found.push(abs);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
};
|
|
215
|
-
walk(rootDir, 0);
|
|
216
|
-
return found;
|
|
217
|
-
}
|
|
218
|
-
function bindingsOfSection(section) {
|
|
219
|
-
const durable = section.durable_objects?.bindings;
|
|
220
|
-
const raw = [...Array.isArray(durable) ? durable : [], ...Array.isArray(section.workflows) ? section.workflows : []];
|
|
221
|
-
return raw.flatMap((entry) => {
|
|
222
|
-
if (!entry || typeof entry !== "object")
|
|
223
|
-
return [];
|
|
224
|
-
const { class_name: className, script_name: scriptName } = entry;
|
|
225
|
-
if (typeof className !== "string" || !className)
|
|
226
|
-
return [];
|
|
227
|
-
return [{ className, ...typeof scriptName === "string" && scriptName ? { scriptName } : {} }];
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
function readWranglerScope(rootDir, abs) {
|
|
231
|
-
let text;
|
|
232
|
-
try {
|
|
233
|
-
text = readFileSync(abs, "utf8");
|
|
234
|
-
} catch {
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
const names = [];
|
|
238
|
-
const mains = [];
|
|
239
|
-
const bindings = [];
|
|
240
|
-
let topName;
|
|
241
|
-
let topMain;
|
|
242
|
-
if (abs.endsWith(".toml")) {
|
|
243
|
-
let header = "";
|
|
244
|
-
let binding;
|
|
245
|
-
for (const rawLine of text.split(`
|
|
246
|
-
`)) {
|
|
247
|
-
const line = rawLine.replace(/\s+#.*$/, "").trim();
|
|
248
|
-
const table = /^\[\[?\s*([^\]]+?)\s*\]\]?$/.exec(line);
|
|
249
|
-
if (table) {
|
|
250
|
-
header = table[1];
|
|
251
|
-
binding = undefined;
|
|
252
|
-
if (/(?:^|\.)(?:durable_objects\.bindings|workflows)$/.test(header) && line.startsWith("[[")) {
|
|
253
|
-
binding = { className: "" };
|
|
254
|
-
bindings.push(binding);
|
|
255
|
-
}
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
const pair = /^([\w.-]+)\s*=\s*["']([^"']*)["']/.exec(line);
|
|
259
|
-
if (!pair)
|
|
260
|
-
continue;
|
|
261
|
-
const [, key, value] = pair;
|
|
262
|
-
const envSection = header === "" || /^env\.[\w-]+$/.test(header);
|
|
263
|
-
if (binding && key === "class_name")
|
|
264
|
-
binding.className = value;
|
|
265
|
-
else if (binding && key === "script_name" && value)
|
|
266
|
-
binding.scriptName = value;
|
|
267
|
-
else if (envSection && key === "name" && value) {
|
|
268
|
-
names.push(value);
|
|
269
|
-
if (header === "")
|
|
270
|
-
topName = value;
|
|
271
|
-
} else if (envSection && key === "main" && value) {
|
|
272
|
-
mains.push(value);
|
|
273
|
-
if (header === "")
|
|
274
|
-
topMain = value;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
} else {
|
|
278
|
-
let config;
|
|
279
|
-
try {
|
|
280
|
-
config = JSON.parse(stripJsonc(text));
|
|
281
|
-
} catch {
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (!config || typeof config !== "object" || Array.isArray(config))
|
|
285
|
-
return;
|
|
286
|
-
const root = config;
|
|
287
|
-
if (typeof root.name === "string")
|
|
288
|
-
topName = root.name;
|
|
289
|
-
if (typeof root.main === "string")
|
|
290
|
-
topMain = root.main;
|
|
291
|
-
for (const section of [root, ...Object.values(root.env ?? {})]) {
|
|
292
|
-
if (!section || typeof section !== "object")
|
|
293
|
-
continue;
|
|
294
|
-
const s = section;
|
|
295
|
-
if (typeof s.name === "string" && s.name)
|
|
296
|
-
names.push(s.name);
|
|
297
|
-
if (typeof s.main === "string" && s.main)
|
|
298
|
-
mains.push(s.main);
|
|
299
|
-
bindings.push(...bindingsOfSection(s));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const path = relative(rootDir, abs).split("\\").join("/");
|
|
303
|
-
if (isTestWorkerConfig({ path, name: topName, mains: [topMain] }))
|
|
304
|
-
return;
|
|
305
|
-
const dir = workerDir(path);
|
|
306
|
-
const dirs = [dir];
|
|
307
|
-
for (const main of mains) {
|
|
308
|
-
const mainDir = workerDir(posix.normalize(posix.join(dir || ".", main)).replace(/^\.\//, ""));
|
|
309
|
-
if (!mainDir.startsWith("..") && !inside(mainDir, dir) && !dirs.includes(mainDir))
|
|
310
|
-
dirs.push(mainDir);
|
|
311
|
-
}
|
|
312
|
-
return { dirs, names, bindings: bindings.filter((entry) => entry.className) };
|
|
313
|
-
}
|
|
314
|
-
function inside(path, dir) {
|
|
315
|
-
return dir === "" || dir === "." || path === dir || path.startsWith(`${dir}/`);
|
|
316
|
-
}
|
|
317
|
-
function boundClassResolver(rootDir) {
|
|
318
|
-
const scopes = findWranglerFiles(rootDir, ANY_WRANGLER_FILE).map((abs) => readWranglerScope(rootDir, abs)).filter((scope) => Boolean(scope));
|
|
319
|
-
const anywhere = new Set;
|
|
320
|
-
const external = new Map;
|
|
321
|
-
for (const scope of scopes) {
|
|
322
|
-
for (const binding of scope.bindings) {
|
|
323
|
-
anywhere.add(binding.className);
|
|
324
|
-
if (!binding.scriptName)
|
|
325
|
-
continue;
|
|
326
|
-
const servers = scopes.filter((other) => other.names.includes(binding.scriptName));
|
|
327
|
-
if (servers.length === 0)
|
|
328
|
-
external.set(binding.className, new Set(["*"]));
|
|
329
|
-
for (const server of servers) {
|
|
330
|
-
const set = external.get(binding.className) ?? new Set;
|
|
331
|
-
server.names.forEach((name) => set.add(name));
|
|
332
|
-
external.set(binding.className, set);
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return (className, path) => {
|
|
337
|
-
if (!anywhere.has(className))
|
|
338
|
-
return false;
|
|
339
|
-
if (external.get(className)?.has("*"))
|
|
340
|
-
return true;
|
|
341
|
-
let nearest = -1;
|
|
342
|
-
let owners = [];
|
|
343
|
-
for (const scope of scopes) {
|
|
344
|
-
const depth = Math.max(-1, ...scope.dirs.filter((dir) => inside(path, dir)).map((dir) => dir ? dir.split("/").length : 0));
|
|
345
|
-
if (depth < 0 || depth < nearest)
|
|
346
|
-
continue;
|
|
347
|
-
if (depth > nearest)
|
|
348
|
-
owners = [];
|
|
349
|
-
nearest = depth;
|
|
350
|
-
owners.push(scope);
|
|
351
|
-
}
|
|
352
|
-
if (owners.length === 0)
|
|
353
|
-
return true;
|
|
354
|
-
const servedNames = external.get(className);
|
|
355
|
-
return owners.some((owner) => owner.bindings.some((binding) => binding.className === className && !binding.scriptName) || servedNames !== undefined && owner.names.some((name) => servedNames.has(name)));
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
function relativeImportStems(path, text) {
|
|
359
|
-
const stems = new Set;
|
|
360
|
-
for (const match of text.matchAll(/\bfrom\s*['"](\.{1,2}\/[^'"\n]*)['"]/g)) {
|
|
361
|
-
const joined = posix.normalize(posix.join(workerDir(path) || ".", match[1])).replace(/^\.\//, "");
|
|
362
|
-
const stem = joined.replace(/\.(?:[cm]?[jt]sx?)$/, "");
|
|
363
|
-
stems.add(stem);
|
|
364
|
-
stems.add(`${stem}/index`);
|
|
365
|
-
}
|
|
366
|
-
return stems;
|
|
367
|
-
}
|
|
368
|
-
function pickBaseDeclaration(candidates, from, importsOf) {
|
|
369
|
-
if (candidates.length < 2)
|
|
370
|
-
return candidates[0];
|
|
371
|
-
const sameFile = candidates.find((candidate) => candidate.path === from.path);
|
|
372
|
-
if (sameFile)
|
|
373
|
-
return sameFile;
|
|
374
|
-
const imports = importsOf(from.path);
|
|
375
|
-
const imported = candidates.find((candidate) => imports.has(candidate.path.replace(/\.(?:[cm]?[jt]sx?)$/, "")));
|
|
376
|
-
if (imported)
|
|
377
|
-
return imported;
|
|
378
|
-
const fromDirs = workerDir(from.path).split("/");
|
|
379
|
-
const shared = (candidate) => {
|
|
380
|
-
const dirs = workerDir(candidate.path).split("/");
|
|
381
|
-
let n = 0;
|
|
382
|
-
while (n < dirs.length && n < fromDirs.length && dirs[n] === fromDirs[n] && dirs[n] !== "")
|
|
383
|
-
n += 1;
|
|
384
|
-
return n;
|
|
385
|
-
};
|
|
386
|
-
let best = candidates[0];
|
|
387
|
-
for (const candidate of candidates)
|
|
388
|
-
if (shared(candidate) > shared(best))
|
|
389
|
-
best = candidate;
|
|
390
|
-
return best;
|
|
391
|
-
}
|
|
392
|
-
function detectDefinedUnboundRuntimeClasses(rootDir) {
|
|
393
|
-
const declarations = [];
|
|
394
|
-
const texts = new Map;
|
|
395
|
-
for (const abs of findSourceFiles(rootDir)) {
|
|
396
|
-
let text;
|
|
397
|
-
try {
|
|
398
|
-
text = readFileSync(abs, "utf8");
|
|
399
|
-
} catch {
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
if (!text.includes("extends"))
|
|
403
|
-
continue;
|
|
404
|
-
const path = relative(rootDir, abs).split("\\").join("/");
|
|
405
|
-
texts.set(path, text);
|
|
406
|
-
for (const match of text.matchAll(CLASS_DECLARATION)) {
|
|
407
|
-
declarations.push({
|
|
408
|
-
className: match[3],
|
|
409
|
-
base: match[4],
|
|
410
|
-
exported: Boolean(match[1]),
|
|
411
|
-
abstract: Boolean(match[2]),
|
|
412
|
-
path,
|
|
413
|
-
line: text.slice(0, match.index).split(`
|
|
414
|
-
`).length
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
const byName = new Map;
|
|
419
|
-
for (const declaration of declarations) {
|
|
420
|
-
const list = byName.get(declaration.className) ?? [];
|
|
421
|
-
list.push(declaration);
|
|
422
|
-
byName.set(declaration.className, list);
|
|
423
|
-
}
|
|
424
|
-
const importCache = new Map;
|
|
425
|
-
const importsOf = (path) => {
|
|
426
|
-
let stems = importCache.get(path);
|
|
427
|
-
if (!stems) {
|
|
428
|
-
stems = relativeImportStems(path, texts.get(path) ?? "");
|
|
429
|
-
importCache.set(path, stems);
|
|
430
|
-
}
|
|
431
|
-
return stems;
|
|
432
|
-
};
|
|
433
|
-
const kindOf = (start) => {
|
|
434
|
-
let current = start;
|
|
435
|
-
for (let depth = 0;current && depth < 8; depth += 1) {
|
|
436
|
-
const base = current.base;
|
|
437
|
-
const kind = Object.hasOwn(RUNTIME_BASES, base) ? RUNTIME_BASES[base] : undefined;
|
|
438
|
-
if (kind)
|
|
439
|
-
return kind;
|
|
440
|
-
current = pickBaseDeclaration(byName.get(base) ?? [], current, importsOf);
|
|
441
|
-
}
|
|
442
|
-
return;
|
|
443
|
-
};
|
|
444
|
-
const isBound = boundClassResolver(rootDir);
|
|
445
|
-
const seen = new Set;
|
|
446
|
-
const found = [];
|
|
447
|
-
for (const declaration of declarations) {
|
|
448
|
-
if (!declaration.exported || declaration.abstract || isBound(declaration.className, declaration.path))
|
|
449
|
-
continue;
|
|
450
|
-
const kind = kindOf(declaration);
|
|
451
|
-
const key = `${declaration.className}|${declaration.path}`;
|
|
452
|
-
if (!kind || seen.has(key))
|
|
453
|
-
continue;
|
|
454
|
-
seen.add(key);
|
|
455
|
-
found.push({ className: declaration.className, kind, path: declaration.path, line: declaration.line });
|
|
456
|
-
}
|
|
457
|
-
return found;
|
|
458
|
-
}
|
|
459
|
-
function definedReason(defined) {
|
|
460
|
-
const what = defined.kind === "workflow" ? "Workflow" : defined.kind === "agent" ? "Agent (Durable Object)" : "Durable Object";
|
|
461
|
-
return `${what} class defined but not bound in any wrangler config`;
|
|
462
|
-
}
|
|
463
|
-
function norm(value) {
|
|
464
|
-
return typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9]/g, "") : "";
|
|
465
|
-
}
|
|
466
|
-
function itemPaths(item) {
|
|
467
|
-
const paths = [];
|
|
468
|
-
for (const value of [item.root, item.sourcePath]) {
|
|
469
|
-
if (typeof value === "string" && value && value !== ".")
|
|
470
|
-
paths.push(value.replace(/^\.\//, ""));
|
|
471
|
-
}
|
|
472
|
-
for (const entry of Array.isArray(item.evidence) ? item.evidence : []) {
|
|
473
|
-
const path = entry && typeof entry === "object" ? entry.path : undefined;
|
|
474
|
-
if (typeof path === "string" && path)
|
|
475
|
-
paths.push(path.replace(/^\.\//, ""));
|
|
476
|
-
}
|
|
477
|
-
return paths;
|
|
478
|
-
}
|
|
479
|
-
function withinWorker(path, dir, otherDirs) {
|
|
480
|
-
const inside2 = (d) => d === "" || path === d || path.startsWith(`${d}/`);
|
|
481
|
-
return inside2(dir) && !otherDirs.some((other) => other.length > dir.length && inside2(other));
|
|
482
|
-
}
|
|
483
|
-
function namesClass(name, className) {
|
|
484
|
-
if (typeof name !== "string")
|
|
485
|
-
return false;
|
|
486
|
-
const target = norm(className);
|
|
487
|
-
const bare = name.replace(/\s*\([^)]*\)\s*$/, "");
|
|
488
|
-
return norm(bare) === target || bare.split(/[^A-Za-z0-9_]+/).some((word) => norm(word) === target);
|
|
489
|
-
}
|
|
490
|
-
function graphNamesClass(graph, bound, all) {
|
|
491
|
-
const named = (name) => namesClass(name, bound.className);
|
|
492
|
-
const sameName = bound.scriptName ? [bound] : all.filter((other) => other.className === bound.className && !other.scriptName);
|
|
493
|
-
const dir = workerDir(bound.path);
|
|
494
|
-
const otherDirs = sameName.map((other) => workerDir(other.path)).filter((other) => other !== dir);
|
|
495
|
-
const covers = (item) => {
|
|
496
|
-
if (!named(item.name))
|
|
497
|
-
return false;
|
|
498
|
-
if (sameName.length < 2)
|
|
499
|
-
return true;
|
|
500
|
-
return itemPaths(item).some((path) => withinWorker(path, dir, otherDirs));
|
|
501
|
-
};
|
|
502
|
-
return graph.runtimeAgents.some(covers) || graph.artifacts.some(covers);
|
|
503
|
-
}
|
|
504
|
-
function citedFile(rootDir, path) {
|
|
505
|
-
let rel = path.trim().replace(/\\/g, "/").replace(/:\d+(?::\d+)?$/, "");
|
|
506
|
-
if (isAbsolute(rel))
|
|
507
|
-
rel = relative(rootDir, rel).split("\\").join("/");
|
|
508
|
-
return posix.normalize(rel).replace(/^\.\//, "");
|
|
509
|
-
}
|
|
510
|
-
function graphNamesDeclaration(graph, defined, rootDir) {
|
|
511
|
-
const named = [...graph.runtimeAgents, ...graph.artifacts].filter((item) => namesClass(item.name, defined.className));
|
|
512
|
-
if (named.length === 0)
|
|
513
|
-
return false;
|
|
514
|
-
const target = citedFile(rootDir, defined.path);
|
|
515
|
-
const cited = named.map((item) => filePaths(item).map((path) => citedFile(rootDir, path)));
|
|
516
|
-
if (cited.every((paths) => paths.length === 0))
|
|
517
|
-
return true;
|
|
518
|
-
return cited.some((paths) => paths.includes(target));
|
|
519
|
-
}
|
|
520
|
-
function filePaths(item) {
|
|
521
|
-
const paths = [];
|
|
522
|
-
if (typeof item.sourcePath === "string" && item.sourcePath.trim())
|
|
523
|
-
paths.push(item.sourcePath);
|
|
524
|
-
for (const entry of Array.isArray(item.evidence) ? item.evidence : []) {
|
|
525
|
-
const path = entry && typeof entry === "object" ? entry.path : undefined;
|
|
526
|
-
if (typeof path === "string" && path.trim())
|
|
527
|
-
paths.push(path);
|
|
528
|
-
}
|
|
529
|
-
return paths;
|
|
530
|
-
}
|
|
531
|
-
function bindingReason(bound) {
|
|
532
|
-
const what = bound.kind === "workflow" ? "Workflow" : "Durable Object";
|
|
533
|
-
const where = bound.scriptName ? ` served by the external Worker "${bound.scriptName}"` : "";
|
|
534
|
-
return `${what} binding ${bound.binding} -> class ${bound.className}${where}`;
|
|
535
|
-
}
|
|
536
|
-
function uniqueId(base, taken) {
|
|
537
|
-
let id = base;
|
|
538
|
-
for (let n = 2;taken.has(id); n += 1)
|
|
539
|
-
id = `${base}-${n}`;
|
|
540
|
-
taken.add(id);
|
|
541
|
-
return id;
|
|
542
|
-
}
|
|
543
|
-
function mergeBoundRuntimeClasses(graph, rootDir) {
|
|
544
|
-
if (process.env.MODA_HARNESS_DETERMINISTIC_MERGE === "0")
|
|
545
|
-
return { added: 0 };
|
|
546
|
-
let bound;
|
|
547
|
-
let defined;
|
|
548
|
-
try {
|
|
549
|
-
bound = detectBoundRuntimeClasses(rootDir);
|
|
550
|
-
defined = detectDefinedUnboundRuntimeClasses(rootDir);
|
|
551
|
-
} catch {
|
|
552
|
-
return { added: 0 };
|
|
553
|
-
}
|
|
554
|
-
const taken = new Set([
|
|
555
|
-
...graph.runtimeAgents.map((agent) => agent.id),
|
|
556
|
-
...graph.artifacts.map((artifact) => artifact.id)
|
|
557
|
-
]);
|
|
558
|
-
let added = 0;
|
|
559
|
-
for (const entry of bound) {
|
|
560
|
-
if (graphNamesClass(graph, entry, bound))
|
|
561
|
-
continue;
|
|
562
|
-
const evidence = [{ path: entry.path, ...entry.line ? { line: entry.line } : {}, reason: bindingReason(entry) }];
|
|
563
|
-
const slug = entry.className.replace(/[^A-Za-z0-9]+/g, "-").toLowerCase();
|
|
564
|
-
if (entry.kind === "durable_object" && AGENT_CLASS.test(entry.className) && !entry.scriptName) {
|
|
565
|
-
const agent = {
|
|
566
|
-
id: uniqueId(`agent.bound.${slug}`, taken),
|
|
567
|
-
name: entry.className,
|
|
568
|
-
kind: "runtime_agent",
|
|
569
|
-
purpose: "Agent class bound as a Durable Object.",
|
|
570
|
-
root: ".",
|
|
571
|
-
entrypointIds: [],
|
|
572
|
-
channelAdapterIds: [],
|
|
573
|
-
artifactIds: [],
|
|
574
|
-
identityNamespaceIds: [],
|
|
575
|
-
deploymentIds: [],
|
|
576
|
-
frameworkIds: [],
|
|
577
|
-
modelProviderIds: [],
|
|
578
|
-
confidence: 0.6,
|
|
579
|
-
evidence
|
|
580
|
-
};
|
|
581
|
-
graph.runtimeAgents.push(agent);
|
|
582
|
-
} else {
|
|
583
|
-
const artifact = {
|
|
584
|
-
id: uniqueId(`entrypoint.bound.${slug}`, taken),
|
|
585
|
-
type: "entrypoint",
|
|
586
|
-
name: entry.className,
|
|
587
|
-
scope: "shared",
|
|
588
|
-
sourcePath: entry.path,
|
|
589
|
-
usedByAgentIds: [],
|
|
590
|
-
confidence: 0.6,
|
|
591
|
-
evidence
|
|
592
|
-
};
|
|
593
|
-
graph.artifacts.push(artifact);
|
|
594
|
-
}
|
|
595
|
-
added += 1;
|
|
596
|
-
}
|
|
597
|
-
for (const entry of defined) {
|
|
598
|
-
if (graphNamesDeclaration(graph, entry, rootDir))
|
|
599
|
-
continue;
|
|
600
|
-
const slug = entry.className.replace(/[^A-Za-z0-9]+/g, "-").toLowerCase();
|
|
601
|
-
graph.artifacts.push({
|
|
602
|
-
id: uniqueId(`entrypoint.defined.${slug}`, taken),
|
|
603
|
-
type: "entrypoint",
|
|
604
|
-
name: entry.className,
|
|
605
|
-
scope: "shared",
|
|
606
|
-
sourcePath: entry.path,
|
|
607
|
-
usedByAgentIds: [],
|
|
608
|
-
confidence: 0.6,
|
|
609
|
-
evidence: [{ path: entry.path, line: entry.line, reason: definedReason(entry) }]
|
|
610
|
-
});
|
|
611
|
-
added += 1;
|
|
612
|
-
}
|
|
613
|
-
return { added };
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// src/harness-component-status.ts
|
|
617
|
-
var HARNESS_COMPONENT_STATUSES = new Set(["in_prod", "reachable", "defined_only"]);
|
|
618
|
-
var STATUS_ALIASES = {
|
|
619
|
-
in_prod: "in_prod",
|
|
620
|
-
inprod: "in_prod",
|
|
621
|
-
prod: "in_prod",
|
|
622
|
-
production: "in_prod",
|
|
623
|
-
in_production: "in_prod",
|
|
624
|
-
deployed: "in_prod",
|
|
625
|
-
live: "in_prod",
|
|
626
|
-
reachable: "reachable",
|
|
627
|
-
defined_only: "defined_only",
|
|
628
|
-
dead: "defined_only",
|
|
629
|
-
dead_code: "defined_only",
|
|
630
|
-
unused: "defined_only",
|
|
631
|
-
unreferenced: "defined_only",
|
|
632
|
-
unreachable: "defined_only",
|
|
633
|
-
orphan: "defined_only",
|
|
634
|
-
orphaned: "defined_only"
|
|
635
|
-
};
|
|
636
|
-
function normalizeComponentStatus(value) {
|
|
637
|
-
if (typeof value !== "string")
|
|
638
|
-
return;
|
|
639
|
-
return STATUS_ALIASES[value.trim().toLowerCase().replace(/[\s-]+/g, "_")];
|
|
640
|
-
}
|
|
641
|
-
var SKIPPED_DIRS2 = new Set(["node_modules", "dist", "build", "vendor", "coverage"]);
|
|
642
|
-
var NON_DEPLOY_DIR = /^(?:test|tests|__tests__|fixtures?|__fixtures__|examples?|e2e)$/;
|
|
643
|
-
var WRANGLER_FILE2 = /^wrangler(?:\.([\w.-]+))?\.(?:json|jsonc|toml)$/;
|
|
644
|
-
var NON_DEPLOY_VARIANT = /(?:^|[.-])(?:preview|dev|local|test|example|sample)(?:$|[.-])/i;
|
|
645
|
-
var CODE_FILE = /\.(?:[cm]?[jt]sx?)$/;
|
|
646
|
-
var TEST_RUNNER_FILE = /(?:^|\/)__tests__\/|\.(?:test|spec)\.[cm]?[jt]sx?$/;
|
|
647
|
-
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
648
|
-
var TS_FOR_JS = { ".js": [".ts", ".tsx"], ".jsx": [".tsx"], ".mjs": [".mts"], ".cjs": [".cts"] };
|
|
649
|
-
var MAX_FILES = 200000;
|
|
650
|
-
var MAX_READ_BYTES = 1024 * 1024;
|
|
651
|
-
var STATIC_IMPORT = /\b(?:import|export)\s+(type\s+)?([^'";]*?)\bfrom\s*['"]([^'"\n]+)['"]/g;
|
|
652
|
-
var SIDE_EFFECT_IMPORT = /\bimport\s*['"]([^'"\n]+)['"]/g;
|
|
653
|
-
var DYNAMIC_IMPORT = /\b(?:import|require)\s*\(\s*['"]([^'"\n]+)['"]\s*\)/g;
|
|
654
|
-
var CLASS_DECL = /\bclass\s+([A-Za-z_$][\w$]*)/g;
|
|
655
|
-
var SCRIPT_FILE = /(?:^|[\s;&|(])(?:bun|node|tsx)\s+((?:-{1,2}[\w-]+(?:=\S+)?\s+)*)(?:run\s+)?([^\s'"&|;()]+)/g;
|
|
656
|
-
function listFiles(rootDir) {
|
|
657
|
-
const files = new Set;
|
|
658
|
-
const walk = (abs, rel) => {
|
|
659
|
-
let entries;
|
|
660
|
-
try {
|
|
661
|
-
entries = readdirSync2(abs, { withFileTypes: true });
|
|
662
|
-
} catch {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
for (const entry of entries) {
|
|
666
|
-
if (files.size >= MAX_FILES)
|
|
667
|
-
return;
|
|
668
|
-
const childRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
669
|
-
if (entry.isDirectory()) {
|
|
670
|
-
if (!entry.name.startsWith(".") && !SKIPPED_DIRS2.has(entry.name))
|
|
671
|
-
walk(join2(abs, entry.name), childRel);
|
|
672
|
-
} else if (entry.isFile()) {
|
|
673
|
-
files.add(childRel);
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
walk(rootDir, "");
|
|
678
|
-
return files;
|
|
679
|
-
}
|
|
680
|
-
function readCapped(abs) {
|
|
681
|
-
const fd = openSync(abs, "r");
|
|
682
|
-
try {
|
|
683
|
-
const buffer = Buffer.alloc(MAX_READ_BYTES);
|
|
684
|
-
const read = readSync(fd, buffer, 0, MAX_READ_BYTES, 0);
|
|
685
|
-
return buffer.subarray(0, read).toString("utf8");
|
|
686
|
-
} finally {
|
|
687
|
-
closeSync(fd);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
function readJson(rootDir, rel) {
|
|
691
|
-
try {
|
|
692
|
-
const parsed = JSON.parse(stripJsonc(readFileSync2(join2(rootDir, rel), "utf8")));
|
|
693
|
-
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : undefined;
|
|
694
|
-
} catch {
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
function dirOf(rel) {
|
|
699
|
-
const slash = rel.lastIndexOf("/");
|
|
700
|
-
return slash < 0 ? "" : rel.slice(0, slash);
|
|
701
|
-
}
|
|
702
|
-
function joinRel(dir, path) {
|
|
703
|
-
const joined = posix2.normalize(posix2.join(dir || ".", path));
|
|
704
|
-
return joined === "." ? "" : joined.replace(/^\.\//, "").replace(/\/$/, "");
|
|
705
|
-
}
|
|
706
|
-
function isRecord(value) {
|
|
707
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
708
|
-
}
|
|
709
|
-
var PREFERRED_CONDITIONS = ["import", "default", "require", "node"];
|
|
710
|
-
function exportTargets(value, out = []) {
|
|
711
|
-
if (typeof value === "string")
|
|
712
|
-
out.push(value);
|
|
713
|
-
else if (Array.isArray(value))
|
|
714
|
-
value.forEach((item) => exportTargets(item, out));
|
|
715
|
-
else if (isRecord(value)) {
|
|
716
|
-
const keys = Object.keys(value).filter((key) => key !== "types" && key !== "typings");
|
|
717
|
-
const preferred = keys.filter((key) => PREFERRED_CONDITIONS.includes(key));
|
|
718
|
-
for (const key of [...preferred, ...keys.filter((key2) => !preferred.includes(key2))])
|
|
719
|
-
exportTargets(value[key], out);
|
|
720
|
-
}
|
|
721
|
-
return out;
|
|
722
|
-
}
|
|
723
|
-
function isTypeOnlyClause(clause) {
|
|
724
|
-
const braces = /^\{([^{}]*)\}$/.exec(clause.trim());
|
|
725
|
-
if (!braces)
|
|
726
|
-
return false;
|
|
727
|
-
const members = braces[1].split(",").map((member) => member.trim()).filter(Boolean);
|
|
728
|
-
return members.length > 0 && members.every((member) => /^type\s+(?!as\s+[\w$]+$)\S/.test(member));
|
|
729
|
-
}
|
|
730
|
-
function stringLeaves(value, out = []) {
|
|
731
|
-
if (typeof value === "string")
|
|
732
|
-
out.push(value);
|
|
733
|
-
else if (Array.isArray(value))
|
|
734
|
-
value.forEach((item) => stringLeaves(item, out));
|
|
735
|
-
else if (isRecord(value)) {
|
|
736
|
-
for (const [key, item] of Object.entries(value)) {
|
|
737
|
-
if (key !== "types" && key !== "typings")
|
|
738
|
-
stringLeaves(item, out);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
return out;
|
|
742
|
-
}
|
|
743
|
-
function boundClassesOf(section) {
|
|
744
|
-
const durable = isRecord(section.durable_objects) ? section.durable_objects.bindings : undefined;
|
|
745
|
-
const raw = [...Array.isArray(durable) ? durable : [], ...Array.isArray(section.workflows) ? section.workflows : []];
|
|
746
|
-
return raw.filter(isRecord).flatMap((entry) => typeof entry.class_name === "string" && entry.class_name ? [{ className: entry.class_name, ...typeof entry.script_name === "string" && entry.script_name ? { scriptName: entry.script_name } : {} }] : []);
|
|
747
|
-
}
|
|
748
|
-
var PRODUCTION_ENV = /^(?:production|prod)$/i;
|
|
749
|
-
function parseWranglerToml(text) {
|
|
750
|
-
const names = [];
|
|
751
|
-
const mains = [];
|
|
752
|
-
const otherMains = [];
|
|
753
|
-
const boundClasses = [];
|
|
754
|
-
let topName;
|
|
755
|
-
let header = "";
|
|
756
|
-
let binding;
|
|
757
|
-
for (const rawLine of text.split(`
|
|
758
|
-
`)) {
|
|
759
|
-
const line = rawLine.replace(/\s+#.*$/, "").trim();
|
|
760
|
-
const table = /^\[\[?\s*([^\]]+?)\s*\]\]?$/.exec(line);
|
|
761
|
-
if (table) {
|
|
762
|
-
header = table[1];
|
|
763
|
-
binding = undefined;
|
|
764
|
-
if (/(?:^|\.)(?:durable_objects\.bindings|workflows)$/.test(header) && line.startsWith("[[")) {
|
|
765
|
-
binding = { className: "" };
|
|
766
|
-
boundClasses.push(binding);
|
|
767
|
-
}
|
|
768
|
-
continue;
|
|
769
|
-
}
|
|
770
|
-
const pair = /^([\w.-]+)\s*=\s*["']([^"']*)["']/.exec(line);
|
|
771
|
-
if (!pair)
|
|
772
|
-
continue;
|
|
773
|
-
const [, key, value] = pair;
|
|
774
|
-
const envName = /^env\.([\w-]+)$/.exec(header)?.[1];
|
|
775
|
-
const envSection = header === "" || envName !== undefined;
|
|
776
|
-
if (binding && key === "class_name")
|
|
777
|
-
binding.className = value;
|
|
778
|
-
else if (binding && key === "script_name" && value)
|
|
779
|
-
binding.scriptName = value;
|
|
780
|
-
else if (envSection && key === "name") {
|
|
781
|
-
if (header === "")
|
|
782
|
-
topName = value;
|
|
783
|
-
names.push(value);
|
|
784
|
-
} else if (envSection && key === "main") {
|
|
785
|
-
(envName === undefined || PRODUCTION_ENV.test(envName) ? mains : otherMains).push(value);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
return { name: topName, names, mains, otherMains, boundClasses: boundClasses.filter((entry) => entry.className) };
|
|
789
|
-
}
|
|
790
|
-
function readWranglerWorker(rootDir, rel) {
|
|
791
|
-
const dir = dirOf(rel);
|
|
792
|
-
const fileName = rel.slice(dir ? dir.length + 1 : 0);
|
|
793
|
-
const variant = WRANGLER_FILE2.exec(fileName)?.[1] ?? "";
|
|
794
|
-
let name;
|
|
795
|
-
const names = [];
|
|
796
|
-
const mains = [];
|
|
797
|
-
const otherMains = [];
|
|
798
|
-
const boundClasses = [];
|
|
799
|
-
if (rel.endsWith(".toml")) {
|
|
800
|
-
let text;
|
|
801
|
-
try {
|
|
802
|
-
text = readFileSync2(join2(rootDir, rel), "utf8");
|
|
803
|
-
} catch {
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
const parsed = parseWranglerToml(text);
|
|
807
|
-
name = parsed.name;
|
|
808
|
-
names.push(...parsed.names);
|
|
809
|
-
mains.push(...parsed.mains);
|
|
810
|
-
otherMains.push(...parsed.otherMains);
|
|
811
|
-
boundClasses.push(...parsed.boundClasses);
|
|
812
|
-
} else {
|
|
813
|
-
const config = readJson(rootDir, rel);
|
|
814
|
-
if (!config)
|
|
815
|
-
return;
|
|
816
|
-
name = typeof config.name === "string" && config.name ? config.name : undefined;
|
|
817
|
-
const sections = [[undefined, config], ...Object.entries(isRecord(config.env) ? config.env : {})];
|
|
818
|
-
for (const [envName, section] of sections) {
|
|
819
|
-
if (!isRecord(section))
|
|
820
|
-
continue;
|
|
821
|
-
if (typeof section.name === "string" && section.name)
|
|
822
|
-
names.push(section.name);
|
|
823
|
-
if (typeof section.main === "string" && section.main) {
|
|
824
|
-
(envName === undefined || PRODUCTION_ENV.test(envName) ? mains : otherMains).push(section.main);
|
|
825
|
-
}
|
|
826
|
-
boundClasses.push(...boundClassesOf(section));
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
const nonDeployDir = rel.split("/").slice(0, -1).some((segment) => NON_DEPLOY_DIR.test(segment));
|
|
830
|
-
return {
|
|
831
|
-
path: rel,
|
|
832
|
-
deployed: Boolean(name) && !nonDeployDir && !NON_DEPLOY_VARIANT.test(variant) && !isTestWorkerConfig({ path: rel, name, mains }),
|
|
833
|
-
names,
|
|
834
|
-
mains: [...new Set(mains)].map((main) => joinRel(dir, main)),
|
|
835
|
-
otherMains: [...new Set(otherMains)].filter((main) => !mains.includes(main)).map((main) => joinRel(dir, main)),
|
|
836
|
-
boundClasses,
|
|
837
|
-
reach: new Set
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
function readWorkspacePackage(rootDir, rel) {
|
|
841
|
-
const pkg = readJson(rootDir, rel);
|
|
842
|
-
if (!pkg)
|
|
843
|
-
return;
|
|
844
|
-
const dir = dirOf(rel);
|
|
845
|
-
const entries = [];
|
|
846
|
-
const subpaths = new Map;
|
|
847
|
-
const patterns = [];
|
|
848
|
-
for (const field of ["main", "module"]) {
|
|
849
|
-
if (typeof pkg[field] === "string")
|
|
850
|
-
entries.push(pkg[field]);
|
|
851
|
-
}
|
|
852
|
-
if (typeof pkg.bin === "string")
|
|
853
|
-
entries.push(pkg.bin);
|
|
854
|
-
else if (isRecord(pkg.bin))
|
|
855
|
-
entries.push(...stringLeaves(pkg.bin));
|
|
856
|
-
const exportsField = pkg.exports;
|
|
857
|
-
if (isRecord(exportsField) && Object.keys(exportsField).some((key) => key.startsWith("."))) {
|
|
858
|
-
for (const [key, value] of Object.entries(exportsField)) {
|
|
859
|
-
const targets = exportTargets(value);
|
|
860
|
-
const star = key.indexOf("*");
|
|
861
|
-
if (star >= 0) {
|
|
862
|
-
if (key.indexOf("*", star + 1) < 0)
|
|
863
|
-
patterns.push({ prefix: key.slice(0, star), suffix: key.slice(star + 1), targets });
|
|
864
|
-
continue;
|
|
865
|
-
}
|
|
866
|
-
subpaths.set(key, targets);
|
|
867
|
-
entries.push(...targets);
|
|
868
|
-
}
|
|
869
|
-
patterns.sort((a, b) => b.prefix.length - a.prefix.length || b.suffix.length - a.suffix.length);
|
|
870
|
-
} else if (exportsField !== undefined) {
|
|
871
|
-
const targets = exportTargets(exportsField);
|
|
872
|
-
subpaths.set(".", targets);
|
|
873
|
-
entries.push(...targets);
|
|
874
|
-
}
|
|
875
|
-
const scriptFiles = [];
|
|
876
|
-
if (isRecord(pkg.scripts)) {
|
|
877
|
-
for (const command of Object.values(pkg.scripts)) {
|
|
878
|
-
if (typeof command !== "string")
|
|
879
|
-
continue;
|
|
880
|
-
for (const match of command.matchAll(SCRIPT_FILE))
|
|
881
|
-
scriptFiles.push(match[2]);
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
return {
|
|
885
|
-
dir,
|
|
886
|
-
...typeof pkg.name === "string" && pkg.name ? { name: pkg.name } : {},
|
|
887
|
-
entries: entries.filter((entry) => !entry.includes("*")),
|
|
888
|
-
subpaths,
|
|
889
|
-
patterns,
|
|
890
|
-
scriptFiles
|
|
891
|
-
};
|
|
892
|
-
}
|
|
893
|
-
function readPathAliases(rootDir, rel, depth = 0) {
|
|
894
|
-
const config = readJson(rootDir, rel);
|
|
895
|
-
if (!config)
|
|
896
|
-
return [];
|
|
897
|
-
const options = isRecord(config.compilerOptions) ? config.compilerOptions : {};
|
|
898
|
-
if (isRecord(options.paths)) {
|
|
899
|
-
const base = joinRel(dirOf(rel), typeof options.baseUrl === "string" ? options.baseUrl : ".");
|
|
900
|
-
return Object.entries(options.paths).flatMap(([pattern, targets]) => {
|
|
901
|
-
if (!Array.isArray(targets))
|
|
902
|
-
return [];
|
|
903
|
-
const star = pattern.indexOf("*");
|
|
904
|
-
return [{
|
|
905
|
-
prefix: star < 0 ? pattern : pattern.slice(0, star),
|
|
906
|
-
suffix: star < 0 ? "" : pattern.slice(star + 1),
|
|
907
|
-
wildcard: star >= 0,
|
|
908
|
-
targets: targets.filter((target) => typeof target === "string").map((target) => joinRel(base, target))
|
|
909
|
-
}];
|
|
910
|
-
});
|
|
911
|
-
}
|
|
912
|
-
if (depth < 3 && typeof config.extends === "string" && config.extends.startsWith(".")) {
|
|
913
|
-
const parent = joinRel(dirOf(rel), config.extends.endsWith(".json") ? config.extends : `${config.extends}.json`);
|
|
914
|
-
return readPathAliases(rootDir, parent, depth + 1);
|
|
915
|
-
}
|
|
916
|
-
return [];
|
|
917
|
-
}
|
|
918
|
-
function analyzeRepository(rootDir) {
|
|
919
|
-
const files = listFiles(rootDir);
|
|
920
|
-
const workers = [];
|
|
921
|
-
const packages = [];
|
|
922
|
-
for (const rel of files) {
|
|
923
|
-
const fileName = rel.slice(rel.lastIndexOf("/") + 1);
|
|
924
|
-
if (WRANGLER_FILE2.test(fileName)) {
|
|
925
|
-
const worker = readWranglerWorker(rootDir, rel);
|
|
926
|
-
if (worker)
|
|
927
|
-
workers.push(worker);
|
|
928
|
-
} else if (fileName === "package.json") {
|
|
929
|
-
const pkg = readWorkspacePackage(rootDir, rel);
|
|
930
|
-
if (pkg)
|
|
931
|
-
packages.push(pkg);
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
workers.sort((a, b) => a.path.localeCompare(b.path));
|
|
935
|
-
const packagesByName = new Map(packages.filter((pkg) => pkg.name).map((pkg) => [pkg.name, pkg]));
|
|
936
|
-
const packageDirs = new Set(packages.map((pkg) => pkg.dir));
|
|
937
|
-
const resolveFile = (base) => {
|
|
938
|
-
if (!base || base.startsWith(".."))
|
|
939
|
-
return;
|
|
940
|
-
const ext = posix2.extname(base);
|
|
941
|
-
for (const tsExt of TS_FOR_JS[ext] ?? []) {
|
|
942
|
-
const candidate = base.slice(0, -ext.length) + tsExt;
|
|
943
|
-
if (files.has(candidate))
|
|
944
|
-
return candidate;
|
|
945
|
-
}
|
|
946
|
-
if (files.has(base))
|
|
947
|
-
return base;
|
|
948
|
-
for (const candidate of RESOLVE_EXTS)
|
|
949
|
-
if (files.has(base + candidate))
|
|
950
|
-
return base + candidate;
|
|
951
|
-
for (const candidate of RESOLVE_EXTS)
|
|
952
|
-
if (files.has(`${base}/index${candidate}`))
|
|
953
|
-
return `${base}/index${candidate}`;
|
|
954
|
-
return;
|
|
955
|
-
};
|
|
956
|
-
const resolveEntry = (dir, entry) => {
|
|
957
|
-
const direct = resolveFile(joinRel(dir, entry));
|
|
958
|
-
if (direct)
|
|
959
|
-
return direct;
|
|
960
|
-
const fromSource = entry.replace(/^\.?\/?(?:dist|build|lib|out)\//, "src/").replace(/\.d\.[cm]?ts$/, ".ts");
|
|
961
|
-
return fromSource !== entry ? resolveFile(joinRel(dir, fromSource)) : undefined;
|
|
962
|
-
};
|
|
963
|
-
const aliasCache = new Map;
|
|
964
|
-
const aliasesFor = (dir) => {
|
|
965
|
-
const cached = aliasCache.get(dir);
|
|
966
|
-
if (cached)
|
|
967
|
-
return cached;
|
|
968
|
-
const tsconfig = dir ? `${dir}/tsconfig.json` : "tsconfig.json";
|
|
969
|
-
const aliases = files.has(tsconfig) ? readPathAliases(rootDir, tsconfig) : dir ? aliasesFor(dirOf(dir)) : [];
|
|
970
|
-
aliasCache.set(dir, aliases);
|
|
971
|
-
return aliases;
|
|
972
|
-
};
|
|
973
|
-
const resolvePackage = (specifier) => {
|
|
974
|
-
const parts = specifier.split("/");
|
|
975
|
-
const nameLength = specifier.startsWith("@") ? 2 : 1;
|
|
976
|
-
const name = parts.slice(0, nameLength).join("/");
|
|
977
|
-
const subpath = parts.slice(nameLength).join("/");
|
|
978
|
-
const pkg = packagesByName.get(name);
|
|
979
|
-
if (pkg) {
|
|
980
|
-
const key = subpath ? `./${subpath}` : ".";
|
|
981
|
-
let targets = pkg.subpaths.get(key);
|
|
982
|
-
if (!targets && subpath) {
|
|
983
|
-
const pattern = pkg.patterns.find((entry) => key.startsWith(entry.prefix) && key.endsWith(entry.suffix) && key.length >= entry.prefix.length + entry.suffix.length);
|
|
984
|
-
const captured = pattern ? key.slice(pattern.prefix.length, key.length - pattern.suffix.length) : "";
|
|
985
|
-
targets = pattern ? pattern.targets.map((target) => target.split("*").join(captured)) : [];
|
|
986
|
-
}
|
|
987
|
-
for (const target of targets ?? pkg.entries) {
|
|
988
|
-
const resolved = resolveEntry(pkg.dir, target);
|
|
989
|
-
if (resolved)
|
|
990
|
-
return resolved;
|
|
991
|
-
}
|
|
992
|
-
if (subpath)
|
|
993
|
-
return resolveFile(joinRel(pkg.dir, subpath)) ?? resolveFile(joinRel(pkg.dir, `src/${subpath}`));
|
|
994
|
-
return resolveFile(joinRel(pkg.dir, "src/index")) ?? resolveFile(joinRel(pkg.dir, "index"));
|
|
995
|
-
}
|
|
996
|
-
const leaf = parts[nameLength - 1];
|
|
997
|
-
for (const parent of ["packages", "apps", "libs"]) {
|
|
998
|
-
const dir = `${parent}/${leaf}`;
|
|
999
|
-
if (packageDirs.has(dir))
|
|
1000
|
-
continue;
|
|
1001
|
-
const resolved = subpath ? resolveFile(`${dir}/src/${subpath}`) ?? resolveFile(`${dir}/${subpath}`) : resolveFile(`${dir}/src/index`) ?? resolveFile(`${dir}/index`);
|
|
1002
|
-
if (resolved)
|
|
1003
|
-
return resolved;
|
|
1004
|
-
}
|
|
1005
|
-
return;
|
|
1006
|
-
};
|
|
1007
|
-
const resolveSpecifier = (raw, from) => {
|
|
1008
|
-
const specifier = raw.split("?")[0];
|
|
1009
|
-
if (!specifier || specifier.startsWith("node:") || specifier.startsWith("/"))
|
|
1010
|
-
return;
|
|
1011
|
-
if (specifier.startsWith("."))
|
|
1012
|
-
return resolveFile(joinRel(dirOf(from), specifier));
|
|
1013
|
-
for (const alias of aliasesFor(dirOf(from))) {
|
|
1014
|
-
let captured;
|
|
1015
|
-
if (!alias.wildcard && specifier === alias.prefix)
|
|
1016
|
-
captured = "";
|
|
1017
|
-
else if (alias.wildcard && specifier.startsWith(alias.prefix) && specifier.endsWith(alias.suffix) && specifier.length >= alias.prefix.length + alias.suffix.length) {
|
|
1018
|
-
captured = specifier.slice(alias.prefix.length, specifier.length - alias.suffix.length);
|
|
1019
|
-
}
|
|
1020
|
-
if (captured === undefined)
|
|
1021
|
-
continue;
|
|
1022
|
-
for (const target of alias.targets) {
|
|
1023
|
-
const resolved = resolveFile(alias.wildcard ? target.replace("*", captured) : target);
|
|
1024
|
-
if (resolved)
|
|
1025
|
-
return resolved;
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
return resolvePackage(specifier);
|
|
1029
|
-
};
|
|
1030
|
-
const classFiles = new Map;
|
|
1031
|
-
const edgeCache = new Map;
|
|
1032
|
-
const edgesOf = (file) => {
|
|
1033
|
-
const cached = edgeCache.get(file);
|
|
1034
|
-
if (cached)
|
|
1035
|
-
return cached;
|
|
1036
|
-
const edges = [];
|
|
1037
|
-
edgeCache.set(file, edges);
|
|
1038
|
-
if (!CODE_FILE.test(file))
|
|
1039
|
-
return edges;
|
|
1040
|
-
let text;
|
|
1041
|
-
try {
|
|
1042
|
-
text = readCapped(join2(rootDir, file));
|
|
1043
|
-
} catch {
|
|
1044
|
-
return edges;
|
|
1045
|
-
}
|
|
1046
|
-
text = text.replace(/^\s*\/\/.*$/gm, "");
|
|
1047
|
-
const specifiers = [];
|
|
1048
|
-
for (const match of text.matchAll(STATIC_IMPORT)) {
|
|
1049
|
-
if (!match[1] && !isTypeOnlyClause(match[2]))
|
|
1050
|
-
specifiers.push(match[3]);
|
|
1051
|
-
}
|
|
1052
|
-
for (const match of text.matchAll(SIDE_EFFECT_IMPORT))
|
|
1053
|
-
specifiers.push(match[1]);
|
|
1054
|
-
for (const match of text.matchAll(DYNAMIC_IMPORT))
|
|
1055
|
-
specifiers.push(match[1]);
|
|
1056
|
-
for (const specifier of new Set(specifiers)) {
|
|
1057
|
-
const resolved = resolveSpecifier(specifier, file);
|
|
1058
|
-
if (resolved && resolved !== file)
|
|
1059
|
-
edges.push(resolved);
|
|
1060
|
-
}
|
|
1061
|
-
for (const match of text.matchAll(CLASS_DECL)) {
|
|
1062
|
-
const list = classFiles.get(match[1]) ?? [];
|
|
1063
|
-
if (!list.includes(file))
|
|
1064
|
-
list.push(file);
|
|
1065
|
-
classFiles.set(match[1], list);
|
|
1066
|
-
}
|
|
1067
|
-
return edges;
|
|
1068
|
-
};
|
|
1069
|
-
const walk = (starts, seen = new Set) => {
|
|
1070
|
-
const queue = [...starts].filter((start) => Boolean(start) && !seen.has(start));
|
|
1071
|
-
for (const start of queue)
|
|
1072
|
-
seen.add(start);
|
|
1073
|
-
while (queue.length > 0) {
|
|
1074
|
-
const file = queue.pop();
|
|
1075
|
-
for (const next of edgesOf(file)) {
|
|
1076
|
-
if (!seen.has(next)) {
|
|
1077
|
-
seen.add(next);
|
|
1078
|
-
queue.push(next);
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
return seen;
|
|
1083
|
-
};
|
|
1084
|
-
const inProd = new Set;
|
|
1085
|
-
for (const worker of workers) {
|
|
1086
|
-
worker.reach = walk(worker.mains.map((main) => resolveEntry("", main)));
|
|
1087
|
-
if (!worker.deployed)
|
|
1088
|
-
continue;
|
|
1089
|
-
inProd.add(worker.path);
|
|
1090
|
-
for (const file of worker.reach)
|
|
1091
|
-
inProd.add(file);
|
|
1092
|
-
}
|
|
1093
|
-
const otherStarts = workers.filter((worker) => !worker.deployed).map((worker) => worker.path);
|
|
1094
|
-
for (const worker of workers) {
|
|
1095
|
-
if (!worker.deployed)
|
|
1096
|
-
otherStarts.push(...worker.reach);
|
|
1097
|
-
otherStarts.push(...worker.otherMains.map((main) => resolveEntry("", main)));
|
|
1098
|
-
}
|
|
1099
|
-
for (const pkg of packages) {
|
|
1100
|
-
otherStarts.push(...pkg.entries.map((entry) => resolveEntry(pkg.dir, entry)));
|
|
1101
|
-
otherStarts.push(...pkg.scriptFiles.map((script) => resolveFile(joinRel(pkg.dir, script))));
|
|
1102
|
-
}
|
|
1103
|
-
const all = walk(otherStarts, new Set(inProd));
|
|
1104
|
-
const reachable = new Set([...all].filter((file) => !inProd.has(file)));
|
|
1105
|
-
return { inProd, reachable, files, workers, classFiles, resolveFile };
|
|
1106
|
-
}
|
|
1107
|
-
function norm2(value) {
|
|
1108
|
-
return typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9]/g, "") : "";
|
|
1109
|
-
}
|
|
1110
|
-
function normalizeItemPath(rootDir, path) {
|
|
1111
|
-
let rel = path.trim().replace(/\\/g, "/").replace(/:\d+(?::\d+)?$/, "");
|
|
1112
|
-
if (isAbsolute2(rel))
|
|
1113
|
-
rel = relative2(rootDir, rel).split("\\").join("/");
|
|
1114
|
-
return joinRel("", rel);
|
|
1115
|
-
}
|
|
1116
|
-
function definingPath(item) {
|
|
1117
|
-
if (typeof item.sourcePath === "string" && item.sourcePath.trim())
|
|
1118
|
-
return item.sourcePath;
|
|
1119
|
-
for (const entry of Array.isArray(item.evidence) ? item.evidence : []) {
|
|
1120
|
-
const path = isRecord(entry) ? entry.path : undefined;
|
|
1121
|
-
if (typeof path === "string" && path.trim())
|
|
1122
|
-
return path;
|
|
1123
|
-
}
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
function boundClassStatus(analysis, worker, item) {
|
|
1127
|
-
const wanted = new Set([norm2(item.name), norm2(typeof item.id === "string" ? item.id.split(".").pop() : "")]);
|
|
1128
|
-
const bound = worker.boundClasses.find((entry) => wanted.has(norm2(entry.className)));
|
|
1129
|
-
if (!bound)
|
|
1130
|
-
return null;
|
|
1131
|
-
const servers = bound.scriptName ? analysis.workers.filter((other) => other.names.includes(bound.scriptName)) : [worker];
|
|
1132
|
-
const declared = analysis.classFiles.get(bound.className) ?? [];
|
|
1133
|
-
if (servers.some((server) => server.deployed && declared.some((file) => server.reach.has(file))))
|
|
1134
|
-
return "in_prod";
|
|
1135
|
-
if (declared.some((file) => analysis.inProd.has(file) || analysis.reachable.has(file)))
|
|
1136
|
-
return "reachable";
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
function itemStatus(analysis, rootDir, item) {
|
|
1140
|
-
const raw = definingPath(item);
|
|
1141
|
-
if (!raw)
|
|
1142
|
-
return;
|
|
1143
|
-
const path = normalizeItemPath(rootDir, raw);
|
|
1144
|
-
const worker = analysis.workers.find((entry) => entry.path === path);
|
|
1145
|
-
if (worker) {
|
|
1146
|
-
const classStatus = boundClassStatus(analysis, worker, item);
|
|
1147
|
-
if (classStatus !== null)
|
|
1148
|
-
return classStatus;
|
|
1149
|
-
return worker.deployed ? "in_prod" : "reachable";
|
|
1150
|
-
}
|
|
1151
|
-
const file = analysis.files.has(path) ? path : analysis.resolveFile(path);
|
|
1152
|
-
if (!file)
|
|
1153
|
-
return;
|
|
1154
|
-
if (analysis.inProd.has(file))
|
|
1155
|
-
return "in_prod";
|
|
1156
|
-
if (analysis.reachable.has(file))
|
|
1157
|
-
return "reachable";
|
|
1158
|
-
return CODE_FILE.test(file) && !TEST_RUNNER_FILE.test(file) ? "defined_only" : undefined;
|
|
1159
|
-
}
|
|
1160
|
-
function tagComponentStatus(graph, rootDir) {
|
|
1161
|
-
if (process.env.MODA_HARNESS_DETERMINISTIC_MERGE === "0")
|
|
1162
|
-
return { tagged: 0, counts: {} };
|
|
1163
|
-
try {
|
|
1164
|
-
const analysis = analyzeRepository(rootDir);
|
|
1165
|
-
const counts = { in_prod: 0, reachable: 0, defined_only: 0, unknown: 0 };
|
|
1166
|
-
let tagged = 0;
|
|
1167
|
-
const items = [
|
|
1168
|
-
...Array.isArray(graph.runtimeAgents) ? graph.runtimeAgents : [],
|
|
1169
|
-
...Array.isArray(graph.artifacts) ? graph.artifacts : []
|
|
1170
|
-
];
|
|
1171
|
-
for (const item of items) {
|
|
1172
|
-
if (!isRecord(item))
|
|
1173
|
-
continue;
|
|
1174
|
-
if (item.status === undefined || !HARNESS_COMPONENT_STATUSES.has(item.status)) {
|
|
1175
|
-
const status = itemStatus(analysis, rootDir, item);
|
|
1176
|
-
if (status) {
|
|
1177
|
-
item.status = status;
|
|
1178
|
-
tagged += 1;
|
|
1179
|
-
} else {
|
|
1180
|
-
delete item.status;
|
|
1181
|
-
}
|
|
1182
|
-
}
|
|
1183
|
-
counts[item.status ?? "unknown"] += 1;
|
|
1184
|
-
}
|
|
1185
|
-
if (isRecord(graph.scan)) {
|
|
1186
|
-
graph.scan.componentStatusCounts = counts;
|
|
1187
|
-
}
|
|
1188
|
-
return { tagged, counts };
|
|
1189
|
-
} catch {
|
|
1190
|
-
return { tagged: 0, counts: {} };
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
|
|
1194
|
-
// src/harness-report.ts
|
|
21
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
1195
22
|
var HARNESS_REPORT_SCHEMA_VERSION = "harness_report.v0.1";
|
|
1196
23
|
var HARNESS_REPORT_VALIDATION_SCHEMA_VERSION = "harness_report_validation.v0.1";
|
|
1197
24
|
var HARNESS_REPORT_APPROVAL_SCHEMA_VERSION = "harness_report_approval.v0.1";
|
|
1198
|
-
var HARNESS_REPORT_PATH =
|
|
1199
|
-
var HARNESS_REPORT_MARKDOWN_PATH =
|
|
1200
|
-
var HARNESS_REPORT_VALIDATION_PATH =
|
|
1201
|
-
var HARNESS_REPORT_APPROVAL_PATH =
|
|
25
|
+
var HARNESS_REPORT_PATH = join(".moda", "harness-report.json");
|
|
26
|
+
var HARNESS_REPORT_MARKDOWN_PATH = join(".moda", "HARNESS_REPORT.md");
|
|
27
|
+
var HARNESS_REPORT_VALIDATION_PATH = join(".moda", "harness-report-validation.json");
|
|
28
|
+
var HARNESS_REPORT_APPROVAL_PATH = join(".moda", "harness-report-approval.json");
|
|
1202
29
|
var HARNESS_ANALYZE_CONCERNS = ["topology", "tools", "prompts", "evals"];
|
|
1203
30
|
var HARNESS_GRAPH_ARRAY_FIELDS = [
|
|
1204
31
|
"agentFamilies",
|
|
@@ -1477,7 +304,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
1477
304
|
check("graph_present", Boolean(report?.graph?.schemaVersion), "Report must include the sanitized harness graph.");
|
|
1478
305
|
check("citations_present", Array.isArray(report?.citations) && report.citations.length > 0, "Report must include citations.");
|
|
1479
306
|
check("claims_present", Array.isArray(report?.claims) && report.claims.length > 0, "Report must include claims.");
|
|
1480
|
-
const syncPlan =
|
|
307
|
+
const syncPlan = isRecord(report?.syncPlan) ? report.syncPlan : undefined;
|
|
1481
308
|
check("sync_plan_present", Boolean(syncPlan), "Report must include syncPlan.");
|
|
1482
309
|
if (syncPlan) {
|
|
1483
310
|
check("sync_plan_command", typeof syncPlan.command === "string" && syncPlan.command.length > 0, "Report syncPlan must include command.");
|
|
@@ -1516,7 +343,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
1516
343
|
const requireLineCitations = report?.analyst?.mode === "agent";
|
|
1517
344
|
for (const [index, citation] of citations.entries()) {
|
|
1518
345
|
const label = citationLabel(citation, index);
|
|
1519
|
-
if (!
|
|
346
|
+
if (!isRecord(citation)) {
|
|
1520
347
|
errors.push(`Citation ${index + 1} must be an object.`);
|
|
1521
348
|
continue;
|
|
1522
349
|
}
|
|
@@ -1543,7 +370,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
1543
370
|
const claimIds = new Set;
|
|
1544
371
|
for (const [index, claim] of claims.entries()) {
|
|
1545
372
|
const label = claimLabel(claim, index);
|
|
1546
|
-
if (!
|
|
373
|
+
if (!isRecord(claim)) {
|
|
1547
374
|
errors.push(`Claim ${index + 1} must be an object.`);
|
|
1548
375
|
continue;
|
|
1549
376
|
}
|
|
@@ -1614,7 +441,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
1614
441
|
if (unknowns.length > 0) {
|
|
1615
442
|
warn("unknowns_present", `${unknowns.length} open harness question(s) require human review.`);
|
|
1616
443
|
for (const [index, unknown] of unknowns.entries()) {
|
|
1617
|
-
if (!
|
|
444
|
+
if (!isRecord(unknown)) {
|
|
1618
445
|
errors.push(`Unknown ${index + 1} must be an object.`);
|
|
1619
446
|
continue;
|
|
1620
447
|
}
|
|
@@ -1642,7 +469,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
1642
469
|
};
|
|
1643
470
|
}
|
|
1644
471
|
function refutedToolCandidateCount(graph) {
|
|
1645
|
-
const unknowns = Array.isArray(graph.unknowns) ? graph.unknowns.filter(
|
|
472
|
+
const unknowns = Array.isArray(graph.unknowns) ? graph.unknowns.filter(isRecord) : [];
|
|
1646
473
|
let count = 0;
|
|
1647
474
|
for (const unknown of unknowns) {
|
|
1648
475
|
const label = `${String(unknown.id ?? "")} ${String(unknown.question ?? "")}`;
|
|
@@ -1661,8 +488,8 @@ function validateDeterministicFloor(report, floor, check, warn, concern) {
|
|
|
1661
488
|
return;
|
|
1662
489
|
const applyToolFloor = concern === undefined || concern === "tools";
|
|
1663
490
|
const applyTopologyFloors = concern === undefined || concern === "topology";
|
|
1664
|
-
const graph =
|
|
1665
|
-
const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) =>
|
|
491
|
+
const graph = isRecord(report?.graph) ? report.graph : {};
|
|
492
|
+
const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord(artifact) && artifact.type === "tool").length : 0;
|
|
1666
493
|
const providerCount = safeArrayLength(graph.modelProviders);
|
|
1667
494
|
const runtimeAgentCount = safeArrayLength(graph.runtimeAgents);
|
|
1668
495
|
if (applyToolFloor && typeof floor.toolCandidates === "number" && floor.toolCandidates >= 5) {
|
|
@@ -1711,9 +538,9 @@ async function approveHarnessReport(options) {
|
|
|
1711
538
|
approved: true,
|
|
1712
539
|
approvedAt: new Date().toISOString(),
|
|
1713
540
|
approvedBy,
|
|
1714
|
-
reportPath: normalizePath(
|
|
541
|
+
reportPath: normalizePath(relative(options.rootDir, options.reportPath)) || HARNESS_REPORT_PATH
|
|
1715
542
|
};
|
|
1716
|
-
writeJson(
|
|
543
|
+
writeJson(join(options.rootDir, HARNESS_REPORT_APPROVAL_PATH), approval);
|
|
1717
544
|
return { report, validation, approval };
|
|
1718
545
|
}
|
|
1719
546
|
function loadHarnessReportForSync(rootDir, reportPath, options) {
|
|
@@ -1723,37 +550,39 @@ function loadHarnessReportForSync(rootDir, reportPath, options) {
|
|
|
1723
550
|
throw new CliInputError("Harness report validation failed; cannot sync.", validation.errors.slice(0, 5).join(`
|
|
1724
551
|
`));
|
|
1725
552
|
}
|
|
1726
|
-
const approvalPath =
|
|
1727
|
-
const approval = existsSync(approvalPath) ? JSON.parse(
|
|
553
|
+
const approvalPath = join(rootDir, HARNESS_REPORT_APPROVAL_PATH);
|
|
554
|
+
const approval = existsSync(approvalPath) ? JSON.parse(readFileSync(approvalPath, "utf8")) : null;
|
|
1728
555
|
const approved = approval?.schemaVersion === HARNESS_REPORT_APPROVAL_SCHEMA_VERSION && approval.reportId === report.reportId && approval.reportHash === hashHarnessReport(report) && approval.approved === true;
|
|
1729
556
|
return { report, validation, approval, approved };
|
|
1730
557
|
}
|
|
1731
558
|
function readHarnessReport(reportPath) {
|
|
1732
|
-
return normalizeHarnessReport(JSON.parse(
|
|
559
|
+
return normalizeHarnessReport(JSON.parse(readFileSync(reportPath, "utf8")));
|
|
1733
560
|
}
|
|
1734
561
|
function normalizeHarnessReport(value) {
|
|
1735
562
|
const report = cloneJson(value);
|
|
1736
|
-
if (!
|
|
563
|
+
if (!isRecord(report.graph))
|
|
1737
564
|
return report;
|
|
1738
565
|
const graph = report.graph;
|
|
566
|
+
const reportSource = isRecord(report.source) ? report.source : undefined;
|
|
567
|
+
const hashMatched = typeof reportSource?.graphHash === "string" && reportSource.graphHash === hashStableJson(graphForReport(graph));
|
|
1739
568
|
if (typeof graph.schemaVersion !== "string" && typeof graph.graphSchemaVersion === "string") {
|
|
1740
569
|
graph.schemaVersion = graph.graphSchemaVersion;
|
|
1741
570
|
}
|
|
1742
|
-
if (typeof graph.schemaVersion !== "string" &&
|
|
571
|
+
if (typeof graph.schemaVersion !== "string" && isRecord(report.source) && typeof report.source.graphSchemaVersion === "string") {
|
|
1743
572
|
graph.schemaVersion = report.source.graphSchemaVersion;
|
|
1744
573
|
}
|
|
1745
574
|
if (typeof graph.harnessId !== "string" && typeof report.harnessId === "string") {
|
|
1746
575
|
graph.harnessId = report.harnessId;
|
|
1747
576
|
}
|
|
1748
|
-
if (!
|
|
577
|
+
if (!isRecord(graph.repo) && isRecord(report.repo)) {
|
|
1749
578
|
graph.repo = report.repo;
|
|
1750
579
|
}
|
|
1751
|
-
if (!
|
|
580
|
+
if (!isRecord(graph.tenant) && isRecord(report.tenant)) {
|
|
1752
581
|
graph.tenant = report.tenant;
|
|
1753
582
|
}
|
|
1754
|
-
if (!
|
|
1755
|
-
const source =
|
|
1756
|
-
const readPlan =
|
|
583
|
+
if (!isRecord(graph.scan)) {
|
|
584
|
+
const source = isRecord(report.source) ? report.source : {};
|
|
585
|
+
const readPlan = isRecord(source.readPlan) ? source.readPlan : defaultHarnessReadPlan();
|
|
1757
586
|
graph.scan = {
|
|
1758
587
|
createdAt: typeof report.createdAt === "string" ? report.createdAt : new Date().toISOString(),
|
|
1759
588
|
scannerVersion: analystAdapter(report),
|
|
@@ -1776,10 +605,12 @@ function normalizeHarnessReport(value) {
|
|
|
1776
605
|
report.claims = report.claims.map((claim) => normalizeHarnessClaim(claim, graph));
|
|
1777
606
|
}
|
|
1778
607
|
normalizeHarnessSyncPlan(report);
|
|
608
|
+
if (reportSource && hashMatched)
|
|
609
|
+
reportSource.graphHash = hashStableJson(graphForReport(graph));
|
|
1779
610
|
return report;
|
|
1780
611
|
}
|
|
1781
612
|
function normalizeHarnessSyncPlan(report) {
|
|
1782
|
-
const syncPlan =
|
|
613
|
+
const syncPlan = isRecord(report.syncPlan) ? report.syncPlan : {};
|
|
1783
614
|
if (typeof syncPlan.command !== "string" || syncPlan.command.length === 0) {
|
|
1784
615
|
syncPlan.command = `moda harness sync --from-report ${HARNESS_REPORT_PATH}`;
|
|
1785
616
|
}
|
|
@@ -1804,7 +635,7 @@ function normalizeRelationshipEvidence(graph) {
|
|
|
1804
635
|
return;
|
|
1805
636
|
const evidenceById = graphEvidenceById(graph);
|
|
1806
637
|
for (const relationship of relationships) {
|
|
1807
|
-
if (!
|
|
638
|
+
if (!isRecord(relationship))
|
|
1808
639
|
continue;
|
|
1809
640
|
if (Array.isArray(relationship.evidence) && relationship.evidence.length > 0)
|
|
1810
641
|
continue;
|
|
@@ -1827,9 +658,9 @@ function graphEvidenceById(graph) {
|
|
|
1827
658
|
if (!Array.isArray(collection))
|
|
1828
659
|
continue;
|
|
1829
660
|
for (const item of collection) {
|
|
1830
|
-
if (!
|
|
661
|
+
if (!isRecord(item) || typeof item.id !== "string" || !Array.isArray(item.evidence))
|
|
1831
662
|
continue;
|
|
1832
|
-
const evidence = item.evidence.filter((entry) =>
|
|
663
|
+
const evidence = item.evidence.filter((entry) => isRecord(entry) && typeof entry.path === "string" && typeof entry.reason === "string").map((entry) => normalizeEvidenceEntry(entry));
|
|
1833
664
|
if (evidence.length > 0)
|
|
1834
665
|
byId.set(item.id, evidence);
|
|
1835
666
|
}
|
|
@@ -1853,7 +684,7 @@ function normalizeEvidenceEntry(item) {
|
|
|
1853
684
|
return line ? { path: item.path, reason: item.reason, line } : { path: item.path, reason: item.reason };
|
|
1854
685
|
}
|
|
1855
686
|
function normalizeHarnessClaim(claim, graph) {
|
|
1856
|
-
if (!
|
|
687
|
+
if (!isRecord(claim))
|
|
1857
688
|
return claim;
|
|
1858
689
|
if (typeof claim.subjectId !== "string" && Array.isArray(claim.subjectIds) && typeof claim.subjectIds[0] === "string") {
|
|
1859
690
|
claim.subjectId = claim.subjectIds[0];
|
|
@@ -1901,10 +732,10 @@ function validateGraphItemShapes(graph, errors) {
|
|
|
1901
732
|
if (typeof graph.harnessId !== "string" || graph.harnessId.length === 0) {
|
|
1902
733
|
errors.push("Graph must include harnessId.");
|
|
1903
734
|
}
|
|
1904
|
-
if (!
|
|
735
|
+
if (!isRecord(graph.repo)) {
|
|
1905
736
|
errors.push("Graph must include repo metadata.");
|
|
1906
737
|
}
|
|
1907
|
-
if (!
|
|
738
|
+
if (!isRecord(graph.scan)) {
|
|
1908
739
|
errors.push("Graph must include scan metadata.");
|
|
1909
740
|
}
|
|
1910
741
|
validateGraphCollection(graph, errors, "runtimeAgents", {
|
|
@@ -1976,7 +807,7 @@ function validateGraphCollection(graph, errors, field, shape) {
|
|
|
1976
807
|
return;
|
|
1977
808
|
for (const [index, item] of collection.entries()) {
|
|
1978
809
|
const label = graphItemLabel(field, item, index);
|
|
1979
|
-
if (!
|
|
810
|
+
if (!isRecord(item)) {
|
|
1980
811
|
errors.push(`Graph ${field} item ${index + 1} must be an object.`);
|
|
1981
812
|
continue;
|
|
1982
813
|
}
|
|
@@ -2005,7 +836,7 @@ function validateGraphCollection(graph, errors, field, shape) {
|
|
|
2005
836
|
}
|
|
2006
837
|
if (Array.isArray(item.evidence)) {
|
|
2007
838
|
for (const [evidenceIndex, evidence] of item.evidence.entries()) {
|
|
2008
|
-
if (!
|
|
839
|
+
if (!isRecord(evidence) || typeof evidence.path !== "string" || typeof evidence.reason !== "string") {
|
|
2009
840
|
errors.push(`Graph ${label} evidence ${evidenceIndex + 1} must include path and reason.`);
|
|
2010
841
|
continue;
|
|
2011
842
|
}
|
|
@@ -2038,7 +869,7 @@ function artifactHasRetrievableLocation(item) {
|
|
|
2038
869
|
}
|
|
2039
870
|
function validateCaptureQuality(graph, warn) {
|
|
2040
871
|
const scan = graph.scan;
|
|
2041
|
-
if (!
|
|
872
|
+
if (!isRecord(scan))
|
|
2042
873
|
return;
|
|
2043
874
|
if (scan.truncated === true) {
|
|
2044
875
|
const reasons = Array.isArray(scan.truncationReasons) && scan.truncationReasons.length > 0 ? scan.truncationReasons.map((reason) => String(reason)).join(", ") : "scan budget or upload cap reached";
|
|
@@ -2053,12 +884,12 @@ function isWholeFileArtifact(item) {
|
|
|
2053
884
|
return false;
|
|
2054
885
|
if (!Array.isArray(item.evidence))
|
|
2055
886
|
return false;
|
|
2056
|
-
return item.evidence.some((entry) =>
|
|
887
|
+
return item.evidence.some((entry) => isRecord(entry) && entry.path === item.sourcePath);
|
|
2057
888
|
}
|
|
2058
889
|
function hasEvidenceLine(evidence) {
|
|
2059
890
|
if (!Array.isArray(evidence))
|
|
2060
891
|
return false;
|
|
2061
|
-
return evidence.some((entry) =>
|
|
892
|
+
return evidence.some((entry) => isRecord(entry) && typeof entry.path === "string" && typeof entry.reason === "string" && evidenceLine({
|
|
2062
893
|
path: entry.path,
|
|
2063
894
|
reason: entry.reason,
|
|
2064
895
|
line: typeof entry.line === "number" ? entry.line : undefined
|
|
@@ -2087,7 +918,7 @@ var HARNESS_ARTIFACT_TYPES = new Set([
|
|
|
2087
918
|
"unknown"
|
|
2088
919
|
]);
|
|
2089
920
|
function normalizeGraphItemFieldAliases(collection, item) {
|
|
2090
|
-
if (!
|
|
921
|
+
if (!isRecord(item))
|
|
2091
922
|
return;
|
|
2092
923
|
if ((collection === "runtimeAgents" || collection === "artifacts") && item.status !== undefined) {
|
|
2093
924
|
const status = normalizeComponentStatus(item.status);
|
|
@@ -2115,6 +946,16 @@ function normalizeGraphItemFieldAliases(collection, item) {
|
|
|
2115
946
|
if (typeof item.type !== "string" || item.type.length === 0) {
|
|
2116
947
|
item.type = typeof item.kind === "string" && HARNESS_ARTIFACT_TYPES.has(item.kind) ? item.kind : "unknown";
|
|
2117
948
|
}
|
|
949
|
+
if (item.type === "tool" && typeof item.name === "string") {
|
|
950
|
+
const decorated = /^([A-Za-z_][\w.:-]*)\s+\((.+)\)$/.exec(item.name.trim());
|
|
951
|
+
if (decorated) {
|
|
952
|
+
if (typeof item.displayName !== "string" || item.displayName.length === 0)
|
|
953
|
+
item.displayName = item.name;
|
|
954
|
+
if (typeof item.variant !== "string" || item.variant.length === 0)
|
|
955
|
+
item.variant = decorated[2];
|
|
956
|
+
item.name = decorated[1];
|
|
957
|
+
}
|
|
958
|
+
}
|
|
2118
959
|
if (typeof item.sourcePath !== "string" && typeof item.path === "string" && item.path.length > 0) {
|
|
2119
960
|
item.sourcePath = item.path;
|
|
2120
961
|
}
|
|
@@ -2164,14 +1005,14 @@ function firstEvidencePath(item) {
|
|
|
2164
1005
|
if (!Array.isArray(item.evidence))
|
|
2165
1006
|
return;
|
|
2166
1007
|
for (const evidence of item.evidence) {
|
|
2167
|
-
if (
|
|
1008
|
+
if (isRecord(evidence) && typeof evidence.path === "string" && evidence.path.length > 0) {
|
|
2168
1009
|
return evidence.path;
|
|
2169
1010
|
}
|
|
2170
1011
|
}
|
|
2171
1012
|
return;
|
|
2172
1013
|
}
|
|
2173
1014
|
function stampRemoteSnapshotTruncation(report) {
|
|
2174
|
-
const scanRecord =
|
|
1015
|
+
const scanRecord = isRecord(report.graph.scan) ? report.graph.scan : {};
|
|
2175
1016
|
const truncationReasons = Array.isArray(scanRecord.truncationReasons) ? scanRecord.truncationReasons.map(String) : [];
|
|
2176
1017
|
const stamped = {
|
|
2177
1018
|
...report,
|
|
@@ -2196,12 +1037,12 @@ function requiresGraphItemConfidence(collection) {
|
|
|
2196
1037
|
return collection !== "relationships" && collection !== "unknowns";
|
|
2197
1038
|
}
|
|
2198
1039
|
function graphItemLabel(collection, item, index) {
|
|
2199
|
-
if (
|
|
1040
|
+
if (isRecord(item) && typeof item.id === "string" && item.id.length > 0)
|
|
2200
1041
|
return `${collection}.${item.id}`;
|
|
2201
1042
|
return `${collection} item ${index + 1}`;
|
|
2202
1043
|
}
|
|
2203
1044
|
function normalizeConfidenceField(value, fallback) {
|
|
2204
|
-
if (!
|
|
1045
|
+
if (!isRecord(value))
|
|
2205
1046
|
return;
|
|
2206
1047
|
const confidence = confidenceNumberFromUnknown(value.confidence);
|
|
2207
1048
|
if (confidence !== undefined)
|
|
@@ -2246,7 +1087,7 @@ function inferClaimKind(subjectId, graph) {
|
|
|
2246
1087
|
];
|
|
2247
1088
|
for (const [field, kind] of collectionKinds) {
|
|
2248
1089
|
const collection = graph[field];
|
|
2249
|
-
if (Array.isArray(collection) && collection.some((item) =>
|
|
1090
|
+
if (Array.isArray(collection) && collection.some((item) => isRecord(item) && item.id === subjectId)) {
|
|
2250
1091
|
return kind;
|
|
2251
1092
|
}
|
|
2252
1093
|
}
|
|
@@ -2323,7 +1164,7 @@ function normalizeClaimKind(value, subjectId, graph) {
|
|
|
2323
1164
|
return inferred;
|
|
2324
1165
|
}
|
|
2325
1166
|
function analystAdapter(report) {
|
|
2326
|
-
const analyst =
|
|
1167
|
+
const analyst = isRecord(report.analyst) ? report.analyst : {};
|
|
2327
1168
|
return typeof analyst.adapter === "string" ? analyst.adapter : "external-analyst";
|
|
2328
1169
|
}
|
|
2329
1170
|
function defaultHarnessReadPlan() {
|
|
@@ -2384,12 +1225,12 @@ function resolveHarnessReportPath(rootDir, value) {
|
|
|
2384
1225
|
}
|
|
2385
1226
|
function writeHarnessReportArtifacts(options) {
|
|
2386
1227
|
const written = [];
|
|
2387
|
-
writeJson(
|
|
1228
|
+
writeJson(join(options.rootDir, HARNESS_REPORT_PATH), options.report);
|
|
2388
1229
|
written.push(HARNESS_REPORT_PATH);
|
|
2389
|
-
writeJson(
|
|
1230
|
+
writeJson(join(options.rootDir, HARNESS_REPORT_VALIDATION_PATH), options.validation);
|
|
2390
1231
|
written.push(HARNESS_REPORT_VALIDATION_PATH);
|
|
2391
1232
|
if (options.markdown) {
|
|
2392
|
-
const markdownPath =
|
|
1233
|
+
const markdownPath = join(options.rootDir, HARNESS_REPORT_MARKDOWN_PATH);
|
|
2393
1234
|
mkdirSync(dirname(markdownPath), { recursive: true });
|
|
2394
1235
|
writeFileSync(markdownPath, renderHarnessReportMarkdown(options.report, options.validation));
|
|
2395
1236
|
written.push(HARNESS_REPORT_MARKDOWN_PATH);
|
|
@@ -2397,7 +1238,7 @@ function writeHarnessReportArtifacts(options) {
|
|
|
2397
1238
|
return written;
|
|
2398
1239
|
}
|
|
2399
1240
|
function renderHarnessReportMarkdown(report, validation) {
|
|
2400
|
-
const summary =
|
|
1241
|
+
const summary = isRecord(report.summary) ? report.summary : {};
|
|
2401
1242
|
const citations = Array.isArray(report.citations) ? report.citations : [];
|
|
2402
1243
|
const claims = Array.isArray(report.claims) ? report.claims : [];
|
|
2403
1244
|
const unknowns = Array.isArray(report.unknowns) ? report.unknowns : [];
|
|
@@ -2421,7 +1262,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
2421
1262
|
""
|
|
2422
1263
|
];
|
|
2423
1264
|
for (const [index, claim] of claims.slice(0, 80).entries()) {
|
|
2424
|
-
const claimRecord =
|
|
1265
|
+
const claimRecord = isRecord(claim) ? claim : {};
|
|
2425
1266
|
const citationIds = Array.isArray(claimRecord.citationIds) ? claimRecord.citationIds : [];
|
|
2426
1267
|
const citedPaths = citationIds.map((id) => citations.find((citation) => citation.id === id)).filter((citation) => Boolean(citation)).map((citation) => `${citation.path ?? citation.id}${citation.line ? `:${citation.line}` : ""}`).join(", ");
|
|
2427
1268
|
lines.push(`### ${typeof claimRecord.title === "string" ? claimRecord.title : claimLabel(claim, index)}`, "", `- Kind: ${typeof claimRecord.kind === "string" ? claimRecord.kind : "unknown"}`, `- Confidence: ${formatConfidence(claimRecord.confidence)}`, `- Evidence: ${citedPaths || "none"}`, `- Summary: ${typeof claimRecord.summary === "string" ? claimRecord.summary : "unknown"}`, "");
|
|
@@ -2432,7 +1273,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
2432
1273
|
if (unknowns.length > 0) {
|
|
2433
1274
|
lines.push("## Open Questions", "");
|
|
2434
1275
|
for (const unknown of unknowns) {
|
|
2435
|
-
lines.push(`- ${
|
|
1276
|
+
lines.push(`- ${isRecord(unknown) && typeof unknown.question === "string" ? unknown.question : "Unknown harness question"}`);
|
|
2436
1277
|
}
|
|
2437
1278
|
lines.push("");
|
|
2438
1279
|
}
|
|
@@ -2442,7 +1283,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
2442
1283
|
`;
|
|
2443
1284
|
}
|
|
2444
1285
|
function claimLabel(claim, index) {
|
|
2445
|
-
if (
|
|
1286
|
+
if (isRecord(claim)) {
|
|
2446
1287
|
if (typeof claim.id === "string" && claim.id.length > 0)
|
|
2447
1288
|
return claim.id;
|
|
2448
1289
|
if (typeof claim.title === "string" && claim.title.length > 0)
|
|
@@ -2451,7 +1292,7 @@ function claimLabel(claim, index) {
|
|
|
2451
1292
|
return `claim #${index + 1}`;
|
|
2452
1293
|
}
|
|
2453
1294
|
function citationLabel(citation, index) {
|
|
2454
|
-
if (
|
|
1295
|
+
if (isRecord(citation) && typeof citation.id === "string" && citation.id.length > 0)
|
|
2455
1296
|
return citation.id;
|
|
2456
1297
|
return `#${index + 1}`;
|
|
2457
1298
|
}
|
|
@@ -2521,10 +1362,10 @@ function citationExcerpt(rootDir, path, line) {
|
|
|
2521
1362
|
if (!isInside(rootDir, absPath) || !existsSync(absPath))
|
|
2522
1363
|
return {};
|
|
2523
1364
|
try {
|
|
2524
|
-
const stat =
|
|
1365
|
+
const stat = lstatSync(absPath);
|
|
2525
1366
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 131072)
|
|
2526
1367
|
return {};
|
|
2527
|
-
const lines =
|
|
1368
|
+
const lines = readFileSync(absPath, "utf8").split(/\r?\n/);
|
|
2528
1369
|
const selected = line ? lines[line - 1] : lines.find((candidate) => candidate.trim().length > 0);
|
|
2529
1370
|
if (!selected)
|
|
2530
1371
|
return {};
|
|
@@ -2534,13 +1375,13 @@ function citationExcerpt(rootDir, path, line) {
|
|
|
2534
1375
|
}
|
|
2535
1376
|
}
|
|
2536
1377
|
function validateSummaryMatchesGraph(summary, graph, check) {
|
|
2537
|
-
if (!
|
|
1378
|
+
if (!isRecord(graph))
|
|
2538
1379
|
return;
|
|
2539
1380
|
const fields = {
|
|
2540
1381
|
runtimeAgentCount: safeArrayLength(graph.runtimeAgents),
|
|
2541
1382
|
agentFamilyCount: safeArrayLength(graph.agentFamilies),
|
|
2542
1383
|
artifactCount: safeArrayLength(graph.artifacts),
|
|
2543
|
-
sharedArtifactCount: Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) =>
|
|
1384
|
+
sharedArtifactCount: Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord(artifact) && artifact.scope === "shared").length : 0,
|
|
2544
1385
|
channelAdapterCount: safeArrayLength(graph.channelAdapters),
|
|
2545
1386
|
identityNamespaceCount: safeArrayLength(graph.identityNamespaces),
|
|
2546
1387
|
environmentCount: safeArrayLength(graph.environments),
|
|
@@ -2583,7 +1424,7 @@ function validateClaimSubjectExists(graph, claim, errors, warnings) {
|
|
|
2583
1424
|
const collection = graph[collectionName];
|
|
2584
1425
|
if (!Array.isArray(collection))
|
|
2585
1426
|
return;
|
|
2586
|
-
if (collection.some((item) =>
|
|
1427
|
+
if (collection.some((item) => isRecord(item) && item.id === claim.subjectId))
|
|
2587
1428
|
return;
|
|
2588
1429
|
const message = `Claim ${claim.id} subject ${claim.subjectId} is not present in graph.${collectionName}.`;
|
|
2589
1430
|
if (claim.kind === "unknown")
|
|
@@ -2592,7 +1433,7 @@ function validateClaimSubjectExists(graph, claim, errors, warnings) {
|
|
|
2592
1433
|
errors.push(message);
|
|
2593
1434
|
}
|
|
2594
1435
|
function validateClaimQuality(claims, graph, warn) {
|
|
2595
|
-
const claimRecords = claims.filter(
|
|
1436
|
+
const claimRecords = claims.filter(isRecord);
|
|
2596
1437
|
if (claimRecords.length === 0)
|
|
2597
1438
|
return;
|
|
2598
1439
|
const graphClaimableItems = graph ? safeArrayLength(graph.runtimeAgents) + safeArrayLength(graph.agentFamilies) + safeArrayLength(graph.developerAgentAdapters) + safeArrayLength(graph.artifacts) + safeArrayLength(graph.channelAdapters) + safeArrayLength(graph.identityNamespaces) + safeArrayLength(graph.agentFrameworks) + safeArrayLength(graph.modelProviders) + safeArrayLength(graph.deployments) + safeArrayLength(graph.relationships) : 0;
|
|
@@ -2672,7 +1513,7 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
|
|
|
2672
1513
|
return;
|
|
2673
1514
|
}
|
|
2674
1515
|
try {
|
|
2675
|
-
const stat =
|
|
1516
|
+
const stat = lstatSync(absPath);
|
|
2676
1517
|
if (stat.isSymbolicLink())
|
|
2677
1518
|
errors.push(`Citation ${citation.id} points to a symlink.`);
|
|
2678
1519
|
if (stat.isDirectory()) {
|
|
@@ -2681,7 +1522,7 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
|
|
|
2681
1522
|
}
|
|
2682
1523
|
if (!stat.isFile())
|
|
2683
1524
|
errors.push(`Citation ${citation.id} does not point to a file.`);
|
|
2684
|
-
const lines =
|
|
1525
|
+
const lines = readFileSync(absPath, "utf8").split(/\r?\n/);
|
|
2685
1526
|
if (citation.line !== undefined) {
|
|
2686
1527
|
const lineCount = lines.length;
|
|
2687
1528
|
if (citation.line < 1 || citation.line > lineCount) {
|
|
@@ -2732,7 +1573,7 @@ function isSecretishPath(path) {
|
|
|
2732
1573
|
return name === ".env" || name.startsWith(".env.") || name.includes("secret") || name.includes("private-key") || name.endsWith(".pem");
|
|
2733
1574
|
}
|
|
2734
1575
|
function isInside(rootDir, absPath) {
|
|
2735
|
-
const rel =
|
|
1576
|
+
const rel = relative(resolve(rootDir), resolve(absPath));
|
|
2736
1577
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
2737
1578
|
}
|
|
2738
1579
|
function writeJson(path, value) {
|
|
@@ -2758,11 +1599,11 @@ function sortJson(value) {
|
|
|
2758
1599
|
}
|
|
2759
1600
|
return value;
|
|
2760
1601
|
}
|
|
2761
|
-
function
|
|
1602
|
+
function isRecord(value) {
|
|
2762
1603
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2763
1604
|
}
|
|
2764
1605
|
function normalizePath(path) {
|
|
2765
1606
|
return path.replace(/\\/g, "/");
|
|
2766
1607
|
}
|
|
2767
1608
|
|
|
2768
|
-
export {
|
|
1609
|
+
export { HARNESS_REPORT_SCHEMA_VERSION, HARNESS_REPORT_VALIDATION_SCHEMA_VERSION, HARNESS_REPORT_APPROVAL_SCHEMA_VERSION, HARNESS_REPORT_PATH, HARNESS_REPORT_MARKDOWN_PATH, HARNESS_REPORT_VALIDATION_PATH, HARNESS_REPORT_APPROVAL_PATH, HARNESS_ANALYZE_CONCERNS, MAX_HARNESS_REPORT_SUMMARY_LENGTH, createHarnessReport, validateHarnessReport, approveHarnessReport, loadHarnessReportForSync, readHarnessReport, normalizeHarnessReport, requiresArtifactBody, artifactHasBody, firstEvidencePath, stampRemoteSnapshotTruncation, resolveHarnessReportPath, writeHarnessReportArtifacts, renderHarnessReportMarkdown, hashHarnessReport, hashStableJson, graphForReport };
|