@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.
@@ -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 = 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");
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 = isRecord(report?.syncPlan) ? report.syncPlan : undefined;
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 (!isRecord(citation)) {
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 (!isRecord(claim)) {
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 (!isRecord(unknown)) {
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(isRecord) : [];
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 = isRecord(report?.graph) ? report.graph : {};
492
- const toolArtifactCount = Array.isArray(graph.artifacts) ? graph.artifacts.filter((artifact) => isRecord(artifact) && artifact.type === "tool").length : 0;
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(relative(options.rootDir, options.reportPath)) || HARNESS_REPORT_PATH
1723
+ reportPath: normalizePath(relative3(options.rootDir, options.reportPath)) || HARNESS_REPORT_PATH
542
1724
  };
543
- writeJson(join(options.rootDir, HARNESS_REPORT_APPROVAL_PATH), approval);
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 = join(rootDir, HARNESS_REPORT_APPROVAL_PATH);
554
- const approval = existsSync(approvalPath) ? JSON.parse(readFileSync(approvalPath, "utf8")) : null;
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(readFileSync(reportPath, "utf8")));
1741
+ return normalizeHarnessReport(JSON.parse(readFileSync3(reportPath, "utf8")));
560
1742
  }
561
1743
  function normalizeHarnessReport(value) {
562
1744
  const report = cloneJson(value);
563
- if (!isRecord(report.graph))
1745
+ if (!isRecord2(report.graph))
564
1746
  return report;
565
1747
  const graph = report.graph;
566
- const reportSource = isRecord(report.source) ? report.source : undefined;
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" && isRecord(report.source) && typeof report.source.graphSchemaVersion === "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 (!isRecord(graph.repo) && isRecord(report.repo)) {
1759
+ if (!isRecord2(graph.repo) && isRecord2(report.repo)) {
578
1760
  graph.repo = report.repo;
579
1761
  }
580
- if (!isRecord(graph.tenant) && isRecord(report.tenant)) {
1762
+ if (!isRecord2(graph.tenant) && isRecord2(report.tenant)) {
581
1763
  graph.tenant = report.tenant;
582
1764
  }
583
- if (!isRecord(graph.scan)) {
584
- const source = isRecord(report.source) ? report.source : {};
585
- const readPlan = isRecord(source.readPlan) ? source.readPlan : defaultHarnessReadPlan();
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 = isRecord(report.syncPlan) ? report.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 (!isRecord(relationship))
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 (!isRecord(item) || typeof item.id !== "string" || !Array.isArray(item.evidence))
1843
+ if (!isRecord2(item) || typeof item.id !== "string" || !Array.isArray(item.evidence))
662
1844
  continue;
663
- const evidence = item.evidence.filter((entry) => isRecord(entry) && typeof entry.path === "string" && typeof entry.reason === "string").map((entry) => normalizeEvidenceEntry(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 (!isRecord(claim))
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 (!isRecord(graph.repo)) {
1917
+ if (!isRecord2(graph.repo)) {
736
1918
  errors.push("Graph must include repo metadata.");
737
1919
  }
738
- if (!isRecord(graph.scan)) {
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 (!isRecord(item)) {
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 (!isRecord(evidence) || typeof evidence.path !== "string" || typeof evidence.reason !== "string") {
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 (!isRecord(scan))
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) => isRecord(entry) && entry.path === item.sourcePath);
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) => isRecord(entry) && typeof entry.path === "string" && typeof entry.reason === "string" && evidenceLine({
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 (!isRecord(item))
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 (isRecord(evidence) && typeof evidence.path === "string" && evidence.path.length > 0) {
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 = isRecord(report.graph.scan) ? report.graph.scan : {};
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 (isRecord(item) && typeof item.id === "string" && item.id.length > 0)
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 (!isRecord(value))
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) => isRecord(item) && item.id === subjectId)) {
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 = isRecord(report.analyst) ? report.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(join(options.rootDir, HARNESS_REPORT_PATH), options.report);
2410
+ writeJson(join3(options.rootDir, HARNESS_REPORT_PATH), options.report);
1229
2411
  written.push(HARNESS_REPORT_PATH);
1230
- writeJson(join(options.rootDir, HARNESS_REPORT_VALIDATION_PATH), options.validation);
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 = join(options.rootDir, HARNESS_REPORT_MARKDOWN_PATH);
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 = isRecord(report.summary) ? report.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 = isRecord(claim) ? claim : {};
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(`- ${isRecord(unknown) && typeof unknown.question === "string" ? unknown.question : "Unknown harness question"}`);
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 (isRecord(claim)) {
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 (isRecord(citation) && typeof citation.id === "string" && citation.id.length > 0)
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 = lstatSync(absPath);
2547
+ const stat = lstatSync2(absPath);
1366
2548
  if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 131072)
1367
2549
  return {};
1368
- const lines = readFileSync(absPath, "utf8").split(/\r?\n/);
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 (!isRecord(graph))
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) => isRecord(artifact) && artifact.scope === "shared").length : 0,
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) => isRecord(item) && item.id === claim.subjectId))
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(isRecord);
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 = lstatSync(absPath);
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 = readFileSync(absPath, "utf8").split(/\r?\n/);
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 = relative(resolve(rootDir), resolve(absPath));
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 isRecord(value) {
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 };