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