@agentskit/doc-bridge 1.7.44 → 1.7.45

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CONTRIBUTING.md +6 -4
  3. package/action.yml +1 -1
  4. package/dist/cli/program.js +378 -189
  5. package/dist/cli/program.js.map +1 -1
  6. package/dist/config/index.d.ts +1 -1
  7. package/dist/config/index.js +5 -3
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/index-BUL0q7s8.d.ts +660 -0
  10. package/dist/index.d.ts +637 -2724
  11. package/dist/index.js +328 -149
  12. package/dist/index.js.map +1 -1
  13. package/docs/RELEASE.md +22 -8
  14. package/docs/agent-corpus/INDEX.md +2 -2
  15. package/docs/agent-corpus/chat.md +2 -2
  16. package/docs/agent-corpus/cli.md +2 -2
  17. package/docs/agent-corpus/conformance.md +2 -2
  18. package/docs/agent-corpus/doc-bridge.md +1 -1
  19. package/docs/agent-corpus/doctor.md +2 -2
  20. package/docs/agent-corpus/gates.md +2 -2
  21. package/docs/agent-corpus/mcp.md +2 -2
  22. package/docs/agent-corpus/memory.md +2 -2
  23. package/docs/agent-corpus/query.md +2 -2
  24. package/docs/knowledge-engine-runbook.md +13 -1
  25. package/docs/spec/benchmark-v1.md +6 -0
  26. package/docs/spec/config-v1.md +45 -0
  27. package/docs/validation-cycle-plan.md +19 -0
  28. package/docs/verification-harness.md +4 -0
  29. package/mcpb/manifest.json +1 -1
  30. package/package.json +68 -70
  31. package/scripts/check-ecosystem-upstream.mjs +3 -2
  32. package/scripts/report-visual-check.mjs +20 -3
  33. package/scripts/verification-harness.mjs +0 -1
  34. package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
  35. package/src/cli/demo.ts +2 -2
  36. package/src/cli/program.ts +15 -5
  37. package/src/config/load-config.ts +7 -1
  38. package/src/config/schema.ts +4 -2
  39. package/src/conformance/documentation-standard-v1.ts +14 -8
  40. package/src/discovery/documentation.ts +44 -18
  41. package/src/discovery/repository.ts +77 -28
  42. package/src/doctor/run-doctor.ts +2 -15
  43. package/src/federation/llms.ts +72 -20
  44. package/src/fixes/proposals.ts +4 -3
  45. package/src/index-builder/human-adapters/fumadocs.ts +1 -1
  46. package/src/index-builder/watch-index.ts +1 -1
  47. package/src/lib/bounded-text.ts +15 -10
  48. package/src/reconciliation/reconcile.ts +47 -5
  49. package/src/report/html.ts +21 -15
  50. package/src/rules/engine.ts +15 -2
  51. package/src/safety/repository.ts +1 -1
  52. package/src/schemas/knowledge.ts +5 -2
  53. package/src/validate.ts +7 -1
  54. package/src/version.ts +1 -1
  55. package/dist/index-C2PCQSrB.d.ts +0 -2251
@@ -1,6 +1,6 @@
1
1
  // src/cli/program.ts
2
- import { existsSync as existsSync21, mkdirSync as mkdirSync8, readFileSync as readFileSync21, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
3
- import { dirname as dirname10, relative as relative10, resolve as resolve18 } from "path";
2
+ import { closeSync as closeSync3, existsSync as existsSync21, mkdirSync as mkdirSync7, openSync as openSync3, readFileSync as readFileSync21, renameSync as renameSync3, rmSync as rmSync3, writeFileSync as writeFileSync8 } from "fs";
3
+ import { dirname as dirname9, relative as relative10, resolve as resolve18 } from "path";
4
4
  import { createInterface } from "readline/promises";
5
5
 
6
6
  // src/config/load-config.ts
@@ -467,17 +467,19 @@ var RuleIdSchema = z.enum([
467
467
  var RuleSeveritySchema = z.enum(["off", "info", "warn", "error"]);
468
468
  var RulesConfigSchema = z.object({
469
469
  mode: z.enum(["default", "recommended", "strict"]).optional(),
470
- severity: z.record(RuleIdSchema, RuleSeveritySchema).optional(),
470
+ severity: z.partialRecord(RuleIdSchema, RuleSeveritySchema).optional(),
471
471
  ignore: z.array(RuleIdSchema).max(128).optional(),
472
472
  criticalEntities: z.array(z.string().min(1).max(256)).max(128).optional(),
473
473
  criticalPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
474
- warningThresholds: z.record(RuleIdSchema, z.number().int().min(1).max(1e5)).optional()
474
+ warningThresholds: z.partialRecord(RuleIdSchema, z.number().int().min(1).max(1e5)).optional()
475
475
  }).strict();
476
476
  var ReconciliationConfigSchema = z.object({
477
477
  /** Semantic comparison level. Raw discovery always keeps file-level relations. */
478
478
  scope: z.enum(["file", "module", "package"]).optional(),
479
479
  /** Relation kinds that must have documentation declarations. Omit to require all observed kinds; [] disables this signal. */
480
480
  requiredRelationKinds: z.array(z.string().min(1).max(128)).max(128).optional(),
481
+ /** Limit missing-declaration findings to relations whose endpoints are internal project entities. */
482
+ requiredRelationTargets: z.enum(["all", "internal"]).optional(),
481
483
  includeOrphanedDocuments: z.boolean().optional()
482
484
  }).strict();
483
485
  var AnalysisConfigSchema = z.object({
@@ -756,7 +758,7 @@ var parseConfig = (input) => {
756
758
  throw new Error(
757
759
  `Invalid doc-bridge config:
758
760
  ${result.error.issues.map(
759
- (issue) => ` - ${issue.path.join(".") || "(root)"}: ${issue.message}`
761
+ (issue) => ` - ${issue.path.join(".") || "(root)"}: ${issue.code === "invalid_type" && issue.message.endsWith("received undefined") ? "Required" : issue.code === "invalid_value" && "values" in issue ? "Invalid enum value" : issue.message}`
760
762
  ).join("\n")}`
761
763
  );
762
764
  };
@@ -780,7 +782,7 @@ var projectRootFromConfigPath = (configFilePath, projectRootField) => {
780
782
  };
781
783
 
782
784
  // src/conformance/documentation-standard-v1.ts
783
- import { existsSync as existsSync9, readFileSync as readFileSync8, realpathSync as realpathSync6, statSync as statSync3 } from "fs";
785
+ import { closeSync as closeSync2, existsSync as existsSync9, fstatSync as fstatSync2, openSync as openSync2, readFileSync as readFileSync8, realpathSync as realpathSync6 } from "fs";
784
786
  import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve6, sep as sep5 } from "path";
785
787
 
786
788
  // src/conformance/ecosystem-contract.ts
@@ -960,7 +962,7 @@ var parseCanonicalEcosystemContract = (manifestInput, claimsInput) => {
960
962
 
961
963
  // src/index-builder/build-index.ts
962
964
  import { mkdirSync, readFileSync as readFileSync7, writeFileSync } from "fs";
963
- import { dirname as dirname4, join as join7 } from "path";
965
+ import { dirname as dirname3, join as join7 } from "path";
964
966
 
965
967
  // src/lib/paths.ts
966
968
  import { existsSync as existsSync2, realpathSync } from "fs";
@@ -1044,22 +1046,27 @@ var defaultChecksForTarget = (root, opts) => {
1044
1046
  import { minimatch } from "minimatch";
1045
1047
 
1046
1048
  // src/lib/bounded-text.ts
1047
- import { readFileSync as readFileSync3, statSync } from "fs";
1049
+ import { closeSync, fstatSync, openSync, readFileSync as readFileSync3 } from "fs";
1048
1050
  var MAX_DOCUMENT_BYTES = 4 * 1024 * 1024;
1049
1051
  var MAX_CORPUS_BYTES = 64 * 1024 * 1024;
1050
1052
  var readBoundedText = (path, budget, limits) => {
1051
1053
  const maxFileBytes = limits?.maxFileBytes ?? MAX_DOCUMENT_BYTES;
1052
1054
  const maxCorpusBytes = limits?.maxCorpusBytes ?? MAX_CORPUS_BYTES;
1053
- const stat = statSync(path);
1054
- if (!stat.isFile()) throw new Error(`Documentation path is not a regular file: ${path}`);
1055
- if (stat.size > maxFileBytes) {
1056
- throw new Error(`Documentation file exceeds the ${maxFileBytes} byte limit: ${path}`);
1057
- }
1058
- if (budget.used + stat.size > maxCorpusBytes) {
1059
- throw new Error(`Documentation corpus exceeds the ${maxCorpusBytes} byte read budget.`);
1055
+ const fd = openSync(path, "r");
1056
+ try {
1057
+ const stat = fstatSync(fd);
1058
+ if (!stat.isFile()) throw new Error(`Documentation path is not a regular file: ${path}`);
1059
+ if (stat.size > maxFileBytes) {
1060
+ throw new Error(`Documentation file exceeds the ${maxFileBytes} byte limit: ${path}`);
1061
+ }
1062
+ if (budget.used + stat.size > maxCorpusBytes) {
1063
+ throw new Error(`Documentation corpus exceeds the ${maxCorpusBytes} byte read budget.`);
1064
+ }
1065
+ budget.used += stat.size;
1066
+ return readFileSync3(fd, "utf8");
1067
+ } finally {
1068
+ closeSync(fd);
1060
1069
  }
1061
- budget.used += stat.size;
1062
- return readFileSync3(path, "utf8");
1063
1070
  };
1064
1071
 
1065
1072
  // src/lib/markdown.ts
@@ -1483,7 +1490,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
1483
1490
  };
1484
1491
 
1485
1492
  // src/version.ts
1486
- var PACKAGE_VERSION = "1.7.44";
1493
+ var PACKAGE_VERSION = "1.7.45";
1487
1494
 
1488
1495
  // src/index-builder/capabilities.ts
1489
1496
  var renderCapabilitiesJson = (config, index, paths) => {
@@ -1914,7 +1921,7 @@ var scanHumanDocs = (root, config) => Object.fromEntries(scanHumanDocRecords(roo
1914
1921
 
1915
1922
  // src/index-builder/plugins/nx.ts
1916
1923
  import { existsSync as existsSync6, lstatSync as lstatSync2, readFileSync as readFileSync5, realpathSync as realpathSync5 } from "fs";
1917
- import { basename as basename2, dirname as dirname3, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve5, sep as sep4 } from "path";
1924
+ import { basename as basename2, dirname as dirname2, isAbsolute as isAbsolute3, relative as relative3, resolve as resolve5, sep as sep4 } from "path";
1918
1925
  var NX_SCAN_SKIP = /* @__PURE__ */ new Set([
1919
1926
  "node_modules",
1920
1927
  ".git",
@@ -1978,7 +1985,7 @@ var discoverNxProjects = (root, config) => {
1978
1985
  for (const manifest of manifests) {
1979
1986
  const json = readJsonRecord(manifest);
1980
1987
  if (!json) continue;
1981
- const manifestDir = dirname3(manifest);
1988
+ const manifestDir = dirname2(manifest);
1982
1989
  const manifestName = basename2(manifest);
1983
1990
  const isProjectJson = manifestName === "project.json";
1984
1991
  if (!isProjectJson && manifestName !== "package.json") continue;
@@ -2026,7 +2033,7 @@ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
2026
2033
  import { basename as basename3, join as join6 } from "path";
2027
2034
 
2028
2035
  // src/lib/glob-expand.ts
2029
- import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2036
+ import { existsSync as existsSync7, readdirSync as readdirSync2, statSync } from "fs";
2030
2037
  import { join as join5 } from "path";
2031
2038
  var expandWorkspaceGlobs = (root, patterns) => {
2032
2039
  const dirs = /* @__PURE__ */ new Set();
@@ -2043,7 +2050,7 @@ var expandWorkspaceGlobs = (root, patterns) => {
2043
2050
  for (const name of readdirSync2(base)) {
2044
2051
  const abs = join5(base, name);
2045
2052
  try {
2046
- if (statSync2(abs).isDirectory()) dirs.add(toPosix(abs));
2053
+ if (statSync(abs).isDirectory()) dirs.add(toPosix(abs));
2047
2054
  } catch {
2048
2055
  }
2049
2056
  }
@@ -2153,7 +2160,7 @@ var buildDocBridgeIndex = (opts) => {
2153
2160
  lookup
2154
2161
  };
2155
2162
  if (write) {
2156
- mkdirSync(dirname4(indexPath), { recursive: true });
2163
+ mkdirSync(dirname3(indexPath), { recursive: true });
2157
2164
  writeFileSync(indexPath, `${JSON.stringify(index, null, 2)}
2158
2165
  `, "utf8");
2159
2166
  }
@@ -2176,7 +2183,7 @@ var buildDocBridgeIndex = (opts) => {
2176
2183
  const capabilitiesOut = config.index?.capabilities?.outFile ?? ".doc-bridge/capabilities.json";
2177
2184
  capabilitiesPath = join7(root, capabilitiesOut);
2178
2185
  if (write) {
2179
- mkdirSync(dirname4(capabilitiesPath), { recursive: true });
2186
+ mkdirSync(dirname3(capabilitiesPath), { recursive: true });
2180
2187
  writeFileSync(
2181
2188
  capabilitiesPath,
2182
2189
  renderCapabilitiesJson(config, index, {
@@ -2222,11 +2229,10 @@ var fileEvidence = (root, path, options) => {
2222
2229
  evidence: { path, detail: "Path escapes the project root." }
2223
2230
  };
2224
2231
  }
2225
- if (!existsSync9(abs)) {
2226
- return { exists: false, content: "", evidence: { path, detail: "File does not exist." } };
2227
- }
2232
+ let fd;
2228
2233
  try {
2229
- const stat = statSync3(abs);
2234
+ fd = openSync2(abs, "r");
2235
+ const stat = fstatSync2(fd);
2230
2236
  if (!stat.isFile()) {
2231
2237
  return { exists: false, content: "", evidence: { path, detail: "Path is not a regular file." } };
2232
2238
  }
@@ -2247,7 +2253,7 @@ var fileEvidence = (root, path, options) => {
2247
2253
  evidence: { path, detail: `Text evidence exceeds ${MAX_TEXT_EVIDENCE_BYTES} bytes.` }
2248
2254
  };
2249
2255
  }
2250
- const content = readFileSync8(abs, "utf8");
2256
+ const content = readFileSync8(fd, "utf8");
2251
2257
  return {
2252
2258
  exists: content.trim().length > 0,
2253
2259
  content,
@@ -2256,8 +2262,15 @@ var fileEvidence = (root, path, options) => {
2256
2262
  detail: content.trim().length > 0 ? "File exists and is non-empty." : "File is empty."
2257
2263
  }
2258
2264
  };
2259
- } catch {
2260
- return { exists: false, content: "", evidence: { path, detail: "File is not readable text." } };
2265
+ } catch (error) {
2266
+ const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
2267
+ return {
2268
+ exists: false,
2269
+ content: "",
2270
+ evidence: { path, detail: code === "ENOENT" ? "File does not exist." : "File is not readable text." }
2271
+ };
2272
+ } finally {
2273
+ if (fd !== void 0) closeSync2(fd);
2261
2274
  }
2262
2275
  };
2263
2276
  var resultWithException = (draft, options) => {
@@ -2623,7 +2636,7 @@ var EntitySchema = z3.object({
2623
2636
  aliases: z3.array(boundedString(256)).max(32).optional(),
2624
2637
  provenance: ProvenanceSchema,
2625
2638
  evidence: z3.array(EvidenceSchema2).max(64),
2626
- metadata: z3.record(z3.unknown()).optional()
2639
+ metadata: z3.record(z3.string(), z3.unknown()).optional()
2627
2640
  }).strict();
2628
2641
  var RelationSchema = z3.object({
2629
2642
  id: boundedString(256),
@@ -2633,7 +2646,7 @@ var RelationSchema = z3.object({
2633
2646
  discriminator: boundedString(256).optional(),
2634
2647
  provenance: ProvenanceSchema,
2635
2648
  evidence: z3.array(EvidenceSchema2).max(64),
2636
- metadata: z3.record(z3.unknown()).optional()
2649
+ metadata: z3.record(z3.string(), z3.unknown()).optional()
2637
2650
  }).strict();
2638
2651
  var DiscoverySnapshotV1Schema = z3.object({
2639
2652
  type: z3.literal("discovery-snapshot"),
@@ -2679,11 +2692,14 @@ var ReconciliationReportV1Schema = z3.object({
2679
2692
  diagnosticCount: z3.number().int().nonnegative(),
2680
2693
  scope: z3.enum(["file", "module", "package"]).optional(),
2681
2694
  requiredRelationKinds: z3.array(boundedString(128)).max(128).optional(),
2695
+ requiredRelationTargets: z3.enum(["all", "internal"]).optional(),
2682
2696
  diagnosticsByCode: z3.record(z3.string().max(128), z3.number().int().nonnegative()).optional(),
2683
2697
  diagnosticsByStatus: z3.record(z3.string().max(128), z3.number().int().nonnegative()).optional(),
2684
2698
  documentation: z3.object({
2685
2699
  documentCount: z3.number().int().nonnegative(),
2686
2700
  documentedDocumentCount: z3.number().int().nonnegative(),
2701
+ documentClassificationCounts: z3.record(z3.string().max(128), z3.number().int().nonnegative()),
2702
+ documentedDocumentClassificationCounts: z3.record(z3.string().max(128), z3.number().int().nonnegative()),
2687
2703
  packageCount: z3.number().int().nonnegative(),
2688
2704
  packageStatus: z3.object({
2689
2705
  fresh: z3.number().int().nonnegative(),
@@ -2809,6 +2825,29 @@ var scalar = (value) => {
2809
2825
  }
2810
2826
  return trimmed;
2811
2827
  };
2828
+ var isFieldName = (value) => {
2829
+ if (!/^[A-Za-z]/.test(value)) return false;
2830
+ for (const character of value.slice(1)) {
2831
+ if (!/[A-Za-z0-9_-]/.test(character)) return false;
2832
+ }
2833
+ return true;
2834
+ };
2835
+ var parseIndentedField = (raw, indentation) => {
2836
+ const prefix = " ".repeat(indentation);
2837
+ if (!raw.startsWith(prefix) || raw[indentation] === " ") return void 0;
2838
+ const body = raw.slice(indentation);
2839
+ const separator = body.indexOf(":");
2840
+ if (separator <= 0) return void 0;
2841
+ const key = body.slice(0, separator).trim();
2842
+ return isFieldName(key) ? { key, value: body.slice(separator + 1).trim() } : void 0;
2843
+ };
2844
+ var parseListItem = (raw, indentation) => {
2845
+ const prefix = `${" ".repeat(indentation)}-`;
2846
+ if (!raw.startsWith(prefix)) return void 0;
2847
+ const rest = raw.slice(prefix.length);
2848
+ if (rest && !/\s/.test(rest[0] ?? "")) return void 0;
2849
+ return rest.trim();
2850
+ };
2812
2851
  var conventionalPackageReference = (path, agentRoot) => {
2813
2852
  const prefix = `${agentRoot.replace(/\/$/, "")}/`;
2814
2853
  if (!path.startsWith(prefix)) return void 0;
@@ -2918,11 +2957,10 @@ var parseBlock = (input, options = {}) => {
2918
2957
  section = void 0;
2919
2958
  continue;
2920
2959
  }
2921
- if (/^ {2}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
2960
+ const sectionField = parseIndentedField(raw, 2);
2961
+ if (sectionField) {
2922
2962
  finishRelation();
2923
- const match = /^ {2}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
2924
- const key = match?.[1];
2925
- const value = match?.[2] ?? "";
2963
+ const { key, value } = sectionField;
2926
2964
  if (key !== "covers" && key !== "relations") {
2927
2965
  addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown docbridge field: ${key ?? "(missing)"}.`, line);
2928
2966
  section = void 0;
@@ -2939,35 +2977,36 @@ var parseBlock = (input, options = {}) => {
2939
2977
  }
2940
2978
  continue;
2941
2979
  }
2942
- if (section === "covers" && /^ {4}-\s*/.test(raw)) {
2943
- const value = scalar(raw.replace(/^ {4}-\s*/, ""));
2980
+ const listValue = parseListItem(raw, 4);
2981
+ if (section === "covers" && listValue !== void 0) {
2982
+ const value = scalar(listValue);
2944
2983
  if (!value) addDiagnostic(diagnostics, input, "DOCBRIDGE_REFERENCE_MISSING", "covers entries must not be empty.", line);
2945
2984
  else covers.push({ value, line });
2946
2985
  continue;
2947
2986
  }
2948
- if (section === "relations" && /^ {4}-\s*/.test(raw)) {
2987
+ if (section === "relations" && listValue !== void 0) {
2949
2988
  finishRelation();
2950
- const firstField = /^ {4}-\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
2989
+ const firstField = parseIndentedField(` ${listValue}`, 4);
2951
2990
  current = { startLine: line, endLine: line, fields: /* @__PURE__ */ new Set() };
2952
- if (firstField?.[1]) {
2953
- current.fields.add(firstField[1]);
2954
- current[firstField[1]] = scalar(firstField[2] ?? "");
2955
- } else if (raw.replace(/^ {4}-\s*/, "").trim()) {
2991
+ if (firstField?.key) {
2992
+ current.fields.add(firstField.key);
2993
+ current[firstField.key] = scalar(firstField.value);
2994
+ } else if (listValue) {
2956
2995
  addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATION_INVALID", "Relation entries must be field mappings.", line);
2957
2996
  }
2958
2997
  continue;
2959
2998
  }
2960
- if (section === "relations" && current && /^ {6}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
2961
- const field = /^ {6}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
2962
- const key = field?.[1];
2999
+ const field = parseIndentedField(raw, 6);
3000
+ if (section === "relations" && current && field) {
3001
+ const { key, value } = field;
2963
3002
  current.endLine = line;
2964
- if (!key || !["from", "to", "kind", "detection"].includes(key)) {
2965
- addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown relation field: ${key ?? "(missing)"}.`, line);
3003
+ if (!["from", "to", "kind", "detection"].includes(key)) {
3004
+ addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown relation field: ${key}.`, line);
2966
3005
  } else if (current.fields.has(key)) {
2967
3006
  addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_DUPLICATE", `Duplicate relation field: ${key}.`, line);
2968
3007
  } else {
2969
3008
  current.fields.add(key);
2970
- current[key] = scalar(field?.[2] ?? "");
3009
+ current[key] = scalar(value);
2971
3010
  }
2972
3011
  continue;
2973
3012
  }
@@ -3058,14 +3097,14 @@ var applyDocumentationDeclarations = (snapshot, documents, options = {}) => {
3058
3097
  // src/discovery/repository.ts
3059
3098
  import { execFileSync } from "child_process";
3060
3099
  import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
3061
- import { basename as basename4, dirname as dirname5, extname, join as join8, relative as relative6, resolve as resolve8, sep as sep7 } from "path";
3100
+ import { basename as basename4, dirname as dirname4, extname, join as join8, relative as relative6, resolve as resolve8, sep as sep7 } from "path";
3062
3101
  import * as ts from "typescript";
3063
3102
 
3064
3103
  // src/safety/repository.ts
3065
- import { lstatSync as lstatSync3, readdirSync as readdirSync3, realpathSync as realpathSync7, statSync as statSync4 } from "fs";
3104
+ import { lstatSync as lstatSync3, readdirSync as readdirSync3, realpathSync as realpathSync7, statSync as statSync2 } from "fs";
3066
3105
  import { isAbsolute as isAbsolute5, relative as relative5, resolve as resolve7, sep as sep6 } from "path";
3067
3106
  import { minimatch as minimatch4 } from "minimatch";
3068
- var DEFAULT_SAFETY_EXCLUDES = ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/build/**", "**/coverage/**", "**/.doc-bridge/**", "**/.turbo/**", "**/.env", "**/.env.*", "**/*secret*", "**/*credential*", "**/*.pem", "**/*.key"];
3107
+ var DEFAULT_SAFETY_EXCLUDES = ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/build/**", "**/coverage/**", "**/.doc-bridge/**", "**/.next/**", "**/out/**", "**/.turbo/**", "**/.svelte-kit/**", "**/.mcpb-build/**", "**/.mcpb-output/**", "**/.env", "**/.env.*", "**/*secret*", "**/*credential*", "**/*.pem", "**/*.key"];
3069
3108
  var containedPath = (root, candidate) => {
3070
3109
  const projectRoot = realpathSync7.native(resolve7(root));
3071
3110
  const unresolved = resolve7(projectRoot, candidate);
@@ -3125,7 +3164,7 @@ var safeWalkFiles = (root, options = {}) => {
3125
3164
  reason = `Repository scan exceeded the ${options.maxFiles ?? 1e4} file limit.`;
3126
3165
  return;
3127
3166
  }
3128
- bytes += statSync4(absolute).size;
3167
+ bytes += statSync2(absolute).size;
3129
3168
  if (options.maxBytes !== void 0 && bytes > options.maxBytes) {
3130
3169
  reason = `Repository scan exceeded the ${options.maxBytes} byte limit.`;
3131
3170
  return;
@@ -3182,6 +3221,7 @@ var firstLineContaining = (text, pattern) => {
3182
3221
  };
3183
3222
  var documentClassification = (path) => {
3184
3223
  if (/(^|\/)docs\/for-agents(?:\/|$)/.test(path)) return "agent";
3224
+ if (/(^|\/)docs-archive(?:\/|$)/.test(path)) return "archive";
3185
3225
  if (/(^|\/)docs(?:\/|$)/.test(path)) return "human";
3186
3226
  if (/(^|\/)(README|CONTRIBUTING|SECURITY|CHANGELOG)(?:\.|$)/i.test(path)) return "project";
3187
3227
  return "unclassified";
@@ -3260,7 +3300,7 @@ var readCompilerOptions = (root) => {
3260
3300
  if (!configPath) return { options: {} };
3261
3301
  const parsed = ts.readConfigFile(configPath, ts.sys.readFile);
3262
3302
  if (parsed.error) return { options: {}, error: ts.flattenDiagnosticMessageText(parsed.error.messageText, "\n") };
3263
- const config = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname5(configPath));
3303
+ const config = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname4(configPath));
3264
3304
  if (config.errors.length) {
3265
3305
  return {
3266
3306
  options: config.options,
@@ -3329,20 +3369,56 @@ var exportedNames = (sourceFile) => {
3329
3369
  };
3330
3370
  var moduleReferences = (root, path, sourceFile, runtimeWiringMethods) => {
3331
3371
  const references = [];
3372
+ const dynamicEvidence = [];
3332
3373
  let hasDynamic = false;
3333
3374
  let hasLiteralDynamic = false;
3334
3375
  let hasRuntimeWiring = false;
3335
3376
  let hasUnresolvedRuntimeWiring = false;
3336
3377
  const importedBindings = /* @__PURE__ */ new Map();
3337
3378
  const staticStringBindings = /* @__PURE__ */ new Map();
3379
+ const localBindings = /* @__PURE__ */ new Set();
3380
+ const resolveStaticString = (expression) => {
3381
+ if (ts.isStringLiteralLike(expression)) return expression.text;
3382
+ if (ts.isIdentifier(expression)) return staticStringBindings.get(expression.text);
3383
+ if (ts.isParenthesizedExpression(expression)) return resolveStaticString(expression.expression);
3384
+ if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.PlusToken) {
3385
+ const left = resolveStaticString(expression.left);
3386
+ const right = resolveStaticString(expression.right);
3387
+ return left !== void 0 && right !== void 0 ? left + right : void 0;
3388
+ }
3389
+ return void 0;
3390
+ };
3338
3391
  const collectStaticStringBindings = (node) => {
3339
- if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isStringLiteralLike(node.initializer) && ts.isVariableDeclarationList(node.parent) && (node.parent.flags & ts.NodeFlags.Const) !== 0) {
3392
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer && ts.isVariableDeclarationList(node.parent) && (node.parent.flags & ts.NodeFlags.Const) !== 0) {
3393
+ const value = resolveStaticString(node.initializer);
3340
3394
  const previous = staticStringBindings.get(node.name.text);
3341
- staticStringBindings.set(node.name.text, !staticStringBindings.has(node.name.text) || previous === node.initializer.text ? node.initializer.text : void 0);
3395
+ staticStringBindings.set(node.name.text, !staticStringBindings.has(node.name.text) || previous === value ? value : void 0);
3342
3396
  }
3397
+ if ((ts.isVariableDeclaration(node) || ts.isParameter(node) || ts.isBindingElement(node)) && ts.isIdentifier(node.name)) localBindings.add(node.name.text);
3398
+ if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isEnumDeclaration(node)) && node.name) localBindings.add(node.name.text);
3343
3399
  ts.forEachChild(node, collectStaticStringBindings);
3344
3400
  };
3345
3401
  collectStaticStringBindings(sourceFile);
3402
+ const addImportedBindingReference = (expression, node) => {
3403
+ if (ts.isIdentifier(expression)) {
3404
+ const specifier = importedBindings.get(expression.text);
3405
+ if (specifier) {
3406
+ addReference({ text: specifier }, "runtime-wiring", node, "runtime-wiring-static");
3407
+ return true;
3408
+ }
3409
+ return false;
3410
+ }
3411
+ if (ts.isPropertyAccessExpression(expression)) return addImportedBindingReference(expression.expression, node);
3412
+ if (ts.isCallExpression(expression)) return addImportedBindingReference(expression.expression, node);
3413
+ return false;
3414
+ };
3415
+ const isKnownLocal = (expression) => {
3416
+ if (ts.isIdentifier(expression)) return localBindings.has(expression.text) || importedBindings.has(expression.text);
3417
+ if (expression.kind === ts.SyntaxKind.ThisKeyword) return true;
3418
+ if (ts.isPropertyAccessExpression(expression)) return isKnownLocal(expression.expression);
3419
+ if (ts.isCallExpression(expression)) return isKnownLocal(expression.expression);
3420
+ return ts.isStringLiteralLike(expression);
3421
+ };
3346
3422
  const addReference = (specifier, kind, node, detection) => {
3347
3423
  references.push({ specifier: specifier.text, kind, evidence: nodeEvidence(root, path, sourceFile, node), ...detection ? { detection } : {} });
3348
3424
  };
@@ -3362,32 +3438,35 @@ var moduleReferences = (root, path, sourceFile, runtimeWiringMethods) => {
3362
3438
  importedBindings.set(node.name.text, node.moduleReference.expression.text);
3363
3439
  } else if (ts.isCallExpression(node)) {
3364
3440
  if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
3365
- if (node.arguments[0] && ts.isStringLiteralLike(node.arguments[0])) {
3366
- hasLiteralDynamic = true;
3367
- addReference(node.arguments[0], "imports", node, "dynamic-literal");
3368
- } else if (node.arguments[0] && ts.isIdentifier(node.arguments[0]) && staticStringBindings.get(node.arguments[0].text)) {
3441
+ const specifier = node.arguments[0] ? resolveStaticString(node.arguments[0]) : void 0;
3442
+ if (specifier !== void 0) {
3369
3443
  hasLiteralDynamic = true;
3370
- addReference({ text: staticStringBindings.get(node.arguments[0].text) }, "imports", node, "dynamic-literal");
3371
- } else hasDynamic = true;
3444
+ dynamicEvidence.push(nodeEvidence(root, path, sourceFile, node));
3445
+ addReference({ text: specifier }, "imports", node, "dynamic-literal");
3446
+ } else {
3447
+ hasDynamic = true;
3448
+ dynamicEvidence.push(nodeEvidence(root, path, sourceFile, node));
3449
+ }
3372
3450
  } else if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
3373
3451
  const argument = node.arguments[0];
3374
- if (argument && ts.isStringLiteralLike(argument)) addReference(argument, "imports", node);
3375
- else if (argument && ts.isIdentifier(argument) && staticStringBindings.get(argument.text)) addReference({ text: staticStringBindings.get(argument.text) }, "imports", node);
3376
- else hasDynamic = true;
3452
+ const specifier = argument ? resolveStaticString(argument) : void 0;
3453
+ if (specifier !== void 0) {
3454
+ dynamicEvidence.push(nodeEvidence(root, path, sourceFile, node));
3455
+ addReference({ text: specifier }, "imports", node);
3456
+ } else {
3457
+ hasDynamic = true;
3458
+ dynamicEvidence.push(nodeEvidence(root, path, sourceFile, node));
3459
+ }
3377
3460
  } else if (ts.isPropertyAccessExpression(node.expression) && runtimeWiringMethods.has(node.expression.name.text)) {
3378
3461
  hasRuntimeWiring = true;
3379
- const before = references.length;
3380
- let hasPotentialTargetArgument = false;
3462
+ let hasUnresolvedTarget = false;
3381
3463
  for (const argument of node.arguments) {
3382
- if (ts.isIdentifier(argument)) {
3383
- hasPotentialTargetArgument = true;
3384
- const specifier = importedBindings.get(argument.text);
3385
- if (specifier) addReference({ text: specifier }, "runtime-wiring", node, "runtime-wiring-static");
3386
- } else if (ts.isPropertyAccessExpression(argument) || ts.isCallExpression(argument)) {
3387
- hasPotentialTargetArgument = true;
3388
- }
3464
+ if (ts.isStringLiteralLike(argument)) continue;
3465
+ if (addImportedBindingReference(argument, node)) continue;
3466
+ if (!isKnownLocal(argument)) hasUnresolvedTarget = true;
3389
3467
  }
3390
- if (hasPotentialTargetArgument && references.length === before) hasUnresolvedRuntimeWiring = true;
3468
+ const receiver = node.expression.expression;
3469
+ if (hasUnresolvedTarget && !isKnownLocal(receiver)) hasUnresolvedRuntimeWiring = true;
3391
3470
  }
3392
3471
  }
3393
3472
  ts.forEachChild(node, visit);
@@ -3396,6 +3475,7 @@ var moduleReferences = (root, path, sourceFile, runtimeWiringMethods) => {
3396
3475
  return {
3397
3476
  references,
3398
3477
  exports: exportedNames(sourceFile),
3478
+ dynamicEvidence,
3399
3479
  hasDynamic,
3400
3480
  hasLiteralDynamic,
3401
3481
  hasRuntimeWiring,
@@ -3403,7 +3483,7 @@ var moduleReferences = (root, path, sourceFile, runtimeWiringMethods) => {
3403
3483
  };
3404
3484
  };
3405
3485
  var resolveRelativeModule = (specifier, containingFile, modulePaths) => {
3406
- const base = resolve8(dirname5(containingFile), specifier);
3486
+ const base = resolve8(dirname4(containingFile), specifier);
3407
3487
  const extension = extname(base);
3408
3488
  const extensionlessBase = extension ? base.slice(0, -extension.length) : base;
3409
3489
  const candidates = [
@@ -3472,11 +3552,11 @@ var artifact = (root, config, files, entities, relations, coverage) => {
3472
3552
  sourceRevision: revision.value,
3473
3553
  sourceRevisionKind: revision.kind,
3474
3554
  configurationHash: sha256NormalizedV1(config ?? {}),
3475
- pipelineVersion: "1.0.0",
3476
- analyzerVersions: { repository: "1.0.0", "js-ts": "1.3.0" },
3555
+ pipelineVersion: "1.1.8",
3556
+ analyzerVersions: { repository: "1.1.1", "js-ts": "1.3.4" },
3477
3557
  entities: [...entities].sort((a, b) => a.id.localeCompare(b.id)),
3478
3558
  relations: [...relations].sort((a, b) => a.id.localeCompare(b.id)),
3479
- coverage: coverage.map((entry) => ({ ...entry, analyzerVersion: entry.analyzerVersion ?? ({ repository: "1.0.0", "js-ts": "1.3.0" }[entry.analyzer] ?? "1.0.0") }))
3559
+ coverage: coverage.map((entry) => ({ ...entry, analyzerVersion: entry.analyzerVersion ?? ({ repository: "1.1.1", "js-ts": "1.3.4" }[entry.analyzer] ?? "1.0.0") }))
3480
3560
  };
3481
3561
  return DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
3482
3562
  };
@@ -3570,6 +3650,7 @@ var discoverRepository = (opts = {}) => {
3570
3650
  const includeTestRuntimeWiring = opts.config?.analysis?.jsTs?.includeTestRuntimeWiring ?? false;
3571
3651
  let observedLiteralDynamic = false;
3572
3652
  let observedUnresolvedDynamic = false;
3653
+ const observedDynamicEvidence = [];
3573
3654
  let observedRuntimeWiring = false;
3574
3655
  let observedUnresolvedRuntimeWiring = false;
3575
3656
  for (const module of modules.values()) {
@@ -3579,6 +3660,7 @@ var discoverRepository = (opts = {}) => {
3579
3660
  const references = moduleReferences(root, module.absPath, sourceFile, runtimeWiringMethods);
3580
3661
  observedLiteralDynamic ||= references.hasLiteralDynamic;
3581
3662
  observedUnresolvedDynamic ||= references.hasDynamic;
3663
+ observedDynamicEvidence.push(...references.dynamicEvidence);
3582
3664
  observedRuntimeWiring ||= references.hasRuntimeWiring;
3583
3665
  observedUnresolvedRuntimeWiring ||= references.hasUnresolvedRuntimeWiring;
3584
3666
  for (const reference of references.references) {
@@ -3590,10 +3672,10 @@ var discoverRepository = (opts = {}) => {
3590
3672
  }
3591
3673
  addRelation({ id: entityId("relation", `${module.entityId}:${reference.kind}:${target.targetId}`), kind: reference.kind, from: module.entityId, to: target.targetId, provenance: "observed", evidence: [reference.evidence], ...reference.detection ? { metadata: { detection: reference.detection } } : {} });
3592
3674
  }
3593
- if (references.hasLiteralDynamic || references.hasDynamic) coverage.push({ analyzer: "js-ts", scope: `dynamic-imports:${module.path}`, status: references.hasDynamic ? "not-analyzed" : "complete", reason: references.hasDynamic ? "A non-literal dynamic import was found; the target is unresolved." : "Literal dynamic imports were resolved.", evidence: [lineEvidence("code", root, module.absPath)] });
3675
+ if (references.hasLiteralDynamic || references.hasDynamic) coverage.push({ analyzer: "js-ts", scope: `dynamic-imports:${module.path}`, status: references.hasDynamic ? "not-analyzed" : "complete", reason: references.hasDynamic ? "A non-literal dynamic import was found; the target is unresolved." : "Literal dynamic imports were resolved.", evidence: [...references.dynamicEvidence.slice(0, 32)] });
3594
3676
  if (references.hasUnresolvedRuntimeWiring) coverage.push({ analyzer: "js-ts", scope: `runtime-wiring:${module.path}`, status: "not-analyzed", reason: "A runtime registration/wiring call was found without a statically imported target.", evidence: [lineEvidence("code", root, module.absPath)] });
3595
3677
  }
3596
- if (dynamicCoverageIndex >= 0) coverage[dynamicCoverageIndex] = observedUnresolvedDynamic ? { analyzer: "js-ts", scope: "dynamic-imports", status: "partial", reason: "Literal dynamic imports are resolved; non-literal import expressions and require calls remain unresolved." } : observedLiteralDynamic ? { analyzer: "js-ts", scope: "dynamic-imports", status: "complete", reason: "All observed dynamic imports used literal targets and were resolved." } : { analyzer: "js-ts", scope: "dynamic-imports", status: "not-applicable", reason: "No dynamic loading expression was observed." };
3678
+ if (dynamicCoverageIndex >= 0) coverage[dynamicCoverageIndex] = observedUnresolvedDynamic ? { analyzer: "js-ts", scope: "dynamic-imports", status: "partial", reason: "Literal dynamic imports are resolved; non-literal import expressions and require calls remain unresolved. Evidence lists representative dynamic loading sites.", evidence: [...observedDynamicEvidence.slice(0, 32)] } : observedLiteralDynamic ? { analyzer: "js-ts", scope: "dynamic-imports", status: "complete", reason: "All observed dynamic imports used literal targets and were resolved.", evidence: [...observedDynamicEvidence.slice(0, 32)] } : { analyzer: "js-ts", scope: "dynamic-imports", status: "not-applicable", reason: "No dynamic loading expression was observed." };
3597
3679
  const runtimeCoverageIndex = coverage.findIndex((entry) => entry.scope === "runtime-wiring");
3598
3680
  if (runtimeCoverageIndex >= 0) coverage[runtimeCoverageIndex] = observedUnresolvedRuntimeWiring ? { analyzer: "js-ts", scope: "runtime-wiring", status: "partial", reason: "Some configured runtime-wiring calls remain unresolved after static binding analysis." } : observedRuntimeWiring ? { analyzer: "js-ts", scope: "runtime-wiring", status: "complete", reason: "All observed configured runtime-wiring calls resolved to static bindings." } : { analyzer: "js-ts", scope: "runtime-wiring", status: "not-applicable", reason: "No configured runtime-wiring call was observed." };
3599
3681
  return artifact(root, opts.config, allFiles, [...entities.values()], [...relations.values()], coverage);
@@ -3727,9 +3809,20 @@ var defaultFetchText = async (url) => {
3727
3809
  if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
3728
3810
  return res.text();
3729
3811
  };
3812
+ var httpUrl = (value) => {
3813
+ try {
3814
+ const parsed = new URL(value);
3815
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return void 0;
3816
+ if (parsed.username || parsed.password) return void 0;
3817
+ return parsed.href;
3818
+ } catch {
3819
+ return void 0;
3820
+ }
3821
+ };
3730
3822
  var sourceText = async (root, source, fetchText) => {
3731
3823
  try {
3732
- if (/^https?:\/\//.test(source)) return await fetchText(source);
3824
+ const remote = httpUrl(source);
3825
+ if (remote) return await fetchText(remote);
3733
3826
  const path = resolve9(root, source);
3734
3827
  if (!existsSync11(path)) return null;
3735
3828
  return readFileSync10(path, "utf8");
@@ -3742,22 +3835,57 @@ var sameOrigin = (base, target) => {
3742
3835
  return new URL(base).origin === new URL(target).origin;
3743
3836
  };
3744
3837
  var parseLlmsTxtLinks = (raw) => {
3745
- const links = [...raw.matchAll(/\[([^\]]+)\]\(([^)]+)\)(?::\s*([^\n]+))?/g)].map((match) => ({
3746
- title: match[1] ?? match[2] ?? "link",
3747
- url: match[2] ?? "",
3748
- ...match[3]?.trim() ? { description: match[3].trim() } : {}
3749
- })).filter((link) => link.url);
3750
- for (const match of raw.matchAll(/(?:^|\s)(?:Raw|llms\.txt|Full bundle|ZIP bundle)?:?\s*(https?:\/\/\S+)/gi)) {
3751
- const url = match[1]?.replace(/[),.;]+$/, "");
3752
- if (url && !links.some((link) => link.url === url)) links.push({ title: slugFromPath(url), url });
3838
+ const links = [];
3839
+ for (const line of raw.split(/\r?\n/)) {
3840
+ let cursor = 0;
3841
+ while (cursor < line.length) {
3842
+ const open = line.indexOf("[", cursor);
3843
+ if (open < 0) break;
3844
+ const titleEnd = line.indexOf("]", open + 1);
3845
+ const urlStart = titleEnd < 0 ? -1 : line.indexOf("(", titleEnd + 1);
3846
+ const urlEnd = urlStart < 0 ? -1 : line.indexOf(")", urlStart + 1);
3847
+ if (titleEnd < 0 || urlStart !== titleEnd + 1 || urlEnd < 0) {
3848
+ cursor = open + 1;
3849
+ continue;
3850
+ }
3851
+ const title = line.slice(open + 1, titleEnd).trim();
3852
+ const url = line.slice(urlStart + 1, urlEnd).trim();
3853
+ const description = line.slice(urlEnd + 1).trim().replace(/^:\s*/, "");
3854
+ if (url) links.push({ title: title || url, url, ...description ? { description } : {} });
3855
+ cursor = urlEnd + 1;
3856
+ }
3857
+ for (const token of line.split(/\s+/)) {
3858
+ let end = token.length;
3859
+ while (end > 0 && "),.;:".includes(token[end - 1] ?? "")) end -= 1;
3860
+ const url = token.slice(0, end);
3861
+ if (!/^https?:\/\//i.test(url) || links.some((link) => link.url === url)) continue;
3862
+ links.push({ title: slugFromPath(url), url });
3863
+ }
3753
3864
  }
3754
3865
  return links;
3755
3866
  };
3867
+ var firstMatchingLine = (section, predicate) => section.split(/\r?\n/).find((line) => predicate(line.trim()))?.trim();
3868
+ var sectionTitle = (section, sourceUrl) => {
3869
+ const titleLine = firstMatchingLine(section, (line) => line.startsWith("title:"));
3870
+ if (titleLine) return titleLine.slice("title:".length).trim();
3871
+ const urlLine = firstMatchingLine(section, (line) => /^https?:\/\//i.test(line));
3872
+ if (urlLine) {
3873
+ try {
3874
+ return new URL(urlLine).pathname.split("/").filter(Boolean).at(-1) ?? slugFromPath(sourceUrl);
3875
+ } catch {
3876
+ return slugFromPath(sourceUrl);
3877
+ }
3878
+ }
3879
+ const heading = firstMatchingLine(section, (line) => line.startsWith("#"));
3880
+ if (heading) return heading.replace(/^#+\s*/, "").trim();
3881
+ return slugFromPath(sourceUrl);
3882
+ };
3756
3883
  var chunksFromMarkdown = (property, raw, sourceUrl) => {
3757
- const searchable = raw.includes("\n==== ") ? raw : raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
3884
+ const frontmatterEnd = raw.startsWith("---\n") ? raw.indexOf("\n---", 4) : -1;
3885
+ const searchable = raw.includes("\n==== ") ? raw : frontmatterEnd >= 0 ? raw.slice(frontmatterEnd + "\n---".length).replace(/^\n/, "") : raw;
3758
3886
  const sections = searchable.includes("\n==== ") ? searchable.split(/\n====\s+/).filter((section) => section.trim()) : searchable.split(/\n(?=##?\s+)/);
3759
3887
  return sections.map((section, index) => {
3760
- const title = /^title:\s*(.+)$/m.exec(section)?.[1]?.trim() ?? /^https?:\/\/\S+\/([^/\s]+)$/m.exec(section)?.[1]?.trim() ?? /^#+\s+(.+)$/m.exec(section)?.[1]?.trim() ?? slugFromPath(sourceUrl);
3888
+ const title = sectionTitle(section, sourceUrl);
3761
3889
  const id = slugFromPath(title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")) || `${index}`;
3762
3890
  return {
3763
3891
  chunkKey: `${property}:federated:${id}`,
@@ -3785,8 +3913,15 @@ var loadFederatedChunks = async (root, config, options = {}) => {
3785
3913
  chunks.push(...chunksFromMarkdown(source.id, llms, source.llmsTxt));
3786
3914
  const links = parseLlmsTxtLinks(llms);
3787
3915
  for (const link of links) {
3788
- const url = !/^https?:\/\//.test(link.url) && source.rawBaseUrl ? `${source.rawBaseUrl.replace(/\/$/, "")}/${link.url.replace(/^\//, "")}` : link.url;
3789
- if (!/\.(md|txt)(?:$|\?)/.test(url)) continue;
3916
+ const linkUrl = httpUrl(link.url);
3917
+ const baseUrl = source.rawBaseUrl ? httpUrl(source.rawBaseUrl) : void 0;
3918
+ const url = linkUrl ?? (baseUrl ? new URL(link.url, baseUrl).href : link.url);
3919
+ let pathname = url;
3920
+ try {
3921
+ pathname = new URL(url).pathname;
3922
+ } catch {
3923
+ }
3924
+ if (!/\.(md|txt)$/i.test(pathname)) continue;
3790
3925
  if (!sameOrigin(source.llmsTxt, url)) continue;
3791
3926
  const raw = await sourceText(root, url, fetchText);
3792
3927
  if (raw) chunks.push(...chunksFromMarkdown(source.id, raw, url));
@@ -3992,9 +4127,14 @@ var MemoryCandidateV1Schema = z6.object({
3992
4127
  }).strict();
3993
4128
 
3994
4129
  // src/validate.ts
4130
+ var zodMessage = (issue) => {
4131
+ if (issue.code === "invalid_type" && issue.message.endsWith("received undefined")) return "Required";
4132
+ if (issue.code === "invalid_value" && "values" in issue) return "Invalid enum value";
4133
+ return issue.message;
4134
+ };
3995
4135
  var zodIssues = (error) => error.issues.map((issue) => ({
3996
4136
  path: issue.path.join(".") || "(root)",
3997
- message: issue.message
4137
+ message: zodMessage(issue)
3998
4138
  }));
3999
4139
  var safeParseAgentHandoff = (input) => {
4000
4140
  const legacy = AgentHandoffLegacySchema.safeParse(input);
@@ -4336,8 +4476,20 @@ var evaluateRules = (report, options = {}) => {
4336
4476
  const sortedFindings = [...findings].sort((a, b) => a.id.localeCompare(b.id));
4337
4477
  return { mode: resolved.mode, findings: sortedFindings, exitCode: sortedFindings.some((finding) => finding.severity === "error") ? 1 : 0 };
4338
4478
  };
4339
- var parseRuleId = (value) => RuleIdSchema.parse(value);
4340
- var parseRuleSeverity = (value) => RuleSeveritySchema.parse(value);
4479
+ var parseRuleId = (value) => {
4480
+ try {
4481
+ return RuleIdSchema.parse(value);
4482
+ } catch {
4483
+ throw new Error(`Invalid enum value: ${value}`);
4484
+ }
4485
+ };
4486
+ var parseRuleSeverity = (value) => {
4487
+ try {
4488
+ return RuleSeveritySchema.parse(value);
4489
+ } catch {
4490
+ throw new Error(`Invalid enum value: ${value}`);
4491
+ }
4492
+ };
4341
4493
 
4342
4494
  // src/query/query.ts
4343
4495
  var handoffForPackage = (index, id, config) => {
@@ -5008,7 +5160,7 @@ ${auth.out}`
5008
5160
 
5009
5161
  // src/index-builder/watch-index.ts
5010
5162
  import { existsSync as existsSync15, watch } from "fs";
5011
- import { dirname as dirname6, resolve as resolve11 } from "path";
5163
+ import { dirname as dirname5, resolve as resolve11 } from "path";
5012
5164
  var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
5013
5165
  var NX_MANIFEST_PATTERN = /(^|[/\\])(project|package)\.json$/i;
5014
5166
  var collectWatchRoots = (root, config, configPath) => {
@@ -5022,7 +5174,7 @@ var collectWatchRoots = (root, config, configPath) => {
5022
5174
  if (typeof value === "string" && value.length) roots.add(resolve11(root, value));
5023
5175
  }
5024
5176
  }
5025
- if (configPath) roots.add(dirname6(resolve11(configPath)));
5177
+ if (configPath) roots.add(dirname5(resolve11(configPath)));
5026
5178
  return [...roots].filter((dir) => existsSync15(dir));
5027
5179
  };
5028
5180
  var watchDocBridgeIndex = (opts) => {
@@ -5091,7 +5243,7 @@ var watchDocBridgeIndex = (opts) => {
5091
5243
 
5092
5244
  // src/workflow/engine.ts
5093
5245
  import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync3, readFileSync as readFileSync15, renameSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
5094
- import { join as join15, relative as relative7, resolve as resolve12 } from "path";
5246
+ import { join as join14, relative as relative7, resolve as resolve12 } from "path";
5095
5247
  var WORKFLOW_STAGES = ["collect", "normalize", "reconcile", "evaluate", "report"];
5096
5248
  var stageState = {
5097
5249
  collect: "discovering",
@@ -5100,7 +5252,7 @@ var stageState = {
5100
5252
  evaluate: "proposed",
5101
5253
  report: "delivered"
5102
5254
  };
5103
- var defaultStateDir = (root) => join15(root, ".doc-bridge", "workflow");
5255
+ var defaultStateDir = (root) => join14(root, ".doc-bridge", "workflow");
5104
5256
  var atomicWrite = (path, value) => {
5105
5257
  const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
5106
5258
  writeFileSync3(temp, `${JSON.stringify(value, null, 2)}
@@ -5108,10 +5260,10 @@ var atomicWrite = (path, value) => {
5108
5260
  renameSync(temp, path);
5109
5261
  };
5110
5262
  var writeManifest = (stateDir, run2) => {
5111
- atomicWrite(join15(stateDir, "manifest.json"), run2);
5263
+ atomicWrite(join14(stateDir, "manifest.json"), run2);
5112
5264
  };
5113
5265
  var appendTransition = (stateDir, transition2) => {
5114
- appendFileSync(join15(stateDir, "transitions.jsonl"), `${JSON.stringify(transition2)}
5266
+ appendFileSync(join14(stateDir, "transitions.jsonl"), `${JSON.stringify(transition2)}
5115
5267
  `, "utf8");
5116
5268
  };
5117
5269
  var transition = (run2, to, reason) => {
@@ -5141,7 +5293,7 @@ var transition = (run2, to, reason) => {
5141
5293
  };
5142
5294
  var runId = () => `${Date.now()}-${process.pid}`;
5143
5295
  var stageInputHash = (options, stage, input) => sha256NormalizedV1({ stage, input, sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, pipelineVersion: options.pipelineVersion ?? "1.0.0", analyzerVersions: options.analyzerVersions ?? {}, toolVersion: options.toolVersion ?? "1.0.0" });
5144
- var stageArtifactPath = (stateDir, stage, inputHash) => join15(stateDir, "artifacts", `${stage}-${inputHash}.json`);
5296
+ var stageArtifactPath = (stateDir, stage, inputHash) => join14(stateDir, "artifacts", `${stage}-${inputHash}.json`);
5145
5297
  var readArtifact = (path) => JSON.parse(readFileSync15(path, "utf8"));
5146
5298
  var readVerifiedArtifact = (path, stage, step) => {
5147
5299
  const artifact2 = readArtifact(path);
@@ -5161,11 +5313,11 @@ var stepOutput = (stateDir, run2, stage) => {
5161
5313
  return readVerifiedArtifact(stepArtifactPath(stateDir, step), stage, step).value;
5162
5314
  };
5163
5315
  var acquireLock = (stateDir) => {
5164
- const lock = join15(stateDir, ".lock");
5316
+ const lock = join14(stateDir, ".lock");
5165
5317
  try {
5166
5318
  mkdirSync3(lock);
5167
5319
  } catch {
5168
- const ownerPath = join15(lock, "owner.json");
5320
+ const ownerPath = join14(lock, "owner.json");
5169
5321
  try {
5170
5322
  const owner = JSON.parse(readFileSync15(ownerPath, "utf8"));
5171
5323
  if (typeof owner.pid === "number") process.kill(owner.pid, 0);
@@ -5176,11 +5328,11 @@ var acquireLock = (stateDir) => {
5176
5328
  mkdirSync3(lock);
5177
5329
  }
5178
5330
  }
5179
- writeFileSync3(join15(lock, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
5331
+ writeFileSync3(join14(lock, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
5180
5332
  return () => rmSync(lock, { recursive: true, force: true });
5181
5333
  };
5182
5334
  var loadManifest = (stateDir) => {
5183
- const path = join15(stateDir, "manifest.json");
5335
+ const path = join14(stateDir, "manifest.json");
5184
5336
  if (!existsSync16(path)) return void 0;
5185
5337
  return WorkflowRunV1Schema.parse(JSON.parse(readFileSync15(path, "utf8")));
5186
5338
  };
@@ -5210,7 +5362,7 @@ var selectedStages = (stage) => stage && stage !== "all" ? [stage] : WORKFLOW_ST
5210
5362
  var runWorkflow = (options) => {
5211
5363
  const root = resolve12(options.root);
5212
5364
  const stateDir = resolve12(root, options.stateDir ?? defaultStateDir(root));
5213
- mkdirSync3(join15(stateDir, "artifacts"), { recursive: true });
5365
+ mkdirSync3(join14(stateDir, "artifacts"), { recursive: true });
5214
5366
  const release = acquireLock(stateDir);
5215
5367
  try {
5216
5368
  let run2 = loadManifest(stateDir);
@@ -5276,7 +5428,7 @@ var runWorkflow = (options) => {
5276
5428
  const value = handler({ root, stage, input, previousOutput });
5277
5429
  const outputHash = sha256NormalizedV1(value);
5278
5430
  const artifact2 = { type: "workflow-step-artifact", stage, inputHash, outputHash, value };
5279
- mkdirSync3(join15(stateDir, "artifacts"), { recursive: true });
5431
+ mkdirSync3(join14(stateDir, "artifacts"), { recursive: true });
5280
5432
  if (existsSync16(artifactPath)) {
5281
5433
  const existingArtifact = readArtifact(artifactPath);
5282
5434
  if (existingArtifact.outputHash !== outputHash) throw new Error(`Immutable workflow artifact collision for stage "${stage}".`);
@@ -5302,14 +5454,14 @@ var runWorkflow = (options) => {
5302
5454
  run2 = complete;
5303
5455
  }
5304
5456
  writeManifest(stateDir, run2);
5305
- if (run2.state === "delivered") atomicWrite(join15(stateDir, "last-known-good.json"), { runId: run2.runId, manifestHash: run2.contentHash, report: run2.steps.find((step) => step.name === "report")?.artifactRefs?.[0] });
5457
+ if (run2.state === "delivered") atomicWrite(join14(stateDir, "last-known-good.json"), { runId: run2.runId, manifestHash: run2.contentHash, report: run2.steps.find((step) => step.name === "report")?.artifactRefs?.[0] });
5306
5458
  }
5307
5459
  return { run: run2, stateDir, reusedStages };
5308
5460
  } finally {
5309
5461
  release();
5310
5462
  }
5311
5463
  };
5312
- var loadWorkflowManifest = (stateDir) => WorkflowRunV1Schema.parse(JSON.parse(readFileSync15(join15(resolve12(stateDir), "manifest.json"), "utf8")));
5464
+ var loadWorkflowManifest = (stateDir) => WorkflowRunV1Schema.parse(JSON.parse(readFileSync15(join14(resolve12(stateDir), "manifest.json"), "utf8")));
5313
5465
  var loadWorkflowStepOutput = (stateDir, stage) => stepOutput(resolve12(stateDir), loadWorkflowManifest(stateDir), stage);
5314
5466
 
5315
5467
  // src/doctor/badge.ts
@@ -5473,13 +5625,13 @@ var docBridgePatternPayload = () => ({
5473
5625
  // src/cli/demo.ts
5474
5626
  import { cpSync, existsSync as existsSync18, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync2 } from "fs";
5475
5627
  import { tmpdir } from "os";
5476
- import { dirname as dirname8, join as join17, resolve as resolve14 } from "path";
5628
+ import { dirname as dirname7, join as join16, resolve as resolve14 } from "path";
5477
5629
  import { fileURLToPath } from "url";
5478
5630
 
5479
5631
  // src/mcp/install.ts
5480
5632
  import { existsSync as existsSync17, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "fs";
5481
5633
  import { homedir } from "os";
5482
- import { dirname as dirname7, join as join16, resolve as resolve13 } from "path";
5634
+ import { dirname as dirname6, join as join15, resolve as resolve13 } from "path";
5483
5635
  var SERVER_NAME = "ak-docs";
5484
5636
  var mcpServerEntry = (root) => ({
5485
5637
  command: "npx",
@@ -5496,13 +5648,13 @@ var readJson2 = (path) => {
5496
5648
  }
5497
5649
  };
5498
5650
  var writeJson = (path, value) => {
5499
- mkdirSync4(dirname7(path), { recursive: true });
5651
+ mkdirSync4(dirname6(path), { recursive: true });
5500
5652
  writeFileSync4(path, `${JSON.stringify(value, null, 2)}
5501
5653
  `, "utf8");
5502
5654
  };
5503
5655
  var resolveTargetPath = (target, root) => {
5504
5656
  if (target === "cursor") return resolve13(root, ".cursor", "mcp.json");
5505
- return join16(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
5657
+ return join15(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
5506
5658
  };
5507
5659
  var installMcpConfig = (root, target) => {
5508
5660
  const configPath = resolveTargetPath(target, root);
@@ -5531,12 +5683,12 @@ var installMcpConfig = (root, target) => {
5531
5683
  var mcpSnippet = (root) => JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpServerEntry(root) } }, null, 2);
5532
5684
 
5533
5685
  // src/cli/demo.ts
5534
- var packageRoot = resolve14(dirname8(fileURLToPath(import.meta.url)), "..", "..");
5686
+ var packageRoot = resolve14(dirname7(fileURLToPath(import.meta.url)), "..", "..");
5535
5687
  var fixturePath = (fixture) => {
5536
5688
  if (fixture === "monorepo") {
5537
- return join17(packageRoot, "examples", "demo-monorepo");
5689
+ return join16(packageRoot, "examples", "demo-monorepo");
5538
5690
  }
5539
- return join17(packageRoot, "examples", "demo-example");
5691
+ return join16(packageRoot, "examples", "demo-example");
5540
5692
  };
5541
5693
  var formatHandoffText = (handoff) => {
5542
5694
  const bridge = handoff.bridge?.humanDoc === "missing" ? `human guide: missing \u2192 ${handoff.bridge.action ?? "ak-docs bootstrap agent-docs"}` : handoff.humanDoc ? `human guide: ${handoff.humanDoc}` : "human guide: (none)";
@@ -5554,9 +5706,9 @@ var runDemo = (root, config, fixture = "example", options = {}) => {
5554
5706
  const fixtureDir = fixturePath(fixture);
5555
5707
  if (options.copyFixture && existsSync18(fixtureDir)) {
5556
5708
  for (const rel of ["doc-bridge.config.json", "docs", "packages", "pnpm-workspace.yaml", "package.json"]) {
5557
- const src = join17(fixtureDir, rel);
5709
+ const src = join16(fixtureDir, rel);
5558
5710
  if (!existsSync18(src)) continue;
5559
- const dest = join17(root, rel);
5711
+ const dest = join16(root, rel);
5560
5712
  cpSync(src, dest, { recursive: true });
5561
5713
  }
5562
5714
  }
@@ -5611,14 +5763,14 @@ var formatDemoText = (result) => {
5611
5763
  return lines;
5612
5764
  };
5613
5765
  var withDemoWorkspace = (fixture, fn) => {
5614
- const dir = mkdtempSync(join17(tmpdir(), "ak-docs-demo-"));
5766
+ const dir = mkdtempSync(join16(tmpdir(), "ak-docs-demo-"));
5615
5767
  try {
5616
5768
  const fixtureDir = fixturePath(fixture);
5617
5769
  if (!existsSync18(fixtureDir)) {
5618
5770
  throw new Error(`Demo fixture "${fixture}" not found at ${fixtureDir}`);
5619
5771
  }
5620
5772
  cpSync(fixtureDir, dir, { recursive: true });
5621
- const config = JSON.parse(readFileSync17(join17(dir, "doc-bridge.config.json"), "utf8"));
5773
+ const config = JSON.parse(readFileSync17(join16(dir, "doc-bridge.config.json"), "utf8"));
5622
5774
  return fn(dir, config);
5623
5775
  } finally {
5624
5776
  rmSync2(dir, { recursive: true, force: true });
@@ -5633,16 +5785,6 @@ var gradeForScore = (score) => {
5633
5785
  if (score >= 40) return "D";
5634
5786
  return "F";
5635
5787
  };
5636
- var agentDocPaths = (index) => {
5637
- const paths = /* @__PURE__ */ new Set();
5638
- for (const owner of Object.values(index.lookup?.ownership ?? {})) {
5639
- if (owner.agentDoc) paths.add(owner.agentDoc);
5640
- }
5641
- for (const handoff of Object.values(index.handoffs ?? {})) {
5642
- if (handoff.startHere) paths.add(handoff.startHere);
5643
- }
5644
- return paths;
5645
- };
5646
5788
  var computeScore = (coverage) => {
5647
5789
  let score = 0;
5648
5790
  if (coverage.freshness.hasIndex) score += 15;
@@ -5730,7 +5872,7 @@ var runDoctor = (root, config) => {
5730
5872
  let index;
5731
5873
  let hasIndex = true;
5732
5874
  let freshnessOk = false;
5733
- let freshnessMessage = "Index is fresh";
5875
+ let freshnessMessage;
5734
5876
  try {
5735
5877
  index = loadDocBridgeIndex(root, config);
5736
5878
  const next = buildDocBridgeIndex({ root, config, write: false }).index.contentHash;
@@ -5750,8 +5892,6 @@ var runDoctor = (root, config) => {
5750
5892
  const missingAgentDoc = ownership.filter(([, owner]) => !owner.agentDoc || owner.agentDoc === config.corpus.agent.index).map(([id]) => id);
5751
5893
  const missingHumanDoc = ownership.filter(([, owner]) => !owner.humanDoc).map(([id]) => id);
5752
5894
  const indexedPaths = new Set(index.knowledge.map((entry) => entry.path));
5753
- const expectedAgentDocs = agentDocPaths(index);
5754
- const unindexed = [...expectedAgentDocs].filter((path) => !indexedPaths.has(path));
5755
5895
  const corpusDocs = scanAgentCorpus(root, config).filter(
5756
5896
  (doc) => doc.path !== config.corpus.agent.index
5757
5897
  );
@@ -5829,13 +5969,13 @@ var formatDoctorText = (report) => {
5829
5969
  };
5830
5970
 
5831
5971
  // src/mcp/server.ts
5832
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync20, realpathSync as realpathSync9, writeFileSync as writeFileSync7 } from "fs";
5833
- import { join as join20, relative as relative9, resolve as resolve17 } from "path";
5972
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync20, realpathSync as realpathSync9, writeFileSync as writeFileSync7 } from "fs";
5973
+ import { join as join19, relative as relative9, resolve as resolve17 } from "path";
5834
5974
  import { z as z8, ZodError } from "zod";
5835
5975
 
5836
5976
  // src/fixes/proposals.ts
5837
- import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync18, realpathSync as realpathSync8, renameSync as renameSync2, statSync as statSync5, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
5838
- import { basename as basename5, dirname as dirname9, extname as extname2, join as join18, relative as relative8, resolve as resolve15, sep as sep8 } from "path";
5977
+ import { existsSync as existsSync19, readdirSync as readdirSync4, readFileSync as readFileSync18, realpathSync as realpathSync8, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
5978
+ import { basename as basename5, dirname as dirname8, extname as extname2, join as join17, relative as relative8, resolve as resolve15, sep as sep8 } from "path";
5839
5979
  var hash2 = (value) => sha256NormalizedV1(value);
5840
5980
  var artifactMetadata = (root, options) => ({
5841
5981
  schemaVersion: 1,
@@ -5874,7 +6014,7 @@ var makeProposal = (root, options, changes, preconditions, postconditions) => {
5874
6014
  };
5875
6015
  var walkMarkdown = (root, directory = root) => readdirSync4(directory, { withFileTypes: true }).flatMap((entry) => {
5876
6016
  if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") return [];
5877
- const path = join18(directory, entry.name);
6017
+ const path = join17(directory, entry.name);
5878
6018
  if (entry.isDirectory()) return walkMarkdown(root, path);
5879
6019
  return entry.isFile() && [".md", ".mdx"].includes(extname2(entry.name).toLowerCase()) ? [relative8(root, path).split(sep8).join("/")] : [];
5880
6020
  });
@@ -5885,18 +6025,18 @@ var createMarkdownLinkFixProposal = (root, options) => {
5885
6025
  const paths = walkMarkdown(projectRoot);
5886
6026
  const changes = [];
5887
6027
  for (const path of paths) {
5888
- const content = readFileSync18(join18(projectRoot, path), "utf8");
6028
+ const content = readFileSync18(join17(projectRoot, path), "utf8");
5889
6029
  let next = content;
5890
6030
  for (const match of content.matchAll(localLink)) {
5891
6031
  const target = match[3];
5892
6032
  if (!target || match[1] === "!" || /^(?:[a-z]+:|\/|#)/i.test(target)) continue;
5893
6033
  const targetPath = target.split("#")[0]?.split("?")[0];
5894
- if (!targetPath || existsSync19(resolve15(projectRoot, dirname9(path), targetPath))) continue;
6034
+ if (!targetPath || existsSync19(resolve15(projectRoot, dirname8(path), targetPath))) continue;
5895
6035
  const targetStem = basename5(targetPath, extname2(targetPath)).toLowerCase();
5896
6036
  const labelStem = (match[2] ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
5897
6037
  const candidates = paths.filter((candidate) => basename5(candidate, extname2(candidate)).toLowerCase() === targetStem || basename5(candidate, extname2(candidate)).toLowerCase().replace(/[^a-z0-9]+/g, "") === labelStem);
5898
6038
  if (candidates.length !== 1) continue;
5899
- let replacement = relative8(dirname9(path), candidates[0]).split(sep8).join("/");
6039
+ let replacement = relative8(dirname8(path), candidates[0]).split(sep8).join("/");
5900
6040
  if (target.startsWith("./") && !replacement.startsWith(".")) replacement = `./${replacement}`;
5901
6041
  next = next.replace(match[0], match[0].replace(target, replacement));
5902
6042
  }
@@ -5908,8 +6048,13 @@ var createArtifactNormalizationProposal = (root, artifactPath, options) => {
5908
6048
  const projectRoot = realpathSync8.native(resolve15(root));
5909
6049
  const path = artifactPath.split(sep8).join("/");
5910
6050
  const absolute = containedPath(projectRoot, path);
5911
- if (!absolute || !existsSync19(absolute) || !statSync5(absolute).isFile()) return void 0;
5912
- const before = readFileSync18(absolute, "utf8");
6051
+ if (!absolute) return void 0;
6052
+ let before;
6053
+ try {
6054
+ before = readFileSync18(absolute, "utf8");
6055
+ } catch {
6056
+ return void 0;
6057
+ }
5913
6058
  let after;
5914
6059
  try {
5915
6060
  after = `${JSON.stringify(sortJson(JSON.parse(before)), null, 2)}
@@ -5974,8 +6119,8 @@ var applyFixProposal = (root, proposalInput, options = {}) => {
5974
6119
  };
5975
6120
 
5976
6121
  // src/agents/registry-adapter.ts
5977
- import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
5978
- import { join as join19, resolve as resolve16 } from "path";
6122
+ import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
6123
+ import { join as join18, resolve as resolve16 } from "path";
5979
6124
  import { pathToFileURL } from "url";
5980
6125
  import { z as z7 } from "zod";
5981
6126
  var DEFAULT_REGISTRY_AGENT_ID = "ecosystem-doc-bridge-corpus-scanner";
@@ -5997,7 +6142,7 @@ var registryConfig = (config) => config.intelligence?.registry;
5997
6142
  var loadRegistryAgentRunner = async (root, config) => {
5998
6143
  const metadata = loadRegistryAgentMetadata(root, config);
5999
6144
  const configured = registryConfig(config)?.runnerModule;
6000
- const modulePath = configured ? containedPath(root, configured) : containedPath(root, join19(metadata.root, "doc-bridge-adapter.js"));
6145
+ const modulePath = configured ? containedPath(root, configured) : containedPath(root, join18(metadata.root, "doc-bridge-adapter.js"));
6001
6146
  if (!modulePath || !existsSync20(modulePath)) throw new Error(`Registry agent "${metadata.id}" has no local runner module. Configure intelligence.registry.runnerModule or add doc-bridge-adapter.js to the installed agent.`);
6002
6147
  const loaded = await import(pathToFileURL(modulePath).href);
6003
6148
  const runner = typeof loaded.run === "function" ? loaded.run : typeof loaded.default === "function" ? loaded.default : loaded.default && typeof loaded.default === "object" && "run" in loaded.default && typeof loaded.default.run === "function" ? loaded.default.run : void 0;
@@ -6008,9 +6153,9 @@ var loadRegistryAgentMetadata = (root, config) => {
6008
6153
  const settings = registryConfig(config);
6009
6154
  const id = settings?.agentId ?? DEFAULT_REGISTRY_AGENT_ID;
6010
6155
  const agentRoot = settings?.agentRoot ?? "agents";
6011
- const agentPath = containedPath(root, join19(agentRoot, id));
6012
- if (!agentPath || !existsSync20(agentPath)) throw new Error(`AgentsKit Registry agent "${id}" is not installed at ${join19(agentRoot, id)}. Install it with: npx agentskit add ${id}`);
6013
- const metadataPath = [join19(agentPath, "agent.json"), join19(agentPath, "manifest.json")].find(existsSync20);
6156
+ const agentPath = containedPath(root, join18(agentRoot, id));
6157
+ if (!agentPath || !existsSync20(agentPath)) throw new Error(`AgentsKit Registry agent "${id}" is not installed at ${join18(agentRoot, id)}. Install it with: npx agentskit add ${id}`);
6158
+ const metadataPath = [join18(agentPath, "agent.json"), join18(agentPath, "manifest.json")].find(existsSync20);
6014
6159
  if (!metadataPath) throw new Error(`Registry agent "${id}" is installed but has no agent.json or manifest.json metadata.`);
6015
6160
  const metadata = RegistryAgentMetadataSchema.parse(JSON.parse(readFileSync19(metadataPath, "utf8")));
6016
6161
  if (metadata.id !== id) throw new Error(`Installed Registry agent metadata id "${metadata.id}" does not match configured id "${id}".`);
@@ -6060,8 +6205,8 @@ var persistRegistryAgentProposal = (stateDir, proposal) => {
6060
6205
  AgentProposalV1Schema.parse(proposal);
6061
6206
  if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Cannot persist a Registry agent proposal with an invalid contentHash.");
6062
6207
  const safeHash = contentHashForArtifactV1(proposal);
6063
- mkdirSync6(join19(resolve16(stateDir), "agents"), { recursive: true });
6064
- const path = join19(resolve16(stateDir), "agents", `${proposal.origin.id}-${safeHash}.json`);
6208
+ mkdirSync5(join18(resolve16(stateDir), "agents"), { recursive: true });
6209
+ const path = join18(resolve16(stateDir), "agents", `${proposal.origin.id}-${safeHash}.json`);
6065
6210
  writeFileSync6(path, `${JSON.stringify(proposal, null, 2)}
6066
6211
  `, "utf8");
6067
6212
  return path;
@@ -6258,7 +6403,7 @@ var workflowReport = (ctx, runId2) => {
6258
6403
  ensureLatestRun(ctx, runId2);
6259
6404
  return parseReconciliationReport(loadWorkflowStepOutput(workflowStateDir(ctx), "reconcile"));
6260
6405
  };
6261
- var proposalPath = (ctx) => join20(ctx.root, ".doc-bridge", "proposal.json");
6406
+ var proposalPath = (ctx) => join19(ctx.root, ".doc-bridge", "proposal.json");
6262
6407
  var readSavedProposal = (ctx, input) => {
6263
6408
  if (input !== void 0) return FixProposalV1Schema.parse(input);
6264
6409
  try {
@@ -6268,7 +6413,7 @@ var readSavedProposal = (ctx, input) => {
6268
6413
  }
6269
6414
  };
6270
6415
  var saveProposal = (ctx, proposal) => {
6271
- mkdirSync7(join20(ctx.root, ".doc-bridge"), { recursive: true });
6416
+ mkdirSync6(join19(ctx.root, ".doc-bridge"), { recursive: true });
6272
6417
  writeFileSync7(proposalPath(ctx), `${JSON.stringify(proposal, null, 2)}
6273
6418
  `, "utf8");
6274
6419
  };
@@ -6485,11 +6630,13 @@ var startMcpStdioServer = (ctx) => {
6485
6630
 
6486
6631
  // src/reconciliation/reconcile.ts
6487
6632
  var ignoredDocumentationRelations = /* @__PURE__ */ new Set(["covers"]);
6633
+ var isInternalEntity = (id) => !id.startsWith("external:") && !id.startsWith("unresolved:");
6488
6634
  var metadataDetection = (relation) => {
6489
6635
  const detection = relation.metadata?.detection;
6490
6636
  return typeof detection === "string" ? detection : void 0;
6491
6637
  };
6492
- var relationDetection = (relation) => relation.discriminator ?? metadataDetection(relation) ?? "static";
6638
+ var normalizeDetection = (value) => value === "dynamic-literal" ? "dynamic" : value;
6639
+ var relationDetection = (relation) => normalizeDetection(relation.discriminator ?? metadataDetection(relation) ?? "static");
6493
6640
  var entityResolver = (snapshots) => {
6494
6641
  const references = /* @__PURE__ */ new Map();
6495
6642
  const entities = snapshots.flatMap((snapshot) => snapshot.entities).sort((a, b) => a.id.localeCompare(b.id));
@@ -6516,6 +6663,19 @@ var diagnosticCounts = (values) => {
6516
6663
  for (const value of values) counts.set(value, (counts.get(value) ?? 0) + 1);
6517
6664
  return Object.fromEntries([...counts.entries()].sort(([a], [b]) => a.localeCompare(b)));
6518
6665
  };
6666
+ var documentClass = (entity) => {
6667
+ const classification = entity.metadata?.classification;
6668
+ return typeof classification === "string" && classification.length > 0 ? classification : "unclassified";
6669
+ };
6670
+ var countDocumentClasses = (documents, selectedIds) => {
6671
+ const counts = /* @__PURE__ */ new Map();
6672
+ for (const document of documents) {
6673
+ if (selectedIds && !selectedIds.has(document.id)) continue;
6674
+ const classification = documentClass(document);
6675
+ counts.set(classification, (counts.get(classification) ?? 0) + 1);
6676
+ }
6677
+ return Object.fromEntries([...counts.entries()].sort(([a], [b]) => a.localeCompare(b)));
6678
+ };
6519
6679
  var diagnosticId = (code, value) => `reconciliation:${code}:${sha256NormalizedV1(value).slice(0, 32)}`;
6520
6680
  var coverageAvailable = (snapshot, relation) => {
6521
6681
  const status = snapshot.coverage.find(
@@ -6568,7 +6728,6 @@ var aggregatedRelations = (relations, scope, entities, packageByModule) => {
6568
6728
  const parts = key.split("\0");
6569
6729
  const from = parts[0] ?? first.from;
6570
6730
  const to = parts[1] ?? first.to;
6571
- const kind = parts[2] ?? first.kind;
6572
6731
  const detection = parts[3] ?? relationDetection(first);
6573
6732
  const mergedEvidence = mergeEvidence(...group);
6574
6733
  return {
@@ -6650,9 +6809,18 @@ var reconcileKnowledge = (observed, declared, options = {}) => {
6650
6809
  group.push(relation);
6651
6810
  declaredByBase.set(base2, group);
6652
6811
  }
6812
+ const observedDetectionsByBase = /* @__PURE__ */ new Map();
6813
+ for (const relation of observedRelations) {
6814
+ const base2 = relationBase(relation, resolveEntity2);
6815
+ const detections = observedDetectionsByBase.get(base2) ?? /* @__PURE__ */ new Set();
6816
+ detections.add(relationDetection(relation));
6817
+ observedDetectionsByBase.set(base2, detections);
6818
+ }
6653
6819
  for (const group of declaredByBase.values()) {
6654
6820
  const detections = new Set(group.map(relationDetection));
6655
6821
  if (detections.size < 2) continue;
6822
+ const observedDetections = observedDetectionsByBase.get(relationBase(group[0], resolveEntity2));
6823
+ if (observedDetections && [...detections].every((detection) => observedDetections.has(detection))) continue;
6656
6824
  diagnostics.push(reportDiagnostic(
6657
6825
  "CONFLICTING_DECLARATIONS",
6658
6826
  "conflict",
@@ -6679,7 +6847,7 @@ var reconcileKnowledge = (observed, declared, options = {}) => {
6679
6847
  void 0,
6680
6848
  [relation.id, match.id]
6681
6849
  ));
6682
- } else if (coverageAvailable(observed, relation) && (requiredRelationKinds === void 0 || requiredRelationKinds.has(relation.kind))) {
6850
+ } else if (coverageAvailable(observed, relation) && (requiredRelationKinds === void 0 || requiredRelationKinds.has(relation.kind)) && (options.requiredRelationTargets !== "internal" || relation.from !== relation.to && isInternalEntity(relation.from) && isInternalEntity(relation.to))) {
6683
6851
  diagnostics.push(reportDiagnostic(
6684
6852
  "RELATION_UNDOCUMENTED",
6685
6853
  "undocumented",
@@ -6742,9 +6910,13 @@ var reconcileKnowledge = (observed, declared, options = {}) => {
6742
6910
  else if (statuses.has("undocumented") || statuses.has("unresolved") || statuses.has("not-analyzed")) packageStatus.unverified += 1;
6743
6911
  else packageStatus.fresh += 1;
6744
6912
  }
6913
+ const documents = observed.entities.filter((entity) => entity.kind === "document");
6914
+ const documentedDocumentIds = new Set(allDeclaredRelations.filter((relation) => relation.from.startsWith("document:")).map((relation) => relation.from));
6745
6915
  const documentation = {
6746
- documentCount: observed.entities.filter((entity) => entity.kind === "document").length,
6747
- documentedDocumentCount: new Set(allDeclaredRelations.filter((relation) => relation.from.startsWith("document:")).map((relation) => relation.from)).size,
6916
+ documentCount: documents.length,
6917
+ documentedDocumentCount: documentedDocumentIds.size,
6918
+ documentClassificationCounts: countDocumentClasses(documents),
6919
+ documentedDocumentClassificationCounts: countDocumentClasses(documents, documentedDocumentIds),
6748
6920
  packageCount: packageEntities.length,
6749
6921
  packageStatus
6750
6922
  };
@@ -6767,6 +6939,7 @@ var reconcileKnowledge = (observed, declared, options = {}) => {
6767
6939
  diagnosticCount: sortedDiagnostics.length,
6768
6940
  ...options.scope === void 0 ? {} : { scope: options.scope },
6769
6941
  ...options.requiredRelationKinds === void 0 ? {} : { requiredRelationKinds: [...new Set(options.requiredRelationKinds)].sort() },
6942
+ ...options.requiredRelationTargets === void 0 ? {} : { requiredRelationTargets: options.requiredRelationTargets },
6770
6943
  diagnosticsByCode: diagnosticCounts(sortedDiagnostics.map((diagnostic2) => diagnostic2.code)),
6771
6944
  diagnosticsByStatus: diagnosticCounts(sortedDiagnostics.map((diagnostic2) => diagnostic2.status)),
6772
6945
  documentation
@@ -6889,7 +7062,11 @@ var reportData = (snapshot, report, includeSnippets, privacy = "private") => {
6889
7062
  pipelineVersion: snapshot.pipelineVersion,
6890
7063
  analyzerVersions: snapshot.analyzerVersions,
6891
7064
  diagnosticCount: report.diagnostics.length,
7065
+ actionableCount: report.diagnostics.filter((diagnostic2) => diagnostic2.status !== "confirmed").length,
7066
+ confirmedCount: report.diagnostics.filter((diagnostic2) => diagnostic2.status === "confirmed").length,
6892
7067
  requiredRelationKinds: report.summary.requiredRelationKinds,
7068
+ requiredRelationTargets: report.summary.requiredRelationTargets,
7069
+ documentation: report.summary.documentation,
6893
7070
  diagnosticSummary: {
6894
7071
  errors: report.diagnostics.filter((diagnostic2) => diagnostic2.severity === "error").length,
6895
7072
  warnings: report.diagnostics.filter((diagnostic2) => diagnostic2.severity === "warn").length,
@@ -7047,8 +7224,8 @@ styles += String.raw`
7047
7224
  :root{--ink:#182b2b;--muted:#536664;--paper:#efe9df;--panel:#f8f5ee;--line:#c8c8b9;--green:#176b51;--blue:#155a68;--amber:#93530b;--red:#9b302b;--shadow:none}
7048
7225
  *{box-sizing:border-box}body{background:var(--paper);font-family:"Avenir Next","Segoe UI",ui-sans-serif,system-ui,sans-serif;font-size:14px;line-height:1.5}.shell{max-width:1720px;padding:30px clamp(20px,4vw,64px) 56px}.masthead{align-items:flex-start;border-bottom:2px solid var(--ink);padding:4px 0 28px}.masthead>div:first-child{max-width:840px}.eyebrow{font-size:10px;letter-spacing:.16em}.masthead h1{font-family:"Iowan Old Style",Georgia,serif;font-size:clamp(3rem,7vw,7.2rem);letter-spacing:-.075em;line-height:.9;margin:16px 0 20px}.lede{font-size:17px;line-height:1.45;max-width:650px}.run-meta{padding-top:3px;min-width:210px}.read-only{border:0;border-radius:0;padding:0;background:none;color:var(--green);font-size:10px;letter-spacing:.1em;text-transform:uppercase}.run-meta strong{color:var(--ink);font-size:12px;overflow-wrap:anywhere;margin-top:16px}.summary{grid-template-columns:repeat(5,minmax(0,1fr));gap:0;margin:0;padding:18px 0 22px;border-bottom:1px solid var(--line)}.metric{border:0;border-left:1px solid var(--line);border-radius:0;background:none;box-shadow:none;padding:0 18px}.metric:first-child{border-left:0;padding-left:0}.metric b{font-family:"Iowan Old Style",Georgia,serif;font-size:38px;letter-spacing:-.05em}.metric span{font-size:11px;letter-spacing:.07em;text-transform:uppercase}.lens-bar{align-items:flex-end;margin:42px 0 6px}.tabs{gap:0}.tab,.level{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;color:var(--muted);padding:8px 14px;min-height:38px;font-weight:700}.tab:first-child,.level:first-child{padding-left:0}.tab:hover,.level:hover{background:none;color:var(--ink);border-color:var(--line)}.tab[aria-selected=true],.level[aria-pressed=true]{background:none;color:var(--ink);border-color:var(--green)}.lens-bar>.subtle{max-width:430px;text-align:right}.filters{border-block:1px solid var(--line);padding:14px 0;margin:12px 0 24px}.filters input,.filters select,.filters button{border:1px solid var(--line);border-radius:3px;background:var(--panel);min-height:38px}.filters button{font-weight:700}.insights{grid-template-columns:repeat(4,minmax(0,1fr));gap:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line);margin:18px 0 30px}.insight{border:0;border-left:1px solid var(--line);border-radius:0;background:none;box-shadow:none;padding:14px 16px 16px}.insight:first-child{border-left:0;padding-left:0}.insight h3{font-size:12px;letter-spacing:.02em}.insight p{font-size:13px}.tag{border-radius:2px;padding:2px 5px;font-size:9px}.workspace{grid-template-columns:minmax(0,1fr) 350px;gap:24px}.panel{border:1px solid var(--line);border-radius:3px;background:var(--panel);box-shadow:none;padding:24px}.panel h2{font-family:"Iowan Old Style",Georgia,serif;font-size:30px;letter-spacing:-.04em}.map-wrap{border:1px solid #aeb9ae;border-radius:2px;background-color:#e8eee7;background-image:linear-gradient(rgba(24,43,43,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(24,43,43,.07) 1px,transparent 1px);background-size:24px 24px}.map-wrap::-webkit-scrollbar{height:10px}.map-wrap::-webkit-scrollbar-thumb{background:var(--line)}#graph{height:620px}.graph-node rect{fill:#f8f5ee;stroke:#274744;stroke-width:1.4;rx:2}.graph-node:hover rect,.graph-node:focus rect{stroke:var(--blue);stroke-width:2.5}.graph-node.selected rect{fill:#dceee1;stroke:var(--green);stroke-width:3}.graph-node.issue rect{fill:#fff0d4;stroke:var(--amber)}.edge{stroke:#69857d;stroke-width:1.5;opacity:.8}.edge.alert{stroke:var(--amber);stroke-width:2.5}.edge-label{fill:var(--ink);font-size:10px;font-weight:700}.node-label{font-size:12px;font-weight:800}.node-kind{font-size:10px;fill:var(--muted)}.node-count{font-size:10px;fill:var(--green);font-weight:800}.side{top:20px}.side .panel-head{border-bottom:1px solid var(--line);padding-bottom:14px}.detail-title{font-family:"Iowan Old Style",Georgia,serif;font-size:28px}.detail-grid{gap:0}.detail-grid>div{border-left:1px solid var(--line);padding-left:10px}.detail-grid>div:nth-child(odd){border-left:0;padding-left:0}.detail-grid b{font-family:"Iowan Old Style",Georgia,serif;font-size:26px}.run{margin-top:24px}.run-summary{border-bottom:1px solid var(--line);padding-bottom:12px}.chip{border:0;border-left:1px solid var(--line);border-radius:0;padding:2px 10px}.chip:first-child{border-left:0;padding-left:0}.finding-groups{grid-template-columns:repeat(2,minmax(0,1fr));gap:0}.finding-group{border:0;border-top:1px solid var(--line);border-radius:0;background:none;padding:16px 14px 16px 0}.finding-group:nth-child(even){padding-left:14px;border-left:1px solid var(--line)}.finding-group h3{font-size:13px;letter-spacing:.02em}.finding-detail{border-top:2px solid var(--ink)}.finding{padding:16px 0}.metadata{gap:24px;margin-top:26px}.metadata div{border-top:1px solid var(--line);padding-top:9px}.map-wrap,.panel,.finding-group,.insight{transition:background-color .16s ease,border-color .16s ease}@media(max-width:900px){.masthead{display:block}.run-meta{padding-top:24px;text-align:left}.summary{grid-template-columns:repeat(3,minmax(0,1fr));row-gap:18px}.metric:nth-child(4){border-left:0;padding-left:0}.workspace{grid-template-columns:1fr}.side{position:static}.lens-bar{display:block}.lens-bar>.subtle{text-align:left;margin-top:10px}.finding-groups{grid-template-columns:1fr}.finding-group:nth-child(even){padding-left:0;border-left:0}}@media(max-width:560px){.shell{padding:20px 16px 40px}.masthead h1{font-size:clamp(3rem,16vw,5rem)}.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.metric:nth-child(odd){border-left:0;padding-left:0}.metric:nth-child(even){border-left:1px solid var(--line);padding-left:12px}.insights{grid-template-columns:1fr}.insight,.insight:first-child{border-left:0;border-top:1px solid var(--line);padding-left:0}.insight:first-child{border-top:0}.panel{padding:16px}.panel-head{display:block}.panel-head>.tabs{margin-top:12px}.filters label{width:100%}.filters input,.filters select{width:100%}.metadata{grid-template-columns:1fr}}@media(prefers-reduced-motion:reduce){.map-wrap,.panel,.finding-group,.insight{transition:none}}@media(prefers-color-scheme:dark){:root{--ink:#e8eee7;--muted:#b0beb3;--paper:#131c1a;--panel:#1b2824;--line:#42534b;--green:#72d09d;--blue:#86cfe1;--amber:#f0bc6b;--red:#ff9186}.map-wrap{background-color:#17231e;background-image:linear-gradient(rgba(232,238,231,.07) 1px,transparent 1px),linear-gradient(90deg,rgba(232,238,231,.07) 1px,transparent 1px)}.graph-node rect{fill:#1c2b25;stroke:#90aa99}.graph-node.selected rect{fill:#294936}.graph-node.issue rect{fill:#40321e}.filters input,.filters select,.filters button{background:#202f29;color:var(--ink)}}
7049
7226
  `;
7050
- styles += String.raw`.graph-node .node-label,.graph-node text.node-label{fill:var(--ink)}.graph-node .node-kind{fill:var(--muted)}.edge-label{fill:var(--ink)}@media(max-width:1100px){.workspace{grid-template-columns:1fr}.side{position:static}.map-wrap #graph{min-width:0!important;width:100%!important}.panel-head>.tabs{margin-top:12px}}@media(max-width:700px){.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.lens-bar{display:block}.lens-bar>.subtle{text-align:left;margin-top:10px}.filters label{width:100%}.filters input,.filters select{width:100%}.finding-groups{grid-template-columns:1fr}.finding-group:nth-child(even){padding-left:0;border-left:0}}`;
7051
- styles += String.raw`.level{display:none!important}.report-tabs{display:flex;gap:0;align-items:flex-end;border-bottom:1px solid var(--line);margin:24px 0 0}.report-tabs .tab{font-size:13px}.breadcrumbs{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin:0 0 14px;color:var(--muted);font-size:12px}.breadcrumbs button{border:0;background:none;color:var(--blue);padding:3px 0;font-weight:700}.breadcrumbs button:hover{text-decoration:underline}.breadcrumbs .current{color:var(--ink);font-weight:700}.scope-note{margin:0 0 12px}.report-dashboard{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,1fr);gap:24px}.report-table{width:100%;border-collapse:collapse;font-size:12px}.report-table th,.report-table td{border-top:1px solid var(--line);padding:10px 8px;text-align:left;vertical-align:top}.report-table th{color:var(--muted);font-size:10px;letter-spacing:.08em;text-transform:uppercase}.report-table td:first-child{font-weight:750}.bar-list{display:grid;gap:10px}.bar-item{display:grid;grid-template-columns:minmax(120px,1fr) 2fr auto;gap:10px;align-items:center;font-size:12px}.bar-item i{height:8px;background:var(--line);display:block;overflow:hidden}.bar-item i b{display:block;height:100%;background:var(--green)}.map-wrap{touch-action:none;cursor:grab}.map-wrap.dragging{cursor:grabbing}@media(max-width:700px){.report-tabs{overflow:auto}.report-tabs .tab{white-space:nowrap}.report-dashboard{grid-template-columns:1fr}.bar-item{grid-template-columns:minmax(100px,1fr) 1fr auto}}`;
7227
+ styles += String.raw`.graph-node .node-label,.graph-node text.node-label{fill:var(--ink)}.graph-node .node-kind{fill:var(--muted)}.edge-label{fill:var(--ink)}.panel-head>div:first-child{min-width:0;flex:1 1 0}.panel-head>.tabs:not(.report-tabs){flex-wrap:nowrap;max-width:100%;overflow-x:auto}.panel-head>.tabs:not(.report-tabs) .level{flex:0 0 auto}@media(max-width:1100px){.workspace{grid-template-columns:1fr}.side{position:static}.map-wrap #graph{min-width:0!important;width:100%!important}.panel-head>.tabs{margin-top:12px}}@media(max-width:700px){.summary{grid-template-columns:repeat(2,minmax(0,1fr))}.lens-bar{display:block}.lens-bar>.subtle{text-align:left;margin-top:10px}.filters label{width:100%}.filters input,.filters select{width:100%}.finding-groups{grid-template-columns:1fr}.finding-group:nth-child(even){padding-left:0;border-left:0}}`;
7228
+ styles += String.raw`.report-tabs{display:flex;gap:0;align-items:flex-end;border-bottom:1px solid var(--line);margin:24px 0 0}.report-tabs .tab{font-size:13px}.breadcrumbs{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin:0 0 14px;color:var(--muted);font-size:12px}.breadcrumbs button{border:0;background:none;color:var(--blue);padding:3px 0;font-weight:700}.breadcrumbs button:hover{text-decoration:underline}.breadcrumbs .current{color:var(--ink);font-weight:700}.scope-note{margin:0 0 12px}.report-dashboard{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,1fr);gap:24px}.report-table{width:100%;border-collapse:collapse;font-size:12px}.report-table th,.report-table td{border-top:1px solid var(--line);padding:10px 8px;text-align:left;vertical-align:top}.report-table th{color:var(--muted);font-size:10px;letter-spacing:.08em;text-transform:uppercase}.report-table td:first-child{font-weight:750}.bar-list{display:grid;gap:10px}.bar-item{display:grid;grid-template-columns:minmax(120px,1fr) 2fr auto;gap:10px;align-items:center;font-size:12px}.bar-item i{height:8px;background:var(--line);display:block;overflow:hidden}.bar-item i b{display:block;height:100%;background:var(--green)}.map-wrap{touch-action:none;cursor:grab}.map-wrap.dragging{cursor:grabbing}@media(max-width:700px){.report-tabs{overflow:auto}.report-tabs .tab{white-space:nowrap}.report-dashboard{grid-template-columns:1fr}.bar-item{grid-template-columns:minmax(100px,1fr) 1fr auto}}`;
7052
7229
  styles += String.raw`body[data-report-view="architecture"] #insights,body[data-report-view="architecture"] .filters,body[data-report-view="architecture"] .run,body[data-report-view="architecture"] .metadata{display:none}body[data-report-view="insights"] .workspace,body[data-report-view="insights"] .filters,body[data-report-view="insights"] .run,body[data-report-view="insights"] .metadata{display:none}body[data-report-view="findings"] .workspace,body[data-report-view="findings"] #insights,body[data-report-view="findings"] #report-dashboard,body[data-report-view="findings"] #coverage-title,body[data-report-view="findings"] #coverage-title~*{display:none}body[data-report-view="findings"] .run[aria-labelledby="coverage-title"]{display:none}body[data-report-view="coverage"] .workspace,body[data-report-view="coverage"] #insights,body[data-report-view="coverage"] #report-dashboard,body[data-report-view="coverage"] .filters,body[data-report-view="coverage"] .run[aria-labelledby="run-title"],body[data-report-view="coverage"] .metadata{display:none}body[data-report-view="insights"] #report-dashboard{display:block}body[data-report-view="map"] #report-dashboard{display:none}`;
7053
7230
  styles += String.raw`body[data-report-view="architecture"] #report-dashboard{display:none!important}.edge{fill:none}`;
7054
7231
  styles += String.raw`:root{--ink:#17251f;--muted:#586b63;--paper:#f2f5f1;--panel:#fbfcf9;--line:#c9d5cc;--green:#13734a;--blue:#155f78;--amber:#995d08;--red:#a52e2b}html,body{max-width:100%;overflow-x:hidden}body{font-size:15px;line-height:1.55}.shell{width:100%;max-width:1600px;margin:0 auto;padding:28px clamp(16px,3vw,48px) 56px}.masthead{gap:32px;padding:0 0 24px;align-items:flex-start}.masthead h1{font-size:clamp(2.8rem,5.5vw,5.4rem);line-height:.94;margin:12px 0 16px;max-width:100%;overflow-wrap:anywhere}.lede{max-width:60ch;font-size:16px}.run-meta{min-width:0;max-width:300px}.summary{margin:0;padding:18px 0 20px;grid-template-columns:repeat(5,minmax(0,1fr));gap:0}.metric{min-width:0;padding:0 16px;border-inline-start:1px solid var(--line)}.metric:first-child{padding-inline-start:0;border-inline-start:0}.metric b{font-size:clamp(1.8rem,3vw,3rem);max-width:100%;overflow-wrap:anywhere}.metric span{font-size:10px;letter-spacing:.06em;line-height:1.25}.lens-bar{margin:24px 0 4px;align-items:flex-end}.lens-bar>.subtle{max-width:52ch}.report-tabs{max-width:100%;overflow-x:auto}.tab,.level{min-height:40px;padding:8px 12px}.filters{max-width:100%;align-items:center}.filters label{min-width:0}.filters input,.filters select{max-width:100%;min-width:0}.workspace{min-width:0;grid-template-columns:minmax(0,1fr) minmax(280px,340px);gap:20px}.panel{min-width:0}.map-wrap{min-width:0;max-width:100%;width:100%;overflow:hidden;contain:layout paint}#graph{display:block;width:100%!important;min-width:0!important;max-width:100%;height:clamp(480px,60vh,680px)!important}.side{min-width:0}.detail-title,.path,.list,.finding,.finding-group,.report-table{min-width:0;overflow-wrap:anywhere}.report-dashboard{min-width:0}.report-table{table-layout:fixed}.report-table th,.report-table td{overflow-wrap:anywhere}.bar-item{min-width:0}.bar-item span{min-width:0;overflow-wrap:anywhere}.tab:focus-visible,.level:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid var(--blue);outline-offset:2px}@media(max-width:1100px){.workspace{grid-template-columns:1fr}.side{position:static}.summary{grid-template-columns:repeat(3,minmax(0,1fr));row-gap:16px}.summary .metric:nth-child(4){border-inline-start:0;padding-inline-start:0}}@media(max-width:700px){.shell{padding:12px 16px 32px}.masthead{padding-bottom:14px}.masthead h1{font-size:clamp(2.2rem,11vw,3.4rem);margin:6px 0 10px}.lede{font-size:14px;line-height:1.35}.run-meta{display:none}.summary{padding:10px 0 12px;grid-template-columns:repeat(2,minmax(0,1fr));row-gap:8px}.summary .metric:nth-child(even){border-inline-start:1px solid var(--line);padding-inline-start:12px}.summary .metric:nth-child(odd){border-inline-start:0;padding-inline-start:0}.metric{padding-inline:10px}.metric b{font-size:1.65rem}.lens-bar{display:block;margin-top:14px}.lens-bar>.subtle{display:none}.filters{display:grid;grid-template-columns:1fr;gap:10px}.filters label{display:grid;grid-template-columns:1fr;gap:5px}.filters input,.filters select,.filters button{width:100%;min-height:44px}.panel{padding:16px}.panel-head{display:block}.panel-head>.tabs{margin-top:12px}.finding-groups{grid-template-columns:1fr}.report-dashboard{grid-template-columns:1fr}#graph{height:520px!important}.lens-bar + #lens-caption{margin:8px 0}.lens-bar + #lens-caption + #insights{margin-top:8px}}@media(prefers-color-scheme:dark){:root{--ink:#edf5ef;--muted:#b7c6ba;--paper:#111815;--panel:#1a2821;--line:#405248;--green:#73d19d;--blue:#8bd1e6;--amber:#f0bd70;--red:#ff9389}}`;
@@ -7058,7 +7235,8 @@ styles += String.raw`#lens-caption + p.subtle{display:none}`;
7058
7235
  var script = String.raw`
7059
7236
  const data=${"${DATA}"};
7060
7237
  data.entities=data.entities||[];data.relations=data.relations||[];data.diagnostics=data.diagnostics||[];
7061
- const policySuffix=data.requiredRelationKinds===undefined?"Policy: all observed relation kinds require declarations.":data.requiredRelationKinds.length?"Policy: declarations required for "+data.requiredRelationKinds.join(", ")+".":"Policy: missing relation declarations disabled; stale, conflicting, and unresolved declarations remain checked.";
7238
+ globalThis.__DOC_BRIDGE_DATA__=data;
7239
+ const policySuffix=data.requiredRelationKinds===undefined?"Policy: all observed relation kinds require declarations.":data.requiredRelationKinds.length?"Policy: declarations required for "+data.requiredRelationKinds.join(", ")+(data.requiredRelationTargets==="internal"?" between internal entities":"")+".":"Policy: missing relation declarations disabled; stale, conflicting, and unresolved declarations remain checked.";
7062
7240
  const ensurePolicyNote=()=>{const note=document.querySelector("#map-note");if(note&&!note.textContent.includes(policySuffix))note.textContent=(note.textContent+" · "+policySuffix).trim()};
7063
7241
  new MutationObserver(ensurePolicyNote).observe(document.querySelector("#map-note"),{childList:true,characterData:true});
7064
7242
  const lazyChunks=globalThis.__DOC_BRIDGE_LAZY_CHUNKS__||[],hasLevelChunks=globalThis.__DOC_BRIDGE_HAS_LEVEL_CHUNKS__===true,loadedChunks=new Set(),loadingChunks=new Map();
@@ -7069,7 +7247,7 @@ const groupById=new Map(Object.entries(data.view?.groups||{}));
7069
7247
  const packageChunkNameById=new Map([...groupById.values()].flatMap((group)=>group.members).filter((id)=>data.entities.find((entity)=>entity.id===id)?.kind==="package").sort().map((id,index)=>[id,data.view?.levelChunks?.packages?.[index]]));
7070
7248
  const addFinding=(index,id,finding)=>{const values=index.get(id)||[];values.push(finding);index.set(id,values)};
7071
7249
  const addRelation=(id,relation)=>{const values=relationsByEntity.get(id)||[];values.push(relation);relationsByEntity.set(id,values)};
7072
- const hydrate=()=>{byId.clear();parent.clear();relationById.clear();packageMembers.clear();findingIndex.clear();relationFindingIndex.clear();relationsByEntity.clear();groupEntityIds.clear();groupFindingIndex.clear();groupFindingCounts.clear();packageCache.clear();nodeIssueCache.clear();data.entities.forEach((entity)=>{byId.set(entity.id,entity);const groupId=data.view?.entityGroup?.[entity.id];if(groupId){const ids=groupEntityIds.get(groupId)||new Set();ids.add(entity.id);groupEntityIds.set(groupId,ids)}});data.relations.forEach((relation)=>{relationById.set(relation.id,relation);addRelation(relation.from,relation);addRelation(relation.to,relation);if(relation.kind==="contains"){if(!parent.has(relation.to))parent.set(relation.to,relation.from);const members=packageMembers.get(relation.from)||new Set();members.add(relation.to);packageMembers.set(relation.from,members)}});data.diagnostics.forEach((finding)=>{(finding.entityIds||[]).forEach((id)=>addFinding(findingIndex,id,finding));(finding.relationIds||[]).forEach((id)=>addFinding(relationFindingIndex,id,finding));const scopes=data.view?.diagnosticGroup?.[finding.id]||[];scopes.forEach((scope)=>{addFinding(groupFindingIndex,scope,finding);groupFindingCounts.set(scope,(groupFindingCounts.get(scope)||0)+1)})})};
7250
+ const hydrate=()=>{byId.clear();parent.clear();relationById.clear();packageMembers.clear();findingIndex.clear();relationFindingIndex.clear();relationsByEntity.clear();groupEntityIds.clear();groupFindingIndex.clear();groupFindingCounts.clear();packageCache.clear();nodeIssueCache.clear();data.entities.forEach((entity)=>{byId.set(entity.id,entity);const groupId=data.view?.entityGroup?.[entity.id];if(groupId){const ids=groupEntityIds.get(groupId)||new Set();ids.add(entity.id);groupEntityIds.set(groupId,ids)}});data.relations.forEach((relation)=>{relationById.set(relation.id,relation);addRelation(relation.from,relation);addRelation(relation.to,relation);if(relation.kind==="contains"){if(!parent.has(relation.to))parent.set(relation.to,relation.from);const members=packageMembers.get(relation.from)||new Set();members.add(relation.to);packageMembers.set(relation.from,members)}});data.diagnostics.forEach((finding)=>{(finding.entityIds||[]).forEach((id)=>addFinding(findingIndex,id,finding));(finding.relationIds||[]).forEach((id)=>addFinding(relationFindingIndex,id,finding));const scopes=data.view?.diagnosticGroup?.[finding.id]||[];if(finding.status!=="confirmed")scopes.forEach((scope)=>{addFinding(groupFindingIndex,scope,finding);groupFindingCounts.set(scope,(groupFindingCounts.get(scope)||0)+1)})})};
7073
7251
  const isLevelChunk=(name)=>name.startsWith("chunks/levels-");
7074
7252
  const isDetailChunk=(name)=>name.startsWith("chunks/details-");
7075
7253
  const hasChunk=(name)=>isLevelChunk(name)?activeLevelChunk===name:isDetailChunk(name)?loadedChunks.has(name):!lazyChunks.includes(name)||loadedChunks.has(name);
@@ -7086,10 +7264,10 @@ const viewLabels={architecture:"Architecture",drift:"Insights",risks:"Findings",
7086
7264
  const ensureReportChrome=()=>{document.querySelectorAll(".tab[data-lens]").forEach((tab)=>{tab.textContent=viewLabels[tab.dataset.lens]||tab.dataset.lens;tab.closest(".tabs")?.classList.add("report-tabs")});let breadcrumbs=document.querySelector("#breadcrumbs");if(!breadcrumbs){breadcrumbs=document.createElement("nav");breadcrumbs.id="breadcrumbs";breadcrumbs.className="breadcrumbs";breadcrumbs.setAttribute("aria-label","Report location");document.querySelector(".workspace")?.before(breadcrumbs)}return breadcrumbs};
7087
7265
  const label=(entity)=>entity?.name||entity?.id||"Unknown";
7088
7266
  const short=(value,max)=>{const text=String(value??""),limit=text==="External dependencies"?16:max;return text.length>limit?text.slice(0,limit-1)+"…":text};
7089
- const renderBreadcrumbs=()=>{const breadcrumbs=ensureReportChrome(),items=[{label:"Repository",selected:"",level:"overview"}];if(state.selected){const group=groupById.get(state.selected),entity=byId.get(state.selected);if(group)items.push({label:group.name,selected:group.id,level:"package"});else if(entity){const packageId=packageOf(entity.id),packageEntity=byId.get(packageId),groupId=groupFor(packageId);if(groupById.has(groupId))items.push({label:groupById.get(groupId).name,selected:groupId,level:"package"});if(packageEntity&&packageEntity.id!==entity.id)items.push({label:label(packageEntity),selected:packageEntity.id,level:"module"});items.push({label:label(entity),selected:entity.id,level:state.level})}}breadcrumbs.innerHTML=items.map((item,index)=>{const separator="<span aria-hidden=\"true\">"+(index?" / ":"")+"</span>";if(index===items.length-1)return separator+"<span class=\"current\">"+esc(item.label)+"</span>";return separator+"<button type=\"button\" data-breadcrumb-selected=\""+esc(item.selected)+"\" data-breadcrumb-level=\""+esc(item.level)+"\">"+esc(item.label)+"</button>"}).join("")};
7267
+ const renderBreadcrumbs=()=>{const breadcrumbs=ensureReportChrome(),items=[{label:"Repository",selected:"",level:"overview"}];if(state.selected){const group=groupById.get(state.selected),entity=byId.get(state.selected);if(group)items.push({label:group.name,selected:group.id,level:"package"});else if(entity){const packageId=packageOf(entity.id),packageEntity=byId.get(packageId),groupId=groupFor(packageId),group=groupById.get(groupId);if(group&&groupId!==packageId&&group.name!==label(packageEntity))items.push({label:group.name,selected:groupId,level:"package"});if(packageEntity&&packageEntity.id!==entity.id)items.push({label:label(packageEntity),selected:packageEntity.id,level:"module"});items.push({label:label(entity),selected:entity.id,level:state.level})}}breadcrumbs.innerHTML=items.map((item,index)=>{const separator="<span aria-hidden=\"true\">"+(index?" / ":"")+"</span>";if(index===items.length-1)return separator+"<span class=\"current\">"+esc(item.label)+"</span>";return separator+"<button type=\"button\" data-breadcrumb-selected=\""+esc(item.selected)+"\" data-breadcrumb-level=\""+esc(item.level)+"\">"+esc(item.label)+"</button>"}).join("")};
7090
7268
  const degreeMap=(relations)=>{const degrees=new Map();relations.forEach((relation)=>{degrees.set(relation.from,(degrees.get(relation.from)||0)+1);degrees.set(relation.to,(degrees.get(relation.to)||0)+1)});return degrees};
7091
7269
  const relationHealth=(relation)=>{if(!relation)return "";const findings=relationFindings(relation.id);return findings.some((finding)=>finding.severity==="error")?"error":findings.length?"warn":""};
7092
- const nodeIssues=(nodeId)=>{if(nodeIssueCache.has(nodeId))return nodeIssueCache.get(nodeId);const ids=groupEntityIds.get(nodeId)||new Set([nodeId]),findings=new Set(groupFindingIndex.get(nodeId)||[]),relations=new Set();for(const id of ids){(findingIndex.get(id)||[]).forEach((finding)=>findings.add(finding));(relationsByEntity.get(id)||[]).forEach((relation)=>relations.add(relation))}relations.forEach((relation)=>(relationFindingIndex.get(relation.id)||[]).forEach((finding)=>findings.add(finding)));const result=[...findings];nodeIssueCache.set(nodeId,result);return result};
7270
+ const nodeIssues=(nodeId)=>{if(nodeIssueCache.has(nodeId))return nodeIssueCache.get(nodeId);const ids=groupEntityIds.get(nodeId)||new Set([nodeId]),findings=new Set(groupFindingIndex.get(nodeId)||[]),relations=new Set();for(const id of ids){(findingIndex.get(id)||[]).filter((finding)=>finding.status!=="confirmed").forEach((finding)=>findings.add(finding));(relationsByEntity.get(id)||[]).forEach((relation)=>relations.add(relation))}relations.forEach((relation)=>(relationFindingIndex.get(relation.id)||[]).filter((finding)=>finding.status!=="confirmed").forEach((finding)=>findings.add(finding)));const result=[...findings];nodeIssueCache.set(nodeId,result);return result};
7093
7271
  const findingInLens=(finding)=>state.lens==="architecture"||(state.lens==="drift"&&finding.status!=="confirmed")||(state.lens==="risks"&&(finding.severity==="error"||finding.severity==="warn"))||(state.lens==="evidence"&&finding.evidence.length>0);
7094
7272
  const relationFindings=(id)=>relationFindingIndex.get(id)||data.view?.diagnosticRelationFindings?.[id]||[];
7095
7273
  const relationInLens=(id)=>relationFindings(id).some(findingInLens);
@@ -7099,8 +7277,8 @@ const detailChunkName=()=>{const entity=state.selected?byId.get(state.selected):
7099
7277
  const nodesFor=()=>{if(state.level==="overview")return data.view?.overview?.nodes||[];const packages=packageNodes(),selectedEntity=state.selected?byId.get(state.selected):null,groupScope=state.selected&&groupById.has(state.selected)?state.selected:null,packageScope=selectedEntity?.kind==="package"?selectedEntity.id:state.selected&&!groupScope?packageOf(state.selected):null;if(state.level==="package"&&!state.selected){const packageIds=new Set(packages.map((node)=>node.id));return packages.concat(state.lens==="architecture"?[]:externalFor(packageIds))}if((state.level==="module"||state.level==="file")&&!state.selected)return[];let nodes=data.entities.filter((entity)=>{if(state.level==="package")return entity.kind==="package"&&(!groupScope||groupFor(entity.id)===groupScope)&&(!packageScope||entity.id===packageScope);if(state.level==="module")return entity.kind==="module"&&(groupScope?groupFor(entity.id)===groupScope:packageOf(entity.id)===packageScope);return Boolean(entity.path)&&(groupScope?groupFor(entity.id)===groupScope:packageOf(entity.id)===packageScope)});if(state.level==="module"&&selectedEntity?.kind==="module"){const neighborhood=new Set([selectedEntity.id]);(relationsByEntity.get(selectedEntity.id)||[]).forEach((relation)=>{if(!["imports","re-exports"].includes(relation.kind))return;if(relation.from===selectedEntity.id)neighborhood.add(relation.to);if(relation.to===selectedEntity.id)neighborhood.add(relation.from)});nodes=data.entities.filter((entity)=>entity.kind==="module"&&neighborhood.has(entity.id))}const scopedIds=new Set(nodes.map((node)=>node.id)),external=state.lens==="architecture"?[]:externalFor(scopedIds),degrees=degreeMap(data.relations);return nodes.concat(external).sort((a,b)=>(degrees.get(b.id)||0)-(degrees.get(a.id)||0)||a.id.localeCompare(b.id)).slice(0,state.level==="file"?80:60)};
7100
7278
  const graphModelUncached=()=>{if(state.level==="overview"){const overviewNodes=data.view?.overview?.nodes||[],overviewNodeIds=new Set(overviewNodes.map((node)=>node.id)),base=(data.view?.overview?.edges||[]).filter((edge)=>overviewNodeIds.has(edge.from)&&overviewNodeIds.has(edge.to)),edges=state.lens==="architecture"?base:base.filter((edge)=>edge.relationIds.some(relationInLens)),visible=new Set(edges.flatMap((edge)=>[edge.from,edge.to]));return{nodes:state.lens==="architecture"?overviewNodes:overviewNodes.filter((node)=>visible.has(node.id)||nodeIssues(node.id).some((finding)=>findingInLens(finding))),edges:edges.map((edge)=>({...edge,health:edge.relationIds.some((id)=>relationHealth(relationById.get(id)))?"error":""}))}}const nodes=nodesFor(),ids=new Set(nodes.map((node)=>node.id)),aggregate=state.level==="package"&&(!state.selected||state.selected.startsWith("group:")),edges=new Map();data.relations.forEach((relation)=>{const essential=state.level==="package"?relation.kind==="depends-on":relation.kind==="imports"||relation.kind==="re-exports";if(relation.kind==="contains"|| (state.lens==="architecture"&&!essential)||(state.lens!=="architecture"&&!relationInLens(relation.id)))return;const from=aggregate?packageOf(relation.from):relation.from,to=aggregate?packageOf(relation.to):relation.to;if(from===to||!ids.has(from)||!ids.has(to))return;const key=from+"→"+to,current=edges.get(key)||{from,to,count:0,kinds:new Set(),relationIds:new Set(),health:""};current.count++;current.kinds.add(relation.kind);current.relationIds.add(relation.id);current.health=current.health==="error"||relationHealth(relation)==="error"?"error":current.health||relationHealth(relation);edges.set(key,current)});const visibleEdges=[...edges.values()].sort((a,b)=>b.count-a.count||a.from.localeCompare(b.from)||a.to.localeCompare(b.to));const visible=new Set(visibleEdges.flatMap((edge)=>[edge.from,edge.to]));return{nodes:state.lens==="architecture"?nodes:nodes.filter((node)=>visible.has(node.id)||nodeIssues(node.id).some((finding)=>findingInLens(finding))),edges:visibleEdges}};
7101
7279
  const graphModel=()=>{const key=state.lens+"|"+state.level+"|"+(state.selected||"");if(graphCacheKey===key&&graphCache)return graphCache;graphCacheKey=key;return graphCache=graphModelUncached()};
7102
- const renderInsights=()=>{const summary=data.diagnosticSummary||{},undocumented=summary.undocumented??data.diagnostics.filter((finding)=>finding.status==="undocumented").length,drift=summary.drift??data.diagnostics.filter((finding)=>finding.status==="stale-or-unverified"||finding.status==="conflict").length,unsupported=(data.coverage||[]).filter((entry)=>entry.status==="not-analyzed"||entry.status==="partial").length,model=graphModel(),degrees=degreeMap(model.edges),values=[...degrees.values()].sort((a,b)=>a-b),median=values.length?values[Math.floor(values.length/2)]:0,hot=[...degrees.values()].filter((value)=>value>=Math.max(4,median*2)).length,isolated=model.nodes.filter((node)=>!model.edges.some((edge)=>edge.from===node.id||edge.to===node.id)).length;document.querySelector("#insights").innerHTML=[["Documentation drift",undocumented+drift,"Relations or docs needing comparison.",""],["Unanalyzed scope",unsupported,"Coverage gaps are explicit.",""],["Connectivity hotspots",hot,"Heuristic: unusually connected nodes.","heuristic"],["Disconnected nodes",isolated,"Heuristic: no visible edge at this level.","heuristic"]].map(([title,count,copy,tag])=>"<article class=\"insight\"><h3>"+esc(title)+" <span class=\"tag "+tag+"\">"+(tag?"heuristic":"signal")+"</span></h3><p><strong>"+count+"</strong> · "+esc(copy)+"</p></article>").join("")};
7103
- const renderInsightsDashboard=()=>{let dashboard=document.querySelector("#report-dashboard");if(!dashboard){dashboard=document.createElement("section");dashboard.id="report-dashboard";dashboard.className="report-dashboard";document.querySelector(".filters")?.before(dashboard)}if(!data.diagnostics.length){dashboard.innerHTML="<p class=\"empty\">Open Findings or load the findings data to calculate insights.</p>";return}const degrees=degreeMap(data.relations),groups=[...groupById.entries()].filter(([,group])=>group.kind!=="external"),rows=groups.map(([id,group])=>{const members=groupEntityIds.get(id)||new Set(),modules=group.moduleCount||[...members].filter((entityId)=>byId.get(entityId)?.kind==="module").length;return{name:group.name,packages:group.members.length,modules,findings:groupFindingCounts.get(id)||0}}).sort((a,b)=>b.findings-a.findings||a.name.localeCompare(b.name)),topPackages=[...data.entities].filter((entity)=>entity.kind==="package").map((entity)=>({name:label(entity),degree:degrees.get(entity.id)||0})).sort((a,b)=>b.degree-a.degree||a.name.localeCompare(b.name)).slice(0,10),max=Math.max(1,...topPackages.map((row)=>row.degree));dashboard.innerHTML="<section><div class=\"eyebrow\">Attention by scope</div><h2>Where the map needs a closer look</h2><table class=\"report-table\"><thead><tr><th>Scope</th><th>Packages</th><th>Modules</th><th>Findings</th></tr></thead><tbody>"+rows.map((row)=>"<tr><td>"+esc(row.name)+"</td><td>"+row.packages+"</td><td>"+row.modules+"</td><td><strong>"+row.findings+"</strong></td></tr>").join("")+"</tbody></table></section><section><div class=\"eyebrow\">Connectivity</div><h2>Most connected packages</h2><div class=\"bar-list\">"+topPackages.map((row)=>"<div class=\"bar-item\"><span>"+esc(row.name)+"</span><i><b style=\"width:"+Math.round(row.degree/max*100)+"%\"></b></i><strong>"+row.degree+"</strong></div>").join("")+"</div></section>"};
7280
+ const renderInsights=()=>{const summary=data.diagnosticSummary||{},documentation=data.documentation||{},classificationCounts=documentation.documentClassificationCounts||{},documentedCounts=documentation.documentedDocumentClassificationCounts||{},agentDocs=classificationCounts.agent??0,documentedAgentDocs=documentedCounts.agent??0,undocumented=summary.undocumented??data.diagnostics.filter((finding)=>finding.status==="undocumented").length,drift=summary.drift??data.diagnostics.filter((finding)=>finding.status==="stale-or-unverified"||finding.status==="conflict").length,unsupported=(data.coverage||[]).filter((entry)=>entry.status==="not-analyzed"||entry.status==="partial").length,model=graphModel(),degrees=degreeMap(model.edges),values=[...degrees.values()].sort((a,b)=>a-b),median=values.length?values[Math.floor(values.length/2)]:0,hot=[...degrees.values()].filter((value)=>value>=Math.max(4,median*2)).length,isolated=model.nodes.filter((node)=>!model.edges.some((edge)=>edge.from===node.id||edge.to===node.id)).length;document.querySelector("#insights").innerHTML=[["Documentation drift",undocumented+drift,"Relations or docs needing comparison.",""],["Agent documentation",documentedAgentDocs+"/"+agentDocs,"Configured agent-corpus documents linked to knowledge.",""],["Unanalyzed scope",unsupported,"Coverage gaps are explicit.",""],["Connectivity hotspots",hot,"Heuristic: unusually connected nodes.","heuristic"],["Disconnected nodes",isolated,"Heuristic: no visible edge at this level.","heuristic"]].map(([title,count,copy,tag])=>"<article class=\"insight\"><h3>"+esc(title)+" <span class=\"tag "+tag+"\">"+(tag?"heuristic":"signal")+"</span></h3><p><strong>"+count+"</strong> · "+esc(copy)+"</p></article>").join("")};
7281
+ const renderInsightsDashboard=()=>{let dashboard=document.querySelector("#report-dashboard");if(!dashboard){dashboard=document.createElement("section");dashboard.id="report-dashboard";dashboard.className="report-dashboard";document.querySelector(".filters")?.before(dashboard)}const actionable=data.diagnostics.filter((finding)=>finding.status!=="confirmed");if(!actionable.length){dashboard.innerHTML="<p class=\"empty\">No actionable findings. Confirmed checks are available in Evidence.</p>";return}const degrees=degreeMap(data.relations),groups=[...groupById.entries()].filter(([,group])=>group.kind!=="external"),rows=groups.map(([id,group])=>{const members=groupEntityIds.get(id)||new Set(),modules=group.moduleCount||[...members].filter((entityId)=>byId.get(entityId)?.kind==="module").length;return{name:group.name,packages:group.members.length,modules,findings:groupFindingCounts.get(id)||0}}).sort((a,b)=>b.findings-a.findings||a.name.localeCompare(b.name)),topPackages=[...data.entities].filter((entity)=>entity.kind==="package").map((entity)=>({name:label(entity),degree:degrees.get(entity.id)||0})).sort((a,b)=>b.degree-a.degree||a.name.localeCompare(b.name)).slice(0,10),max=Math.max(1,...topPackages.map((row)=>row.degree));dashboard.innerHTML="<section><div class=\"eyebrow\">Attention by scope</div><h2>Where the map needs a closer look</h2><table class=\"report-table\"><thead><tr><th>Scope</th><th>Packages</th><th>Modules</th><th>Findings</th></tr></thead><tbody>"+rows.map((row)=>"<tr><td>"+esc(row.name)+"</td><td>"+row.packages+"</td><td>"+row.modules+"</td><td><strong>"+row.findings+"</strong></td></tr>").join("")+"</tbody></table></section><section><div class=\"eyebrow\">Connectivity</div><h2>Most connected packages</h2><div class=\"bar-list\">"+topPackages.map((row)=>"<div class=\"bar-item\"><span>"+esc(row.name)+"</span><i><b style=\"width:"+Math.round(row.degree/max*100)+"%\"></b></i><strong>"+row.degree+"</strong></div>").join("")+"</div></section>"};
7104
7282
  const mapState={scale:1,dragging:false,x:0,y:0};
7105
7283
  const applyMapTransform=()=>{const svg=document.querySelector("#graph");if(!svg)return;const dense=svg.closest(".map-wrap")?.classList.contains("dense");svg.setAttribute("preserveAspectRatio",dense?"xMinYMin meet":"xMidYMid meet");svg.style.transform="translate("+mapState.x+"px,"+mapState.y+"px) scale("+mapState.scale+")";svg.style.transformOrigin="center center"};
7106
7284
  const routeGraphEdges=()=>{const svg=document.querySelector("#graph");if(!svg)return;svg.querySelectorAll("line.edge").forEach((line,index)=>{const x1=Number(line.getAttribute("x1")),y1=Number(line.getAttribute("y1")),x2=Number(line.getAttribute("x2")),y2=Number(line.getAttribute("y2")),midX=(x1+x2)/2+((index%5)-2)*14,path=document.createElementNS("http://www.w3.org/2000/svg","path");path.setAttribute("class",line.getAttribute("class")||"edge");path.setAttribute("fill","none");path.setAttribute("marker-end",line.getAttribute("marker-end")||"url(#arrow)");path.setAttribute("d","M "+x1+" "+y1+" H "+midX+" V "+y2+" H "+x2);const label=line.parentElement?.querySelector("text.edge-label");if(label){label.setAttribute("x",String(midX+3));label.setAttribute("y",String((y1+y2)/2))}line.replaceWith(path)})};
@@ -7109,16 +7287,16 @@ let nodeClickTimer;
7109
7287
  document.addEventListener("dblclick",(event)=>{const node=event.target.closest?.("[data-node]");if(!node)return;event.preventDefault();event.stopImmediatePropagation();clearTimeout(nodeClickTimer);enterNode(node.dataset.node)},true);
7110
7288
  document.addEventListener("click",(event)=>{const node=event.target.closest?.("[data-node]");if(!node)return;event.preventDefault();event.stopImmediatePropagation();clearTimeout(nodeClickTimer);nodeClickTimer=setTimeout(async()=>{state.selected=node.dataset.node;render();const chunk=detailChunkName();if(chunk&&!hasChunk(chunk)){await loadChunk(chunk);render()}},380)},true);
7111
7289
  document.addEventListener("click",(event)=>{const crumb=event.target.closest?.("[data-breadcrumb-level]");if(!crumb)return;event.preventDefault();event.stopImmediatePropagation();state.level=crumb.dataset.breadcrumbLevel;state.selected=crumb.dataset.breadcrumbSelected||null;mapState.scale=1;mapState.x=0;mapState.y=0;render()},true);
7112
- const diagnosticsFor=(id)=>findingIndex.get(id)||[];
7290
+ const diagnosticsFor=(id)=>(findingIndex.get(id)||[]).filter((finding)=>finding.status!=="confirmed");
7113
7291
  const renderDetails=()=>{const panel=document.querySelector("#details"),entity=state.selected?byId.get(state.selected):null,group=state.selected?groupById.get(state.selected):null;if(group){const members=group.members.map((id)=>byId.get(id)).filter(Boolean),relations=new Set(),ids=groupEntityIds.get(state.selected)||new Set();ids.forEach((id)=>(relationsByEntity.get(id)||[]).forEach((relation)=>relations.add(relation)));const findings=nodeIssues(state.selected),evidence=members.flatMap((member)=>member.evidence||[]).slice(0,8),unit=group.kind==="external"?"dependencies":"packages";panel.innerHTML="<div class=\"eyebrow\">Selected group</div><h2 class=\"detail-title\">"+esc(group.name)+"</h2><span class=\"tag\">"+esc(group.kind)+"</span><p class=\"path\">"+esc(group.path||"Derived from stable package identity and repository structure")+"</p><div class=\"detail-grid\"><div><b>"+members.length+"</b><span>"+unit+"</span></div><div><b>"+relations.size+"</b><span>relations</span></div><div><b>"+findings.length+"</b><span>findings</span></div><div><b>"+evidence.length+"</b><span>evidence items</span></div></div><h3>Members</h3><ul class=\"list\">"+(members.length?members.slice(0,8).map((member)=>"<li>"+esc(label(member))+"</li>").join(""):"<li>No members recorded.</li>")+(members.length>8?"<li class=\"subtle\">+"+(members.length-8)+" more — choose Package level to inspect all.</li>":"")+"</ul>"+(findings.length?"<h3 style=\"margin-top:16px\">Attention</h3><ul class=\"list\">"+findings.slice(0,5).map((finding)=>"<li><span class=\"tag "+esc(finding.severity)+"\">"+esc(finding.severity)+"</span> "+esc(finding.code)+"</li>").join("")+"</ul>":"");return}if(!entity){panel.innerHTML="<p class=\"subtle\">Select a node in the map to inspect its evidence, connectivity, and findings.</p>";return}const relations=relationsByEntity.get(entity.id)||[],incoming=relations.filter((relation)=>relation.to===entity.id),outgoing=relations.filter((relation)=>relation.from===entity.id),findings=diagnosticsFor(entity.id),evidence=[...(entity.evidence||[]),...incoming.flatMap((relation)=>relation.evidence||[]),...outgoing.flatMap((relation)=>relation.evidence||[])].slice(0,8);panel.innerHTML="<div class=\"eyebrow\">Selected entity</div><h2 class=\"detail-title\">"+esc(label(entity))+"</h2><span class=\"tag\">"+esc(entity.kind)+"</span><p class=\"path\">"+esc(entity.path||entity.id)+"</p><div class=\"detail-grid\"><div><b>"+incoming.length+"</b><span>incoming</span></div><div><b>"+outgoing.length+"</b><span>outgoing</span></div><div><b>"+findings.length+"</b><span>findings</span></div><div><b>"+evidence.length+"</b><span>evidence items</span></div></div><h3>Evidence</h3><ul class=\"list\">"+(evidence.length?evidence.map((item)=>"<li>"+esc(item.path+(item.lineStart?":"+item.lineStart:"")+(item.context?" — "+item.context:""))+"</li>").join(""):"<li>No evidence recorded.</li>")+"</ul>"+(findings.length?"<h3 style=\"margin-top:16px\">Attention</h3><ul class=\"list\">"+findings.slice(0,5).map((finding)=>"<li><span class=\"tag "+esc(finding.severity)+"\">"+esc(finding.severity)+"</span> "+esc(finding.code)+"</li>").join("")+"</ul>":"")};
7114
7292
  const renderGraph=()=>{const model=graphModel(),svg=document.querySelector("#graph"),dense=model.edges.length>64,edges=dense?[...model.edges].sort((a,b)=>(b.health?1:0)-(a.health?1:0)||b.count-a.count||a.from.localeCompare(b.from)||a.to.localeCompare(b.to)).slice(0,64):model.edges;if(!model.nodes.length){svg.innerHTML="<text x=\"500\" y=\"270\" text-anchor=\"middle\" class=\"subtle\">Select an app or package to expand this view.</text>";return}const ids=new Set(model.nodes.map((node)=>node.id)),incoming=new Map(model.nodes.map((node)=>[node.id,0])),outgoing=new Map(model.nodes.map((node)=>[node.id,[]]));edges.forEach((edge)=>{if(!ids.has(edge.from)||!ids.has(edge.to))return;incoming.set(edge.to,(incoming.get(edge.to)||0)+1);outgoing.get(edge.from).push(edge.to)});const rank=new Map(),work=[];model.nodes.filter((node)=>(incoming.get(node.id)||0)===0).sort((a,b)=>label(a).localeCompare(label(b))||a.id.localeCompare(b.id)).forEach((node)=>{rank.set(node.id,0);work.push(node)});for(let index=0;index<work.length;index++){const node=work[index];for(const next of outgoing.get(node.id)||[]){const nextRank=Math.max(rank.get(next)||0,(rank.get(node.id)||0)+1);rank.set(next,nextRank);if(!work.some((item)=>item.id===next))work.push(byId.get(next)||{id:next,name:next})}}model.nodes.forEach((node)=>{if(!rank.has(node.id))rank.set(node.id,0)});const columns=new Map();model.nodes.forEach((node)=>{const items=columns.get(rank.get(node.id))||[];items.push(node);columns.set(rank.get(node.id),items)});for(const items of columns.values())items.sort((a,b)=>label(a).localeCompare(label(b))||a.id.localeCompare(b.id));if(dense){columns.clear();model.nodes.forEach((node,index)=>{const column=Math.floor(index/8),items=columns.get(column)||[];items.push(node);columns.set(column,items)})}const maxRows=Math.max(1,...[...columns.values()].map((items)=>items.length)),cellWidth=190,cellHeight=82,pad=28,columnCount=Math.max(1,...columns.keys())+1,width=Math.max(620,columnCount*cellWidth+pad*2,Math.min(1000,svg.clientWidth*1.05)),height=Math.max(520,maxRows*cellHeight+pad*2);svg.setAttribute("viewBox","0 0 "+width+" "+height);svg.style.width=dense?Math.max(1000,width)+"px":"100%";svg.style.minWidth="0";svg.style.height="100%";document.querySelector(".map-wrap")?.classList.toggle("dense",dense);const positions=new Map();for(const [column,items] of columns)items.forEach((node,row)=>positions.set(node.id,{x:pad+column*cellWidth+78,y:pad+row*cellHeight+27}));const defs="<defs><marker id=\"arrow\" markerWidth=\"8\" markerHeight=\"8\" refX=\"7\" refY=\"3\" orient=\"auto\"><path d=\"M0,0 L0,6 L7,3 z\" fill=\"#789087\"/></marker></defs>",edgeMarkup=edges.map((edge)=>{const from=positions.get(edge.from),to=positions.get(edge.to);if(!from||!to)return "";const midX=(from.x+to.x)/2,midY=(from.y+to.y)/2;return "<g><line class=\"edge "+(edge.health?"alert":"")+"\" x1=\""+from.x+"\" y1=\""+from.y+"\" x2=\""+to.x+"\" y2=\""+to.y+"\" marker-end=\"url(#arrow)\"/><text class=\"edge-label\" x=\""+midX+"\" y=\""+midY+"\">"+esc(edge.count>1?edge.count+"×":"")+"</text></g>"}).join(""),nodes=model.nodes.map((node)=>{const pos=positions.get(node.id),issues=nodeIssues(node.id),degree=degreeMap(edges).get(node.id)||0,selected=state.selected===node.id?" selected":"",issue=issues.length?" issue":"",nodeLabel=label(node),nodeKind=node.kind==="domain"?(node.memberCount||0)+" packages":node.kind;return "<g class=\"graph-node"+selected+issue+"\" transform=\"translate("+(pos.x-78)+","+(pos.y-27)+")\"><title>"+esc(nodeLabel)+" · "+esc(nodeKind)+"</title><rect width=\"156\" height=\"54\" rx=\"8\" role=\"button\" tabindex=\"0\" aria-label=\""+esc(nodeLabel+" "+nodeKind)+"\" data-node=\""+esc(node.id)+"\"></rect><text class=\"node-label\" x=\"10\" y=\"19\">"+esc(short(nodeLabel,22))+"</text><text class=\"node-kind\" x=\"10\" y=\"34\">"+esc(short(nodeKind,22))+"</text><text class=\"node-count\" x=\"144\" y=\"19\" text-anchor=\"end\">"+(degree||"")+"</text></g>"}).join("");svg.innerHTML=defs+edgeMarkup+nodes;routeGraphEdges();applyMapTransform();document.querySelector("#map-note").textContent=(state.level==="overview"?"Bounded domain view. ":state.level+" view. ")+"Directional edges are aggregated from canonical relations; "+model.edges.length+" visible connection groups"+(dense?" (showing "+edges.length+" prioritized of "+model.edges.length+").":".");};
7115
7293
  const findingMatches=(finding)=>{const query=state.query.toLowerCase();return(!query||[finding.id,finding.code,finding.message,...finding.entityIds,...finding.relationIds].join(" ").toLowerCase().includes(query))&&(!state.status||finding.status===state.status)&&(!state.severity||finding.severity===state.severity)};
7116
7294
  const findingScope=(finding)=>data.view?.diagnosticGroup?.[finding.id]?.[0]||(()=>{const ids=[...(finding.entityIds||[])];(finding.relationIds||[]).forEach((id)=>{const relation=relationById.get(id);if(relation)ids.push(relation.from,relation.to)});return ids.map(groupFor).sort()[0]||"repository"})();
7117
7295
  const findingGroups=(findings)=>{const groups=new Map();findings.forEach((finding)=>{const scope=findingScope(finding),key=[scope,finding.code,finding.status,finding.severity].join("|"),group=groups.get(key)||{key,scope,code:finding.code,status:finding.status,severity:finding.severity,findings:[]};group.findings.push(finding);groups.set(key,group)});return[...groups.values()].sort((left,right)=>right.findings.length-left.findings.length||left.code.localeCompare(right.code)||left.key.localeCompare(right.key))};
7118
7296
  const renderFinding=(finding)=>"<article class=\"finding\" id=\""+esc("diagnostic-"+finding.id.replace(/[^A-Za-z0-9_-]+/g,"-"))+"\"><div class=\"finding-head\"><h3>"+esc(finding.code)+"</h3><span><span class=\"tag "+esc(finding.severity)+"\">"+esc(finding.severity)+"</span> <span class=\"tag\">"+esc(finding.status)+"</span></span></div><p>"+esc(finding.message)+"</p>"+(finding.evidence.length?"<ul>"+finding.evidence.slice(0,4).map((item)=>"<li>"+esc(item.path+(item.lineStart?":"+item.lineStart:"")+(item.context?" — "+item.context:""))+"</li>").join("")+"</ul>":"")+(finding.remediation?"<p><strong>Next check:</strong> "+esc(finding.remediation)+"</p>":"")+"</article>";
7119
- const renderFindings=()=>{let findings=data.diagnostics.filter(findingMatches);if(state.lens==="risks")findings=findings.filter((finding)=>finding.severity==="error"||finding.severity==="warn");if(state.lens==="evidence")findings=findings.filter((finding)=>finding.evidence.length);if(state.lens==="drift")findings=findings.filter((finding)=>finding.status!=="confirmed");const groups=findingGroups(findings),active=groups.find((group)=>group.key===state.findingGroup);if(!active){state.findingGroup=null;state.findingPage=0}syncHash();const selected=active||null,pageSize=40,pageCount=selected?Math.ceil(selected.findings.length/pageSize):0,page=Math.max(0,Math.min(state.findingPage,Math.max(0,pageCount-1))),start=page*pageSize;document.querySelector("#finding-count").textContent=findings.length+" findings · "+groups.length+" groups";document.querySelector("#findings").innerHTML=findings.length?"<div class=\"finding-groups\">"+groups.map((group)=>"<article class=\"finding-group\"><div class=\"finding-head\"><h3>"+esc(group.code)+"</h3><span><span class=\"tag "+esc(group.severity)+"\">"+esc(group.severity)+"</span> <span class=\"tag\">"+esc(group.status)+"</span></span></div><p><strong>"+group.findings.length+"</strong> finding"+(group.findings.length===1?"":"s")+" · "+esc(groupById.get(group.scope)?.name||group.scope)+"</p><p>"+esc(group.findings[0].message)+"</p><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(group.key)+"\" aria-expanded=\""+String(selected?.key===group.key)+"\">Inspect group</button></article>").join("")+"</div>"+(selected?"<div class=\"finding-detail\"><div class=\"finding-head\"><h3>"+esc(selected.code)+" · "+esc(groupById.get(selected.scope)?.name||selected.scope)+"</h3><span class=\"subtle\">"+selected.findings.length+" total</span></div>"+selected.findings.slice(start,start+pageSize).map(renderFinding).join("")+"<div class=\"finding-actions\"><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page-1)+"\" "+(page===0?"disabled":"")+">Previous</button><span class=\"subtle\">Showing "+(start+1)+"–"+Math.min(start+pageSize,selected.findings.length)+" of "+selected.findings.length+" · page "+(page+1)+"/"+pageCount+"</span><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page+1)+"\" "+(page+1>=pageCount?"disabled":"")+">Next</button></div></div>":""):"<p class=\"empty\">No findings match the current lens and filters.</p>"};
7297
+ const renderFindings=()=>{let findings=data.diagnostics.filter(findingMatches);if(state.lens==="risks")findings=findings.filter((finding)=>finding.severity==="error"||finding.severity==="warn");if(state.lens==="evidence")findings=findings.filter((finding)=>finding.evidence.length);if(state.lens==="drift")findings=findings.filter((finding)=>finding.status!=="confirmed");const itemLabel=state.lens==="evidence"?"evidence checks":"findings",groups=findingGroups(findings),active=groups.find((group)=>group.key===state.findingGroup);if(!active){state.findingGroup=null;state.findingPage=0}syncHash();const selected=active||null,pageSize=40,pageCount=selected?Math.ceil(selected.findings.length/pageSize):0,page=Math.max(0,Math.min(state.findingPage,Math.max(0,pageCount-1))),start=page*pageSize;document.querySelector("#finding-count").textContent=findings.length+" "+itemLabel+" · "+groups.length+" groups";document.querySelector("#findings").innerHTML=findings.length?"<div class=\"finding-groups\">"+groups.map((group)=>"<article class=\"finding-group\"><div class=\"finding-head\"><h3>"+esc(group.code)+"</h3><span><span class=\"tag "+esc(group.severity)+"\">"+esc(group.severity)+"</span> <span class=\"tag\">"+esc(group.status)+"</span></span></div><p><strong>"+group.findings.length+"</strong> "+itemLabel+" · "+esc(groupById.get(group.scope)?.name||group.scope)+"</p><p>"+esc(group.findings[0].message)+"</p><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(group.key)+"\" aria-expanded=\""+String(selected?.key===group.key)+"\">Inspect group</button></article>").join("")+"</div>"+(selected?"<div class=\"finding-detail\"><div class=\"finding-head\"><h3>"+esc(selected.code)+" · "+esc(groupById.get(selected.scope)?.name||selected.scope)+"</h3><span class=\"subtle\">"+selected.findings.length+" "+itemLabel+"</span></div>"+selected.findings.slice(start,start+pageSize).map(renderFinding).join("")+"<div class=\"finding-actions\"><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page-1)+"\" "+(page===0?"disabled":"")+">Previous</button><span class=\"subtle\">Showing "+(start+1)+"–"+Math.min(start+pageSize,selected.findings.length)+" of "+selected.findings.length+" · page "+(page+1)+"/"+pageCount+"</span><button class=\"tab\" type=\"button\" data-finding-group=\""+esc(selected.key)+"\" data-finding-page=\""+(page+1)+"\" "+(page+1>=pageCount?"disabled":"")+">Next</button></div></div>":""):"<p class=\"empty\">No "+itemLabel+" match the current lens and filters.</p>"};
7120
7298
  const renderCoverage=()=>{document.querySelector("#coverage-list").innerHTML=(data.coverage||[]).map((entry)=>{const width=entry.status==="complete"?100:entry.status==="partial"?55:12;return"<div class=\"coverage-row\"><div><b>"+esc(entry.analyzer)+"</b><br><span class=\"subtle\">"+esc(entry.scope)+"</span></div><div class=\"bar\"><i class=\""+(entry.status==="complete"?"":entry.status==="partial"?"partial":"none")+"\" style=\"width:"+width+"%\"></i></div><div>"+esc(entry.status)+"</div></div>"}).join("")||"<p class=\"empty\">No coverage metadata.</p>"};
7121
- const deferFindings=()=>{if(findingsLoaded())return;const count=data.diagnosticCount||0;document.querySelector("#finding-count").textContent=count+" findings · available on demand";document.querySelector("#findings").innerHTML="<div class=\"empty\"><p>Findings stay out of the first paint so large repositories remain responsive.</p><button id=\"load-findings\" class=\"tab\" type=\"button\">Load findings</button></div>"};
7299
+ const deferFindings=()=>{if(findingsLoaded())return;const actionable=data.actionableCount??data.diagnosticCount??0,confirmed=data.confirmedCount??0;document.querySelector("#finding-count").textContent=actionable+" actionable findings · "+confirmed+" confirmed checks available on demand";document.querySelector("#findings").innerHTML="<div class=\"empty\"><p>Findings stay out of the first paint so large repositories remain responsive. Confirmed checks are evidence, not issues.</p><button id=\"load-findings\" class=\"tab\" type=\"button\">Load findings</button></div>"};
7122
7300
  const scopePrompt=()=>{if((state.level==="module"||state.level==="file")&&!state.selected){document.querySelector("#map-note").textContent="Select an app or package to inspect this level.";document.querySelector("#graph").innerHTML="<text x=\"500\" y=\"270\" text-anchor=\"middle\" class=\"subtle\">Select an app or package to expand this view.</text>"}};
7123
7301
  const findingsLoaded=()=>!lazyChunks.includes("chunks/findings.js")||loadedChunks.has("chunks/findings.js");
7124
7302
  const levelsLoaded=()=>!hasLevelChunks||state.level==="overview"||activeLevelChunk===levelChunkName();
@@ -7154,11 +7332,12 @@ var render = (input, options) => {
7154
7332
  const data = reportData(snapshot, report, includeSnippets, options.privacy);
7155
7333
  const scriptBody = script.replace("${DATA}", options.dataExpression ?? embeddedJson(data));
7156
7334
  const largeNote = snapshot.entities.length > 500 || report.diagnostics.length > 1e3 ? "Large snapshots are rendered from compact canonical data with progressive graph levels." : "The viewer starts with a grouped topology and expands into canonical entities on demand.";
7157
- const issueCount = report.diagnostics.filter((diagnostic2) => diagnostic2.severity === "error" || diagnostic2.severity === "warn").length;
7335
+ const actionableCount = report.diagnostics.filter((diagnostic2) => diagnostic2.status !== "confirmed").length;
7336
+ const confirmedCount = report.diagnostics.filter((diagnostic2) => diagnostic2.status === "confirmed").length;
7158
7337
  const unsupportedCount = snapshot.coverage.filter((entry) => entry.status !== "complete").length;
7159
- const policyNote = report.summary.requiredRelationKinds === void 0 ? "Missing relation declarations are checked for every observed relation kind." : report.summary.requiredRelationKinds.length ? `Missing relation declarations are checked for: ${report.summary.requiredRelationKinds.join(", ")}.` : "Missing relation declarations are disabled by configuration; stale, conflicting, and unresolved declarations remain checked.";
7338
+ const policyNote = report.summary.requiredRelationKinds === void 0 ? "Missing relation declarations are checked for every observed relation kind." : report.summary.requiredRelationKinds.length ? `Missing relation declarations are checked for: ${report.summary.requiredRelationKinds.join(", ")}${report.summary.requiredRelationTargets === "internal" ? " between internal entities" : ""}.` : "Missing relation declarations are disabled by configuration; stale, conflicting, and unresolved declarations remain checked.";
7160
7339
  const statusOptions = ["confirmed", "undocumented", "stale-or-unverified", "conflict", "unresolved", "not-analyzed"];
7161
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Doc Bridge \u2014 ${escapeHtml(data.project.name)}</title><style>${styles}</style></head><body>${options.dataScripts ?? ""}<main class="shell"><header class="masthead"><div><div class="eyebrow">Doc Bridge / Knowledge report</div><h1>${escapeHtml(data.project.name)}</h1><p class="lede">A read-only architecture and documentation map. Start broad, then follow evidence to the exact entity, relation, or finding.</p></div><div class="run-meta"><span class="read-only">Read-only snapshot</span><strong>${escapeHtml(data.revision)}</strong><span>${escapeHtml(data.revisionKind)} \xB7 pipeline ${escapeHtml(data.pipelineVersion)}</span></div></header><section class="summary" aria-label="Snapshot summary"><div class="metric"><b>${snapshot.entities.length}</b><span>entities</span></div><div class="metric"><b>${snapshot.relations.length}</b><span>canonical relations</span></div><div class="metric ${issueCount ? "warn" : ""}"><b>${issueCount}</b><span>warn/error findings</span></div><div class="metric ${report.diagnostics.length ? "warn" : ""}"><b>${report.diagnostics.length}</b><span>total findings</span></div><div class="metric ${unsupportedCount ? "bad" : ""}"><b>${unsupportedCount}</b><span>partial / unanalyzed scopes</span></div></section><div class="lens-bar"><nav class="tabs" aria-label="Report lenses"><button class="tab" data-lens="architecture" aria-selected="true">Architecture</button><button class="tab" data-lens="drift" aria-selected="false">Documentation drift</button><button class="tab" data-lens="risks" aria-selected="false">Risks &amp; hotspots</button><button class="tab" data-lens="evidence" aria-selected="false">Evidence</button></nav><span class="subtle">${escapeHtml(largeNote)}</span></div><p id="lens-caption" class="subtle">The repository topology at the selected level.</p><p class="subtle">${escapeHtml(policyNote)}</p><section id="insights" class="insights" aria-label="Attention signals"></section><section class="filters" aria-label="Finding filters"><label>Search <input id="search" type="search" placeholder="Press / to search findings"></label><label>Status <select id="status"><option value="">Any status</option>${statusOptions.map((value) => `<option>${escapeHtml(value)}</option>`).join("")}</select></label><label>Severity <select id="severity"><option value="">Any severity</option><option>error</option><option>warn</option><option>info</option></select></label><button id="reset" type="button">Reset filters</button></section><div class="workspace"><section class="panel" aria-labelledby="architecture-title"><div class="panel-head"><div><h2 id="architecture-title">Architecture map</h2><p id="map-note" class="subtle">Grouped package/external view.</p></div><div class="tabs" aria-label="Graph level"><button class="level" data-level="overview" aria-pressed="true">Overview</button><button class="level" data-level="package" aria-pressed="false">Package</button><button class="level" data-level="module" aria-pressed="false">Module</button><button class="level" data-level="file" aria-pressed="false">File</button></div></div><div class="map-wrap"><svg id="graph" viewBox="0 0 1000 560" role="img" aria-label="Interactive architecture graph"></svg></div><p class="subtle">Node number = visible connection degree. Amber nodes/edges have findings. Click or focus a node to inspect its evidence, connectivity, and findings. Grouped edges preserve canonical relation direction and count.</p></section><aside class="panel side" aria-labelledby="details-title"><div class="panel-head"><div><div class="eyebrow">Evidence trail</div><h2 id="details-title">Details</h2></div><button id="clear-selection" class="tab" type="button">Clear</button></div><div id="details"><p class="subtle">Select a node in the map to inspect its evidence, connectivity, and findings.</p></div></aside></div><section class="panel run" aria-labelledby="run-title"><div class="panel-head"><div><div class="eyebrow">Jest-like diagnostics</div><h2 id="run-title">Run report</h2></div><span id="finding-count" class="subtle">${report.diagnostics.length} shown</span></div><div class="run-summary"><span class="chip"><b>${report.diagnostics.filter((item) => item.severity === "error").length}</b> errors</span><span class="chip"><b>${report.diagnostics.filter((item) => item.severity === "warn").length}</b> warnings</span><span class="chip"><b>${report.diagnostics.filter((item) => item.status === "undocumented").length}</b> undocumented</span><span class="chip"><b>${report.diagnostics.filter((item) => item.status === "stale-or-unverified" || item.status === "conflict").length}</b> drift/conflict</span></div><div id="findings"></div></section><section class="panel run" aria-labelledby="coverage-title"><div class="panel-head"><div><div class="eyebrow">Analyzer boundaries</div><h2 id="coverage-title">Coverage &amp; unsupported areas</h2></div><span class="subtle">Explicit limits are part of the evidence</span></div><div id="coverage-list"></div></section><section class="metadata" aria-label="Run metadata"><div><b>Snapshot</b>${escapeHtml(data.snapshotHash)}</div><div><b>Report</b>${escapeHtml(data.reportHash)}</div><div><b>Configuration</b>${escapeHtml(data.configurationHash)}</div><div><b>Analyzers</b>${escapeHtml(Object.entries(data.analyzerVersions).map(([name, version]) => `${name} ${version}`).join(", "))}</div><div><b>Source revision</b>${escapeHtml(data.revision)} (${escapeHtml(data.revisionKind)})</div><div><b>Mode</b>${options.privacy === "anonymized" ? "Anonymized read-only browser viewer; evidence paths and project identity are redacted." : "Read-only browser viewer; approvals and fixes remain outside this artifact."}</div></section></main><script>${scriptBody}</script></body></html>`;
7340
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Doc Bridge \u2014 ${escapeHtml(data.project.name)}</title><style>${styles}</style></head><body>${options.dataScripts ?? ""}<main class="shell"><header class="masthead"><div><div class="eyebrow">Doc Bridge / Knowledge report</div><h1>${escapeHtml(data.project.name)}</h1><p class="lede">A read-only architecture and documentation map. Start broad, then follow evidence to the exact entity, relation, or finding.</p></div><div class="run-meta"><span class="read-only">Read-only snapshot</span><strong>${escapeHtml(data.revision)}</strong><span>${escapeHtml(data.revisionKind)} \xB7 pipeline ${escapeHtml(data.pipelineVersion)}</span></div></header><section class="summary" aria-label="Snapshot summary"><div class="metric"><b>${snapshot.entities.length}</b><span>entities</span></div><div class="metric"><b>${snapshot.relations.length}</b><span>canonical relations</span></div><div class="metric ${actionableCount ? "warn" : ""}"><b>${actionableCount}</b><span>actionable findings</span></div><div class="metric"><b>${confirmedCount}</b><span>confirmed checks</span></div><div class="metric ${unsupportedCount ? "bad" : ""}"><b>${unsupportedCount}</b><span>partial / unanalyzed scopes</span></div></section><div class="lens-bar"><nav class="tabs" aria-label="Report lenses"><button class="tab" data-lens="architecture" aria-selected="true">Architecture</button><button class="tab" data-lens="drift" aria-selected="false">Documentation drift</button><button class="tab" data-lens="risks" aria-selected="false">Risks &amp; hotspots</button><button class="tab" data-lens="evidence" aria-selected="false">Evidence</button></nav><span class="subtle">${escapeHtml(largeNote)}</span></div><p id="lens-caption" class="subtle">The repository topology at the selected level.</p><p class="subtle">${escapeHtml(policyNote)}</p><section id="insights" class="insights" aria-label="Attention signals"></section><section class="filters" aria-label="Finding filters"><label>Search <input id="search" type="search" placeholder="Press / to search findings"></label><label>Status <select id="status"><option value="">Any status</option>${statusOptions.map((value) => `<option>${escapeHtml(value)}</option>`).join("")}</select></label><label>Severity <select id="severity"><option value="">Any severity</option><option>error</option><option>warn</option><option>info</option></select></label><button id="reset" type="button">Reset filters</button></section><div class="workspace"><section class="panel" aria-labelledby="architecture-title"><div class="panel-head"><div><h2 id="architecture-title">Architecture map</h2><p id="map-note" class="subtle">Grouped package/external view.</p></div><div class="tabs" aria-label="Graph level"><button class="level" data-level="overview" aria-pressed="true">Overview</button><button class="level" data-level="package" aria-pressed="false">Package</button><button class="level" data-level="module" aria-pressed="false">Module</button><button class="level" data-level="file" aria-pressed="false">File</button></div></div><div class="map-wrap"><svg id="graph" viewBox="0 0 1000 560" role="img" aria-label="Interactive architecture graph"></svg></div><p class="subtle">Node number = visible connection degree. Amber nodes/edges have actionable findings. Click or focus a node to inspect its evidence, connectivity, and findings. Grouped edges preserve canonical relation direction and count.</p></section><aside class="panel side" aria-labelledby="details-title"><div class="panel-head"><div><div class="eyebrow">Evidence trail</div><h2 id="details-title">Details</h2></div><button id="clear-selection" class="tab" type="button">Clear</button></div><div id="details"><p class="subtle">Select a node in the map to inspect its evidence, connectivity, and findings.</p></div></aside></div><section class="panel run" aria-labelledby="run-title"><div class="panel-head"><div><div class="eyebrow">Jest-like diagnostics</div><h2 id="run-title">Run report</h2></div><span id="finding-count" class="subtle">${actionableCount} actionable findings \xB7 ${confirmedCount} confirmed checks</span></div><div class="run-summary"><span class="chip"><b>${report.diagnostics.filter((item) => item.severity === "error").length}</b> errors</span><span class="chip"><b>${report.diagnostics.filter((item) => item.severity === "warn").length}</b> warnings</span><span class="chip"><b>${report.diagnostics.filter((item) => item.status === "undocumented").length}</b> undocumented</span><span class="chip"><b>${report.diagnostics.filter((item) => item.status === "stale-or-unverified" || item.status === "conflict").length}</b> drift/conflict</span><span class="chip"><b>${confirmedCount}</b> confirmed checks</span></div><div id="findings"></div></section><section class="panel run" aria-labelledby="coverage-title"><div class="panel-head"><div><div class="eyebrow">Analyzer boundaries</div><h2 id="coverage-title">Coverage &amp; unsupported areas</h2></div><span class="subtle">Explicit limits are part of the evidence</span></div><div id="coverage-list"></div></section><section class="metadata" aria-label="Run metadata"><div><b>Snapshot</b>${escapeHtml(data.snapshotHash)}</div><div><b>Report</b>${escapeHtml(data.reportHash)}</div><div><b>Configuration</b>${escapeHtml(data.configurationHash)}</div><div><b>Analyzers</b>${escapeHtml(Object.entries(data.analyzerVersions).map(([name, version]) => `${name} ${version}`).join(", "))}</div><div><b>Source revision</b>${escapeHtml(data.revision)} (${escapeHtml(data.revisionKind)})</div><div><b>Mode</b>${options.privacy === "anonymized" ? "Anonymized read-only browser viewer; evidence paths and project identity are redacted." : "Read-only browser viewer; approvals and fixes remain outside this artifact."}</div></section></main><script>${scriptBody}</script></body></html>`;
7162
7341
  };
7163
7342
  var parseReportInput = (input) => {
7164
7343
  if (!input || typeof input !== "object") throw new Error("Input must contain snapshot and report artifacts.");
@@ -7264,7 +7443,7 @@ var renderOfflineReportArtifact = (input, options = {}) => {
7264
7443
  diagnosticRelationFindings: Object.fromEntries(diagnosticRelationFindings)
7265
7444
  };
7266
7445
  const files = {
7267
- "chunks/overview.js": chunkScript({ project: data.project, revision: data.revision, revisionKind: data.revisionKind, snapshotHash: data.snapshotHash, reportHash: data.reportHash, configurationHash: data.configurationHash, pipelineVersion: data.pipelineVersion, analyzerVersions: data.analyzerVersions, coverage: data.coverage, diagnosticCount: data.diagnosticCount, diagnosticSummary: data.diagnosticSummary, requiredRelationKinds: data.requiredRelationKinds, entities: packageEntities, relations: packageRelations, view: overviewView }),
7446
+ "chunks/overview.js": chunkScript({ project: data.project, revision: data.revision, revisionKind: data.revisionKind, snapshotHash: data.snapshotHash, reportHash: data.reportHash, configurationHash: data.configurationHash, pipelineVersion: data.pipelineVersion, analyzerVersions: data.analyzerVersions, coverage: data.coverage, diagnosticCount: data.diagnosticCount, actionableCount: data.actionableCount, confirmedCount: data.confirmedCount, diagnosticSummary: data.diagnosticSummary, documentation: data.documentation, requiredRelationKinds: data.requiredRelationKinds, entities: packageEntities, relations: packageRelations, view: overviewView }),
7268
7447
  ...levelFiles,
7269
7448
  ...detailFiles,
7270
7449
  "chunks/findings.js": chunkScript({ diagnostics: data.diagnostics })
@@ -7672,6 +7851,7 @@ var reconcileWorkflow = (root, config) => {
7672
7851
  const report = reconcileKnowledge(snapshot, declared, {
7673
7852
  ...config.reconciliation?.scope === void 0 ? {} : { scope: config.reconciliation.scope },
7674
7853
  ...config.reconciliation?.requiredRelationKinds === void 0 ? {} : { requiredRelationKinds: config.reconciliation.requiredRelationKinds },
7854
+ ...config.reconciliation?.requiredRelationTargets === void 0 ? {} : { requiredRelationTargets: config.reconciliation.requiredRelationTargets },
7675
7855
  ...config.reconciliation?.includeOrphanedDocuments === void 0 ? {} : { includeOrphanedDocuments: config.reconciliation.includeOrphanedDocuments }
7676
7856
  });
7677
7857
  return runWorkflow(workflowOptions(root, config, snapshot.sourceRevision, "reconcile", { reconcile: () => report }, { pipelineVersion: snapshot.pipelineVersion, analyzerVersions: snapshot.analyzerVersions }));
@@ -7725,7 +7905,7 @@ var writeAtomicFile = (path, content) => {
7725
7905
  renameSync3(temporaryPath, path);
7726
7906
  };
7727
7907
  var writeReportArtifact = (htmlPath, artifact2) => {
7728
- mkdirSync8(dirname10(htmlPath), { recursive: true });
7908
+ mkdirSync7(dirname9(htmlPath), { recursive: true });
7729
7909
  if (artifact2.mode === "single-file") {
7730
7910
  writeAtomicFile(htmlPath, artifact2.indexHtml);
7731
7911
  const artifactDir2 = htmlPath.replace(/\.html?$/i, "");
@@ -7738,14 +7918,14 @@ var writeReportArtifact = (htmlPath, artifact2) => {
7738
7918
  const launcherTemp = `${htmlPath}.tmp-${process.pid}`;
7739
7919
  rmSync3(temporaryDir, { recursive: true, force: true });
7740
7920
  rmSync3(backupDir, { recursive: true, force: true });
7741
- mkdirSync8(temporaryDir, { recursive: true });
7921
+ mkdirSync7(temporaryDir, { recursive: true });
7742
7922
  for (const [file, content] of Object.entries(artifact2.files)) {
7743
7923
  const filePath = resolve18(temporaryDir, file);
7744
- mkdirSync8(dirname10(filePath), { recursive: true });
7924
+ mkdirSync7(dirname9(filePath), { recursive: true });
7745
7925
  writeFileSync8(filePath, content, "utf8");
7746
7926
  }
7747
7927
  writeFileSync8(resolve18(temporaryDir, "manifest.json"), artifact2.manifest, "utf8");
7748
- const frameSource = `${relative10(dirname10(htmlPath), artifactDir)}/index.html`;
7928
+ const frameSource = `${relative10(dirname9(htmlPath), artifactDir)}/index.html`;
7749
7929
  const launcher = `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Doc Bridge report</title></head><body style="margin:0"><iframe title="Doc Bridge report" src="${frameSource}" style="border:0;width:100vw;height:100vh"></iframe></body></html>`;
7750
7930
  writeFileSync8(launcherTemp, launcher, "utf8");
7751
7931
  try {
@@ -7777,7 +7957,7 @@ var runWorkflowCommand = (command, flags, configPath, argv) => {
7777
7957
  const report = parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, "reconcile"));
7778
7958
  const outputPath = optionValues(argv, "--output")[0] ?? ".doc-bridge/report.html";
7779
7959
  const htmlPath = resolve18(root, outputPath);
7780
- mkdirSync8(dirname10(htmlPath), { recursive: true });
7960
+ mkdirSync7(dirname9(htmlPath), { recursive: true });
7781
7961
  const thresholdValue = optionValues(argv, "--report-threshold")[0];
7782
7962
  const thresholdBytes = thresholdValue === void 0 ? void 0 : Number(thresholdValue);
7783
7963
  if (thresholdBytes !== void 0 && (!Number.isSafeInteger(thresholdBytes) || thresholdBytes < 1)) throw new Error("--report-threshold must be a positive integer.");
@@ -7855,7 +8035,7 @@ var runFixCommand = (argv, positional, configPath) => {
7855
8035
  }
7856
8036
  const outputPath = optionValues(argv, "--output")[0];
7857
8037
  if (outputPath) {
7858
- mkdirSync8(dirname10(resolve18(root, outputPath)), { recursive: true });
8038
+ mkdirSync7(dirname9(resolve18(root, outputPath)), { recursive: true });
7859
8039
  writeFileSync8(resolve18(root, outputPath), `${JSON.stringify(proposal2, null, 2)}
7860
8040
  `, "utf8");
7861
8041
  }
@@ -7895,10 +8075,19 @@ var runSuggestCommand = async (flags, configPath) => {
7895
8075
  }
7896
8076
  };
7897
8077
  var writeIfMissing = (path, contents) => {
7898
- if (existsSync21(path)) return false;
7899
- mkdirSync8(dirname10(path), { recursive: true });
7900
- writeFileSync8(path, contents, "utf8");
7901
- return true;
8078
+ mkdirSync7(dirname9(path), { recursive: true });
8079
+ try {
8080
+ const fd = openSync3(path, "wx");
8081
+ try {
8082
+ writeFileSync8(fd, contents, "utf8");
8083
+ return true;
8084
+ } finally {
8085
+ closeSync3(fd);
8086
+ }
8087
+ } catch (error) {
8088
+ if (error.code === "EEXIST") return false;
8089
+ throw error;
8090
+ }
7902
8091
  };
7903
8092
  var demoOwnership = {
7904
8093
  example: {
@@ -8397,7 +8586,7 @@ var runCli = (argv) => {
8397
8586
  const report = runDoctor(root, config);
8398
8587
  if (flags.has("--write-badge")) {
8399
8588
  const badgePath = resolve18(root, ".doc-bridge", "coverage-badge.json");
8400
- mkdirSync8(dirname10(badgePath), { recursive: true });
8589
+ mkdirSync7(dirname9(badgePath), { recursive: true });
8401
8590
  writeFileSync8(badgePath, `${formatDoctorBadgeJson(report.badge)}
8402
8591
  `, "utf8");
8403
8592
  }