@moda-ai/cli 1.55.53 → 1.55.55
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-0wpfk7ed.js → agent-dc9zkdsm.js} +10 -10
- package/dist/{cli-3dj84cbk.js → cli-2jwxdhh5.js} +1 -1
- package/dist/{cli-1ehmy9rh.js → cli-31ybq5f2.js} +2 -2
- package/dist/{cli-rdh9jjr2.js → cli-94r5x9bg.js} +1 -1
- package/dist/{cli-c1k20j6e.js → cli-9a4b1wxz.js} +1 -1
- package/dist/{cli-4pzbgxcn.js → cli-9g8kp42c.js} +2 -2
- package/dist/{cli-mf37dxpc.js → cli-b918vs2m.js} +4 -4
- package/dist/{cli-thhggczw.js → cli-eqj302as.js} +5 -5
- package/dist/{cli-wry78bg8.js → cli-hawwf30b.js} +1 -1
- package/dist/{cli-r4k0yfkt.js → cli-jnwba1f3.js} +8 -8
- package/dist/{cli-dxxc7tzq.js → cli-m25c0rke.js} +3 -3
- package/dist/{cli-40fqk078.js → cli-pnarsb96.js} +1 -1
- package/dist/{cli-7nghchj8.js → cli-px4dmcy0.js} +107 -306
- package/dist/{cli-6hdqp65a.js → cli-rtkjjf0a.js} +1255 -66
- package/dist/{cli-gtke9wrw.js → cli-v2h21cgf.js} +1 -1
- package/dist/{cli-vswckv9g.js → cli-y5d33a1y.js} +1 -1
- package/dist/cli.js +20 -20
- package/dist/{doctor-wstqcfm4.js → doctor-r16n0me8.js} +8 -8
- package/dist/{harness-github-actions-1031t1kt.js → harness-github-actions-6bex338w.js} +8 -8
- package/dist/{harness-3bcakwrv.js → harness-j0n400bv.js} +8 -8
- package/dist/{harness-report-rjcjbh93.js → harness-report-gz5d3y48.js} +1 -1
- package/dist/{index-msk4gfn5.js → index-ecy3ntzc.js} +14 -14
- package/dist/{production-intelligence-zek0rkzj.js → production-intelligence-fqvhzq6s.js} +4 -4
- package/dist/{profile-commands-nbaec4y9.js → profile-commands-yvpe4d8y.js} +5 -5
- package/dist/{prompt-attribution-2687ns4k.js → prompt-attribution-x84cgdct.js} +8 -8
- package/dist/prompt-source-mcp.js +8 -8
- package/dist/{prompt-sources-qw2p4t5h.js → prompt-sources-1hycgx6r.js} +2 -2
- package/dist/{prompts-887e9w1v.js → prompts-r0mv69yx.js} +6 -6
- package/dist/{provision-tch8rc3y.js → provision-aqhfbnez.js} +6 -6
- package/dist/{registry-tv8fktxc.js → registry-wv1zzyce.js} +4 -4
- package/dist/{skills-mwftqzyk.js → skills-etzcqhp8.js} +6 -6
- package/dist/{sync-dxnw1mwa.js → sync-p4dz7qmn.js} +10 -10
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
|
@@ -9,19 +9,1196 @@ import {
|
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
10
|
import {
|
|
11
11
|
existsSync,
|
|
12
|
-
lstatSync,
|
|
12
|
+
lstatSync as lstatSync2,
|
|
13
13
|
mkdirSync,
|
|
14
|
-
readFileSync,
|
|
14
|
+
readFileSync as readFileSync3,
|
|
15
15
|
writeFileSync
|
|
16
16
|
} from "node:fs";
|
|
17
|
-
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
17
|
+
import { basename, dirname, join as join3, relative as relative3, resolve } from "node:path";
|
|
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
|
|
18
1195
|
var HARNESS_REPORT_SCHEMA_VERSION = "harness_report.v0.1";
|
|
19
1196
|
var HARNESS_REPORT_VALIDATION_SCHEMA_VERSION = "harness_report_validation.v0.1";
|
|
20
1197
|
var HARNESS_REPORT_APPROVAL_SCHEMA_VERSION = "harness_report_approval.v0.1";
|
|
21
|
-
var HARNESS_REPORT_PATH =
|
|
22
|
-
var HARNESS_REPORT_MARKDOWN_PATH =
|
|
23
|
-
var HARNESS_REPORT_VALIDATION_PATH =
|
|
24
|
-
var HARNESS_REPORT_APPROVAL_PATH =
|
|
1198
|
+
var HARNESS_REPORT_PATH = join3(".moda", "harness-report.json");
|
|
1199
|
+
var HARNESS_REPORT_MARKDOWN_PATH = join3(".moda", "HARNESS_REPORT.md");
|
|
1200
|
+
var HARNESS_REPORT_VALIDATION_PATH = join3(".moda", "harness-report-validation.json");
|
|
1201
|
+
var HARNESS_REPORT_APPROVAL_PATH = join3(".moda", "harness-report-approval.json");
|
|
25
1202
|
var HARNESS_ANALYZE_CONCERNS = ["topology", "tools", "prompts", "evals"];
|
|
26
1203
|
var HARNESS_GRAPH_ARRAY_FIELDS = [
|
|
27
1204
|
"agentFamilies",
|
|
@@ -300,7 +1477,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
300
1477
|
check("graph_present", Boolean(report?.graph?.schemaVersion), "Report must include the sanitized harness graph.");
|
|
301
1478
|
check("citations_present", Array.isArray(report?.citations) && report.citations.length > 0, "Report must include citations.");
|
|
302
1479
|
check("claims_present", Array.isArray(report?.claims) && report.claims.length > 0, "Report must include claims.");
|
|
303
|
-
const syncPlan =
|
|
1480
|
+
const syncPlan = isRecord2(report?.syncPlan) ? report.syncPlan : undefined;
|
|
304
1481
|
check("sync_plan_present", Boolean(syncPlan), "Report must include syncPlan.");
|
|
305
1482
|
if (syncPlan) {
|
|
306
1483
|
check("sync_plan_command", typeof syncPlan.command === "string" && syncPlan.command.length > 0, "Report syncPlan must include command.");
|
|
@@ -339,7 +1516,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
339
1516
|
const requireLineCitations = report?.analyst?.mode === "agent";
|
|
340
1517
|
for (const [index, citation] of citations.entries()) {
|
|
341
1518
|
const label = citationLabel(citation, index);
|
|
342
|
-
if (!
|
|
1519
|
+
if (!isRecord2(citation)) {
|
|
343
1520
|
errors.push(`Citation ${index + 1} must be an object.`);
|
|
344
1521
|
continue;
|
|
345
1522
|
}
|
|
@@ -366,7 +1543,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
366
1543
|
const claimIds = new Set;
|
|
367
1544
|
for (const [index, claim] of claims.entries()) {
|
|
368
1545
|
const label = claimLabel(claim, index);
|
|
369
|
-
if (!
|
|
1546
|
+
if (!isRecord2(claim)) {
|
|
370
1547
|
errors.push(`Claim ${index + 1} must be an object.`);
|
|
371
1548
|
continue;
|
|
372
1549
|
}
|
|
@@ -437,7 +1614,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
437
1614
|
if (unknowns.length > 0) {
|
|
438
1615
|
warn("unknowns_present", `${unknowns.length} open harness question(s) require human review.`);
|
|
439
1616
|
for (const [index, unknown] of unknowns.entries()) {
|
|
440
|
-
if (!
|
|
1617
|
+
if (!isRecord2(unknown)) {
|
|
441
1618
|
errors.push(`Unknown ${index + 1} must be an object.`);
|
|
442
1619
|
continue;
|
|
443
1620
|
}
|
|
@@ -465,7 +1642,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
465
1642
|
};
|
|
466
1643
|
}
|
|
467
1644
|
function refutedToolCandidateCount(graph) {
|
|
468
|
-
const unknowns = Array.isArray(graph.unknowns) ? graph.unknowns.filter(
|
|
1645
|
+
const unknowns = Array.isArray(graph.unknowns) ? graph.unknowns.filter(isRecord2) : [];
|
|
469
1646
|
let count = 0;
|
|
470
1647
|
for (const unknown of unknowns) {
|
|
471
1648
|
const label = `${String(unknown.id ?? "")} ${String(unknown.question ?? "")}`;
|
|
@@ -484,8 +1661,8 @@ function validateDeterministicFloor(report, floor, check, warn, concern) {
|
|
|
484
1661
|
return;
|
|
485
1662
|
const applyToolFloor = concern === undefined || concern === "tools";
|
|
486
1663
|
const applyTopologyFloors = concern === undefined || concern === "topology";
|
|
487
|
-
const graph =
|
|
488
|
-
const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) =>
|
|
1664
|
+
const graph = isRecord2(report?.graph) ? report.graph : {};
|
|
1665
|
+
const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord2(artifact) && artifact.type === "tool").length : 0;
|
|
489
1666
|
const providerCount = safeArrayLength(graph.modelProviders);
|
|
490
1667
|
const runtimeAgentCount = safeArrayLength(graph.runtimeAgents);
|
|
491
1668
|
if (applyToolFloor && typeof floor.toolCandidates === "number" && floor.toolCandidates >= 5) {
|
|
@@ -534,9 +1711,9 @@ async function approveHarnessReport(options) {
|
|
|
534
1711
|
approved: true,
|
|
535
1712
|
approvedAt: new Date().toISOString(),
|
|
536
1713
|
approvedBy,
|
|
537
|
-
reportPath: normalizePath(
|
|
1714
|
+
reportPath: normalizePath(relative3(options.rootDir, options.reportPath)) || HARNESS_REPORT_PATH
|
|
538
1715
|
};
|
|
539
|
-
writeJson(
|
|
1716
|
+
writeJson(join3(options.rootDir, HARNESS_REPORT_APPROVAL_PATH), approval);
|
|
540
1717
|
return { report, validation, approval };
|
|
541
1718
|
}
|
|
542
1719
|
function loadHarnessReportForSync(rootDir, reportPath, options) {
|
|
@@ -546,37 +1723,37 @@ function loadHarnessReportForSync(rootDir, reportPath, options) {
|
|
|
546
1723
|
throw new CliInputError("Harness report validation failed; cannot sync.", validation.errors.slice(0, 5).join(`
|
|
547
1724
|
`));
|
|
548
1725
|
}
|
|
549
|
-
const approvalPath =
|
|
550
|
-
const approval = existsSync(approvalPath) ? JSON.parse(
|
|
1726
|
+
const approvalPath = join3(rootDir, HARNESS_REPORT_APPROVAL_PATH);
|
|
1727
|
+
const approval = existsSync(approvalPath) ? JSON.parse(readFileSync3(approvalPath, "utf8")) : null;
|
|
551
1728
|
const approved = approval?.schemaVersion === HARNESS_REPORT_APPROVAL_SCHEMA_VERSION && approval.reportId === report.reportId && approval.reportHash === hashHarnessReport(report) && approval.approved === true;
|
|
552
1729
|
return { report, validation, approval, approved };
|
|
553
1730
|
}
|
|
554
1731
|
function readHarnessReport(reportPath) {
|
|
555
|
-
return normalizeHarnessReport(JSON.parse(
|
|
1732
|
+
return normalizeHarnessReport(JSON.parse(readFileSync3(reportPath, "utf8")));
|
|
556
1733
|
}
|
|
557
1734
|
function normalizeHarnessReport(value) {
|
|
558
1735
|
const report = cloneJson(value);
|
|
559
|
-
if (!
|
|
1736
|
+
if (!isRecord2(report.graph))
|
|
560
1737
|
return report;
|
|
561
1738
|
const graph = report.graph;
|
|
562
1739
|
if (typeof graph.schemaVersion !== "string" && typeof graph.graphSchemaVersion === "string") {
|
|
563
1740
|
graph.schemaVersion = graph.graphSchemaVersion;
|
|
564
1741
|
}
|
|
565
|
-
if (typeof graph.schemaVersion !== "string" &&
|
|
1742
|
+
if (typeof graph.schemaVersion !== "string" && isRecord2(report.source) && typeof report.source.graphSchemaVersion === "string") {
|
|
566
1743
|
graph.schemaVersion = report.source.graphSchemaVersion;
|
|
567
1744
|
}
|
|
568
1745
|
if (typeof graph.harnessId !== "string" && typeof report.harnessId === "string") {
|
|
569
1746
|
graph.harnessId = report.harnessId;
|
|
570
1747
|
}
|
|
571
|
-
if (!
|
|
1748
|
+
if (!isRecord2(graph.repo) && isRecord2(report.repo)) {
|
|
572
1749
|
graph.repo = report.repo;
|
|
573
1750
|
}
|
|
574
|
-
if (!
|
|
1751
|
+
if (!isRecord2(graph.tenant) && isRecord2(report.tenant)) {
|
|
575
1752
|
graph.tenant = report.tenant;
|
|
576
1753
|
}
|
|
577
|
-
if (!
|
|
578
|
-
const source =
|
|
579
|
-
const readPlan =
|
|
1754
|
+
if (!isRecord2(graph.scan)) {
|
|
1755
|
+
const source = isRecord2(report.source) ? report.source : {};
|
|
1756
|
+
const readPlan = isRecord2(source.readPlan) ? source.readPlan : defaultHarnessReadPlan();
|
|
580
1757
|
graph.scan = {
|
|
581
1758
|
createdAt: typeof report.createdAt === "string" ? report.createdAt : new Date().toISOString(),
|
|
582
1759
|
scannerVersion: analystAdapter(report),
|
|
@@ -602,7 +1779,7 @@ function normalizeHarnessReport(value) {
|
|
|
602
1779
|
return report;
|
|
603
1780
|
}
|
|
604
1781
|
function normalizeHarnessSyncPlan(report) {
|
|
605
|
-
const syncPlan =
|
|
1782
|
+
const syncPlan = isRecord2(report.syncPlan) ? report.syncPlan : {};
|
|
606
1783
|
if (typeof syncPlan.command !== "string" || syncPlan.command.length === 0) {
|
|
607
1784
|
syncPlan.command = `moda harness sync --from-report ${HARNESS_REPORT_PATH}`;
|
|
608
1785
|
}
|
|
@@ -627,7 +1804,7 @@ function normalizeRelationshipEvidence(graph) {
|
|
|
627
1804
|
return;
|
|
628
1805
|
const evidenceById = graphEvidenceById(graph);
|
|
629
1806
|
for (const relationship of relationships) {
|
|
630
|
-
if (!
|
|
1807
|
+
if (!isRecord2(relationship))
|
|
631
1808
|
continue;
|
|
632
1809
|
if (Array.isArray(relationship.evidence) && relationship.evidence.length > 0)
|
|
633
1810
|
continue;
|
|
@@ -650,9 +1827,9 @@ function graphEvidenceById(graph) {
|
|
|
650
1827
|
if (!Array.isArray(collection))
|
|
651
1828
|
continue;
|
|
652
1829
|
for (const item of collection) {
|
|
653
|
-
if (!
|
|
1830
|
+
if (!isRecord2(item) || typeof item.id !== "string" || !Array.isArray(item.evidence))
|
|
654
1831
|
continue;
|
|
655
|
-
const evidence = item.evidence.filter((entry) =>
|
|
1832
|
+
const evidence = item.evidence.filter((entry) => isRecord2(entry) && typeof entry.path === "string" && typeof entry.reason === "string").map((entry) => normalizeEvidenceEntry(entry));
|
|
656
1833
|
if (evidence.length > 0)
|
|
657
1834
|
byId.set(item.id, evidence);
|
|
658
1835
|
}
|
|
@@ -676,7 +1853,7 @@ function normalizeEvidenceEntry(item) {
|
|
|
676
1853
|
return line ? { path: item.path, reason: item.reason, line } : { path: item.path, reason: item.reason };
|
|
677
1854
|
}
|
|
678
1855
|
function normalizeHarnessClaim(claim, graph) {
|
|
679
|
-
if (!
|
|
1856
|
+
if (!isRecord2(claim))
|
|
680
1857
|
return claim;
|
|
681
1858
|
if (typeof claim.subjectId !== "string" && Array.isArray(claim.subjectIds) && typeof claim.subjectIds[0] === "string") {
|
|
682
1859
|
claim.subjectId = claim.subjectIds[0];
|
|
@@ -724,17 +1901,18 @@ function validateGraphItemShapes(graph, errors) {
|
|
|
724
1901
|
if (typeof graph.harnessId !== "string" || graph.harnessId.length === 0) {
|
|
725
1902
|
errors.push("Graph must include harnessId.");
|
|
726
1903
|
}
|
|
727
|
-
if (!
|
|
1904
|
+
if (!isRecord2(graph.repo)) {
|
|
728
1905
|
errors.push("Graph must include repo metadata.");
|
|
729
1906
|
}
|
|
730
|
-
if (!
|
|
1907
|
+
if (!isRecord2(graph.scan)) {
|
|
731
1908
|
errors.push("Graph must include scan metadata.");
|
|
732
1909
|
}
|
|
733
1910
|
validateGraphCollection(graph, errors, "runtimeAgents", {
|
|
734
1911
|
strings: ["id", "name", "kind", "root"],
|
|
735
1912
|
arrays: ["entrypointIds", "channelAdapterIds", "artifactIds", "identityNamespaceIds", "deploymentIds", "frameworkIds", "modelProviderIds", "evidence"],
|
|
736
1913
|
expected: { kind: "runtime_agent" },
|
|
737
|
-
confidence: true
|
|
1914
|
+
confidence: true,
|
|
1915
|
+
status: true
|
|
738
1916
|
});
|
|
739
1917
|
validateGraphCollection(graph, errors, "agentFamilies", {
|
|
740
1918
|
strings: ["id", "name"],
|
|
@@ -744,7 +1922,8 @@ function validateGraphItemShapes(graph, errors) {
|
|
|
744
1922
|
validateGraphCollection(graph, errors, "artifacts", {
|
|
745
1923
|
strings: ["id", "type", "name", "scope"],
|
|
746
1924
|
arrays: ["usedByAgentIds", "evidence"],
|
|
747
|
-
confidence: true
|
|
1925
|
+
confidence: true,
|
|
1926
|
+
status: true
|
|
748
1927
|
});
|
|
749
1928
|
validateGraphCollection(graph, errors, "developerAgentAdapters", {
|
|
750
1929
|
strings: ["id", "name", "kind", "sourcePath"],
|
|
@@ -797,7 +1976,7 @@ function validateGraphCollection(graph, errors, field, shape) {
|
|
|
797
1976
|
return;
|
|
798
1977
|
for (const [index, item] of collection.entries()) {
|
|
799
1978
|
const label = graphItemLabel(field, item, index);
|
|
800
|
-
if (!
|
|
1979
|
+
if (!isRecord2(item)) {
|
|
801
1980
|
errors.push(`Graph ${field} item ${index + 1} must be an object.`);
|
|
802
1981
|
continue;
|
|
803
1982
|
}
|
|
@@ -821,9 +2000,12 @@ function validateGraphCollection(graph, errors, field, shape) {
|
|
|
821
2000
|
} else if (shape.confidence && typeof item.confidence === "number" && (item.confidence < 0 || item.confidence > 1)) {
|
|
822
2001
|
errors.push(`Graph ${label} confidence must be between 0 and 1.`);
|
|
823
2002
|
}
|
|
2003
|
+
if (shape.status && item.status !== undefined && (typeof item.status !== "string" || !HARNESS_COMPONENT_STATUSES.has(item.status))) {
|
|
2004
|
+
errors.push(`Graph ${label} status must be one of ${[...HARNESS_COMPONENT_STATUSES].join(", ")}.`);
|
|
2005
|
+
}
|
|
824
2006
|
if (Array.isArray(item.evidence)) {
|
|
825
2007
|
for (const [evidenceIndex, evidence] of item.evidence.entries()) {
|
|
826
|
-
if (!
|
|
2008
|
+
if (!isRecord2(evidence) || typeof evidence.path !== "string" || typeof evidence.reason !== "string") {
|
|
827
2009
|
errors.push(`Graph ${label} evidence ${evidenceIndex + 1} must include path and reason.`);
|
|
828
2010
|
continue;
|
|
829
2011
|
}
|
|
@@ -856,7 +2038,7 @@ function artifactHasRetrievableLocation(item) {
|
|
|
856
2038
|
}
|
|
857
2039
|
function validateCaptureQuality(graph, warn) {
|
|
858
2040
|
const scan = graph.scan;
|
|
859
|
-
if (!
|
|
2041
|
+
if (!isRecord2(scan))
|
|
860
2042
|
return;
|
|
861
2043
|
if (scan.truncated === true) {
|
|
862
2044
|
const reasons = Array.isArray(scan.truncationReasons) && scan.truncationReasons.length > 0 ? scan.truncationReasons.map((reason) => String(reason)).join(", ") : "scan budget or upload cap reached";
|
|
@@ -871,12 +2053,12 @@ function isWholeFileArtifact(item) {
|
|
|
871
2053
|
return false;
|
|
872
2054
|
if (!Array.isArray(item.evidence))
|
|
873
2055
|
return false;
|
|
874
|
-
return item.evidence.some((entry) =>
|
|
2056
|
+
return item.evidence.some((entry) => isRecord2(entry) && entry.path === item.sourcePath);
|
|
875
2057
|
}
|
|
876
2058
|
function hasEvidenceLine(evidence) {
|
|
877
2059
|
if (!Array.isArray(evidence))
|
|
878
2060
|
return false;
|
|
879
|
-
return evidence.some((entry) =>
|
|
2061
|
+
return evidence.some((entry) => isRecord2(entry) && typeof entry.path === "string" && typeof entry.reason === "string" && evidenceLine({
|
|
880
2062
|
path: entry.path,
|
|
881
2063
|
reason: entry.reason,
|
|
882
2064
|
line: typeof entry.line === "number" ? entry.line : undefined
|
|
@@ -905,8 +2087,15 @@ var HARNESS_ARTIFACT_TYPES = new Set([
|
|
|
905
2087
|
"unknown"
|
|
906
2088
|
]);
|
|
907
2089
|
function normalizeGraphItemFieldAliases(collection, item) {
|
|
908
|
-
if (!
|
|
2090
|
+
if (!isRecord2(item))
|
|
909
2091
|
return;
|
|
2092
|
+
if ((collection === "runtimeAgents" || collection === "artifacts") && item.status !== undefined) {
|
|
2093
|
+
const status = normalizeComponentStatus(item.status);
|
|
2094
|
+
if (status)
|
|
2095
|
+
item.status = status;
|
|
2096
|
+
else
|
|
2097
|
+
delete item.status;
|
|
2098
|
+
}
|
|
910
2099
|
if (collection === "developerAgentAdapters") {
|
|
911
2100
|
item.kind = "developer_agent_adapter";
|
|
912
2101
|
if (typeof item.sourcePath !== "string")
|
|
@@ -975,14 +2164,14 @@ function firstEvidencePath(item) {
|
|
|
975
2164
|
if (!Array.isArray(item.evidence))
|
|
976
2165
|
return;
|
|
977
2166
|
for (const evidence of item.evidence) {
|
|
978
|
-
if (
|
|
2167
|
+
if (isRecord2(evidence) && typeof evidence.path === "string" && evidence.path.length > 0) {
|
|
979
2168
|
return evidence.path;
|
|
980
2169
|
}
|
|
981
2170
|
}
|
|
982
2171
|
return;
|
|
983
2172
|
}
|
|
984
2173
|
function stampRemoteSnapshotTruncation(report) {
|
|
985
|
-
const scanRecord =
|
|
2174
|
+
const scanRecord = isRecord2(report.graph.scan) ? report.graph.scan : {};
|
|
986
2175
|
const truncationReasons = Array.isArray(scanRecord.truncationReasons) ? scanRecord.truncationReasons.map(String) : [];
|
|
987
2176
|
const stamped = {
|
|
988
2177
|
...report,
|
|
@@ -1007,12 +2196,12 @@ function requiresGraphItemConfidence(collection) {
|
|
|
1007
2196
|
return collection !== "relationships" && collection !== "unknowns";
|
|
1008
2197
|
}
|
|
1009
2198
|
function graphItemLabel(collection, item, index) {
|
|
1010
|
-
if (
|
|
2199
|
+
if (isRecord2(item) && typeof item.id === "string" && item.id.length > 0)
|
|
1011
2200
|
return `${collection}.${item.id}`;
|
|
1012
2201
|
return `${collection} item ${index + 1}`;
|
|
1013
2202
|
}
|
|
1014
2203
|
function normalizeConfidenceField(value, fallback) {
|
|
1015
|
-
if (!
|
|
2204
|
+
if (!isRecord2(value))
|
|
1016
2205
|
return;
|
|
1017
2206
|
const confidence = confidenceNumberFromUnknown(value.confidence);
|
|
1018
2207
|
if (confidence !== undefined)
|
|
@@ -1057,7 +2246,7 @@ function inferClaimKind(subjectId, graph) {
|
|
|
1057
2246
|
];
|
|
1058
2247
|
for (const [field, kind] of collectionKinds) {
|
|
1059
2248
|
const collection = graph[field];
|
|
1060
|
-
if (Array.isArray(collection) && collection.some((item) =>
|
|
2249
|
+
if (Array.isArray(collection) && collection.some((item) => isRecord2(item) && item.id === subjectId)) {
|
|
1061
2250
|
return kind;
|
|
1062
2251
|
}
|
|
1063
2252
|
}
|
|
@@ -1134,7 +2323,7 @@ function normalizeClaimKind(value, subjectId, graph) {
|
|
|
1134
2323
|
return inferred;
|
|
1135
2324
|
}
|
|
1136
2325
|
function analystAdapter(report) {
|
|
1137
|
-
const analyst =
|
|
2326
|
+
const analyst = isRecord2(report.analyst) ? report.analyst : {};
|
|
1138
2327
|
return typeof analyst.adapter === "string" ? analyst.adapter : "external-analyst";
|
|
1139
2328
|
}
|
|
1140
2329
|
function defaultHarnessReadPlan() {
|
|
@@ -1195,12 +2384,12 @@ function resolveHarnessReportPath(rootDir, value) {
|
|
|
1195
2384
|
}
|
|
1196
2385
|
function writeHarnessReportArtifacts(options) {
|
|
1197
2386
|
const written = [];
|
|
1198
|
-
writeJson(
|
|
2387
|
+
writeJson(join3(options.rootDir, HARNESS_REPORT_PATH), options.report);
|
|
1199
2388
|
written.push(HARNESS_REPORT_PATH);
|
|
1200
|
-
writeJson(
|
|
2389
|
+
writeJson(join3(options.rootDir, HARNESS_REPORT_VALIDATION_PATH), options.validation);
|
|
1201
2390
|
written.push(HARNESS_REPORT_VALIDATION_PATH);
|
|
1202
2391
|
if (options.markdown) {
|
|
1203
|
-
const markdownPath =
|
|
2392
|
+
const markdownPath = join3(options.rootDir, HARNESS_REPORT_MARKDOWN_PATH);
|
|
1204
2393
|
mkdirSync(dirname(markdownPath), { recursive: true });
|
|
1205
2394
|
writeFileSync(markdownPath, renderHarnessReportMarkdown(options.report, options.validation));
|
|
1206
2395
|
written.push(HARNESS_REPORT_MARKDOWN_PATH);
|
|
@@ -1208,7 +2397,7 @@ function writeHarnessReportArtifacts(options) {
|
|
|
1208
2397
|
return written;
|
|
1209
2398
|
}
|
|
1210
2399
|
function renderHarnessReportMarkdown(report, validation) {
|
|
1211
|
-
const summary =
|
|
2400
|
+
const summary = isRecord2(report.summary) ? report.summary : {};
|
|
1212
2401
|
const citations = Array.isArray(report.citations) ? report.citations : [];
|
|
1213
2402
|
const claims = Array.isArray(report.claims) ? report.claims : [];
|
|
1214
2403
|
const unknowns = Array.isArray(report.unknowns) ? report.unknowns : [];
|
|
@@ -1232,7 +2421,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
1232
2421
|
""
|
|
1233
2422
|
];
|
|
1234
2423
|
for (const [index, claim] of claims.slice(0, 80).entries()) {
|
|
1235
|
-
const claimRecord =
|
|
2424
|
+
const claimRecord = isRecord2(claim) ? claim : {};
|
|
1236
2425
|
const citationIds = Array.isArray(claimRecord.citationIds) ? claimRecord.citationIds : [];
|
|
1237
2426
|
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(", ");
|
|
1238
2427
|
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"}`, "");
|
|
@@ -1243,7 +2432,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
1243
2432
|
if (unknowns.length > 0) {
|
|
1244
2433
|
lines.push("## Open Questions", "");
|
|
1245
2434
|
for (const unknown of unknowns) {
|
|
1246
|
-
lines.push(`- ${
|
|
2435
|
+
lines.push(`- ${isRecord2(unknown) && typeof unknown.question === "string" ? unknown.question : "Unknown harness question"}`);
|
|
1247
2436
|
}
|
|
1248
2437
|
lines.push("");
|
|
1249
2438
|
}
|
|
@@ -1253,7 +2442,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
1253
2442
|
`;
|
|
1254
2443
|
}
|
|
1255
2444
|
function claimLabel(claim, index) {
|
|
1256
|
-
if (
|
|
2445
|
+
if (isRecord2(claim)) {
|
|
1257
2446
|
if (typeof claim.id === "string" && claim.id.length > 0)
|
|
1258
2447
|
return claim.id;
|
|
1259
2448
|
if (typeof claim.title === "string" && claim.title.length > 0)
|
|
@@ -1262,7 +2451,7 @@ function claimLabel(claim, index) {
|
|
|
1262
2451
|
return `claim #${index + 1}`;
|
|
1263
2452
|
}
|
|
1264
2453
|
function citationLabel(citation, index) {
|
|
1265
|
-
if (
|
|
2454
|
+
if (isRecord2(citation) && typeof citation.id === "string" && citation.id.length > 0)
|
|
1266
2455
|
return citation.id;
|
|
1267
2456
|
return `#${index + 1}`;
|
|
1268
2457
|
}
|
|
@@ -1332,10 +2521,10 @@ function citationExcerpt(rootDir, path, line) {
|
|
|
1332
2521
|
if (!isInside(rootDir, absPath) || !existsSync(absPath))
|
|
1333
2522
|
return {};
|
|
1334
2523
|
try {
|
|
1335
|
-
const stat =
|
|
2524
|
+
const stat = lstatSync2(absPath);
|
|
1336
2525
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 131072)
|
|
1337
2526
|
return {};
|
|
1338
|
-
const lines =
|
|
2527
|
+
const lines = readFileSync3(absPath, "utf8").split(/\r?\n/);
|
|
1339
2528
|
const selected = line ? lines[line - 1] : lines.find((candidate) => candidate.trim().length > 0);
|
|
1340
2529
|
if (!selected)
|
|
1341
2530
|
return {};
|
|
@@ -1345,13 +2534,13 @@ function citationExcerpt(rootDir, path, line) {
|
|
|
1345
2534
|
}
|
|
1346
2535
|
}
|
|
1347
2536
|
function validateSummaryMatchesGraph(summary, graph, check) {
|
|
1348
|
-
if (!
|
|
2537
|
+
if (!isRecord2(graph))
|
|
1349
2538
|
return;
|
|
1350
2539
|
const fields = {
|
|
1351
2540
|
runtimeAgentCount: safeArrayLength(graph.runtimeAgents),
|
|
1352
2541
|
agentFamilyCount: safeArrayLength(graph.agentFamilies),
|
|
1353
2542
|
artifactCount: safeArrayLength(graph.artifacts),
|
|
1354
|
-
sharedArtifactCount: Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) =>
|
|
2543
|
+
sharedArtifactCount: Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord2(artifact) && artifact.scope === "shared").length : 0,
|
|
1355
2544
|
channelAdapterCount: safeArrayLength(graph.channelAdapters),
|
|
1356
2545
|
identityNamespaceCount: safeArrayLength(graph.identityNamespaces),
|
|
1357
2546
|
environmentCount: safeArrayLength(graph.environments),
|
|
@@ -1394,7 +2583,7 @@ function validateClaimSubjectExists(graph, claim, errors, warnings) {
|
|
|
1394
2583
|
const collection = graph[collectionName];
|
|
1395
2584
|
if (!Array.isArray(collection))
|
|
1396
2585
|
return;
|
|
1397
|
-
if (collection.some((item) =>
|
|
2586
|
+
if (collection.some((item) => isRecord2(item) && item.id === claim.subjectId))
|
|
1398
2587
|
return;
|
|
1399
2588
|
const message = `Claim ${claim.id} subject ${claim.subjectId} is not present in graph.${collectionName}.`;
|
|
1400
2589
|
if (claim.kind === "unknown")
|
|
@@ -1403,7 +2592,7 @@ function validateClaimSubjectExists(graph, claim, errors, warnings) {
|
|
|
1403
2592
|
errors.push(message);
|
|
1404
2593
|
}
|
|
1405
2594
|
function validateClaimQuality(claims, graph, warn) {
|
|
1406
|
-
const claimRecords = claims.filter(
|
|
2595
|
+
const claimRecords = claims.filter(isRecord2);
|
|
1407
2596
|
if (claimRecords.length === 0)
|
|
1408
2597
|
return;
|
|
1409
2598
|
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;
|
|
@@ -1483,7 +2672,7 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
|
|
|
1483
2672
|
return;
|
|
1484
2673
|
}
|
|
1485
2674
|
try {
|
|
1486
|
-
const stat =
|
|
2675
|
+
const stat = lstatSync2(absPath);
|
|
1487
2676
|
if (stat.isSymbolicLink())
|
|
1488
2677
|
errors.push(`Citation ${citation.id} points to a symlink.`);
|
|
1489
2678
|
if (stat.isDirectory()) {
|
|
@@ -1492,7 +2681,7 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
|
|
|
1492
2681
|
}
|
|
1493
2682
|
if (!stat.isFile())
|
|
1494
2683
|
errors.push(`Citation ${citation.id} does not point to a file.`);
|
|
1495
|
-
const lines =
|
|
2684
|
+
const lines = readFileSync3(absPath, "utf8").split(/\r?\n/);
|
|
1496
2685
|
if (citation.line !== undefined) {
|
|
1497
2686
|
const lineCount = lines.length;
|
|
1498
2687
|
if (citation.line < 1 || citation.line > lineCount) {
|
|
@@ -1543,7 +2732,7 @@ function isSecretishPath(path) {
|
|
|
1543
2732
|
return name === ".env" || name.startsWith(".env.") || name.includes("secret") || name.includes("private-key") || name.endsWith(".pem");
|
|
1544
2733
|
}
|
|
1545
2734
|
function isInside(rootDir, absPath) {
|
|
1546
|
-
const rel =
|
|
2735
|
+
const rel = relative3(resolve(rootDir), resolve(absPath));
|
|
1547
2736
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
1548
2737
|
}
|
|
1549
2738
|
function writeJson(path, value) {
|
|
@@ -1569,11 +2758,11 @@ function sortJson(value) {
|
|
|
1569
2758
|
}
|
|
1570
2759
|
return value;
|
|
1571
2760
|
}
|
|
1572
|
-
function
|
|
2761
|
+
function isRecord2(value) {
|
|
1573
2762
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1574
2763
|
}
|
|
1575
2764
|
function normalizePath(path) {
|
|
1576
2765
|
return path.replace(/\\/g, "/");
|
|
1577
2766
|
}
|
|
1578
2767
|
|
|
1579
|
-
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 };
|
|
2768
|
+
export { mergeBoundRuntimeClasses, tagComponentStatus, 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 };
|