@moda-ai/cli 1.55.67 → 1.55.68
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-gg0jgjcw.js → agent-vep29dgt.js} +4 -5
- package/dist/{cli-nhnr529r.js → cli-0hhje3n9.js} +5 -5
- package/dist/{cli-8ac7g36q.js → cli-3hkwxecd.js} +25169 -19749
- package/dist/{cli-z0v1j51p.js → cli-9ww72mrd.js} +1251 -69
- package/dist/{cli-ff4675az.js → cli-k5zerb77.js} +2 -2
- package/dist/{cli-gmvbapx7.js → cli-y5d33a1y.js} +1517 -2
- package/dist/cli.js +7 -7
- package/dist/{doctor-seknfzwz.js → doctor-m9tg540h.js} +2 -2
- package/dist/{harness-cp1j845n.js → harness-djtzm1c5.js} +5 -6
- package/dist/{harness-github-actions-3tkqqmn3.js → harness-github-actions-g0tmyctn.js} +3 -6
- package/dist/{harness-report-d28wg4w5.js → harness-report-3n7bg314.js} +1 -2
- package/dist/{index-ttxbqyr2.js → index-beap2kft.js} +5 -8
- package/dist/{index-a3k99ct8.js → index-c9c01ayp.js} +21 -3
- package/dist/{prompt-attribution-68r2331s.js → prompt-attribution-9b4j1d3f.js} +5 -8
- package/dist/prompt-source-mcp.js +3 -6
- package/dist/{prompt-sources-r2xbxr0r.js → prompt-sources-1hycgx6r.js} +1 -2
- package/dist/{prompts-12f51m9n.js → prompts-82tfn871.js} +1 -1
- package/dist/{sync-vq1cb51s.js → sync-0cpxcrtd.js} +4 -7
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/dist/cli-fsgh8mq5.js +0 -1522
- package/dist/cli-p2s3nwc6.js +0 -3470
- package/dist/cli-ysh0mdwp.js +0 -1183
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CliInputError
|
|
3
3
|
} from "./cli-k4tqwah4.js";
|
|
4
|
-
import {
|
|
5
|
-
HARNESS_COMPONENT_STATUSES,
|
|
6
|
-
normalizeComponentStatus
|
|
7
|
-
} from "./cli-ysh0mdwp.js";
|
|
8
4
|
import {
|
|
9
5
|
__require
|
|
10
6
|
} from "./cli-0v6na3yp.js";
|
|
@@ -13,19 +9,1205 @@ import {
|
|
|
13
9
|
import { createHash } from "node:crypto";
|
|
14
10
|
import {
|
|
15
11
|
existsSync,
|
|
16
|
-
lstatSync,
|
|
12
|
+
lstatSync as lstatSync2,
|
|
17
13
|
mkdirSync,
|
|
18
|
-
readFileSync,
|
|
14
|
+
readFileSync as readFileSync3,
|
|
19
15
|
writeFileSync
|
|
20
16
|
} from "node:fs";
|
|
21
|
-
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 buildImportGraphReachability(rootDir) {
|
|
1108
|
+
const { inProd, reachable } = analyzeRepository(rootDir);
|
|
1109
|
+
return { inProd, reachable };
|
|
1110
|
+
}
|
|
1111
|
+
function norm2(value) {
|
|
1112
|
+
return typeof value === "string" ? value.toLowerCase().replace(/[^a-z0-9]/g, "") : "";
|
|
1113
|
+
}
|
|
1114
|
+
function normalizeItemPath(rootDir, path) {
|
|
1115
|
+
let rel = path.trim().replace(/\\/g, "/").replace(/:\d+(?::\d+)?$/, "");
|
|
1116
|
+
if (isAbsolute2(rel))
|
|
1117
|
+
rel = relative2(rootDir, rel).split("\\").join("/");
|
|
1118
|
+
return joinRel("", rel);
|
|
1119
|
+
}
|
|
1120
|
+
function definingPath(item) {
|
|
1121
|
+
if (typeof item.sourcePath === "string" && item.sourcePath.trim())
|
|
1122
|
+
return item.sourcePath;
|
|
1123
|
+
for (const entry of Array.isArray(item.evidence) ? item.evidence : []) {
|
|
1124
|
+
const path = isRecord(entry) ? entry.path : undefined;
|
|
1125
|
+
if (typeof path === "string" && path.trim())
|
|
1126
|
+
return path;
|
|
1127
|
+
}
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
function boundClassStatus(analysis, worker, item) {
|
|
1131
|
+
const wanted = new Set([norm2(item.name), norm2(typeof item.id === "string" ? item.id.split(".").pop() : "")]);
|
|
1132
|
+
const bound = worker.boundClasses.find((entry) => wanted.has(norm2(entry.className)));
|
|
1133
|
+
if (!bound)
|
|
1134
|
+
return null;
|
|
1135
|
+
const servers = bound.scriptName ? analysis.workers.filter((other) => other.names.includes(bound.scriptName)) : [worker];
|
|
1136
|
+
const declared = analysis.classFiles.get(bound.className) ?? [];
|
|
1137
|
+
if (servers.some((server) => server.deployed && declared.some((file) => server.reach.has(file))))
|
|
1138
|
+
return "in_prod";
|
|
1139
|
+
if (declared.some((file) => analysis.inProd.has(file) || analysis.reachable.has(file)))
|
|
1140
|
+
return "reachable";
|
|
1141
|
+
return;
|
|
1142
|
+
}
|
|
1143
|
+
function itemStatus(analysis, rootDir, item) {
|
|
1144
|
+
const raw = definingPath(item);
|
|
1145
|
+
if (!raw)
|
|
1146
|
+
return;
|
|
1147
|
+
const path = normalizeItemPath(rootDir, raw);
|
|
1148
|
+
const worker = analysis.workers.find((entry) => entry.path === path);
|
|
1149
|
+
if (worker) {
|
|
1150
|
+
const classStatus = boundClassStatus(analysis, worker, item);
|
|
1151
|
+
if (classStatus !== null)
|
|
1152
|
+
return classStatus;
|
|
1153
|
+
return worker.deployed ? "in_prod" : "reachable";
|
|
1154
|
+
}
|
|
1155
|
+
const file = analysis.files.has(path) ? path : analysis.resolveFile(path);
|
|
1156
|
+
if (!file)
|
|
1157
|
+
return;
|
|
1158
|
+
if (analysis.inProd.has(file))
|
|
1159
|
+
return "in_prod";
|
|
1160
|
+
if (analysis.reachable.has(file))
|
|
1161
|
+
return "reachable";
|
|
1162
|
+
return CODE_FILE.test(file) && !TEST_RUNNER_FILE.test(file) ? "defined_only" : undefined;
|
|
1163
|
+
}
|
|
1164
|
+
function staticStatus(status) {
|
|
1165
|
+
return status === "in_prod" ? "reachable" : status;
|
|
1166
|
+
}
|
|
1167
|
+
function tagComponentStatus(graph, rootDir) {
|
|
1168
|
+
if (process.env.MODA_HARNESS_DETERMINISTIC_MERGE === "0")
|
|
1169
|
+
return { tagged: 0, counts: {} };
|
|
1170
|
+
try {
|
|
1171
|
+
const analysis = analyzeRepository(rootDir);
|
|
1172
|
+
const counts = { in_prod: 0, reachable: 0, defined_only: 0, unknown: 0 };
|
|
1173
|
+
let tagged = 0;
|
|
1174
|
+
const items = [
|
|
1175
|
+
...Array.isArray(graph.runtimeAgents) ? graph.runtimeAgents : [],
|
|
1176
|
+
...Array.isArray(graph.artifacts) ? graph.artifacts : []
|
|
1177
|
+
];
|
|
1178
|
+
for (const item of items) {
|
|
1179
|
+
if (!isRecord(item))
|
|
1180
|
+
continue;
|
|
1181
|
+
if (item.status === undefined || !HARNESS_COMPONENT_STATUSES.has(item.status)) {
|
|
1182
|
+
const status = itemStatus(analysis, rootDir, item);
|
|
1183
|
+
if (status) {
|
|
1184
|
+
item.status = staticStatus(status);
|
|
1185
|
+
tagged += 1;
|
|
1186
|
+
} else {
|
|
1187
|
+
delete item.status;
|
|
1188
|
+
}
|
|
1189
|
+
} else {
|
|
1190
|
+
item.status = staticStatus(item.status);
|
|
1191
|
+
}
|
|
1192
|
+
counts[item.status ?? "unknown"] += 1;
|
|
1193
|
+
}
|
|
1194
|
+
if (isRecord(graph.scan)) {
|
|
1195
|
+
graph.scan.componentStatusCounts = counts;
|
|
1196
|
+
}
|
|
1197
|
+
return { tagged, counts };
|
|
1198
|
+
} catch {
|
|
1199
|
+
return { tagged: 0, counts: {} };
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// src/harness-report.ts
|
|
22
1204
|
var HARNESS_REPORT_SCHEMA_VERSION = "harness_report.v0.1";
|
|
23
1205
|
var HARNESS_REPORT_VALIDATION_SCHEMA_VERSION = "harness_report_validation.v0.1";
|
|
24
1206
|
var HARNESS_REPORT_APPROVAL_SCHEMA_VERSION = "harness_report_approval.v0.1";
|
|
25
|
-
var HARNESS_REPORT_PATH =
|
|
26
|
-
var HARNESS_REPORT_MARKDOWN_PATH =
|
|
27
|
-
var HARNESS_REPORT_VALIDATION_PATH =
|
|
28
|
-
var HARNESS_REPORT_APPROVAL_PATH =
|
|
1207
|
+
var HARNESS_REPORT_PATH = join3(".moda", "harness-report.json");
|
|
1208
|
+
var HARNESS_REPORT_MARKDOWN_PATH = join3(".moda", "HARNESS_REPORT.md");
|
|
1209
|
+
var HARNESS_REPORT_VALIDATION_PATH = join3(".moda", "harness-report-validation.json");
|
|
1210
|
+
var HARNESS_REPORT_APPROVAL_PATH = join3(".moda", "harness-report-approval.json");
|
|
29
1211
|
var HARNESS_ANALYZE_CONCERNS = ["topology", "tools", "prompts", "evals"];
|
|
30
1212
|
var HARNESS_GRAPH_ARRAY_FIELDS = [
|
|
31
1213
|
"agentFamilies",
|
|
@@ -304,7 +1486,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
304
1486
|
check("graph_present", Boolean(report?.graph?.schemaVersion), "Report must include the sanitized harness graph.");
|
|
305
1487
|
check("citations_present", Array.isArray(report?.citations) && report.citations.length > 0, "Report must include citations.");
|
|
306
1488
|
check("claims_present", Array.isArray(report?.claims) && report.claims.length > 0, "Report must include claims.");
|
|
307
|
-
const syncPlan =
|
|
1489
|
+
const syncPlan = isRecord2(report?.syncPlan) ? report.syncPlan : undefined;
|
|
308
1490
|
check("sync_plan_present", Boolean(syncPlan), "Report must include syncPlan.");
|
|
309
1491
|
if (syncPlan) {
|
|
310
1492
|
check("sync_plan_command", typeof syncPlan.command === "string" && syncPlan.command.length > 0, "Report syncPlan must include command.");
|
|
@@ -343,7 +1525,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
343
1525
|
const requireLineCitations = report?.analyst?.mode === "agent";
|
|
344
1526
|
for (const [index, citation] of citations.entries()) {
|
|
345
1527
|
const label = citationLabel(citation, index);
|
|
346
|
-
if (!
|
|
1528
|
+
if (!isRecord2(citation)) {
|
|
347
1529
|
errors.push(`Citation ${index + 1} must be an object.`);
|
|
348
1530
|
continue;
|
|
349
1531
|
}
|
|
@@ -370,7 +1552,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
370
1552
|
const claimIds = new Set;
|
|
371
1553
|
for (const [index, claim] of claims.entries()) {
|
|
372
1554
|
const label = claimLabel(claim, index);
|
|
373
|
-
if (!
|
|
1555
|
+
if (!isRecord2(claim)) {
|
|
374
1556
|
errors.push(`Claim ${index + 1} must be an object.`);
|
|
375
1557
|
continue;
|
|
376
1558
|
}
|
|
@@ -441,7 +1623,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
441
1623
|
if (unknowns.length > 0) {
|
|
442
1624
|
warn("unknowns_present", `${unknowns.length} open harness question(s) require human review.`);
|
|
443
1625
|
for (const [index, unknown] of unknowns.entries()) {
|
|
444
|
-
if (!
|
|
1626
|
+
if (!isRecord2(unknown)) {
|
|
445
1627
|
errors.push(`Unknown ${index + 1} must be an object.`);
|
|
446
1628
|
continue;
|
|
447
1629
|
}
|
|
@@ -469,7 +1651,7 @@ function validateHarnessReport(report, rootDir, options) {
|
|
|
469
1651
|
};
|
|
470
1652
|
}
|
|
471
1653
|
function refutedToolCandidateCount(graph) {
|
|
472
|
-
const unknowns = Array.isArray(graph.unknowns) ? graph.unknowns.filter(
|
|
1654
|
+
const unknowns = Array.isArray(graph.unknowns) ? graph.unknowns.filter(isRecord2) : [];
|
|
473
1655
|
let count = 0;
|
|
474
1656
|
for (const unknown of unknowns) {
|
|
475
1657
|
const label = `${String(unknown.id ?? "")} ${String(unknown.question ?? "")}`;
|
|
@@ -488,8 +1670,8 @@ function validateDeterministicFloor(report, floor, check, warn, concern) {
|
|
|
488
1670
|
return;
|
|
489
1671
|
const applyToolFloor = concern === undefined || concern === "tools";
|
|
490
1672
|
const applyTopologyFloors = concern === undefined || concern === "topology";
|
|
491
|
-
const graph =
|
|
492
|
-
const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) =>
|
|
1673
|
+
const graph = isRecord2(report?.graph) ? report.graph : {};
|
|
1674
|
+
const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord2(artifact) && artifact.type === "tool").length : 0;
|
|
493
1675
|
const providerCount = safeArrayLength(graph.modelProviders);
|
|
494
1676
|
const runtimeAgentCount = safeArrayLength(graph.runtimeAgents);
|
|
495
1677
|
if (applyToolFloor && typeof floor.toolCandidates === "number" && floor.toolCandidates >= 5) {
|
|
@@ -538,9 +1720,9 @@ async function approveHarnessReport(options) {
|
|
|
538
1720
|
approved: true,
|
|
539
1721
|
approvedAt: new Date().toISOString(),
|
|
540
1722
|
approvedBy,
|
|
541
|
-
reportPath: normalizePath(
|
|
1723
|
+
reportPath: normalizePath(relative3(options.rootDir, options.reportPath)) || HARNESS_REPORT_PATH
|
|
542
1724
|
};
|
|
543
|
-
writeJson(
|
|
1725
|
+
writeJson(join3(options.rootDir, HARNESS_REPORT_APPROVAL_PATH), approval);
|
|
544
1726
|
return { report, validation, approval };
|
|
545
1727
|
}
|
|
546
1728
|
function loadHarnessReportForSync(rootDir, reportPath, options) {
|
|
@@ -550,39 +1732,39 @@ function loadHarnessReportForSync(rootDir, reportPath, options) {
|
|
|
550
1732
|
throw new CliInputError("Harness report validation failed; cannot sync.", validation.errors.slice(0, 5).join(`
|
|
551
1733
|
`));
|
|
552
1734
|
}
|
|
553
|
-
const approvalPath =
|
|
554
|
-
const approval = existsSync(approvalPath) ? JSON.parse(
|
|
1735
|
+
const approvalPath = join3(rootDir, HARNESS_REPORT_APPROVAL_PATH);
|
|
1736
|
+
const approval = existsSync(approvalPath) ? JSON.parse(readFileSync3(approvalPath, "utf8")) : null;
|
|
555
1737
|
const approved = approval?.schemaVersion === HARNESS_REPORT_APPROVAL_SCHEMA_VERSION && approval.reportId === report.reportId && approval.reportHash === hashHarnessReport(report) && approval.approved === true;
|
|
556
1738
|
return { report, validation, approval, approved };
|
|
557
1739
|
}
|
|
558
1740
|
function readHarnessReport(reportPath) {
|
|
559
|
-
return normalizeHarnessReport(JSON.parse(
|
|
1741
|
+
return normalizeHarnessReport(JSON.parse(readFileSync3(reportPath, "utf8")));
|
|
560
1742
|
}
|
|
561
1743
|
function normalizeHarnessReport(value) {
|
|
562
1744
|
const report = cloneJson(value);
|
|
563
|
-
if (!
|
|
1745
|
+
if (!isRecord2(report.graph))
|
|
564
1746
|
return report;
|
|
565
1747
|
const graph = report.graph;
|
|
566
|
-
const reportSource =
|
|
1748
|
+
const reportSource = isRecord2(report.source) ? report.source : undefined;
|
|
567
1749
|
const hashMatched = typeof reportSource?.graphHash === "string" && reportSource.graphHash === hashStableJson(graphForReport(graph));
|
|
568
1750
|
if (typeof graph.schemaVersion !== "string" && typeof graph.graphSchemaVersion === "string") {
|
|
569
1751
|
graph.schemaVersion = graph.graphSchemaVersion;
|
|
570
1752
|
}
|
|
571
|
-
if (typeof graph.schemaVersion !== "string" &&
|
|
1753
|
+
if (typeof graph.schemaVersion !== "string" && isRecord2(report.source) && typeof report.source.graphSchemaVersion === "string") {
|
|
572
1754
|
graph.schemaVersion = report.source.graphSchemaVersion;
|
|
573
1755
|
}
|
|
574
1756
|
if (typeof graph.harnessId !== "string" && typeof report.harnessId === "string") {
|
|
575
1757
|
graph.harnessId = report.harnessId;
|
|
576
1758
|
}
|
|
577
|
-
if (!
|
|
1759
|
+
if (!isRecord2(graph.repo) && isRecord2(report.repo)) {
|
|
578
1760
|
graph.repo = report.repo;
|
|
579
1761
|
}
|
|
580
|
-
if (!
|
|
1762
|
+
if (!isRecord2(graph.tenant) && isRecord2(report.tenant)) {
|
|
581
1763
|
graph.tenant = report.tenant;
|
|
582
1764
|
}
|
|
583
|
-
if (!
|
|
584
|
-
const source =
|
|
585
|
-
const readPlan =
|
|
1765
|
+
if (!isRecord2(graph.scan)) {
|
|
1766
|
+
const source = isRecord2(report.source) ? report.source : {};
|
|
1767
|
+
const readPlan = isRecord2(source.readPlan) ? source.readPlan : defaultHarnessReadPlan();
|
|
586
1768
|
graph.scan = {
|
|
587
1769
|
createdAt: typeof report.createdAt === "string" ? report.createdAt : new Date().toISOString(),
|
|
588
1770
|
scannerVersion: analystAdapter(report),
|
|
@@ -610,7 +1792,7 @@ function normalizeHarnessReport(value) {
|
|
|
610
1792
|
return report;
|
|
611
1793
|
}
|
|
612
1794
|
function normalizeHarnessSyncPlan(report) {
|
|
613
|
-
const syncPlan =
|
|
1795
|
+
const syncPlan = isRecord2(report.syncPlan) ? report.syncPlan : {};
|
|
614
1796
|
if (typeof syncPlan.command !== "string" || syncPlan.command.length === 0) {
|
|
615
1797
|
syncPlan.command = `moda harness sync --from-report ${HARNESS_REPORT_PATH}`;
|
|
616
1798
|
}
|
|
@@ -635,7 +1817,7 @@ function normalizeRelationshipEvidence(graph) {
|
|
|
635
1817
|
return;
|
|
636
1818
|
const evidenceById = graphEvidenceById(graph);
|
|
637
1819
|
for (const relationship of relationships) {
|
|
638
|
-
if (!
|
|
1820
|
+
if (!isRecord2(relationship))
|
|
639
1821
|
continue;
|
|
640
1822
|
if (Array.isArray(relationship.evidence) && relationship.evidence.length > 0)
|
|
641
1823
|
continue;
|
|
@@ -658,9 +1840,9 @@ function graphEvidenceById(graph) {
|
|
|
658
1840
|
if (!Array.isArray(collection))
|
|
659
1841
|
continue;
|
|
660
1842
|
for (const item of collection) {
|
|
661
|
-
if (!
|
|
1843
|
+
if (!isRecord2(item) || typeof item.id !== "string" || !Array.isArray(item.evidence))
|
|
662
1844
|
continue;
|
|
663
|
-
const evidence = item.evidence.filter((entry) =>
|
|
1845
|
+
const evidence = item.evidence.filter((entry) => isRecord2(entry) && typeof entry.path === "string" && typeof entry.reason === "string").map((entry) => normalizeEvidenceEntry(entry));
|
|
664
1846
|
if (evidence.length > 0)
|
|
665
1847
|
byId.set(item.id, evidence);
|
|
666
1848
|
}
|
|
@@ -684,7 +1866,7 @@ function normalizeEvidenceEntry(item) {
|
|
|
684
1866
|
return line ? { path: item.path, reason: item.reason, line } : { path: item.path, reason: item.reason };
|
|
685
1867
|
}
|
|
686
1868
|
function normalizeHarnessClaim(claim, graph) {
|
|
687
|
-
if (!
|
|
1869
|
+
if (!isRecord2(claim))
|
|
688
1870
|
return claim;
|
|
689
1871
|
if (typeof claim.subjectId !== "string" && Array.isArray(claim.subjectIds) && typeof claim.subjectIds[0] === "string") {
|
|
690
1872
|
claim.subjectId = claim.subjectIds[0];
|
|
@@ -732,10 +1914,10 @@ function validateGraphItemShapes(graph, errors) {
|
|
|
732
1914
|
if (typeof graph.harnessId !== "string" || graph.harnessId.length === 0) {
|
|
733
1915
|
errors.push("Graph must include harnessId.");
|
|
734
1916
|
}
|
|
735
|
-
if (!
|
|
1917
|
+
if (!isRecord2(graph.repo)) {
|
|
736
1918
|
errors.push("Graph must include repo metadata.");
|
|
737
1919
|
}
|
|
738
|
-
if (!
|
|
1920
|
+
if (!isRecord2(graph.scan)) {
|
|
739
1921
|
errors.push("Graph must include scan metadata.");
|
|
740
1922
|
}
|
|
741
1923
|
validateGraphCollection(graph, errors, "runtimeAgents", {
|
|
@@ -807,7 +1989,7 @@ function validateGraphCollection(graph, errors, field, shape) {
|
|
|
807
1989
|
return;
|
|
808
1990
|
for (const [index, item] of collection.entries()) {
|
|
809
1991
|
const label = graphItemLabel(field, item, index);
|
|
810
|
-
if (!
|
|
1992
|
+
if (!isRecord2(item)) {
|
|
811
1993
|
errors.push(`Graph ${field} item ${index + 1} must be an object.`);
|
|
812
1994
|
continue;
|
|
813
1995
|
}
|
|
@@ -836,7 +2018,7 @@ function validateGraphCollection(graph, errors, field, shape) {
|
|
|
836
2018
|
}
|
|
837
2019
|
if (Array.isArray(item.evidence)) {
|
|
838
2020
|
for (const [evidenceIndex, evidence] of item.evidence.entries()) {
|
|
839
|
-
if (!
|
|
2021
|
+
if (!isRecord2(evidence) || typeof evidence.path !== "string" || typeof evidence.reason !== "string") {
|
|
840
2022
|
errors.push(`Graph ${label} evidence ${evidenceIndex + 1} must include path and reason.`);
|
|
841
2023
|
continue;
|
|
842
2024
|
}
|
|
@@ -869,7 +2051,7 @@ function artifactHasRetrievableLocation(item) {
|
|
|
869
2051
|
}
|
|
870
2052
|
function validateCaptureQuality(graph, warn) {
|
|
871
2053
|
const scan = graph.scan;
|
|
872
|
-
if (!
|
|
2054
|
+
if (!isRecord2(scan))
|
|
873
2055
|
return;
|
|
874
2056
|
if (scan.truncated === true) {
|
|
875
2057
|
const reasons = Array.isArray(scan.truncationReasons) && scan.truncationReasons.length > 0 ? scan.truncationReasons.map((reason) => String(reason)).join(", ") : "scan budget or upload cap reached";
|
|
@@ -884,12 +2066,12 @@ function isWholeFileArtifact(item) {
|
|
|
884
2066
|
return false;
|
|
885
2067
|
if (!Array.isArray(item.evidence))
|
|
886
2068
|
return false;
|
|
887
|
-
return item.evidence.some((entry) =>
|
|
2069
|
+
return item.evidence.some((entry) => isRecord2(entry) && entry.path === item.sourcePath);
|
|
888
2070
|
}
|
|
889
2071
|
function hasEvidenceLine(evidence) {
|
|
890
2072
|
if (!Array.isArray(evidence))
|
|
891
2073
|
return false;
|
|
892
|
-
return evidence.some((entry) =>
|
|
2074
|
+
return evidence.some((entry) => isRecord2(entry) && typeof entry.path === "string" && typeof entry.reason === "string" && evidenceLine({
|
|
893
2075
|
path: entry.path,
|
|
894
2076
|
reason: entry.reason,
|
|
895
2077
|
line: typeof entry.line === "number" ? entry.line : undefined
|
|
@@ -918,7 +2100,7 @@ var HARNESS_ARTIFACT_TYPES = new Set([
|
|
|
918
2100
|
"unknown"
|
|
919
2101
|
]);
|
|
920
2102
|
function normalizeGraphItemFieldAliases(collection, item) {
|
|
921
|
-
if (!
|
|
2103
|
+
if (!isRecord2(item))
|
|
922
2104
|
return;
|
|
923
2105
|
if ((collection === "runtimeAgents" || collection === "artifacts") && item.status !== undefined) {
|
|
924
2106
|
const status = normalizeComponentStatus(item.status);
|
|
@@ -1005,14 +2187,14 @@ function firstEvidencePath(item) {
|
|
|
1005
2187
|
if (!Array.isArray(item.evidence))
|
|
1006
2188
|
return;
|
|
1007
2189
|
for (const evidence of item.evidence) {
|
|
1008
|
-
if (
|
|
2190
|
+
if (isRecord2(evidence) && typeof evidence.path === "string" && evidence.path.length > 0) {
|
|
1009
2191
|
return evidence.path;
|
|
1010
2192
|
}
|
|
1011
2193
|
}
|
|
1012
2194
|
return;
|
|
1013
2195
|
}
|
|
1014
2196
|
function stampRemoteSnapshotTruncation(report) {
|
|
1015
|
-
const scanRecord =
|
|
2197
|
+
const scanRecord = isRecord2(report.graph.scan) ? report.graph.scan : {};
|
|
1016
2198
|
const truncationReasons = Array.isArray(scanRecord.truncationReasons) ? scanRecord.truncationReasons.map(String) : [];
|
|
1017
2199
|
const stamped = {
|
|
1018
2200
|
...report,
|
|
@@ -1037,12 +2219,12 @@ function requiresGraphItemConfidence(collection) {
|
|
|
1037
2219
|
return collection !== "relationships" && collection !== "unknowns";
|
|
1038
2220
|
}
|
|
1039
2221
|
function graphItemLabel(collection, item, index) {
|
|
1040
|
-
if (
|
|
2222
|
+
if (isRecord2(item) && typeof item.id === "string" && item.id.length > 0)
|
|
1041
2223
|
return `${collection}.${item.id}`;
|
|
1042
2224
|
return `${collection} item ${index + 1}`;
|
|
1043
2225
|
}
|
|
1044
2226
|
function normalizeConfidenceField(value, fallback) {
|
|
1045
|
-
if (!
|
|
2227
|
+
if (!isRecord2(value))
|
|
1046
2228
|
return;
|
|
1047
2229
|
const confidence = confidenceNumberFromUnknown(value.confidence);
|
|
1048
2230
|
if (confidence !== undefined)
|
|
@@ -1087,7 +2269,7 @@ function inferClaimKind(subjectId, graph) {
|
|
|
1087
2269
|
];
|
|
1088
2270
|
for (const [field, kind] of collectionKinds) {
|
|
1089
2271
|
const collection = graph[field];
|
|
1090
|
-
if (Array.isArray(collection) && collection.some((item) =>
|
|
2272
|
+
if (Array.isArray(collection) && collection.some((item) => isRecord2(item) && item.id === subjectId)) {
|
|
1091
2273
|
return kind;
|
|
1092
2274
|
}
|
|
1093
2275
|
}
|
|
@@ -1164,7 +2346,7 @@ function normalizeClaimKind(value, subjectId, graph) {
|
|
|
1164
2346
|
return inferred;
|
|
1165
2347
|
}
|
|
1166
2348
|
function analystAdapter(report) {
|
|
1167
|
-
const analyst =
|
|
2349
|
+
const analyst = isRecord2(report.analyst) ? report.analyst : {};
|
|
1168
2350
|
return typeof analyst.adapter === "string" ? analyst.adapter : "external-analyst";
|
|
1169
2351
|
}
|
|
1170
2352
|
function defaultHarnessReadPlan() {
|
|
@@ -1225,12 +2407,12 @@ function resolveHarnessReportPath(rootDir, value) {
|
|
|
1225
2407
|
}
|
|
1226
2408
|
function writeHarnessReportArtifacts(options) {
|
|
1227
2409
|
const written = [];
|
|
1228
|
-
writeJson(
|
|
2410
|
+
writeJson(join3(options.rootDir, HARNESS_REPORT_PATH), options.report);
|
|
1229
2411
|
written.push(HARNESS_REPORT_PATH);
|
|
1230
|
-
writeJson(
|
|
2412
|
+
writeJson(join3(options.rootDir, HARNESS_REPORT_VALIDATION_PATH), options.validation);
|
|
1231
2413
|
written.push(HARNESS_REPORT_VALIDATION_PATH);
|
|
1232
2414
|
if (options.markdown) {
|
|
1233
|
-
const markdownPath =
|
|
2415
|
+
const markdownPath = join3(options.rootDir, HARNESS_REPORT_MARKDOWN_PATH);
|
|
1234
2416
|
mkdirSync(dirname(markdownPath), { recursive: true });
|
|
1235
2417
|
writeFileSync(markdownPath, renderHarnessReportMarkdown(options.report, options.validation));
|
|
1236
2418
|
written.push(HARNESS_REPORT_MARKDOWN_PATH);
|
|
@@ -1238,7 +2420,7 @@ function writeHarnessReportArtifacts(options) {
|
|
|
1238
2420
|
return written;
|
|
1239
2421
|
}
|
|
1240
2422
|
function renderHarnessReportMarkdown(report, validation) {
|
|
1241
|
-
const summary =
|
|
2423
|
+
const summary = isRecord2(report.summary) ? report.summary : {};
|
|
1242
2424
|
const citations = Array.isArray(report.citations) ? report.citations : [];
|
|
1243
2425
|
const claims = Array.isArray(report.claims) ? report.claims : [];
|
|
1244
2426
|
const unknowns = Array.isArray(report.unknowns) ? report.unknowns : [];
|
|
@@ -1262,7 +2444,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
1262
2444
|
""
|
|
1263
2445
|
];
|
|
1264
2446
|
for (const [index, claim] of claims.slice(0, 80).entries()) {
|
|
1265
|
-
const claimRecord =
|
|
2447
|
+
const claimRecord = isRecord2(claim) ? claim : {};
|
|
1266
2448
|
const citationIds = Array.isArray(claimRecord.citationIds) ? claimRecord.citationIds : [];
|
|
1267
2449
|
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(", ");
|
|
1268
2450
|
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"}`, "");
|
|
@@ -1273,7 +2455,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
1273
2455
|
if (unknowns.length > 0) {
|
|
1274
2456
|
lines.push("## Open Questions", "");
|
|
1275
2457
|
for (const unknown of unknowns) {
|
|
1276
|
-
lines.push(`- ${
|
|
2458
|
+
lines.push(`- ${isRecord2(unknown) && typeof unknown.question === "string" ? unknown.question : "Unknown harness question"}`);
|
|
1277
2459
|
}
|
|
1278
2460
|
lines.push("");
|
|
1279
2461
|
}
|
|
@@ -1283,7 +2465,7 @@ function renderHarnessReportMarkdown(report, validation) {
|
|
|
1283
2465
|
`;
|
|
1284
2466
|
}
|
|
1285
2467
|
function claimLabel(claim, index) {
|
|
1286
|
-
if (
|
|
2468
|
+
if (isRecord2(claim)) {
|
|
1287
2469
|
if (typeof claim.id === "string" && claim.id.length > 0)
|
|
1288
2470
|
return claim.id;
|
|
1289
2471
|
if (typeof claim.title === "string" && claim.title.length > 0)
|
|
@@ -1292,7 +2474,7 @@ function claimLabel(claim, index) {
|
|
|
1292
2474
|
return `claim #${index + 1}`;
|
|
1293
2475
|
}
|
|
1294
2476
|
function citationLabel(citation, index) {
|
|
1295
|
-
if (
|
|
2477
|
+
if (isRecord2(citation) && typeof citation.id === "string" && citation.id.length > 0)
|
|
1296
2478
|
return citation.id;
|
|
1297
2479
|
return `#${index + 1}`;
|
|
1298
2480
|
}
|
|
@@ -1362,10 +2544,10 @@ function citationExcerpt(rootDir, path, line) {
|
|
|
1362
2544
|
if (!isInside(rootDir, absPath) || !existsSync(absPath))
|
|
1363
2545
|
return {};
|
|
1364
2546
|
try {
|
|
1365
|
-
const stat =
|
|
2547
|
+
const stat = lstatSync2(absPath);
|
|
1366
2548
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 131072)
|
|
1367
2549
|
return {};
|
|
1368
|
-
const lines =
|
|
2550
|
+
const lines = readFileSync3(absPath, "utf8").split(/\r?\n/);
|
|
1369
2551
|
const selected = line ? lines[line - 1] : lines.find((candidate) => candidate.trim().length > 0);
|
|
1370
2552
|
if (!selected)
|
|
1371
2553
|
return {};
|
|
@@ -1375,13 +2557,13 @@ function citationExcerpt(rootDir, path, line) {
|
|
|
1375
2557
|
}
|
|
1376
2558
|
}
|
|
1377
2559
|
function validateSummaryMatchesGraph(summary, graph, check) {
|
|
1378
|
-
if (!
|
|
2560
|
+
if (!isRecord2(graph))
|
|
1379
2561
|
return;
|
|
1380
2562
|
const fields = {
|
|
1381
2563
|
runtimeAgentCount: safeArrayLength(graph.runtimeAgents),
|
|
1382
2564
|
agentFamilyCount: safeArrayLength(graph.agentFamilies),
|
|
1383
2565
|
artifactCount: safeArrayLength(graph.artifacts),
|
|
1384
|
-
sharedArtifactCount: Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) =>
|
|
2566
|
+
sharedArtifactCount: Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord2(artifact) && artifact.scope === "shared").length : 0,
|
|
1385
2567
|
channelAdapterCount: safeArrayLength(graph.channelAdapters),
|
|
1386
2568
|
identityNamespaceCount: safeArrayLength(graph.identityNamespaces),
|
|
1387
2569
|
environmentCount: safeArrayLength(graph.environments),
|
|
@@ -1424,7 +2606,7 @@ function validateClaimSubjectExists(graph, claim, errors, warnings) {
|
|
|
1424
2606
|
const collection = graph[collectionName];
|
|
1425
2607
|
if (!Array.isArray(collection))
|
|
1426
2608
|
return;
|
|
1427
|
-
if (collection.some((item) =>
|
|
2609
|
+
if (collection.some((item) => isRecord2(item) && item.id === claim.subjectId))
|
|
1428
2610
|
return;
|
|
1429
2611
|
const message = `Claim ${claim.id} subject ${claim.subjectId} is not present in graph.${collectionName}.`;
|
|
1430
2612
|
if (claim.kind === "unknown")
|
|
@@ -1433,7 +2615,7 @@ function validateClaimSubjectExists(graph, claim, errors, warnings) {
|
|
|
1433
2615
|
errors.push(message);
|
|
1434
2616
|
}
|
|
1435
2617
|
function validateClaimQuality(claims, graph, warn) {
|
|
1436
|
-
const claimRecords = claims.filter(
|
|
2618
|
+
const claimRecords = claims.filter(isRecord2);
|
|
1437
2619
|
if (claimRecords.length === 0)
|
|
1438
2620
|
return;
|
|
1439
2621
|
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;
|
|
@@ -1513,7 +2695,7 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
|
|
|
1513
2695
|
return;
|
|
1514
2696
|
}
|
|
1515
2697
|
try {
|
|
1516
|
-
const stat =
|
|
2698
|
+
const stat = lstatSync2(absPath);
|
|
1517
2699
|
if (stat.isSymbolicLink())
|
|
1518
2700
|
errors.push(`Citation ${citation.id} points to a symlink.`);
|
|
1519
2701
|
if (stat.isDirectory()) {
|
|
@@ -1522,7 +2704,7 @@ function validateCitation(citation, rootDir, errors, warnings, options) {
|
|
|
1522
2704
|
}
|
|
1523
2705
|
if (!stat.isFile())
|
|
1524
2706
|
errors.push(`Citation ${citation.id} does not point to a file.`);
|
|
1525
|
-
const lines =
|
|
2707
|
+
const lines = readFileSync3(absPath, "utf8").split(/\r?\n/);
|
|
1526
2708
|
if (citation.line !== undefined) {
|
|
1527
2709
|
const lineCount = lines.length;
|
|
1528
2710
|
if (citation.line < 1 || citation.line > lineCount) {
|
|
@@ -1573,7 +2755,7 @@ function isSecretishPath(path) {
|
|
|
1573
2755
|
return name === ".env" || name.startsWith(".env.") || name.includes("secret") || name.includes("private-key") || name.endsWith(".pem");
|
|
1574
2756
|
}
|
|
1575
2757
|
function isInside(rootDir, absPath) {
|
|
1576
|
-
const rel =
|
|
2758
|
+
const rel = relative3(resolve(rootDir), resolve(absPath));
|
|
1577
2759
|
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
1578
2760
|
}
|
|
1579
2761
|
function writeJson(path, value) {
|
|
@@ -1599,11 +2781,11 @@ function sortJson(value) {
|
|
|
1599
2781
|
}
|
|
1600
2782
|
return value;
|
|
1601
2783
|
}
|
|
1602
|
-
function
|
|
2784
|
+
function isRecord2(value) {
|
|
1603
2785
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1604
2786
|
}
|
|
1605
2787
|
function normalizePath(path) {
|
|
1606
2788
|
return path.replace(/\\/g, "/");
|
|
1607
2789
|
}
|
|
1608
2790
|
|
|
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 };
|
|
2791
|
+
export { mergeBoundRuntimeClasses, buildImportGraphReachability, 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 };
|