@agentskit/doc-bridge 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/action.yml +1 -1
- package/dist/cli/program.js +2461 -274
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +77 -4
- package/dist/config/index.js.map +1 -1
- package/dist/{index-DAeq_OIi.d.ts → index-Di7PkJuf.d.ts} +209 -8
- package/dist/index.d.ts +1942 -4
- package/dist/index.js +2190 -193
- package/dist/index.js.map +1 -1
- package/docs/PRD-doc-bridge-knowledge-engine.md +338 -0
- package/docs/knowledge-engine-runbook.md +44 -0
- package/ecosystem-claims.json +19 -19
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +117 -109
- package/mcpb/manifest.json +25 -1
- package/package.json +2 -2
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +97 -0
- package/src/cli/program.ts +285 -3
- package/src/config/defaults.ts +14 -1
- package/src/config/schema.ts +69 -0
- package/src/conformance/ecosystem-contract.ts +6 -3
- package/src/discovery/documentation.ts +320 -0
- package/src/discovery/repository.ts +514 -0
- package/src/fixes/proposals.ts +165 -0
- package/src/index-builder/content-hash.ts +9 -2
- package/src/index-builder/human-adapters/plain-markdown.ts +17 -1
- package/src/index-builder/llms-txt.ts +22 -2
- package/src/index-builder/scan-corpus.ts +17 -1
- package/src/index.ts +99 -2
- package/src/mcp/server.ts +178 -8
- package/src/reconciliation/reconcile.ts +227 -0
- package/src/report/html.ts +74 -0
- package/src/rules/engine.ts +180 -0
- package/src/safety/repository.ts +84 -0
- package/src/schemas/knowledge.ts +315 -0
- package/src/validate.ts +24 -0
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +238 -0
- package/tsup.config.ts +2 -1
package/dist/cli/program.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/cli/program.ts
|
|
2
|
-
import { existsSync as
|
|
3
|
-
import { dirname as
|
|
2
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync8, readFileSync as readFileSync21, writeFileSync as writeFileSync8 } from "fs";
|
|
3
|
+
import { dirname as dirname10, resolve as resolve18 } from "path";
|
|
4
4
|
import { createInterface } from "readline/promises";
|
|
5
5
|
|
|
6
6
|
// src/config/load-config.ts
|
|
@@ -302,6 +302,14 @@ var applyConfigDefaults = (config) => {
|
|
|
302
302
|
preset: "minimal",
|
|
303
303
|
...config.gates
|
|
304
304
|
},
|
|
305
|
+
rules: {
|
|
306
|
+
mode: "default",
|
|
307
|
+
...config.rules
|
|
308
|
+
},
|
|
309
|
+
safety: {
|
|
310
|
+
redactSecrets: true,
|
|
311
|
+
...config.safety
|
|
312
|
+
},
|
|
305
313
|
surfaces: {
|
|
306
314
|
cli: {
|
|
307
315
|
bin: "ak-docs",
|
|
@@ -310,7 +318,22 @@ var applyConfigDefaults = (config) => {
|
|
|
310
318
|
},
|
|
311
319
|
mcp: {
|
|
312
320
|
enabled: true,
|
|
313
|
-
tools: [
|
|
321
|
+
tools: [
|
|
322
|
+
"handoff.resolve",
|
|
323
|
+
"doc.search",
|
|
324
|
+
"doc.get",
|
|
325
|
+
"gate.status",
|
|
326
|
+
"retriever.query",
|
|
327
|
+
"memory.classify",
|
|
328
|
+
"memory.promoteDraft",
|
|
329
|
+
"registry.topology",
|
|
330
|
+
"docbridge.snapshot",
|
|
331
|
+
"docbridge.report",
|
|
332
|
+
"docbridge.diagnostics",
|
|
333
|
+
"docbridge.relations",
|
|
334
|
+
"docbridge.run",
|
|
335
|
+
"docbridge.proposals"
|
|
336
|
+
],
|
|
314
337
|
transport: "stdio",
|
|
315
338
|
...config.surfaces?.mcp
|
|
316
339
|
},
|
|
@@ -356,7 +379,9 @@ var IndexConfigSchema = z.object({
|
|
|
356
379
|
llmsTxt: z.object({
|
|
357
380
|
enabled: z.boolean().optional(),
|
|
358
381
|
outFile: z.string().min(1).max(512).optional(),
|
|
359
|
-
preamble: z.string().max(4e3).optional()
|
|
382
|
+
preamble: z.string().max(4e3).optional(),
|
|
383
|
+
urlPrefix: z.string().url().optional(),
|
|
384
|
+
pathPrefix: z.string().min(1).max(512).optional()
|
|
360
385
|
}).strict().optional(),
|
|
361
386
|
capabilities: z.object({
|
|
362
387
|
enabled: z.boolean().optional(),
|
|
@@ -426,6 +451,39 @@ var GatesConfigSchema = z.object({
|
|
|
426
451
|
).max(16).optional(),
|
|
427
452
|
options: z.record(z.string(), z.unknown()).optional()
|
|
428
453
|
}).strict();
|
|
454
|
+
var RuleIdSchema = z.enum([
|
|
455
|
+
"documentation-quality",
|
|
456
|
+
"graph-undocumented-relation",
|
|
457
|
+
"declared-unobserved-relation",
|
|
458
|
+
"unresolved-reference",
|
|
459
|
+
"conflicting-declaration",
|
|
460
|
+
"not-analyzed-coverage",
|
|
461
|
+
"stale-documentation",
|
|
462
|
+
"centrality-risk",
|
|
463
|
+
"critical-path-risk",
|
|
464
|
+
"freshness",
|
|
465
|
+
"ownership"
|
|
466
|
+
]);
|
|
467
|
+
var RuleSeveritySchema = z.enum(["off", "info", "warn", "error"]);
|
|
468
|
+
var RulesConfigSchema = z.object({
|
|
469
|
+
mode: z.enum(["default", "recommended", "strict"]).optional(),
|
|
470
|
+
severity: z.record(RuleIdSchema, RuleSeveritySchema).optional(),
|
|
471
|
+
ignore: z.array(RuleIdSchema).max(128).optional(),
|
|
472
|
+
criticalEntities: z.array(z.string().min(1).max(256)).max(128).optional(),
|
|
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()
|
|
475
|
+
}).strict();
|
|
476
|
+
var WorkflowConfigSchema = z.object({
|
|
477
|
+
stateDir: z.string().min(1).max(512).optional()
|
|
478
|
+
}).strict();
|
|
479
|
+
var RepositorySafetyConfigSchema = z.object({
|
|
480
|
+
exclude: z.array(z.string().min(1).max(512)).max(128).optional(),
|
|
481
|
+
maxFiles: z.number().int().positive().max(1e6).optional(),
|
|
482
|
+
maxBytes: z.number().int().positive().max(1e10).optional(),
|
|
483
|
+
maxTimeMs: z.number().int().positive().max(864e5).optional(),
|
|
484
|
+
maxMemoryMb: z.number().int().positive().max(1048576).optional(),
|
|
485
|
+
redactSecrets: z.boolean().optional()
|
|
486
|
+
}).strict();
|
|
429
487
|
var SurfacesConfigSchema = z.object({
|
|
430
488
|
cli: z.object({
|
|
431
489
|
bin: z.string().min(1).max(64).optional(),
|
|
@@ -443,7 +501,13 @@ var SurfacesConfigSchema = z.object({
|
|
|
443
501
|
"retriever.query",
|
|
444
502
|
"memory.classify",
|
|
445
503
|
"memory.promoteDraft",
|
|
446
|
-
"registry.topology"
|
|
504
|
+
"registry.topology",
|
|
505
|
+
"docbridge.snapshot",
|
|
506
|
+
"docbridge.report",
|
|
507
|
+
"docbridge.diagnostics",
|
|
508
|
+
"docbridge.relations",
|
|
509
|
+
"docbridge.run",
|
|
510
|
+
"docbridge.proposals"
|
|
447
511
|
])
|
|
448
512
|
).max(16).optional(),
|
|
449
513
|
transport: z.enum(["stdio", "http"]).optional(),
|
|
@@ -486,7 +550,13 @@ var IntelligenceConfigSchema = z.object({
|
|
|
486
550
|
}).strict().optional()
|
|
487
551
|
}).strict().optional(),
|
|
488
552
|
runtime: z.enum(["agentskit", "custom"]).optional(),
|
|
489
|
-
runtimeModule: z.string().min(1).max(512).optional()
|
|
553
|
+
runtimeModule: z.string().min(1).max(512).optional(),
|
|
554
|
+
registry: z.object({
|
|
555
|
+
enabled: z.boolean().optional(),
|
|
556
|
+
agentId: z.string().min(1).max(256).optional(),
|
|
557
|
+
agentRoot: z.string().min(1).max(512).optional(),
|
|
558
|
+
runnerModule: z.string().min(1).max(512).optional()
|
|
559
|
+
}).strict().optional()
|
|
490
560
|
}).strict();
|
|
491
561
|
var FederationConfigSchema = z.object({
|
|
492
562
|
enabled: z.boolean().optional(),
|
|
@@ -576,6 +646,9 @@ var DocBridgeConfigV1Schema = z.object({
|
|
|
576
646
|
index: IndexConfigSchema.optional(),
|
|
577
647
|
routing: RoutingConfigSchema.optional(),
|
|
578
648
|
gates: GatesConfigSchema.optional(),
|
|
649
|
+
rules: RulesConfigSchema.optional(),
|
|
650
|
+
workflow: WorkflowConfigSchema.optional(),
|
|
651
|
+
safety: RepositorySafetyConfigSchema.optional(),
|
|
579
652
|
surfaces: SurfacesConfigSchema.optional(),
|
|
580
653
|
intelligence: IntelligenceConfigSchema.optional(),
|
|
581
654
|
federation: FederationConfigSchema.optional(),
|
|
@@ -692,7 +765,7 @@ var ProductSchema = z2.object({
|
|
|
692
765
|
role: NonEmptyStringSchema,
|
|
693
766
|
promise: NonEmptyStringSchema,
|
|
694
767
|
maturity: z2.enum(["planning", "alpha", "beta", "stable", "deprecated"]),
|
|
695
|
-
repo: RepoSchema,
|
|
768
|
+
repo: RepoSchema.nullable(),
|
|
696
769
|
accent: z2.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
697
770
|
surfaces: SurfaceSchema,
|
|
698
771
|
navigation: z2.object({
|
|
@@ -707,7 +780,7 @@ var LegacyPropertySchema = z2.object({
|
|
|
707
780
|
barLabel: NonEmptyStringSchema,
|
|
708
781
|
domain: NonEmptyStringSchema,
|
|
709
782
|
url: HttpsUrlSchema,
|
|
710
|
-
repo: RepoSchema,
|
|
783
|
+
repo: RepoSchema.nullable(),
|
|
711
784
|
tagline: NonEmptyStringSchema,
|
|
712
785
|
kind: NonEmptyStringSchema,
|
|
713
786
|
accent: z2.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
@@ -744,7 +817,8 @@ var ClaimProductSchema = z2.object({
|
|
|
744
817
|
productId: SlugSchema,
|
|
745
818
|
source: z2.discriminatedUnion("type", [
|
|
746
819
|
z2.object({ type: z2.literal("endpoint"), url: HttpsUrlSchema }).passthrough(),
|
|
747
|
-
z2.object({ type: z2.literal("repository"), repo: RepoSchema }).passthrough()
|
|
820
|
+
z2.object({ type: z2.literal("repository"), repo: RepoSchema }).passthrough(),
|
|
821
|
+
z2.object({ type: z2.literal("declaration"), summary: NonEmptyStringSchema }).passthrough()
|
|
748
822
|
]),
|
|
749
823
|
verification: z2.enum(["verified", "declared"]),
|
|
750
824
|
claims: z2.array(ClaimSchema)
|
|
@@ -821,8 +895,10 @@ var parseCanonicalEcosystemContract = (manifestInput, claimsInput) => {
|
|
|
821
895
|
if (!claimProduct) throw new Error(`Claims are missing product ${productId}.`);
|
|
822
896
|
if (claimProduct.source.type === "endpoint") {
|
|
823
897
|
if (claimProduct.source.url !== product.surfaces.stats) throw new Error(`Claims source for ${productId} must match stats.`);
|
|
824
|
-
} else if (claimProduct.source.repo !== product.repo) {
|
|
898
|
+
} else if (claimProduct.source.type === "repository" && claimProduct.source.repo !== product.repo) {
|
|
825
899
|
throw new Error(`Claims source for ${productId} must match repo.`);
|
|
900
|
+
} else if (claimProduct.source.type === "declaration" && product.repo !== null) {
|
|
901
|
+
throw new Error(`Declaration source for ${productId} requires a null repo.`);
|
|
826
902
|
}
|
|
827
903
|
if (claimProduct.verification === "declared" && claimProduct.claims.length > 0) {
|
|
828
904
|
throw new Error(`Declared product ${productId} cannot publish claims.`);
|
|
@@ -924,6 +1000,9 @@ var defaultChecksForTarget = (root, opts) => {
|
|
|
924
1000
|
return opts.strict ? ["npm test", "npm run lint"] : ["npm test"];
|
|
925
1001
|
};
|
|
926
1002
|
|
|
1003
|
+
// src/index-builder/scan-corpus.ts
|
|
1004
|
+
import { minimatch } from "minimatch";
|
|
1005
|
+
|
|
927
1006
|
// src/lib/bounded-text.ts
|
|
928
1007
|
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
929
1008
|
var MAX_DOCUMENT_BYTES = 4 * 1024 * 1024;
|
|
@@ -1084,10 +1163,20 @@ var walkFiles = (root, opts) => {
|
|
|
1084
1163
|
};
|
|
1085
1164
|
|
|
1086
1165
|
// src/index-builder/scan-corpus.ts
|
|
1166
|
+
var configuredPathMatches = (relPath, include, exclude) => {
|
|
1167
|
+
const normalize = (pattern) => toPosix(pattern).replace(/^\.\//, "");
|
|
1168
|
+
const included = include?.filter(Boolean).map(normalize) ?? [];
|
|
1169
|
+
const excluded = exclude?.filter(Boolean).map(normalize) ?? [];
|
|
1170
|
+
if (excluded.some((pattern) => minimatch(relPath, pattern, { dot: true }))) return false;
|
|
1171
|
+
return included.length === 0 || included.some((pattern) => minimatch(relPath, pattern, { dot: true }));
|
|
1172
|
+
};
|
|
1087
1173
|
var scanAgentCorpus = (root, config) => {
|
|
1088
1174
|
const agentRoot = containedProjectPath(root, config.corpus.agent.root);
|
|
1089
1175
|
if (!agentRoot) throw new Error("Agent corpus root escapes the project root.");
|
|
1090
|
-
const files = walkFiles(agentRoot, { extensions: [".md", ".mdx"] })
|
|
1176
|
+
const files = walkFiles(agentRoot, { extensions: [".md", ".mdx"] }).filter((abs) => {
|
|
1177
|
+
const relToCorpus = toPosix(abs.replace(`${toPosix(agentRoot)}/`, ""));
|
|
1178
|
+
return configuredPathMatches(relToCorpus, config.corpus.agent.include, config.corpus.agent.exclude);
|
|
1179
|
+
});
|
|
1091
1180
|
const corpusRelRoot = toPosix(config.corpus.agent.root);
|
|
1092
1181
|
const budget = { used: 0 };
|
|
1093
1182
|
return files.map((abs) => {
|
|
@@ -1350,7 +1439,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1350
1439
|
};
|
|
1351
1440
|
|
|
1352
1441
|
// src/version.ts
|
|
1353
|
-
var PACKAGE_VERSION = "1.
|
|
1442
|
+
var PACKAGE_VERSION = "1.5.0";
|
|
1354
1443
|
|
|
1355
1444
|
// src/index-builder/capabilities.ts
|
|
1356
1445
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -1395,12 +1484,29 @@ var sortValue = (value) => {
|
|
|
1395
1484
|
}
|
|
1396
1485
|
return value;
|
|
1397
1486
|
};
|
|
1487
|
+
var canonicalJsonV1 = (payload) => JSON.stringify(sortValue(payload));
|
|
1398
1488
|
var sha256NormalizedV1 = (payload) => {
|
|
1399
|
-
const normalized =
|
|
1489
|
+
const normalized = canonicalJsonV1(payload);
|
|
1400
1490
|
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
1401
1491
|
};
|
|
1492
|
+
var contentHashForArtifactV1 = (artifact2) => {
|
|
1493
|
+
const { contentHash: _contentHash, ...payload } = artifact2;
|
|
1494
|
+
return sha256NormalizedV1(payload);
|
|
1495
|
+
};
|
|
1402
1496
|
|
|
1403
1497
|
// src/index-builder/llms-txt.ts
|
|
1498
|
+
var routePath = (path, pathPrefix) => {
|
|
1499
|
+
const normalizedPath = path.replaceAll("\\", "/").replace(/\.(?:md|mdx)$/, "");
|
|
1500
|
+
const normalizedPrefix = pathPrefix?.replaceAll("\\", "/").replace(/\/$/, "");
|
|
1501
|
+
const relativePath3 = normalizedPrefix && normalizedPath.startsWith(`${normalizedPrefix}/`) ? normalizedPath.slice(normalizedPrefix.length + 1) : normalizedPath;
|
|
1502
|
+
return relativePath3.replace(/^\/+/, "");
|
|
1503
|
+
};
|
|
1504
|
+
var knowledgeUrl = (path, options = {}) => {
|
|
1505
|
+
const relativePath3 = routePath(path, options.pathPrefix);
|
|
1506
|
+
if (!options.urlPrefix) return path;
|
|
1507
|
+
const base = options.urlPrefix.endsWith("/") ? options.urlPrefix : `${options.urlPrefix}/`;
|
|
1508
|
+
return new URL(relativePath3, base).toString();
|
|
1509
|
+
};
|
|
1404
1510
|
var renderLlmsTxt = (config, knowledge, projectName2) => {
|
|
1405
1511
|
const preamble = config.index?.llmsTxt?.preamble ?? `# ${projectName2}
|
|
1406
1512
|
|
|
@@ -1408,7 +1514,8 @@ var renderLlmsTxt = (config, knowledge, projectName2) => {
|
|
|
1408
1514
|
`;
|
|
1409
1515
|
const lines = knowledge.slice(0, 500).map((entry) => {
|
|
1410
1516
|
const desc = entry.description ? `: ${entry.description}` : "";
|
|
1411
|
-
|
|
1517
|
+
const url = knowledgeUrl(entry.path, config.index?.llmsTxt);
|
|
1518
|
+
return `- [${entry.title}](${url})${desc}`;
|
|
1412
1519
|
});
|
|
1413
1520
|
return `${preamble.trim()}
|
|
1414
1521
|
|
|
@@ -1640,11 +1747,23 @@ var nextraAdapter = {
|
|
|
1640
1747
|
};
|
|
1641
1748
|
|
|
1642
1749
|
// src/index-builder/human-adapters/plain-markdown.ts
|
|
1750
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
1751
|
+
var stringPatterns = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
|
|
1752
|
+
var matchesConfiguredPath = (relPath, options) => {
|
|
1753
|
+
const normalize = (pattern) => pattern.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
1754
|
+
const include = stringPatterns(options?.include).map(normalize);
|
|
1755
|
+
const exclude = stringPatterns(options?.exclude).map(normalize);
|
|
1756
|
+
if (exclude.some((pattern) => minimatch2(relPath, pattern, { dot: true }))) return false;
|
|
1757
|
+
return include.length === 0 || include.some((pattern) => minimatch2(relPath, pattern, { dot: true }));
|
|
1758
|
+
};
|
|
1643
1759
|
var plainMarkdownAdapter = {
|
|
1644
1760
|
plugin: "plain-markdown",
|
|
1645
1761
|
scan: ({ root, config }) => {
|
|
1646
1762
|
const humanRoot = optionString(config.options, ["contentDir", "root", "docsDir"]) ?? "docs";
|
|
1647
|
-
return scanMarkdownDocs(root, humanRoot, {
|
|
1763
|
+
return scanMarkdownDocs(root, humanRoot, {
|
|
1764
|
+
urlPrefix: config.options?.urlPrefix,
|
|
1765
|
+
includeRelPath: (relPath) => matchesConfiguredPath(relPath, config.options)
|
|
1766
|
+
});
|
|
1648
1767
|
}
|
|
1649
1768
|
};
|
|
1650
1769
|
|
|
@@ -1673,7 +1792,7 @@ var starlightAdapter = {
|
|
|
1673
1792
|
};
|
|
1674
1793
|
|
|
1675
1794
|
// src/index-builder/human-adapters/vitepress.ts
|
|
1676
|
-
import { minimatch } from "minimatch";
|
|
1795
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
1677
1796
|
var isVitePressPage = (relPath) => !relPath.split("/").some((part) => part === ".vitepress" || part.startsWith("."));
|
|
1678
1797
|
var srcExcludePatterns = (value) => {
|
|
1679
1798
|
if (value === void 0) return [];
|
|
@@ -1687,7 +1806,7 @@ var srcExcludePatterns = (value) => {
|
|
|
1687
1806
|
return pattern;
|
|
1688
1807
|
});
|
|
1689
1808
|
};
|
|
1690
|
-
var isExcluded = (relPath, patterns) => patterns.some((pattern) =>
|
|
1809
|
+
var isExcluded = (relPath, patterns) => patterns.some((pattern) => minimatch3(relPath, pattern, { dot: true }));
|
|
1691
1810
|
var vitepressSlug = (relPath, cleanUrls) => {
|
|
1692
1811
|
const slug2 = routeSlug(relPath);
|
|
1693
1812
|
if (cleanUrls || /(?:^|\/)index\.mdx?$/.test(relPath)) return slug2;
|
|
@@ -2142,26 +2261,26 @@ var llmsRule = (root, config, options) => {
|
|
|
2142
2261
|
if (key !== llmsKey && !rawSources.has(key)) rawSources.set(key, path);
|
|
2143
2262
|
}
|
|
2144
2263
|
const paths = [llmsPath, ...rawSources.values()];
|
|
2145
|
-
const
|
|
2264
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2146
2265
|
const generated = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
2147
2266
|
const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? "project");
|
|
2148
|
-
const llmsIsFresh =
|
|
2149
|
-
if (
|
|
2150
|
-
|
|
2151
|
-
...
|
|
2267
|
+
const llmsIsFresh = evidence2[0]?.content === expectedLlms;
|
|
2268
|
+
if (evidence2[0]?.exists) {
|
|
2269
|
+
evidence2[0] = {
|
|
2270
|
+
...evidence2[0],
|
|
2152
2271
|
evidence: {
|
|
2153
|
-
...
|
|
2272
|
+
...evidence2[0].evidence,
|
|
2154
2273
|
detail: llmsIsFresh ? "File matches the deterministic ak-docs output." : "File is stale or was not generated by the current ak-docs inputs."
|
|
2155
2274
|
}
|
|
2156
2275
|
};
|
|
2157
2276
|
}
|
|
2158
|
-
const passed = config.index?.llmsTxt?.enabled !== false && paths.length > 1 && llmsIsFresh &&
|
|
2277
|
+
const passed = config.index?.llmsTxt?.enabled !== false && paths.length > 1 && llmsIsFresh && evidence2.every((item) => item.exists);
|
|
2159
2278
|
return {
|
|
2160
2279
|
id: "llms-and-raw-source",
|
|
2161
2280
|
level: "required",
|
|
2162
2281
|
passed,
|
|
2163
2282
|
message: passed ? `Resolved llms.txt and ${paths.length - 1} raw source(s).` : "llms.txt must be enabled, current, and accompanied by at least one readable raw source.",
|
|
2164
|
-
evidence:
|
|
2283
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2165
2284
|
remediation: {
|
|
2166
2285
|
command: "ak-docs index",
|
|
2167
2286
|
detail: "Generate llms.txt and configure conformance.documentationStandardV1.rawSources."
|
|
@@ -2180,8 +2299,8 @@ var ecosystemContract = (root, options) => {
|
|
|
2180
2299
|
}
|
|
2181
2300
|
const manifestFile = fileEvidence(root, declaration.manifest);
|
|
2182
2301
|
const claimsFile = fileEvidence(root, declaration.claims);
|
|
2183
|
-
const
|
|
2184
|
-
if (!manifestFile.exists || !claimsFile.exists) return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
2302
|
+
const evidence2 = [manifestFile.evidence, claimsFile.evidence];
|
|
2303
|
+
if (!manifestFile.exists || !claimsFile.exists) return { passed: false, urls: /* @__PURE__ */ new Set(), evidence: evidence2 };
|
|
2185
2304
|
try {
|
|
2186
2305
|
const manifest = JSON.parse(manifestFile.content);
|
|
2187
2306
|
const claims = JSON.parse(claimsFile.content);
|
|
@@ -2199,15 +2318,15 @@ var ecosystemContract = (root, options) => {
|
|
|
2199
2318
|
if (typeof value === "string" && /^https:\/\//.test(value)) urls.add(normalizedUrl(value));
|
|
2200
2319
|
}
|
|
2201
2320
|
}
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
return { passed: urls.size > 0, urls, evidence };
|
|
2321
|
+
evidence2[0] = { path: declaration.manifest, detail: `Validated ${productIds.length} canonical product(s), including ${declaration.productId}.` };
|
|
2322
|
+
evidence2[1] = { path: declaration.claims, detail: `Validated claim-ledger identity for ${contract.claims.products.length} product(s).` };
|
|
2323
|
+
return { passed: urls.size > 0, urls, evidence: evidence2 };
|
|
2205
2324
|
} catch (error) {
|
|
2206
|
-
|
|
2325
|
+
evidence2.push({
|
|
2207
2326
|
path: `${declaration.manifest}, ${declaration.claims}`,
|
|
2208
2327
|
detail: error instanceof Error ? error.message : "Canonical ecosystem contract is invalid."
|
|
2209
2328
|
});
|
|
2210
|
-
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
2329
|
+
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence: evidence2 };
|
|
2211
2330
|
}
|
|
2212
2331
|
};
|
|
2213
2332
|
var handoffsRule = (root, config) => {
|
|
@@ -2233,13 +2352,13 @@ var handoffsRule = (root, config) => {
|
|
|
2233
2352
|
};
|
|
2234
2353
|
var contributionRule = (root, options) => {
|
|
2235
2354
|
const paths = options.contributionPaths?.length ? options.contributionPaths : ["CONTRIBUTING.md"];
|
|
2236
|
-
const
|
|
2355
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2237
2356
|
return {
|
|
2238
2357
|
id: "contribution",
|
|
2239
2358
|
level: "required",
|
|
2240
|
-
passed:
|
|
2241
|
-
message:
|
|
2242
|
-
evidence:
|
|
2359
|
+
passed: evidence2.some((item) => item.exists),
|
|
2360
|
+
message: evidence2.some((item) => item.exists) ? "Contribution guidance is available." : "Contribution guidance is missing.",
|
|
2361
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2243
2362
|
remediation: {
|
|
2244
2363
|
command: "edit CONTRIBUTING.md",
|
|
2245
2364
|
detail: "Document setup, validation commands, and the pull-request workflow."
|
|
@@ -2248,13 +2367,13 @@ var contributionRule = (root, options) => {
|
|
|
2248
2367
|
};
|
|
2249
2368
|
var markersRule = (root, options, kind, level) => {
|
|
2250
2369
|
const declarations = options[kind === "metadata" ? "metadata" : "diagrams"] ?? [];
|
|
2251
|
-
const
|
|
2370
|
+
const evidence2 = [];
|
|
2252
2371
|
let passed = declarations.length > 0;
|
|
2253
2372
|
for (const declaration of declarations) {
|
|
2254
2373
|
const file = fileEvidence(root, declaration.path);
|
|
2255
2374
|
const missing = declaration.contains.filter((marker) => !file.content.includes(marker));
|
|
2256
2375
|
if (!file.exists || missing.length > 0) passed = false;
|
|
2257
|
-
|
|
2376
|
+
evidence2.push({
|
|
2258
2377
|
path: declaration.path,
|
|
2259
2378
|
detail: !file.exists ? file.evidence.detail : missing.length ? `Missing marker(s): ${missing.join(", ")}` : `Found marker(s): ${declaration.contains.join(", ")}`
|
|
2260
2379
|
});
|
|
@@ -2264,7 +2383,7 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2264
2383
|
level,
|
|
2265
2384
|
passed,
|
|
2266
2385
|
message: passed ? `${kind} evidence is complete.` : `${kind} evidence is incomplete.`,
|
|
2267
|
-
evidence,
|
|
2386
|
+
evidence: evidence2,
|
|
2268
2387
|
remediation: {
|
|
2269
2388
|
command: "edit doc-bridge.config.json",
|
|
2270
2389
|
detail: `Declare ${kind} evidence paths and markers that exist in the repository.`
|
|
@@ -2274,14 +2393,14 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2274
2393
|
var linksRule = (root, options) => {
|
|
2275
2394
|
const links = options.links ?? [];
|
|
2276
2395
|
const contract = ecosystemContract(root, options);
|
|
2277
|
-
const
|
|
2396
|
+
const evidence2 = [...contract.evidence];
|
|
2278
2397
|
let passed = links.length > 0 && contract.passed;
|
|
2279
2398
|
for (const link of links) {
|
|
2280
2399
|
const sources = link.paths.map((path) => ({ path, file: fileEvidence(root, path) }));
|
|
2281
2400
|
const matches = sources.filter(({ file }) => file.exists && file.content.includes(link.url));
|
|
2282
2401
|
const canonical = contract.urls.has(normalizedUrl(link.url));
|
|
2283
2402
|
if (matches.length === 0 || !canonical) passed = false;
|
|
2284
|
-
|
|
2403
|
+
evidence2.push({
|
|
2285
2404
|
path: sources.map((source) => source.path).join(", "),
|
|
2286
2405
|
detail: matches.length === 0 ? `Missing ${link.url}` : canonical ? `Found canonical ecosystem URL ${link.url}` : `Found ${link.url}, but it is absent from the canonical ecosystem manifest.`
|
|
2287
2406
|
});
|
|
@@ -2291,7 +2410,7 @@ var linksRule = (root, options) => {
|
|
|
2291
2410
|
level: "required",
|
|
2292
2411
|
passed,
|
|
2293
2412
|
message: passed ? `${links.length} required ecosystem link(s) resolve in source.` : "One or more required ecosystem links are missing from source.",
|
|
2294
|
-
evidence,
|
|
2413
|
+
evidence: evidence2,
|
|
2295
2414
|
remediation: {
|
|
2296
2415
|
command: "edit README.md",
|
|
2297
2416
|
detail: "Sync the canonical ecosystem snapshots and add each configured canonical URL to a declared documentation source."
|
|
@@ -2300,14 +2419,14 @@ var linksRule = (root, options) => {
|
|
|
2300
2419
|
};
|
|
2301
2420
|
var quickstartsRule = (root, options) => {
|
|
2302
2421
|
const quickstarts = options.quickstarts ?? [];
|
|
2303
|
-
const
|
|
2422
|
+
const evidence2 = [];
|
|
2304
2423
|
let passed = quickstarts.length > 0;
|
|
2305
2424
|
for (const quickstart of quickstarts) {
|
|
2306
2425
|
const doc = fileEvidence(root, quickstart.doc);
|
|
2307
2426
|
const test = fileEvidence(root, quickstart.test);
|
|
2308
2427
|
const missingMarkers = quickstart.testContains.filter((marker) => !test.content.includes(marker));
|
|
2309
2428
|
if (!doc.exists || !test.exists || missingMarkers.length > 0 || !quickstart.command.trim()) passed = false;
|
|
2310
|
-
|
|
2429
|
+
evidence2.push(
|
|
2311
2430
|
{ path: quickstart.doc, detail: `${quickstart.id}: ${doc.evidence.detail}` },
|
|
2312
2431
|
{
|
|
2313
2432
|
path: quickstart.test,
|
|
@@ -2320,7 +2439,7 @@ var quickstartsRule = (root, options) => {
|
|
|
2320
2439
|
level: "required",
|
|
2321
2440
|
passed,
|
|
2322
2441
|
message: passed ? `${quickstarts.length} quickstart(s) have executable test evidence.` : "Quickstart test evidence is incomplete.",
|
|
2323
|
-
evidence,
|
|
2442
|
+
evidence: evidence2,
|
|
2324
2443
|
remediation: {
|
|
2325
2444
|
command: "pnpm test",
|
|
2326
2445
|
detail: "Map every quickstart to a documentation path, test file, identifying marker, and CI command."
|
|
@@ -2329,13 +2448,13 @@ var quickstartsRule = (root, options) => {
|
|
|
2329
2448
|
};
|
|
2330
2449
|
var visualsRule = (root, options) => {
|
|
2331
2450
|
const visuals = options.visuals ?? [];
|
|
2332
|
-
const
|
|
2451
|
+
const evidence2 = visuals.map((path) => fileEvidence(root, path, { readContent: false }));
|
|
2333
2452
|
return {
|
|
2334
2453
|
id: "visual-explanations",
|
|
2335
2454
|
level: "recommended",
|
|
2336
|
-
passed: visuals.length > 0 &&
|
|
2337
|
-
message: visuals.length > 0 &&
|
|
2338
|
-
evidence:
|
|
2455
|
+
passed: visuals.length > 0 && evidence2.every((item) => item.exists),
|
|
2456
|
+
message: visuals.length > 0 && evidence2.every((item) => item.exists) ? `${visuals.length} visual asset(s) found.` : "Visual explanation evidence is incomplete.",
|
|
2457
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2339
2458
|
remediation: {
|
|
2340
2459
|
command: "edit doc-bridge.config.json",
|
|
2341
2460
|
detail: "Declare the images or animations that explain the product workflow."
|
|
@@ -2384,14 +2503,941 @@ var formatDocumentationStandardText = (report) => [
|
|
|
2384
2503
|
"",
|
|
2385
2504
|
...report.results.flatMap((result) => [
|
|
2386
2505
|
`${result.status === "pass" ? "PASS" : result.status === "excepted" ? "EXCEPTED" : "FAIL"} [${result.level}] ${result.id}: ${result.message}`,
|
|
2387
|
-
...result.evidence.map((
|
|
2506
|
+
...result.evidence.map((evidence2) => ` evidence: ${evidence2.path} \u2014 ${evidence2.detail}`),
|
|
2388
2507
|
...result.exception ? [` exception: ${result.exception.reason} \u2014 ${result.exception.approvedBy} (${result.exception.trackingUrl})`] : result.ok ? [] : [` fix: ${result.remediation.command} \u2014 ${result.remediation.detail}`]
|
|
2389
2508
|
])
|
|
2390
2509
|
];
|
|
2391
2510
|
|
|
2392
|
-
// src/
|
|
2511
|
+
// src/schemas/knowledge.ts
|
|
2512
|
+
import { z as z3 } from "zod";
|
|
2513
|
+
var KNOWLEDGE_SCHEMA_VERSION = 1;
|
|
2514
|
+
var KNOWLEDGE_CONTENT_HASH_ALGO = "sha256-normalized-v1";
|
|
2515
|
+
var hash = z3.string().regex(/^[a-f0-9]{64}$/);
|
|
2516
|
+
var boundedString = (max) => z3.string().min(1).max(max);
|
|
2517
|
+
var ProvenanceSchema = z3.enum(["observed", "declared", "proposed"]);
|
|
2518
|
+
var FindingStatusSchema = z3.enum([
|
|
2519
|
+
"confirmed",
|
|
2520
|
+
"undocumented",
|
|
2521
|
+
"stale-or-unverified",
|
|
2522
|
+
"conflict",
|
|
2523
|
+
"unresolved",
|
|
2524
|
+
"not-analyzed"
|
|
2525
|
+
]);
|
|
2526
|
+
var DiagnosticSeveritySchema = z3.enum(["off", "info", "warn", "error"]);
|
|
2527
|
+
var EvidenceSourceSchema = z3.enum([
|
|
2528
|
+
"code",
|
|
2529
|
+
"configuration",
|
|
2530
|
+
"documentation",
|
|
2531
|
+
"agent",
|
|
2532
|
+
"derived"
|
|
2533
|
+
]);
|
|
2534
|
+
var EvidenceSchema2 = z3.object({
|
|
2535
|
+
source: EvidenceSourceSchema,
|
|
2536
|
+
path: boundedString(512),
|
|
2537
|
+
lineStart: z3.number().int().positive().optional(),
|
|
2538
|
+
lineEnd: z3.number().int().positive().optional(),
|
|
2539
|
+
contentHash: hash.optional(),
|
|
2540
|
+
context: z3.string().max(1024).optional()
|
|
2541
|
+
}).strict().superRefine((value, context) => {
|
|
2542
|
+
if (value.lineStart !== void 0 && value.lineEnd !== void 0 && value.lineEnd < value.lineStart) {
|
|
2543
|
+
context.addIssue({
|
|
2544
|
+
code: z3.ZodIssueCode.custom,
|
|
2545
|
+
path: ["lineEnd"],
|
|
2546
|
+
message: "Must be greater than or equal to lineStart"
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
});
|
|
2550
|
+
var CoverageStatusSchema = z3.enum(["complete", "partial", "not-analyzed"]);
|
|
2551
|
+
var CoverageSchema = z3.object({
|
|
2552
|
+
analyzer: boundedString(128),
|
|
2553
|
+
scope: boundedString(512),
|
|
2554
|
+
status: CoverageStatusSchema,
|
|
2555
|
+
reason: z3.string().max(1024).optional(),
|
|
2556
|
+
evidence: z3.array(EvidenceSchema2).max(32).optional()
|
|
2557
|
+
}).strict();
|
|
2558
|
+
var ProjectIdentitySchema = z3.object({
|
|
2559
|
+
name: boundedString(128),
|
|
2560
|
+
root: boundedString(512).optional()
|
|
2561
|
+
}).strict();
|
|
2562
|
+
var ArtifactMetadata = {
|
|
2563
|
+
schemaVersion: z3.literal(KNOWLEDGE_SCHEMA_VERSION),
|
|
2564
|
+
contentHash: hash,
|
|
2565
|
+
contentHashAlgo: z3.literal(KNOWLEDGE_CONTENT_HASH_ALGO),
|
|
2566
|
+
project: ProjectIdentitySchema,
|
|
2567
|
+
sourceRevision: boundedString(128),
|
|
2568
|
+
sourceRevisionKind: z3.enum(["git", "content"]),
|
|
2569
|
+
configurationHash: hash,
|
|
2570
|
+
pipelineVersion: boundedString(64),
|
|
2571
|
+
analyzerVersions: z3.record(boundedString(128), boundedString(64))
|
|
2572
|
+
};
|
|
2573
|
+
var EntitySchema = z3.object({
|
|
2574
|
+
id: boundedString(256),
|
|
2575
|
+
kind: boundedString(128),
|
|
2576
|
+
name: boundedString(256),
|
|
2577
|
+
path: boundedString(512).optional(),
|
|
2578
|
+
aliases: z3.array(boundedString(256)).max(32).optional(),
|
|
2579
|
+
provenance: ProvenanceSchema,
|
|
2580
|
+
evidence: z3.array(EvidenceSchema2).max(64),
|
|
2581
|
+
metadata: z3.record(z3.unknown()).optional()
|
|
2582
|
+
}).strict();
|
|
2583
|
+
var RelationSchema = z3.object({
|
|
2584
|
+
id: boundedString(256),
|
|
2585
|
+
kind: boundedString(128),
|
|
2586
|
+
from: boundedString(256),
|
|
2587
|
+
to: boundedString(256),
|
|
2588
|
+
discriminator: boundedString(256).optional(),
|
|
2589
|
+
provenance: ProvenanceSchema,
|
|
2590
|
+
evidence: z3.array(EvidenceSchema2).max(64),
|
|
2591
|
+
metadata: z3.record(z3.unknown()).optional()
|
|
2592
|
+
}).strict();
|
|
2593
|
+
var DiscoverySnapshotV1Schema = z3.object({
|
|
2594
|
+
type: z3.literal("discovery-snapshot"),
|
|
2595
|
+
...ArtifactMetadata,
|
|
2596
|
+
entities: z3.array(EntitySchema).max(5e4),
|
|
2597
|
+
relations: z3.array(RelationSchema).max(1e5),
|
|
2598
|
+
coverage: z3.array(CoverageSchema).max(1e3)
|
|
2599
|
+
}).strict().superRefine((value, context) => {
|
|
2600
|
+
const entityIds = /* @__PURE__ */ new Set();
|
|
2601
|
+
for (const [index, entity] of value.entities.entries()) {
|
|
2602
|
+
if (entityIds.has(entity.id)) {
|
|
2603
|
+
context.addIssue({ code: z3.ZodIssueCode.custom, path: ["entities", index, "id"], message: `Duplicate entity id: ${entity.id}` });
|
|
2604
|
+
}
|
|
2605
|
+
entityIds.add(entity.id);
|
|
2606
|
+
}
|
|
2607
|
+
const relationIds = /* @__PURE__ */ new Set();
|
|
2608
|
+
for (const [index, relation] of value.relations.entries()) {
|
|
2609
|
+
if (relationIds.has(relation.id)) {
|
|
2610
|
+
context.addIssue({ code: z3.ZodIssueCode.custom, path: ["relations", index, "id"], message: `Duplicate relation id: ${relation.id}` });
|
|
2611
|
+
}
|
|
2612
|
+
relationIds.add(relation.id);
|
|
2613
|
+
}
|
|
2614
|
+
});
|
|
2615
|
+
var DiagnosticSchema = z3.object({
|
|
2616
|
+
id: boundedString(256),
|
|
2617
|
+
code: boundedString(128),
|
|
2618
|
+
status: FindingStatusSchema,
|
|
2619
|
+
severity: DiagnosticSeveritySchema,
|
|
2620
|
+
message: boundedString(2048),
|
|
2621
|
+
evidence: z3.array(EvidenceSchema2).max(64),
|
|
2622
|
+
entityIds: z3.array(boundedString(256)).max(64).optional(),
|
|
2623
|
+
relationIds: z3.array(boundedString(256)).max(64).optional(),
|
|
2624
|
+
remediation: z3.string().max(2048).optional()
|
|
2625
|
+
}).strict();
|
|
2626
|
+
var ReconciliationReportV1Schema = z3.object({
|
|
2627
|
+
type: z3.literal("reconciliation-report"),
|
|
2628
|
+
...ArtifactMetadata,
|
|
2629
|
+
snapshotHash: hash,
|
|
2630
|
+
diagnostics: z3.array(DiagnosticSchema).max(1e5),
|
|
2631
|
+
summary: z3.object({
|
|
2632
|
+
entityCount: z3.number().int().nonnegative(),
|
|
2633
|
+
relationCount: z3.number().int().nonnegative(),
|
|
2634
|
+
diagnosticCount: z3.number().int().nonnegative()
|
|
2635
|
+
}).strict()
|
|
2636
|
+
}).strict();
|
|
2637
|
+
var WorkflowStateSchema = z3.enum([
|
|
2638
|
+
"created",
|
|
2639
|
+
"discovering",
|
|
2640
|
+
"analyzed",
|
|
2641
|
+
"compared",
|
|
2642
|
+
"awaiting-agent",
|
|
2643
|
+
"proposed",
|
|
2644
|
+
"awaiting-approval",
|
|
2645
|
+
"validating",
|
|
2646
|
+
"delivered",
|
|
2647
|
+
"failed",
|
|
2648
|
+
"cancelled",
|
|
2649
|
+
"stale",
|
|
2650
|
+
"superseded"
|
|
2651
|
+
]);
|
|
2652
|
+
var WorkflowStepSchema = z3.object({
|
|
2653
|
+
name: boundedString(128),
|
|
2654
|
+
status: z3.enum(["pending", "running", "completed", "failed", "skipped"]),
|
|
2655
|
+
inputHash: hash,
|
|
2656
|
+
outputHash: hash.optional(),
|
|
2657
|
+
artifactRefs: z3.array(boundedString(512)).max(32).optional()
|
|
2658
|
+
}).strict();
|
|
2659
|
+
var WorkflowTransitionSchema = z3.object({
|
|
2660
|
+
from: WorkflowStateSchema.nullable(),
|
|
2661
|
+
to: WorkflowStateSchema,
|
|
2662
|
+
at: z3.string().datetime(),
|
|
2663
|
+
reason: z3.string().max(1024).optional()
|
|
2664
|
+
}).strict();
|
|
2665
|
+
var WorkflowRunV1Schema = z3.object({
|
|
2666
|
+
type: z3.literal("workflow-run"),
|
|
2667
|
+
...ArtifactMetadata,
|
|
2668
|
+
runId: boundedString(128),
|
|
2669
|
+
state: WorkflowStateSchema,
|
|
2670
|
+
steps: z3.array(WorkflowStepSchema).max(32),
|
|
2671
|
+
transitions: z3.array(WorkflowTransitionSchema).max(1e3),
|
|
2672
|
+
artifactRefs: z3.array(boundedString(512)).max(128)
|
|
2673
|
+
}).strict();
|
|
2674
|
+
var ProposalOriginSchema = z3.object({
|
|
2675
|
+
kind: z3.enum(["registry-agent", "manual", "deterministic"]),
|
|
2676
|
+
id: boundedString(256).optional(),
|
|
2677
|
+
version: boundedString(64).optional(),
|
|
2678
|
+
provider: boundedString(128).optional(),
|
|
2679
|
+
model: boundedString(256).optional(),
|
|
2680
|
+
capabilities: z3.array(boundedString(128)).max(32).optional()
|
|
2681
|
+
}).strict().superRefine((value, context) => {
|
|
2682
|
+
if (value.kind === "registry-agent" && value.id === void 0) {
|
|
2683
|
+
context.addIssue({ code: z3.ZodIssueCode.custom, path: ["id"], message: "Registry agent origin requires an id" });
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
var AgentProposalV1Schema = z3.object({
|
|
2687
|
+
type: z3.literal("agent-proposal"),
|
|
2688
|
+
...ArtifactMetadata,
|
|
2689
|
+
proposalId: boundedString(128),
|
|
2690
|
+
baseSnapshotHash: hash,
|
|
2691
|
+
baseReportHash: hash,
|
|
2692
|
+
relatedDiagnosticIds: z3.array(boundedString(256)).max(64),
|
|
2693
|
+
rationale: boundedString(4e3),
|
|
2694
|
+
confidence: z3.number().min(0).max(1),
|
|
2695
|
+
evidence: z3.array(EvidenceSchema2).max(128),
|
|
2696
|
+
intendedChanges: z3.array(boundedString(4e3)).max(64),
|
|
2697
|
+
origin: ProposalOriginSchema,
|
|
2698
|
+
checks: z3.array(boundedString(512)).max(32)
|
|
2699
|
+
}).strict();
|
|
2700
|
+
var FixProposalStatusSchema = z3.enum(["proposed", "approved", "rejected", "stale", "applied", "failed"]);
|
|
2701
|
+
var AffectedFileSchema = z3.object({
|
|
2702
|
+
path: boundedString(512),
|
|
2703
|
+
contentHash: hash
|
|
2704
|
+
}).strict();
|
|
2705
|
+
var FixChangeSchema = z3.object({
|
|
2706
|
+
path: boundedString(512),
|
|
2707
|
+
before: z3.string().max(1e5),
|
|
2708
|
+
after: z3.string().max(1e5)
|
|
2709
|
+
}).strict();
|
|
2710
|
+
var FixProposalV1Schema = z3.object({
|
|
2711
|
+
type: z3.literal("fix-proposal"),
|
|
2712
|
+
...ArtifactMetadata,
|
|
2713
|
+
proposalId: boundedString(128),
|
|
2714
|
+
baseRevision: boundedString(128),
|
|
2715
|
+
affectedFiles: z3.array(AffectedFileSchema).max(256),
|
|
2716
|
+
changes: z3.array(FixChangeSchema).max(256).optional(),
|
|
2717
|
+
preconditions: z3.array(boundedString(2048)).max(64),
|
|
2718
|
+
diff: boundedString(1e5),
|
|
2719
|
+
postconditions: z3.array(boundedString(2048)).max(64),
|
|
2720
|
+
approval: z3.object({
|
|
2721
|
+
proposalHash: hash,
|
|
2722
|
+
approvedAt: z3.string().datetime(),
|
|
2723
|
+
approvedBy: boundedString(256)
|
|
2724
|
+
}).strict().optional(),
|
|
2725
|
+
status: FixProposalStatusSchema
|
|
2726
|
+
}).strict().superRefine((value, context) => {
|
|
2727
|
+
if ((value.status === "approved" || value.status === "applied") && value.approval === void 0) {
|
|
2728
|
+
context.addIssue({
|
|
2729
|
+
code: z3.ZodIssueCode.custom,
|
|
2730
|
+
path: ["approval"],
|
|
2731
|
+
message: `Fix proposal status ${value.status} requires an approval record`
|
|
2732
|
+
});
|
|
2733
|
+
}
|
|
2734
|
+
});
|
|
2735
|
+
|
|
2736
|
+
// src/discovery/documentation.ts
|
|
2737
|
+
var detectionValues = /* @__PURE__ */ new Set(["static", "dynamic", "external"]);
|
|
2738
|
+
var evidence = (path, lineStart, lineEnd = lineStart) => ({
|
|
2739
|
+
source: "documentation",
|
|
2740
|
+
path,
|
|
2741
|
+
lineStart,
|
|
2742
|
+
lineEnd
|
|
2743
|
+
});
|
|
2744
|
+
var diagnostic = (path, code, message, lineStart, lineEnd = lineStart) => ({ code, message, path: "docbridge", evidence: evidence(path, lineStart, lineEnd) });
|
|
2745
|
+
var scalar = (value) => {
|
|
2746
|
+
const trimmed = value.trim();
|
|
2747
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
2748
|
+
return trimmed.slice(1, -1);
|
|
2749
|
+
}
|
|
2750
|
+
return trimmed;
|
|
2751
|
+
};
|
|
2752
|
+
var list = (value) => {
|
|
2753
|
+
const trimmed = value.trim();
|
|
2754
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
2755
|
+
const body = trimmed.slice(1, -1).trim();
|
|
2756
|
+
return body ? body.split(",").map(scalar).filter(Boolean) : [];
|
|
2757
|
+
};
|
|
2758
|
+
var findFrontmatter = (content) => {
|
|
2759
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
2760
|
+
if (lines[0] !== "---") return void 0;
|
|
2761
|
+
const end = lines.findIndex((line, index) => index > 0 && line === "---");
|
|
2762
|
+
return end < 0 ? void 0 : { lines, end };
|
|
2763
|
+
};
|
|
2764
|
+
var addDiagnostic = (diagnostics, input, code, message, line, endLine = line) => {
|
|
2765
|
+
diagnostics.push(diagnostic(input.path, code, message, line, endLine));
|
|
2766
|
+
};
|
|
2767
|
+
var resolveEntity = (reference, entities, input, lineStart, unresolved) => {
|
|
2768
|
+
const resolved = entities.find((entity2) => entity2.id === reference || entity2.aliases?.includes(reference));
|
|
2769
|
+
if (resolved) return resolved;
|
|
2770
|
+
const id = `unresolved:${reference}`;
|
|
2771
|
+
const existing = unresolved.get(id);
|
|
2772
|
+
if (existing) return existing;
|
|
2773
|
+
const entity = {
|
|
2774
|
+
id,
|
|
2775
|
+
kind: "unresolved-reference",
|
|
2776
|
+
name: reference,
|
|
2777
|
+
provenance: "declared",
|
|
2778
|
+
evidence: [evidence(input.path, lineStart)]
|
|
2779
|
+
};
|
|
2780
|
+
unresolved.set(id, entity);
|
|
2781
|
+
return entity;
|
|
2782
|
+
};
|
|
2783
|
+
var relationKey = (from, to, kind) => `${from}\0${to}\0${kind}`;
|
|
2784
|
+
var parseBlock = (input) => {
|
|
2785
|
+
const frontmatter = findFrontmatter(input.content);
|
|
2786
|
+
if (!frontmatter) {
|
|
2787
|
+
if (input.content.replace(/^\uFEFF/, "").startsWith("---")) {
|
|
2788
|
+
return {
|
|
2789
|
+
covers: [],
|
|
2790
|
+
relations: [],
|
|
2791
|
+
hasDocbridge: true,
|
|
2792
|
+
diagnostics: [diagnostic(input.path, "DOCBRIDGE_FRONTMATTER_MALFORMED", "Frontmatter must close with a line containing only ---.", 1)]
|
|
2793
|
+
};
|
|
2794
|
+
}
|
|
2795
|
+
return { covers: [], relations: [], diagnostics: [], hasDocbridge: false };
|
|
2796
|
+
}
|
|
2797
|
+
const { lines, end } = frontmatter;
|
|
2798
|
+
const docbridgeLine = lines.findIndex((line, index) => index > 0 && index < end && /^docbridge\s*:/.test(line));
|
|
2799
|
+
if (docbridgeLine < 0) return { covers: [], relations: [], diagnostics: [], hasDocbridge: false };
|
|
2800
|
+
const diagnostics = [];
|
|
2801
|
+
const covers = [];
|
|
2802
|
+
const relations = [];
|
|
2803
|
+
const inline = lines[docbridgeLine]?.slice("docbridge:".length).trim() ?? "";
|
|
2804
|
+
if (inline && inline !== "{}") addDiagnostic(diagnostics, input, "DOCBRIDGE_BLOCK_MALFORMED", "docbridge must be a nested frontmatter object.", docbridgeLine + 1);
|
|
2805
|
+
let section;
|
|
2806
|
+
let current;
|
|
2807
|
+
const finishRelation = () => {
|
|
2808
|
+
if (current) relations.push(current);
|
|
2809
|
+
current = void 0;
|
|
2810
|
+
};
|
|
2811
|
+
for (let index = docbridgeLine + 1; index < end; index += 1) {
|
|
2812
|
+
const raw = lines[index] ?? "";
|
|
2813
|
+
const trimmed = raw.trim();
|
|
2814
|
+
const line = index + 1;
|
|
2815
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
2816
|
+
if (/\t/.test(raw)) {
|
|
2817
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_INDENTATION_INVALID", "docbridge indentation must use spaces.", line);
|
|
2818
|
+
continue;
|
|
2819
|
+
}
|
|
2820
|
+
if (!raw.startsWith(" ")) {
|
|
2821
|
+
finishRelation();
|
|
2822
|
+
section = void 0;
|
|
2823
|
+
continue;
|
|
2824
|
+
}
|
|
2825
|
+
if (/^ {2}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
|
|
2826
|
+
finishRelation();
|
|
2827
|
+
const match = /^ {2}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
2828
|
+
const key = match?.[1];
|
|
2829
|
+
const value = match?.[2] ?? "";
|
|
2830
|
+
if (key !== "covers" && key !== "relations") {
|
|
2831
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown docbridge field: ${key ?? "(missing)"}.`, line);
|
|
2832
|
+
section = void 0;
|
|
2833
|
+
} else if (key === "covers") {
|
|
2834
|
+
section = "covers";
|
|
2835
|
+
if (value) {
|
|
2836
|
+
const values = list(value);
|
|
2837
|
+
if (!values) addDiagnostic(diagnostics, input, "DOCBRIDGE_COVERS_INVALID", "covers must be a list of entity references.", line);
|
|
2838
|
+
else values.forEach((item) => covers.push({ value: item, line }));
|
|
2839
|
+
}
|
|
2840
|
+
} else {
|
|
2841
|
+
section = "relations";
|
|
2842
|
+
if (value) addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATIONS_INVALID", "relations must be a list of relation objects.", line);
|
|
2843
|
+
}
|
|
2844
|
+
continue;
|
|
2845
|
+
}
|
|
2846
|
+
if (section === "covers" && /^ {4}-\s*/.test(raw)) {
|
|
2847
|
+
const value = scalar(raw.replace(/^ {4}-\s*/, ""));
|
|
2848
|
+
if (!value) addDiagnostic(diagnostics, input, "DOCBRIDGE_REFERENCE_MISSING", "covers entries must not be empty.", line);
|
|
2849
|
+
else covers.push({ value, line });
|
|
2850
|
+
continue;
|
|
2851
|
+
}
|
|
2852
|
+
if (section === "relations" && /^ {4}-\s*/.test(raw)) {
|
|
2853
|
+
finishRelation();
|
|
2854
|
+
const firstField = /^ {4}-\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
2855
|
+
current = { startLine: line, endLine: line, fields: /* @__PURE__ */ new Set() };
|
|
2856
|
+
if (firstField?.[1]) {
|
|
2857
|
+
current.fields.add(firstField[1]);
|
|
2858
|
+
current[firstField[1]] = scalar(firstField[2] ?? "");
|
|
2859
|
+
} else if (raw.replace(/^ {4}-\s*/, "").trim()) {
|
|
2860
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATION_INVALID", "Relation entries must be field mappings.", line);
|
|
2861
|
+
}
|
|
2862
|
+
continue;
|
|
2863
|
+
}
|
|
2864
|
+
if (section === "relations" && current && /^ {6}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
|
|
2865
|
+
const field = /^ {6}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
2866
|
+
const key = field?.[1];
|
|
2867
|
+
current.endLine = line;
|
|
2868
|
+
if (!key || !["from", "to", "kind", "detection"].includes(key)) {
|
|
2869
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown relation field: ${key ?? "(missing)"}.`, line);
|
|
2870
|
+
} else if (current.fields.has(key)) {
|
|
2871
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_DUPLICATE", `Duplicate relation field: ${key}.`, line);
|
|
2872
|
+
} else {
|
|
2873
|
+
current.fields.add(key);
|
|
2874
|
+
current[key] = scalar(field?.[2] ?? "");
|
|
2875
|
+
}
|
|
2876
|
+
continue;
|
|
2877
|
+
}
|
|
2878
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_STRUCTURE_INVALID", `Invalid docbridge structure at line ${line}.`, line);
|
|
2879
|
+
}
|
|
2880
|
+
finishRelation();
|
|
2881
|
+
return { covers, relations, diagnostics, hasDocbridge: true };
|
|
2882
|
+
};
|
|
2883
|
+
var parseDocumentationDeclarations = (input, options) => {
|
|
2884
|
+
const parsed = parseBlock(input);
|
|
2885
|
+
if (!parsed.hasDocbridge) return { hasDocbridge: false, entities: [], relations: [], diagnostics: [] };
|
|
2886
|
+
const diagnostics = [...parsed.diagnostics];
|
|
2887
|
+
const unresolved = /* @__PURE__ */ new Map();
|
|
2888
|
+
const relations = [];
|
|
2889
|
+
const documentId = options.documentId ?? `document:${input.path}`;
|
|
2890
|
+
const relationClaims = /* @__PURE__ */ new Map();
|
|
2891
|
+
if (!parsed.covers.length && !parsed.relations.length) {
|
|
2892
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_CONTENT_MISSING", "docbridge must declare covers or relations.", 1);
|
|
2893
|
+
}
|
|
2894
|
+
for (const [index, cover] of parsed.covers.entries()) {
|
|
2895
|
+
const target = resolveEntity(cover.value, options.snapshot.entities, input, cover.line, unresolved);
|
|
2896
|
+
relations.push({
|
|
2897
|
+
id: `relation:declared:${input.path}:covers:${index}`,
|
|
2898
|
+
kind: "covers",
|
|
2899
|
+
from: documentId,
|
|
2900
|
+
to: target.id,
|
|
2901
|
+
provenance: "declared",
|
|
2902
|
+
evidence: [evidence(input.path, cover.line)]
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
for (const [index, declaration] of parsed.relations.entries()) {
|
|
2906
|
+
const basePath = `relations[${index}]`;
|
|
2907
|
+
const missing = ["from", "to", "kind", "detection"].filter((field) => !declaration[field]);
|
|
2908
|
+
if (missing.length) {
|
|
2909
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATION_FIELD_MISSING", `Relation is missing required field(s): ${missing.join(", ")}.`, declaration.startLine, declaration.endLine);
|
|
2910
|
+
continue;
|
|
2911
|
+
}
|
|
2912
|
+
const from = declaration.from;
|
|
2913
|
+
const to = declaration.to;
|
|
2914
|
+
const kind = declaration.kind;
|
|
2915
|
+
const detection = declaration.detection;
|
|
2916
|
+
if (!detectionValues.has(detection)) {
|
|
2917
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_DETECTION_INVALID", `Invalid relation detection: ${detection}.`, declaration.startLine, declaration.endLine);
|
|
2918
|
+
continue;
|
|
2919
|
+
}
|
|
2920
|
+
const fromEntity = resolveEntity(from, options.snapshot.entities, input, declaration.startLine, unresolved);
|
|
2921
|
+
const toEntity = resolveEntity(to, options.snapshot.entities, input, declaration.startLine, unresolved);
|
|
2922
|
+
const key = relationKey(fromEntity.id, toEntity.id, kind);
|
|
2923
|
+
const previousDetection = relationClaims.get(key);
|
|
2924
|
+
if (previousDetection === detection) addDiagnostic(diagnostics, input, "DOCBRIDGE_DECLARATION_DUPLICATE", "Duplicate relation declaration.", declaration.startLine, declaration.endLine);
|
|
2925
|
+
if (previousDetection && previousDetection !== detection) addDiagnostic(diagnostics, input, "DOCBRIDGE_DECLARATION_CONFLICT", "Conflicting relation declarations use different detection values.", declaration.startLine, declaration.endLine);
|
|
2926
|
+
relationClaims.set(key, previousDetection ?? detection);
|
|
2927
|
+
relations.push({
|
|
2928
|
+
id: `relation:declared:${input.path}:${basePath}`,
|
|
2929
|
+
kind,
|
|
2930
|
+
from: fromEntity.id,
|
|
2931
|
+
to: toEntity.id,
|
|
2932
|
+
discriminator: detection,
|
|
2933
|
+
provenance: "declared",
|
|
2934
|
+
evidence: [evidence(input.path, declaration.startLine, declaration.endLine)],
|
|
2935
|
+
metadata: { detection }
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
return {
|
|
2939
|
+
hasDocbridge: true,
|
|
2940
|
+
entities: [...unresolved.values()].sort((a, b) => a.id.localeCompare(b.id)),
|
|
2941
|
+
relations,
|
|
2942
|
+
diagnostics
|
|
2943
|
+
};
|
|
2944
|
+
};
|
|
2945
|
+
var applyDocumentationDeclarations = (snapshot, documents) => {
|
|
2946
|
+
const entities = new Map(snapshot.entities.map((entity) => [entity.id, entity]));
|
|
2947
|
+
const relations = new Map(snapshot.relations.map((relation) => [relation.id, relation]));
|
|
2948
|
+
const diagnostics = [];
|
|
2949
|
+
for (const document of documents) {
|
|
2950
|
+
const result = parseDocumentationDeclarations(document, { snapshot });
|
|
2951
|
+
diagnostics.push(...result.diagnostics);
|
|
2952
|
+
for (const entity of result.entities) entities.set(entity.id, entity);
|
|
2953
|
+
for (const relation of result.relations) relations.set(relation.id, relation);
|
|
2954
|
+
}
|
|
2955
|
+
const base = { ...snapshot, contentHash: "0".repeat(64), entities: [...entities.values()], relations: [...relations.values()] };
|
|
2956
|
+
return {
|
|
2957
|
+
snapshot: DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) }),
|
|
2958
|
+
diagnostics
|
|
2959
|
+
};
|
|
2960
|
+
};
|
|
2961
|
+
|
|
2962
|
+
// src/discovery/repository.ts
|
|
2963
|
+
import { execFileSync } from "child_process";
|
|
2393
2964
|
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
2394
|
-
import { resolve as
|
|
2965
|
+
import { basename as basename4, dirname as dirname5, extname, join as join8, relative as relative6, resolve as resolve8, sep as sep7 } from "path";
|
|
2966
|
+
import * as ts from "typescript";
|
|
2967
|
+
|
|
2968
|
+
// src/safety/repository.ts
|
|
2969
|
+
import { lstatSync as lstatSync3, readdirSync as readdirSync3, realpathSync as realpathSync7, statSync as statSync4 } from "fs";
|
|
2970
|
+
import { isAbsolute as isAbsolute5, relative as relative5, resolve as resolve7, sep as sep6 } from "path";
|
|
2971
|
+
import { minimatch as minimatch4 } from "minimatch";
|
|
2972
|
+
var DEFAULT_SAFETY_EXCLUDES = ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/build/**", "**/coverage/**", "**/.doc-bridge/**", "**/.env", "**/.env.*", "**/*secret*", "**/*credential*", "**/*.pem", "**/*.key"];
|
|
2973
|
+
var containedPath = (root, candidate) => {
|
|
2974
|
+
const projectRoot = realpathSync7.native(resolve7(root));
|
|
2975
|
+
const unresolved = resolve7(projectRoot, candidate);
|
|
2976
|
+
const unresolvedRelative = relative5(projectRoot, unresolved);
|
|
2977
|
+
if (isAbsolute5(unresolvedRelative) || unresolvedRelative === ".." || unresolvedRelative.startsWith(`..${sep6}`)) return void 0;
|
|
2978
|
+
try {
|
|
2979
|
+
const canonical = realpathSync7.native(unresolved);
|
|
2980
|
+
const canonicalRelative = relative5(projectRoot, canonical);
|
|
2981
|
+
return isAbsolute5(canonicalRelative) || canonicalRelative === ".." || canonicalRelative.startsWith(`..${sep6}`) ? void 0 : canonical;
|
|
2982
|
+
} catch {
|
|
2983
|
+
return unresolved;
|
|
2984
|
+
}
|
|
2985
|
+
};
|
|
2986
|
+
var safeWalkFiles = (root, options = {}) => {
|
|
2987
|
+
const projectRoot = resolve7(root);
|
|
2988
|
+
const extensions = options.extensions ?? [];
|
|
2989
|
+
const excludes = options.exclude ?? DEFAULT_SAFETY_EXCLUDES;
|
|
2990
|
+
const files = [];
|
|
2991
|
+
let bytes = 0;
|
|
2992
|
+
let reason;
|
|
2993
|
+
const started = Date.now();
|
|
2994
|
+
const matchesExclude = (path) => excludes.some((pattern) => minimatch4(path, pattern, { dot: true }));
|
|
2995
|
+
const visit = (directory) => {
|
|
2996
|
+
if (reason) return;
|
|
2997
|
+
if (options.maxTimeMs !== void 0 && Date.now() - started >= options.maxTimeMs) {
|
|
2998
|
+
reason = `Repository scan exceeded the ${options.maxTimeMs} ms time limit.`;
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
if (options.maxMemoryMb !== void 0 && process.memoryUsage().heapUsed > options.maxMemoryMb * 1024 * 1024) {
|
|
3002
|
+
reason = `Repository scan exceeded the ${options.maxMemoryMb} MiB memory limit.`;
|
|
3003
|
+
return;
|
|
3004
|
+
}
|
|
3005
|
+
let entries;
|
|
3006
|
+
try {
|
|
3007
|
+
entries = readdirSync3(directory);
|
|
3008
|
+
} catch {
|
|
3009
|
+
return;
|
|
3010
|
+
}
|
|
3011
|
+
for (const name of entries.sort()) {
|
|
3012
|
+
const absolute = resolve7(directory, name);
|
|
3013
|
+
const relativePath3 = relative5(projectRoot, absolute).split(sep6).join("/");
|
|
3014
|
+
if (matchesExclude(relativePath3) || name === ".git") continue;
|
|
3015
|
+
let stats;
|
|
3016
|
+
try {
|
|
3017
|
+
stats = lstatSync3(absolute);
|
|
3018
|
+
} catch {
|
|
3019
|
+
continue;
|
|
3020
|
+
}
|
|
3021
|
+
if (stats.isSymbolicLink()) continue;
|
|
3022
|
+
if (stats.isDirectory()) {
|
|
3023
|
+
visit(absolute);
|
|
3024
|
+
if (reason) return;
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (!stats.isFile() || extensions.length > 0 && !extensions.some((extension) => name.endsWith(extension))) continue;
|
|
3028
|
+
if (files.length >= (options.maxFiles ?? 1e4)) {
|
|
3029
|
+
reason = `Repository scan exceeded the ${options.maxFiles ?? 1e4} file limit.`;
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
bytes += statSync4(absolute).size;
|
|
3033
|
+
if (options.maxBytes !== void 0 && bytes > options.maxBytes) {
|
|
3034
|
+
reason = `Repository scan exceeded the ${options.maxBytes} byte limit.`;
|
|
3035
|
+
return;
|
|
3036
|
+
}
|
|
3037
|
+
files.push(absolute);
|
|
3038
|
+
}
|
|
3039
|
+
};
|
|
3040
|
+
visit(projectRoot);
|
|
3041
|
+
return { files: files.sort(), incomplete: reason !== void 0, ...reason ? { reason } : {} };
|
|
3042
|
+
};
|
|
3043
|
+
var SECRET_PATTERNS = [
|
|
3044
|
+
/\b(?:sk|pk)[_-](?:live|test)[_-][A-Za-z0-9_-]{12,}\b/g,
|
|
3045
|
+
/\b(?:ghp|github_pat|xox[baprs])_[A-Za-z0-9_-]{12,}\b/g,
|
|
3046
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
3047
|
+
/(?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*["']?[^\s,"']+/gi
|
|
3048
|
+
];
|
|
3049
|
+
var redactSecrets = (value) => SECRET_PATTERNS.reduce((result, pattern) => result.replace(pattern, "[REDACTED]"), value);
|
|
3050
|
+
var redactValue = (value) => Array.isArray(value) ? value.map(redactValue) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactValue(item)])) : typeof value === "string" ? redactSecrets(value) : value;
|
|
3051
|
+
|
|
3052
|
+
// src/discovery/repository.ts
|
|
3053
|
+
var SOURCE_EXTENSIONS = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"];
|
|
3054
|
+
var DOCUMENT_EXTENSIONS = [".md", ".mdx"];
|
|
3055
|
+
var DEFAULT_MAX_FILES2 = 1e4;
|
|
3056
|
+
var EMPTY_HASH = "0".repeat(64);
|
|
3057
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3058
|
+
var readJson = (path) => {
|
|
3059
|
+
try {
|
|
3060
|
+
const value = JSON.parse(readFileSync9(path, "utf8"));
|
|
3061
|
+
return isRecord2(value) ? { value } : { error: "JSON root is not an object" };
|
|
3062
|
+
} catch (error) {
|
|
3063
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
3064
|
+
}
|
|
3065
|
+
};
|
|
3066
|
+
var relativePath = (root, path) => toPosix(relative6(root, path)) || ".";
|
|
3067
|
+
var MAX_ID_LENGTH = 256;
|
|
3068
|
+
var ID_HASH_LENGTH = 32;
|
|
3069
|
+
var entityId = (kind, value) => {
|
|
3070
|
+
const fullId = `${kind}:${value}`;
|
|
3071
|
+
if (fullId.length <= MAX_ID_LENGTH) return fullId;
|
|
3072
|
+
const suffix = `:${sha256NormalizedV1(fullId).slice(0, ID_HASH_LENGTH)}`;
|
|
3073
|
+
return `${fullId.slice(0, MAX_ID_LENGTH - suffix.length)}${suffix}`;
|
|
3074
|
+
};
|
|
3075
|
+
var lineEvidence = (source, root, path, lineStart, lineEnd) => ({
|
|
3076
|
+
source,
|
|
3077
|
+
path: relativePath(root, path),
|
|
3078
|
+
...lineStart !== void 0 ? { lineStart } : {},
|
|
3079
|
+
...lineEnd !== void 0 ? { lineEnd } : {}
|
|
3080
|
+
});
|
|
3081
|
+
var firstLineContaining = (text, pattern) => {
|
|
3082
|
+
const line = text.split(/\r?\n/).findIndex((value) => value.includes(pattern));
|
|
3083
|
+
return line >= 0 ? line + 1 : void 0;
|
|
3084
|
+
};
|
|
3085
|
+
var packageName = (manifest, fallback) => typeof manifest.name === "string" && manifest.name.length > 0 ? manifest.name : fallback || void 0;
|
|
3086
|
+
var workspacePatterns = (root, rootManifest) => {
|
|
3087
|
+
const fromPackageJson = rootManifest?.workspaces;
|
|
3088
|
+
if (Array.isArray(fromPackageJson)) return fromPackageJson.filter((value) => typeof value === "string");
|
|
3089
|
+
if (isRecord2(fromPackageJson) && Array.isArray(fromPackageJson.packages)) {
|
|
3090
|
+
return fromPackageJson.packages.filter((value) => typeof value === "string");
|
|
3091
|
+
}
|
|
3092
|
+
const workspacePath = join8(root, "pnpm-workspace.yaml");
|
|
3093
|
+
if (!existsSync10(workspacePath)) return [];
|
|
3094
|
+
const patterns = [];
|
|
3095
|
+
let inPackages = false;
|
|
3096
|
+
for (const line of readFileSync9(workspacePath, "utf8").split(/\r?\n/)) {
|
|
3097
|
+
const trimmed = line.trim();
|
|
3098
|
+
if (trimmed === "packages:") {
|
|
3099
|
+
inPackages = true;
|
|
3100
|
+
continue;
|
|
3101
|
+
}
|
|
3102
|
+
if (!inPackages) continue;
|
|
3103
|
+
if (trimmed.startsWith("- ")) {
|
|
3104
|
+
patterns.push(trimmed.slice(2).trim().replace(/^['"]|['"]$/g, ""));
|
|
3105
|
+
continue;
|
|
3106
|
+
}
|
|
3107
|
+
if (trimmed && !trimmed.startsWith("#")) inPackages = false;
|
|
3108
|
+
}
|
|
3109
|
+
return patterns;
|
|
3110
|
+
};
|
|
3111
|
+
var discoverPackages = (root, rootManifest, config) => {
|
|
3112
|
+
const packages = [];
|
|
3113
|
+
const coverage = [];
|
|
3114
|
+
const rootManifestPath = join8(root, "package.json");
|
|
3115
|
+
if (rootManifest) {
|
|
3116
|
+
const name = packageName(rootManifest, "");
|
|
3117
|
+
packages.push({
|
|
3118
|
+
id: entityId("package", packageName(rootManifest, "root") ?? "root"),
|
|
3119
|
+
...name ? { name } : {},
|
|
3120
|
+
path: ".",
|
|
3121
|
+
absPath: root,
|
|
3122
|
+
manifestPath: rootManifestPath,
|
|
3123
|
+
manifest: rootManifest
|
|
3124
|
+
});
|
|
3125
|
+
}
|
|
3126
|
+
const configuredPatterns = config?.routing?.options?.packages;
|
|
3127
|
+
const patterns = configuredPatterns?.length ? [...configuredPatterns] : workspacePatterns(root, rootManifest);
|
|
3128
|
+
if (!patterns.length) {
|
|
3129
|
+
coverage.push({ status: "complete" });
|
|
3130
|
+
return { packages, coverage };
|
|
3131
|
+
}
|
|
3132
|
+
const dirs = expandWorkspaceGlobs(root, patterns);
|
|
3133
|
+
for (const absPath of dirs) {
|
|
3134
|
+
const manifestPath = join8(absPath, "package.json");
|
|
3135
|
+
const parsed = readJson(manifestPath);
|
|
3136
|
+
if (!parsed.value) {
|
|
3137
|
+
coverage.push({ status: "partial", reason: `${relativePath(root, manifestPath)}: ${parsed.error ?? "invalid package.json"}` });
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3140
|
+
const path = relativePath(root, absPath);
|
|
3141
|
+
const name = packageName(parsed.value, path);
|
|
3142
|
+
const id = entityId("package", name ?? path);
|
|
3143
|
+
const duplicate = packages.find((pkg) => pkg.id === id);
|
|
3144
|
+
if (duplicate && duplicate.absPath !== absPath) {
|
|
3145
|
+
throw new Error(`Package identity collision for "${id}": "${duplicate.path}" and "${path}".`);
|
|
3146
|
+
}
|
|
3147
|
+
if (!duplicate) packages.push({ id, ...name ? { name } : {}, path, absPath, manifestPath, manifest: parsed.value });
|
|
3148
|
+
}
|
|
3149
|
+
coverage.push({ status: "complete" });
|
|
3150
|
+
return { packages: packages.sort((a, b) => a.id.localeCompare(b.id)), coverage };
|
|
3151
|
+
};
|
|
3152
|
+
var packageForModule = (packages, absPath) => [...packages].filter((pkg) => absPath === pkg.absPath || absPath.startsWith(`${pkg.absPath}${sep7}`)).sort((a, b) => b.absPath.length - a.absPath.length)[0];
|
|
3153
|
+
var readCompilerOptions = (root) => {
|
|
3154
|
+
const configPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
|
|
3155
|
+
if (!configPath) return { options: {} };
|
|
3156
|
+
const parsed = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
3157
|
+
if (parsed.error) return { options: {}, error: ts.flattenDiagnosticMessageText(parsed.error.messageText, "\n") };
|
|
3158
|
+
const config = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname5(configPath));
|
|
3159
|
+
if (config.errors.length) {
|
|
3160
|
+
return {
|
|
3161
|
+
options: config.options,
|
|
3162
|
+
error: ts.flattenDiagnosticMessageText(config.errors[0]?.messageText ?? "Invalid tsconfig", "\n")
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
return { options: config.options };
|
|
3166
|
+
};
|
|
3167
|
+
var scriptKind = (path) => {
|
|
3168
|
+
switch (extname(path)) {
|
|
3169
|
+
case ".js":
|
|
3170
|
+
return ts.ScriptKind.JS;
|
|
3171
|
+
case ".jsx":
|
|
3172
|
+
return ts.ScriptKind.JSX;
|
|
3173
|
+
case ".mjs":
|
|
3174
|
+
return ts.ScriptKind.JS;
|
|
3175
|
+
case ".cjs":
|
|
3176
|
+
return ts.ScriptKind.JS;
|
|
3177
|
+
case ".ts":
|
|
3178
|
+
return ts.ScriptKind.TS;
|
|
3179
|
+
case ".tsx":
|
|
3180
|
+
return ts.ScriptKind.TSX;
|
|
3181
|
+
case ".mts":
|
|
3182
|
+
return ts.ScriptKind.TS;
|
|
3183
|
+
case ".cts":
|
|
3184
|
+
return ts.ScriptKind.TS;
|
|
3185
|
+
default:
|
|
3186
|
+
return ts.ScriptKind.Unknown;
|
|
3187
|
+
}
|
|
3188
|
+
};
|
|
3189
|
+
var nodeEvidence = (root, path, sourceFile, node) => {
|
|
3190
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
3191
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
3192
|
+
return lineEvidence("code", root, path, start, end);
|
|
3193
|
+
};
|
|
3194
|
+
var isExported = (node) => {
|
|
3195
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
|
|
3196
|
+
return modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
3197
|
+
};
|
|
3198
|
+
var exportedNames = (sourceFile) => {
|
|
3199
|
+
const names = /* @__PURE__ */ new Set();
|
|
3200
|
+
const addDeclarationName = (node) => {
|
|
3201
|
+
if (!isExported(node)) return;
|
|
3202
|
+
const name = ts.getNameOfDeclaration(node);
|
|
3203
|
+
if (name && ts.isIdentifier(name)) names.add(name.text);
|
|
3204
|
+
};
|
|
3205
|
+
const visit = (node) => {
|
|
3206
|
+
if (ts.isExportDeclaration(node)) {
|
|
3207
|
+
if (!node.exportClause) names.add("*");
|
|
3208
|
+
else if (ts.isNamedExports(node.exportClause)) {
|
|
3209
|
+
for (const element of node.exportClause.elements) names.add(element.name.text);
|
|
3210
|
+
}
|
|
3211
|
+
} else if (ts.isExportAssignment(node)) {
|
|
3212
|
+
names.add("default");
|
|
3213
|
+
} else if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
3214
|
+
addDeclarationName(node);
|
|
3215
|
+
} else if (ts.isVariableStatement(node) && isExported(node)) {
|
|
3216
|
+
for (const declaration of node.declarationList.declarations) {
|
|
3217
|
+
if (ts.isIdentifier(declaration.name)) names.add(declaration.name.text);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
ts.forEachChild(node, visit);
|
|
3221
|
+
};
|
|
3222
|
+
visit(sourceFile);
|
|
3223
|
+
return [...names].sort();
|
|
3224
|
+
};
|
|
3225
|
+
var moduleReferences = (root, path, sourceFile) => {
|
|
3226
|
+
const references = [];
|
|
3227
|
+
let hasDynamic = false;
|
|
3228
|
+
let hasRuntimeWiring = false;
|
|
3229
|
+
const addReference = (specifier, kind, node) => {
|
|
3230
|
+
references.push({ specifier: specifier.text, kind, evidence: nodeEvidence(root, path, sourceFile, node) });
|
|
3231
|
+
};
|
|
3232
|
+
const visit = (node) => {
|
|
3233
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
3234
|
+
addReference(node.moduleSpecifier, "imports", node);
|
|
3235
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
3236
|
+
addReference(node.moduleSpecifier, "re-exports", node);
|
|
3237
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
3238
|
+
addReference(node.moduleReference.expression, "imports", node);
|
|
3239
|
+
} else if (ts.isCallExpression(node)) {
|
|
3240
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
3241
|
+
if (!node.arguments[0] || !ts.isStringLiteralLike(node.arguments[0])) hasDynamic = true;
|
|
3242
|
+
} else if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
|
|
3243
|
+
const argument = node.arguments[0];
|
|
3244
|
+
if (argument && ts.isStringLiteralLike(argument)) addReference(argument, "imports", node);
|
|
3245
|
+
else hasDynamic = true;
|
|
3246
|
+
} else if (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "register") {
|
|
3247
|
+
hasRuntimeWiring = true;
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
ts.forEachChild(node, visit);
|
|
3251
|
+
};
|
|
3252
|
+
visit(sourceFile);
|
|
3253
|
+
return {
|
|
3254
|
+
references,
|
|
3255
|
+
exports: exportedNames(sourceFile),
|
|
3256
|
+
hasDynamic,
|
|
3257
|
+
hasRuntimeWiring
|
|
3258
|
+
};
|
|
3259
|
+
};
|
|
3260
|
+
var resolveRelativeModule = (specifier, containingFile, modulePaths) => {
|
|
3261
|
+
const base = resolve8(dirname5(containingFile), specifier);
|
|
3262
|
+
const extension = extname(base);
|
|
3263
|
+
const extensionlessBase = extension ? base.slice(0, -extension.length) : base;
|
|
3264
|
+
const candidates = [
|
|
3265
|
+
base,
|
|
3266
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${base}${extension2}`),
|
|
3267
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(base, `index${extension2}`)),
|
|
3268
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${extensionlessBase}${extension2}`),
|
|
3269
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(extensionlessBase, `index${extension2}`))
|
|
3270
|
+
];
|
|
3271
|
+
return candidates.map((candidate) => modulePaths.get(resolve8(candidate))).find(Boolean);
|
|
3272
|
+
};
|
|
3273
|
+
var resolveReference = (reference, containingFile, modules, packages, compilerOptions) => {
|
|
3274
|
+
if (reference.specifier.startsWith(".") || reference.specifier.startsWith("/")) {
|
|
3275
|
+
const relativeTarget = resolveRelativeModule(reference.specifier, containingFile, modules);
|
|
3276
|
+
return relativeTarget ? { targetId: relativeTarget.entityId } : void 0;
|
|
3277
|
+
}
|
|
3278
|
+
const packageTarget = [...packages].filter((pkg) => pkg.name && (reference.specifier === pkg.name || reference.specifier.startsWith(`${pkg.name}/`))).sort((a, b) => (b.name?.length ?? 0) - (a.name?.length ?? 0))[0];
|
|
3279
|
+
if (packageTarget) return { targetId: packageTarget.id };
|
|
3280
|
+
const resolved = ts.resolveModuleName(reference.specifier, containingFile, compilerOptions, ts.sys).resolvedModule?.resolvedFileName;
|
|
3281
|
+
const resolvedTarget = resolved ? modules.get(resolve8(resolved)) : void 0;
|
|
3282
|
+
if (resolvedTarget) return { targetId: resolvedTarget.entityId };
|
|
3283
|
+
return { targetId: entityId("external", reference.specifier) };
|
|
3284
|
+
};
|
|
3285
|
+
var dependencyEntries = (manifest) => {
|
|
3286
|
+
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
3287
|
+
return sections.flatMap((type) => {
|
|
3288
|
+
const value = manifest[type];
|
|
3289
|
+
if (!isRecord2(value)) return [];
|
|
3290
|
+
return Object.keys(value).sort().map((name) => ({ name, type }));
|
|
3291
|
+
});
|
|
3292
|
+
};
|
|
3293
|
+
var sourceRevision = (root, files) => {
|
|
3294
|
+
const contentRevision = () => ({
|
|
3295
|
+
value: sha256NormalizedV1(
|
|
3296
|
+
files.map((path) => ({
|
|
3297
|
+
path: relativePath(root, path),
|
|
3298
|
+
contentHash: sha256NormalizedV1(readFileSync9(path, "utf8"))
|
|
3299
|
+
}))
|
|
3300
|
+
),
|
|
3301
|
+
kind: "content"
|
|
3302
|
+
});
|
|
3303
|
+
try {
|
|
3304
|
+
const status = execFileSync("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
3305
|
+
cwd: root,
|
|
3306
|
+
encoding: "utf8",
|
|
3307
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3308
|
+
}).trim();
|
|
3309
|
+
if (status) return contentRevision();
|
|
3310
|
+
const value = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
3311
|
+
if (value) return { value, kind: "git" };
|
|
3312
|
+
} catch {
|
|
3313
|
+
}
|
|
3314
|
+
return contentRevision();
|
|
3315
|
+
};
|
|
3316
|
+
var hasPackageManagerMetadata = (root, rootManifest) => Boolean(
|
|
3317
|
+
rootManifest?.packageManager || existsSync10(join8(root, "pnpm-lock.yaml")) || existsSync10(join8(root, "pnpm-workspace.yaml")) || existsSync10(join8(root, "yarn.lock")) || existsSync10(join8(root, "bun.lock")) || existsSync10(join8(root, "bun.lockb")) || existsSync10(join8(root, "package-lock.json"))
|
|
3318
|
+
);
|
|
3319
|
+
var artifact = (root, config, files, entities, relations, coverage) => {
|
|
3320
|
+
const revision = sourceRevision(root, files);
|
|
3321
|
+
const base = {
|
|
3322
|
+
type: "discovery-snapshot",
|
|
3323
|
+
schemaVersion: 1,
|
|
3324
|
+
contentHash: EMPTY_HASH,
|
|
3325
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
3326
|
+
project: { name: entities.find((entity) => entity.kind === "package" && entity.path === ".")?.name ?? basename4(root), root: "." },
|
|
3327
|
+
sourceRevision: revision.value,
|
|
3328
|
+
sourceRevisionKind: revision.kind,
|
|
3329
|
+
configurationHash: sha256NormalizedV1(config ?? {}),
|
|
3330
|
+
pipelineVersion: "1.0.0",
|
|
3331
|
+
analyzerVersions: { repository: "1.0.0", "js-ts": "1.0.0" },
|
|
3332
|
+
entities: [...entities].sort((a, b) => a.id.localeCompare(b.id)),
|
|
3333
|
+
relations: [...relations].sort((a, b) => a.id.localeCompare(b.id)),
|
|
3334
|
+
coverage: [...coverage]
|
|
3335
|
+
};
|
|
3336
|
+
return DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
3337
|
+
};
|
|
3338
|
+
var discoverRepository = (opts = {}) => {
|
|
3339
|
+
const root = resolve8(opts.root ?? process.cwd());
|
|
3340
|
+
const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES2;
|
|
3341
|
+
const safety = opts.config?.safety;
|
|
3342
|
+
const maxBytes = opts.maxBytes ?? safety?.maxBytes;
|
|
3343
|
+
const safeOptions = {
|
|
3344
|
+
exclude: [...DEFAULT_SAFETY_EXCLUDES, ...safety?.exclude ?? []],
|
|
3345
|
+
maxFiles,
|
|
3346
|
+
...maxBytes !== void 0 ? { maxBytes } : {},
|
|
3347
|
+
...safety?.maxTimeMs !== void 0 ? { maxTimeMs: safety.maxTimeMs } : {},
|
|
3348
|
+
...safety?.maxMemoryMb !== void 0 ? { maxMemoryMb: safety.maxMemoryMb } : {}
|
|
3349
|
+
};
|
|
3350
|
+
const rootManifestPath = join8(root, "package.json");
|
|
3351
|
+
const rootManifest = readJson(rootManifestPath).value;
|
|
3352
|
+
const packageResult = discoverPackages(root, rootManifest, opts.config);
|
|
3353
|
+
const sourceWalk = safeWalkFiles(root, { extensions: SOURCE_EXTENSIONS, ...safeOptions });
|
|
3354
|
+
const documentWalk = safeWalkFiles(root, { extensions: DOCUMENT_EXTENSIONS, ...safeOptions });
|
|
3355
|
+
const configWalk = safeWalkFiles(root, { extensions: [".json", ".yaml", ".yml", ".js", ".ts"], ...safeOptions });
|
|
3356
|
+
const sourcePaths = sourceWalk.files;
|
|
3357
|
+
const documentPaths = documentWalk.files;
|
|
3358
|
+
const configPaths = configWalk.files.filter((path) => /(?:^|\/)(?:tsconfig|jsconfig|vite\.config|webpack\.config|rollup\.config|next\.config|jest\.config|eslint\.config|vitest\.config)/.test(relativePath(root, path)));
|
|
3359
|
+
const allFiles = [...new Set([rootManifestPath, ...sourcePaths, ...documentPaths, ...configPaths].filter(existsSync10))].sort();
|
|
3360
|
+
const entities = /* @__PURE__ */ new Map();
|
|
3361
|
+
const relations = /* @__PURE__ */ new Map();
|
|
3362
|
+
const addEntity = (entity) => {
|
|
3363
|
+
const existing = entities.get(entity.id);
|
|
3364
|
+
if (existing && (existing.kind !== entity.kind || existing.path !== entity.path)) throw new Error(`Entity identity collision for "${entity.id}".`);
|
|
3365
|
+
entities.set(entity.id, existing ?? entity);
|
|
3366
|
+
};
|
|
3367
|
+
const addRelation = (relation) => {
|
|
3368
|
+
const existing = relations.get(relation.id);
|
|
3369
|
+
if (!existing) {
|
|
3370
|
+
relations.set(relation.id, relation);
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
const evidence2 = new Map(
|
|
3374
|
+
[...existing.evidence, ...relation.evidence].map((item) => [
|
|
3375
|
+
`${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.source}`,
|
|
3376
|
+
item
|
|
3377
|
+
])
|
|
3378
|
+
);
|
|
3379
|
+
relations.set(relation.id, { ...existing, evidence: [...evidence2.values()] });
|
|
3380
|
+
};
|
|
3381
|
+
for (const pkg of packageResult.packages) {
|
|
3382
|
+
const text = readFileSync9(pkg.manifestPath, "utf8");
|
|
3383
|
+
addEntity({ id: pkg.id, kind: "package", name: pkg.name ?? pkg.path, path: pkg.path, provenance: "observed", evidence: [lineEvidence("configuration", root, pkg.manifestPath, firstLineContaining(text, '"name"'))] });
|
|
3384
|
+
}
|
|
3385
|
+
const modules = /* @__PURE__ */ new Map();
|
|
3386
|
+
for (const absPath of sourcePaths) {
|
|
3387
|
+
const path = relativePath(root, absPath);
|
|
3388
|
+
const pkg = packageForModule(packageResult.packages, absPath);
|
|
3389
|
+
const id = entityId("module", path);
|
|
3390
|
+
const text = readFileSync9(absPath, "utf8");
|
|
3391
|
+
const sourceFile = ts.createSourceFile(absPath, text, ts.ScriptTarget.Latest, true, scriptKind(absPath));
|
|
3392
|
+
const exports = exportedNames(sourceFile);
|
|
3393
|
+
modules.set(resolve8(absPath), { absPath, path, entityId: id, ...pkg ? { packageId: pkg.id } : {} });
|
|
3394
|
+
addEntity({ id, kind: "module", name: basename4(absPath), path, provenance: "observed", evidence: [lineEvidence("code", root, absPath, 1, sourceFile.getLineAndCharacterOfPosition(sourceFile.getEnd()).line + 1)], ...exports.length ? { metadata: { exports, test: /(?:\.test|\.spec|__tests__)/.test(path) } } : {} });
|
|
3395
|
+
if (pkg) addRelation({ id: entityId("relation", `${pkg.id}:contains:${id}`), kind: "contains", from: pkg.id, to: id, provenance: "observed", evidence: [lineEvidence("code", root, absPath, 1)] });
|
|
3396
|
+
}
|
|
3397
|
+
for (const absPath of documentPaths) {
|
|
3398
|
+
const path = relativePath(root, absPath);
|
|
3399
|
+
addEntity({ id: entityId("document", path), kind: "document", name: basename4(absPath), path, provenance: "observed", evidence: [lineEvidence("documentation", root, absPath, 1)] });
|
|
3400
|
+
}
|
|
3401
|
+
const compiler = readCompilerOptions(root);
|
|
3402
|
+
const coverage = [
|
|
3403
|
+
...[sourceWalk, documentWalk, configWalk].flatMap((walk, index) => walk.incomplete ? [{ analyzer: "repository", scope: `limits:${["source", "documentation", "configuration"][index]}`, status: "partial", reason: walk.reason }] : []),
|
|
3404
|
+
{ analyzer: "repository", scope: "package-manager", status: hasPackageManagerMetadata(root, rootManifest) ? "complete" : "partial", ...!hasPackageManagerMetadata(root, rootManifest) ? { reason: `No package manager metadata found; default helper would fall back to ${detectPackageManager(root)}.` } : {} },
|
|
3405
|
+
{ analyzer: "repository", scope: "workspace-packages", status: packageResult.coverage.some((item) => item.status === "partial") ? "partial" : "complete", ...packageResult.coverage.find((item) => item.reason)?.reason ? { reason: packageResult.coverage.find((item) => item.reason)?.reason } : {} },
|
|
3406
|
+
{ analyzer: "js-ts", scope: "static-imports-and-exports", status: compiler.error ? "partial" : "complete", ...compiler.error ? { reason: compiler.error } : {} },
|
|
3407
|
+
{ analyzer: "js-ts", scope: "dynamic-imports", status: "not-analyzed", reason: "Dynamic import expressions and non-literal require calls are not resolved." },
|
|
3408
|
+
{ analyzer: "js-ts", scope: "runtime-wiring", status: "not-analyzed", reason: "Reflection, dependency injection and runtime wiring are not inferred." },
|
|
3409
|
+
{ analyzer: "js-ts", scope: "generated-code", status: "not-analyzed", reason: "Generated code is not interpreted as source architecture." }
|
|
3410
|
+
];
|
|
3411
|
+
for (const pkg of packageResult.packages) {
|
|
3412
|
+
const text = readFileSync9(pkg.manifestPath, "utf8");
|
|
3413
|
+
for (const dependency of dependencyEntries(pkg.manifest)) {
|
|
3414
|
+
const target = packageResult.packages.find((candidate) => candidate.name === dependency.name)?.id ?? entityId("external", dependency.name);
|
|
3415
|
+
if (!entities.has(target)) addEntity({ id: target, kind: "external", name: dependency.name, provenance: "observed", evidence: [lineEvidence("configuration", root, pkg.manifestPath, firstLineContaining(text, `"${dependency.name}"`))] });
|
|
3416
|
+
addRelation({ id: entityId("relation", `${pkg.id}:depends-on:${target}:${dependency.type}`), kind: "depends-on", from: pkg.id, to: target, provenance: "observed", evidence: [lineEvidence("configuration", root, pkg.manifestPath, firstLineContaining(text, `"${dependency.name}"`))], metadata: { dependencyType: dependency.type } });
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
3419
|
+
for (const module of modules.values()) {
|
|
3420
|
+
const text = readFileSync9(module.absPath, "utf8");
|
|
3421
|
+
const sourceFile = ts.createSourceFile(module.absPath, text, ts.ScriptTarget.Latest, true, scriptKind(module.absPath));
|
|
3422
|
+
const references = moduleReferences(root, module.absPath, sourceFile);
|
|
3423
|
+
for (const reference of references.references) {
|
|
3424
|
+
const target = resolveReference(reference, module.absPath, modules, packageResult.packages, compiler.options);
|
|
3425
|
+
if (!target) continue;
|
|
3426
|
+
if (!entities.has(target.targetId)) {
|
|
3427
|
+
const externalName = target.targetId.replace(/^external:/, "");
|
|
3428
|
+
addEntity({ id: target.targetId, kind: "external", name: externalName, provenance: "observed", evidence: [reference.evidence] });
|
|
3429
|
+
}
|
|
3430
|
+
addRelation({ id: entityId("relation", `${module.entityId}:${reference.kind}:${target.targetId}`), kind: reference.kind, from: module.entityId, to: target.targetId, provenance: "observed", evidence: [reference.evidence] });
|
|
3431
|
+
}
|
|
3432
|
+
if (references.hasDynamic) coverage.push({ analyzer: "js-ts", scope: `dynamic-imports:${module.path}`, status: "not-analyzed", reason: "A dynamic import or non-literal require was found.", evidence: [lineEvidence("code", root, module.absPath)] });
|
|
3433
|
+
if (references.hasRuntimeWiring) coverage.push({ analyzer: "js-ts", scope: `runtime-wiring:${module.path}`, status: "not-analyzed", reason: "A possible runtime registration/wiring call was found.", evidence: [lineEvidence("code", root, module.absPath)] });
|
|
3434
|
+
}
|
|
3435
|
+
return artifact(root, opts.config, allFiles, [...entities.values()], [...relations.values()], coverage);
|
|
3436
|
+
};
|
|
3437
|
+
|
|
3438
|
+
// src/federation/llms.ts
|
|
3439
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
3440
|
+
import { resolve as resolve9 } from "path";
|
|
2395
3441
|
|
|
2396
3442
|
// src/query/search.ts
|
|
2397
3443
|
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
@@ -2520,9 +3566,9 @@ var defaultFetchText = async (url) => {
|
|
|
2520
3566
|
var sourceText = async (root, source, fetchText) => {
|
|
2521
3567
|
try {
|
|
2522
3568
|
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
2523
|
-
const path =
|
|
2524
|
-
if (!
|
|
2525
|
-
return
|
|
3569
|
+
const path = resolve9(root, source);
|
|
3570
|
+
if (!existsSync11(path)) return null;
|
|
3571
|
+
return readFileSync10(path, "utf8");
|
|
2526
3572
|
} catch {
|
|
2527
3573
|
return null;
|
|
2528
3574
|
}
|
|
@@ -2607,16 +3653,16 @@ var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
|
2607
3653
|
};
|
|
2608
3654
|
|
|
2609
3655
|
// src/gates/run-gates.ts
|
|
2610
|
-
import { readFileSync as
|
|
3656
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
2611
3657
|
|
|
2612
3658
|
// src/query/load-index.ts
|
|
2613
|
-
import { existsSync as
|
|
2614
|
-
import { join as
|
|
3659
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3660
|
+
import { join as join9, resolve as resolve10 } from "path";
|
|
2615
3661
|
|
|
2616
3662
|
// src/schemas/agent-handoff.ts
|
|
2617
|
-
import { z as
|
|
3663
|
+
import { z as z4 } from "zod";
|
|
2618
3664
|
var HANDOFF_SCHEMA_VERSION = 1;
|
|
2619
|
-
var HandoffTargetTypeSchema =
|
|
3665
|
+
var HandoffTargetTypeSchema = z4.enum([
|
|
2620
3666
|
"package",
|
|
2621
3667
|
"module",
|
|
2622
3668
|
"app",
|
|
@@ -2627,133 +3673,133 @@ var HandoffTargetTypeSchema = z3.enum([
|
|
|
2627
3673
|
"change",
|
|
2628
3674
|
"search"
|
|
2629
3675
|
]);
|
|
2630
|
-
var HandoffTargetSchema =
|
|
3676
|
+
var HandoffTargetSchema = z4.object({
|
|
2631
3677
|
type: HandoffTargetTypeSchema,
|
|
2632
|
-
id:
|
|
2633
|
-
path:
|
|
2634
|
-
group:
|
|
2635
|
-
layer:
|
|
3678
|
+
id: z4.string().min(1).max(256),
|
|
3679
|
+
path: z4.string().min(1).max(512).optional(),
|
|
3680
|
+
group: z4.string().min(1).max(128).optional(),
|
|
3681
|
+
layer: z4.string().min(1).max(32).optional()
|
|
2636
3682
|
}).strict();
|
|
2637
|
-
var HandoffBridgeSchema =
|
|
2638
|
-
humanDoc:
|
|
2639
|
-
action:
|
|
2640
|
-
bootstrap:
|
|
3683
|
+
var HandoffBridgeSchema = z4.object({
|
|
3684
|
+
humanDoc: z4.enum(["linked", "missing", "external"]),
|
|
3685
|
+
action: z4.string().min(1).max(256).optional(),
|
|
3686
|
+
bootstrap: z4.string().min(1).max(256).optional()
|
|
2641
3687
|
}).strict();
|
|
2642
|
-
var AgentHandoffV1Schema =
|
|
2643
|
-
type:
|
|
2644
|
-
schemaVersion:
|
|
2645
|
-
source:
|
|
3688
|
+
var AgentHandoffV1Schema = z4.object({
|
|
3689
|
+
type: z4.literal("agent-handoff"),
|
|
3690
|
+
schemaVersion: z4.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
3691
|
+
source: z4.string().min(1).max(512),
|
|
2646
3692
|
target: HandoffTargetSchema,
|
|
2647
|
-
startHere:
|
|
2648
|
-
readBeforeEditing:
|
|
2649
|
-
editRoots:
|
|
2650
|
-
checks:
|
|
2651
|
-
humanDoc:
|
|
3693
|
+
startHere: z4.string().min(1).max(512),
|
|
3694
|
+
readBeforeEditing: z4.array(z4.string().min(1).max(512)).max(64),
|
|
3695
|
+
editRoots: z4.array(z4.string().min(1).max(512)).max(32),
|
|
3696
|
+
checks: z4.array(z4.string().min(1).max(256)).max(32),
|
|
3697
|
+
humanDoc: z4.string().min(1).max(512).nullable().optional(),
|
|
2652
3698
|
bridge: HandoffBridgeSchema.optional(),
|
|
2653
|
-
playbookPatterns:
|
|
2654
|
-
notes:
|
|
3699
|
+
playbookPatterns: z4.array(z4.string().url()).max(16).optional(),
|
|
3700
|
+
notes: z4.array(z4.string().min(1).max(1024)).max(16)
|
|
2655
3701
|
}).strict();
|
|
2656
3702
|
var AgentHandoffLegacySchema = AgentHandoffV1Schema.omit({ schemaVersion: true }).extend({
|
|
2657
|
-
schemaVersion:
|
|
3703
|
+
schemaVersion: z4.literal(HANDOFF_SCHEMA_VERSION).optional()
|
|
2658
3704
|
});
|
|
2659
|
-
var AgentSearchMatchSchema =
|
|
2660
|
-
type:
|
|
2661
|
-
id:
|
|
2662
|
-
path:
|
|
2663
|
-
summary:
|
|
2664
|
-
score:
|
|
2665
|
-
refs:
|
|
3705
|
+
var AgentSearchMatchSchema = z4.object({
|
|
3706
|
+
type: z4.string().min(1).max(64),
|
|
3707
|
+
id: z4.string().min(1).max(256),
|
|
3708
|
+
path: z4.string().min(1).max(512),
|
|
3709
|
+
summary: z4.string().max(2048).optional(),
|
|
3710
|
+
score: z4.number().optional(),
|
|
3711
|
+
refs: z4.number().int().nonnegative().optional()
|
|
2666
3712
|
}).strict();
|
|
2667
|
-
var AgentSearchV1Schema =
|
|
2668
|
-
type:
|
|
2669
|
-
schemaVersion:
|
|
2670
|
-
source:
|
|
2671
|
-
term:
|
|
2672
|
-
count:
|
|
3713
|
+
var AgentSearchV1Schema = z4.object({
|
|
3714
|
+
type: z4.literal("agent-search"),
|
|
3715
|
+
schemaVersion: z4.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
3716
|
+
source: z4.string().min(1).max(512),
|
|
3717
|
+
term: z4.string().min(1).max(512),
|
|
3718
|
+
count: z4.number().int().nonnegative(),
|
|
2673
3719
|
bestMatch: AgentSearchMatchSchema.nullable(),
|
|
2674
|
-
matches:
|
|
2675
|
-
nextCommands:
|
|
3720
|
+
matches: z4.array(AgentSearchMatchSchema).max(32),
|
|
3721
|
+
nextCommands: z4.array(z4.string().min(1).max(512)).max(16)
|
|
2676
3722
|
}).strict();
|
|
2677
3723
|
var normalizeAgentHandoff = (input) => AgentHandoffV1Schema.parse(AgentHandoffLegacySchema.parse(input));
|
|
2678
3724
|
|
|
2679
3725
|
// src/schemas/doc-bridge-index.ts
|
|
2680
|
-
import { z as
|
|
3726
|
+
import { z as z5 } from "zod";
|
|
2681
3727
|
var INDEX_SCHEMA_VERSION = 1;
|
|
2682
|
-
var ContentHashAlgoSchema =
|
|
2683
|
-
var EcosystemPropertySchema =
|
|
2684
|
-
id:
|
|
2685
|
-
name:
|
|
2686
|
-
url:
|
|
2687
|
-
llms:
|
|
3728
|
+
var ContentHashAlgoSchema = z5.literal("sha256-normalized-v1");
|
|
3729
|
+
var EcosystemPropertySchema = z5.object({
|
|
3730
|
+
id: z5.string().min(1).max(64),
|
|
3731
|
+
name: z5.string().min(1).max(128),
|
|
3732
|
+
url: z5.string().url().optional(),
|
|
3733
|
+
llms: z5.string().url().optional()
|
|
2688
3734
|
}).strict();
|
|
2689
|
-
var KnowledgeEntrySchema =
|
|
2690
|
-
id:
|
|
2691
|
-
type:
|
|
2692
|
-
title:
|
|
2693
|
-
path:
|
|
2694
|
-
description:
|
|
3735
|
+
var KnowledgeEntrySchema = z5.object({
|
|
3736
|
+
id: z5.string().min(1).max(256),
|
|
3737
|
+
type: z5.string().min(1).max(128),
|
|
3738
|
+
title: z5.string().min(1).max(256),
|
|
3739
|
+
path: z5.string().min(1).max(512),
|
|
3740
|
+
description: z5.string().max(2048).optional(),
|
|
2695
3741
|
/** Flattened body excerpt for full-text search (not for display). */
|
|
2696
|
-
body:
|
|
2697
|
-
links:
|
|
2698
|
-
tags:
|
|
3742
|
+
body: z5.string().max(8e3).optional(),
|
|
3743
|
+
links: z5.array(z5.string().min(1).max(512)).max(64).optional(),
|
|
3744
|
+
tags: z5.array(z5.string().min(1).max(64)).max(32).optional()
|
|
2699
3745
|
}).strict();
|
|
2700
|
-
var CapabilityRefSchema =
|
|
2701
|
-
id:
|
|
2702
|
-
kind:
|
|
2703
|
-
description:
|
|
3746
|
+
var CapabilityRefSchema = z5.object({
|
|
3747
|
+
id: z5.string().min(1).max(256),
|
|
3748
|
+
kind: z5.string().min(1).max(64),
|
|
3749
|
+
description: z5.string().max(512).optional()
|
|
2704
3750
|
}).strict();
|
|
2705
|
-
var OwnershipRecordSchema =
|
|
2706
|
-
id:
|
|
2707
|
-
path:
|
|
2708
|
-
group:
|
|
2709
|
-
layer:
|
|
2710
|
-
purpose:
|
|
2711
|
-
checks:
|
|
2712
|
-
agentDoc:
|
|
2713
|
-
humanDoc:
|
|
2714
|
-
readme:
|
|
3751
|
+
var OwnershipRecordSchema = z5.object({
|
|
3752
|
+
id: z5.string().min(1).max(256),
|
|
3753
|
+
path: z5.string().min(1).max(512),
|
|
3754
|
+
group: z5.string().min(1).max(128).optional(),
|
|
3755
|
+
layer: z5.string().min(1).max(32).optional(),
|
|
3756
|
+
purpose: z5.string().max(1024).optional(),
|
|
3757
|
+
checks: z5.array(z5.string().min(1).max(256)).max(32),
|
|
3758
|
+
agentDoc: z5.string().min(1).max(512).optional(),
|
|
3759
|
+
humanDoc: z5.string().min(1).max(512).optional(),
|
|
3760
|
+
readme: z5.string().min(1).max(512).optional()
|
|
2715
3761
|
}).strict();
|
|
2716
|
-
var IndexLookupSchema =
|
|
2717
|
-
packages:
|
|
2718
|
-
ownership:
|
|
2719
|
-
intents:
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
id:
|
|
2723
|
-
title:
|
|
2724
|
-
paths:
|
|
3762
|
+
var IndexLookupSchema = z5.object({
|
|
3763
|
+
packages: z5.array(z5.string().min(1).max(256)).max(2e3),
|
|
3764
|
+
ownership: z5.record(z5.string().min(1).max(256), OwnershipRecordSchema).optional(),
|
|
3765
|
+
intents: z5.record(
|
|
3766
|
+
z5.string().min(1).max(128),
|
|
3767
|
+
z5.object({
|
|
3768
|
+
id: z5.string().min(1).max(128),
|
|
3769
|
+
title: z5.string().min(1).max(256),
|
|
3770
|
+
paths: z5.array(z5.string().min(1).max(512)).max(32)
|
|
2725
3771
|
}).strict()
|
|
2726
3772
|
).optional(),
|
|
2727
|
-
changes:
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
id:
|
|
2731
|
-
title:
|
|
2732
|
-
startHere:
|
|
2733
|
-
relatedPackages:
|
|
3773
|
+
changes: z5.record(
|
|
3774
|
+
z5.string().min(1).max(128),
|
|
3775
|
+
z5.object({
|
|
3776
|
+
id: z5.string().min(1).max(128),
|
|
3777
|
+
title: z5.string().min(1).max(256),
|
|
3778
|
+
startHere: z5.string().min(1).max(512),
|
|
3779
|
+
relatedPackages: z5.array(z5.string().min(1).max(256)).max(32).optional()
|
|
2734
3780
|
}).strict()
|
|
2735
3781
|
).optional()
|
|
2736
3782
|
}).strict();
|
|
2737
|
-
var DocBridgeIndexV1Schema =
|
|
2738
|
-
schemaVersion:
|
|
2739
|
-
contentHash:
|
|
3783
|
+
var DocBridgeIndexV1Schema = z5.object({
|
|
3784
|
+
schemaVersion: z5.literal(INDEX_SCHEMA_VERSION),
|
|
3785
|
+
contentHash: z5.string().regex(/^[a-f0-9]{64}$/),
|
|
2740
3786
|
contentHashAlgo: ContentHashAlgoSchema,
|
|
2741
|
-
generatedAt:
|
|
2742
|
-
project:
|
|
2743
|
-
name:
|
|
2744
|
-
root:
|
|
3787
|
+
generatedAt: z5.string().datetime().optional(),
|
|
3788
|
+
project: z5.object({
|
|
3789
|
+
name: z5.string().min(1).max(128),
|
|
3790
|
+
root: z5.string().min(1).max(512).optional()
|
|
2745
3791
|
}).strict().optional(),
|
|
2746
|
-
properties:
|
|
2747
|
-
knowledge:
|
|
2748
|
-
capabilities:
|
|
2749
|
-
handoffs:
|
|
3792
|
+
properties: z5.array(EcosystemPropertySchema).max(16).optional(),
|
|
3793
|
+
knowledge: z5.array(KnowledgeEntrySchema).max(1e4),
|
|
3794
|
+
capabilities: z5.array(CapabilityRefSchema).max(5e3).optional(),
|
|
3795
|
+
handoffs: z5.record(z5.string().min(1).max(256), AgentHandoffLegacySchema).optional(),
|
|
2750
3796
|
lookup: IndexLookupSchema.optional()
|
|
2751
3797
|
}).strict();
|
|
2752
3798
|
|
|
2753
3799
|
// src/schemas/memory-candidate.ts
|
|
2754
|
-
import { z as
|
|
3800
|
+
import { z as z6 } from "zod";
|
|
2755
3801
|
var MEMORY_CANDIDATE_SCHEMA_VERSION = 1;
|
|
2756
|
-
var MemorySourceSchema =
|
|
3802
|
+
var MemorySourceSchema = z6.enum([
|
|
2757
3803
|
"cursor",
|
|
2758
3804
|
"claude",
|
|
2759
3805
|
"codex",
|
|
@@ -2761,24 +3807,24 @@ var MemorySourceSchema = z5.enum([
|
|
|
2761
3807
|
"agent-memory",
|
|
2762
3808
|
"manual"
|
|
2763
3809
|
]);
|
|
2764
|
-
var MemorySuggestedTypeSchema =
|
|
3810
|
+
var MemorySuggestedTypeSchema = z6.enum([
|
|
2765
3811
|
"user",
|
|
2766
3812
|
"feedback",
|
|
2767
3813
|
"project",
|
|
2768
3814
|
"reference",
|
|
2769
3815
|
"unknown"
|
|
2770
3816
|
]);
|
|
2771
|
-
var MemoryCandidateV1Schema =
|
|
2772
|
-
schemaVersion:
|
|
2773
|
-
id:
|
|
3817
|
+
var MemoryCandidateV1Schema = z6.object({
|
|
3818
|
+
schemaVersion: z6.literal(MEMORY_CANDIDATE_SCHEMA_VERSION),
|
|
3819
|
+
id: z6.string().min(1).max(256),
|
|
2774
3820
|
source: MemorySourceSchema,
|
|
2775
|
-
rawPath:
|
|
2776
|
-
fact:
|
|
2777
|
-
why:
|
|
2778
|
-
howToApply:
|
|
3821
|
+
rawPath: z6.string().min(1).max(512).optional(),
|
|
3822
|
+
fact: z6.string().min(1).max(8e3),
|
|
3823
|
+
why: z6.string().max(4e3).optional(),
|
|
3824
|
+
howToApply: z6.string().max(4e3).optional(),
|
|
2779
3825
|
suggestedType: MemorySuggestedTypeSchema,
|
|
2780
|
-
confidence:
|
|
2781
|
-
references:
|
|
3826
|
+
confidence: z6.number().min(0).max(1),
|
|
3827
|
+
references: z6.array(z6.string().min(1).max(512)).max(64)
|
|
2782
3828
|
}).strict();
|
|
2783
3829
|
|
|
2784
3830
|
// src/validate.ts
|
|
@@ -2804,6 +3850,8 @@ ${result.issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`
|
|
|
2804
3850
|
return result.value;
|
|
2805
3851
|
};
|
|
2806
3852
|
var parseDocBridgeIndex = (input) => DocBridgeIndexV1Schema.parse(input);
|
|
3853
|
+
var parseDiscoverySnapshot = (input) => DiscoverySnapshotV1Schema.parse(input);
|
|
3854
|
+
var parseReconciliationReport = (input) => ReconciliationReportV1Schema.parse(input);
|
|
2807
3855
|
var parseDocBridgeConfig = (input) => {
|
|
2808
3856
|
const result = DocBridgeConfigV1Schema.safeParse(input);
|
|
2809
3857
|
if (!result.success) {
|
|
@@ -2824,11 +3872,11 @@ var IndexNotFoundError = class extends Error {
|
|
|
2824
3872
|
}
|
|
2825
3873
|
path;
|
|
2826
3874
|
};
|
|
2827
|
-
var indexFilePath = (root, config) =>
|
|
3875
|
+
var indexFilePath = (root, config) => join9(root, config.index?.outFile ?? ".doc-bridge/index.json");
|
|
2828
3876
|
var loadDocBridgeIndex = (root, config) => {
|
|
2829
3877
|
const path = indexFilePath(root, config);
|
|
2830
|
-
if (!
|
|
2831
|
-
const raw = JSON.parse(
|
|
3878
|
+
if (!existsSync12(path)) throw new IndexNotFoundError(path);
|
|
3879
|
+
const raw = JSON.parse(readFileSync11(path, "utf8"));
|
|
2832
3880
|
return parseDocBridgeIndex(raw);
|
|
2833
3881
|
};
|
|
2834
3882
|
|
|
@@ -2903,7 +3951,7 @@ var runOkfTypeGate = (root, config) => {
|
|
|
2903
3951
|
const required = config.corpus.agent.okf?.requireType ?? config.gates?.preset === "strict";
|
|
2904
3952
|
if (!required) return { id: "okf-type", ok: true, message: "OKF type frontmatter not required" };
|
|
2905
3953
|
const allowed = config.corpus.agent.okf?.allowedTypes;
|
|
2906
|
-
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(
|
|
3954
|
+
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(readFileSync12(doc.absPath, "utf8")) })).filter((doc) => !doc.type || allowed && !allowed.includes(doc.type));
|
|
2907
3955
|
if (bad.length) {
|
|
2908
3956
|
return {
|
|
2909
3957
|
id: "okf-type",
|
|
@@ -2930,10 +3978,10 @@ var DOCS_STYLE_RULES = {
|
|
|
2930
3978
|
"playbook-okf-soft": ["title", "no-stale-wording"],
|
|
2931
3979
|
"title-only": ["title"]
|
|
2932
3980
|
};
|
|
2933
|
-
var
|
|
3981
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2934
3982
|
var docsStyleOptions = (config) => {
|
|
2935
3983
|
const raw = config.gates?.options?.["docs-style"];
|
|
2936
|
-
if (!
|
|
3984
|
+
if (!isRecord3(raw)) {
|
|
2937
3985
|
if (config.gates?.preset === "playbook") {
|
|
2938
3986
|
return { profile: "playbook-okf-soft", required: DOCS_STYLE_RULES["playbook-okf-soft"] };
|
|
2939
3987
|
}
|
|
@@ -2977,7 +4025,7 @@ var runDocsStyleGate = (root, config) => {
|
|
|
2977
4025
|
}
|
|
2978
4026
|
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({
|
|
2979
4027
|
path: doc.path,
|
|
2980
|
-
missing: missingStyleRules(
|
|
4028
|
+
missing: missingStyleRules(readFileSync12(doc.absPath, "utf8"), required)
|
|
2981
4029
|
})).filter((doc) => doc.missing.length > 0);
|
|
2982
4030
|
if (bad.length) {
|
|
2983
4031
|
return {
|
|
@@ -3021,6 +4069,112 @@ var resolveGateIds = (config) => {
|
|
|
3021
4069
|
return [...ids];
|
|
3022
4070
|
};
|
|
3023
4071
|
|
|
4072
|
+
// src/rules/engine.ts
|
|
4073
|
+
import { minimatch as minimatch5 } from "minimatch";
|
|
4074
|
+
var diagnosticRules = {
|
|
4075
|
+
DOCUMENTATION_QUALITY: "documentation-quality",
|
|
4076
|
+
RELATION_UNDOCUMENTED: "graph-undocumented-relation",
|
|
4077
|
+
DECLARED_RELATION_STALE: "declared-unobserved-relation",
|
|
4078
|
+
UNRESOLVED_ENTITY_REFERENCE: "unresolved-reference",
|
|
4079
|
+
CONFLICTING_DECLARATIONS: "conflicting-declaration",
|
|
4080
|
+
RELATION_NOT_ANALYZED: "not-analyzed-coverage",
|
|
4081
|
+
STALE_DOCUMENTATION: "stale-documentation",
|
|
4082
|
+
FRESHNESS_FAILURE: "freshness",
|
|
4083
|
+
OWNERSHIP_GAP: "ownership",
|
|
4084
|
+
CENTRALITY_RISK: "centrality-risk",
|
|
4085
|
+
CRITICAL_PATH_RISK: "critical-path-risk"
|
|
4086
|
+
};
|
|
4087
|
+
var defaultSeverity = (mode, ruleId) => {
|
|
4088
|
+
if (mode === "default") return "info";
|
|
4089
|
+
if (mode === "strict") return ruleId === "not-analyzed-coverage" ? "warn" : "error";
|
|
4090
|
+
return ruleId === "not-analyzed-coverage" ? "info" : "warn";
|
|
4091
|
+
};
|
|
4092
|
+
var resolvedOptions = (options) => {
|
|
4093
|
+
const config = RulesConfigSchema.parse(options.config ?? {});
|
|
4094
|
+
const mode = options.preset ?? config.mode ?? "default";
|
|
4095
|
+
const severity = { ...config.severity, ...options.severity };
|
|
4096
|
+
const ignore = /* @__PURE__ */ new Set([...config.ignore ?? [], ...options.ignore ?? []]);
|
|
4097
|
+
return {
|
|
4098
|
+
mode,
|
|
4099
|
+
severity,
|
|
4100
|
+
ignore,
|
|
4101
|
+
criticalEntities: options.criticalEntities ?? config.criticalEntities ?? [],
|
|
4102
|
+
criticalPaths: options.criticalPaths ?? config.criticalPaths ?? [],
|
|
4103
|
+
warningThresholds: { ...config.warningThresholds, ...options.warningThresholds }
|
|
4104
|
+
};
|
|
4105
|
+
};
|
|
4106
|
+
var severityFor = (ruleId, mode, overrides) => overrides[ruleId] ?? defaultSeverity(mode, ruleId);
|
|
4107
|
+
var findingFromDiagnostic = (diagnostic2, ruleId, severity) => ({
|
|
4108
|
+
id: `${diagnostic2.id}:${ruleId}`,
|
|
4109
|
+
ruleId,
|
|
4110
|
+
code: ruleId,
|
|
4111
|
+
status: diagnostic2.status,
|
|
4112
|
+
severity,
|
|
4113
|
+
message: diagnostic2.message,
|
|
4114
|
+
evidence: diagnostic2.evidence,
|
|
4115
|
+
...diagnostic2.entityIds ? { entityIds: diagnostic2.entityIds } : {},
|
|
4116
|
+
...diagnostic2.relationIds ? { relationIds: diagnostic2.relationIds } : {},
|
|
4117
|
+
...diagnostic2.remediation ? { remediation: diagnostic2.remediation } : {},
|
|
4118
|
+
sourceDiagnosticCode: diagnostic2.code
|
|
4119
|
+
});
|
|
4120
|
+
var criticalFinding = (finding, severity, target) => ({
|
|
4121
|
+
...finding,
|
|
4122
|
+
id: `${finding.id}:critical:${target}`,
|
|
4123
|
+
ruleId: "critical-path-risk",
|
|
4124
|
+
code: "critical-path-risk",
|
|
4125
|
+
severity,
|
|
4126
|
+
message: `Critical path or entity is affected: ${target}. ${finding.message}`
|
|
4127
|
+
});
|
|
4128
|
+
var evaluateRules = (report, options = {}) => {
|
|
4129
|
+
const resolved = resolvedOptions(options);
|
|
4130
|
+
const findings = [];
|
|
4131
|
+
for (const diagnostic2 of [...report.diagnostics].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
4132
|
+
const ruleId = diagnosticRules[diagnostic2.code];
|
|
4133
|
+
if (!ruleId || resolved.ignore.has(ruleId)) continue;
|
|
4134
|
+
const severity = severityFor(ruleId, resolved.mode, resolved.severity);
|
|
4135
|
+
if (severity !== "off") findings.push(findingFromDiagnostic(diagnostic2, ruleId, severity));
|
|
4136
|
+
}
|
|
4137
|
+
const criticalSeverity = severityFor("critical-path-risk", resolved.mode, resolved.severity);
|
|
4138
|
+
const criticalEntitySet = new Set(resolved.criticalEntities);
|
|
4139
|
+
for (const finding of [...findings]) {
|
|
4140
|
+
const matchingEntity = (finding.entityIds ?? []).find((id) => criticalEntitySet.has(id));
|
|
4141
|
+
if (matchingEntity && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
4142
|
+
findings.push(criticalFinding(finding, criticalSeverity, matchingEntity));
|
|
4143
|
+
}
|
|
4144
|
+
for (const path of resolved.criticalPaths) {
|
|
4145
|
+
if (finding.evidence.some((item) => minimatch5(item.path, path, { dot: true })) && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
4146
|
+
findings.push(criticalFinding(finding, criticalSeverity, path));
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
const centralityThreshold = resolved.warningThresholds["centrality-risk"] ?? 3;
|
|
4151
|
+
const centralitySeverity = severityFor("centrality-risk", resolved.mode, resolved.severity);
|
|
4152
|
+
if (!resolved.ignore.has("centrality-risk") && centralitySeverity !== "off") {
|
|
4153
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4154
|
+
for (const finding of findings.filter((item) => item.ruleId === "graph-undocumented-relation")) {
|
|
4155
|
+
for (const entityId2 of finding.entityIds ?? []) counts.set(entityId2, (counts.get(entityId2) ?? 0) + 1);
|
|
4156
|
+
}
|
|
4157
|
+
for (const [entityId2, count2] of [...counts.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
4158
|
+
if (count2 < centralityThreshold || !criticalEntitySet.has(entityId2)) continue;
|
|
4159
|
+
findings.push({
|
|
4160
|
+
id: `centrality-risk:${entityId2}`,
|
|
4161
|
+
ruleId: "centrality-risk",
|
|
4162
|
+
code: "centrality-risk",
|
|
4163
|
+
status: "unresolved",
|
|
4164
|
+
severity: centralitySeverity,
|
|
4165
|
+
message: `Critical entity has ${count2} undocumented relation finding(s); static centrality is a review signal, not a runtime availability claim.`,
|
|
4166
|
+
evidence: findings.filter((item) => item.ruleId === "graph-undocumented-relation" && item.entityIds?.includes(entityId2)).flatMap((item) => item.evidence),
|
|
4167
|
+
entityIds: [entityId2],
|
|
4168
|
+
remediation: "Review ownership, dependency boundaries, and runtime availability before declaring an SPOF."
|
|
4169
|
+
});
|
|
4170
|
+
}
|
|
4171
|
+
}
|
|
4172
|
+
const sortedFindings = [...findings].sort((a, b) => a.id.localeCompare(b.id));
|
|
4173
|
+
return { mode: resolved.mode, findings: sortedFindings, exitCode: sortedFindings.some((finding) => finding.severity === "error") ? 1 : 0 };
|
|
4174
|
+
};
|
|
4175
|
+
var parseRuleId = (value) => RuleIdSchema.parse(value);
|
|
4176
|
+
var parseRuleSeverity = (value) => RuleSeveritySchema.parse(value);
|
|
4177
|
+
|
|
3024
4178
|
// src/query/query.ts
|
|
3025
4179
|
var handoffForPackage = (index, id, config) => {
|
|
3026
4180
|
const fromIndex = index.handoffs?.[id];
|
|
@@ -3132,7 +4286,7 @@ var runQuery = (index, config, req) => {
|
|
|
3132
4286
|
};
|
|
3133
4287
|
|
|
3134
4288
|
// src/intelligence/adapter.ts
|
|
3135
|
-
import { join as
|
|
4289
|
+
import { join as join10 } from "path";
|
|
3136
4290
|
|
|
3137
4291
|
// src/intelligence/peers.ts
|
|
3138
4292
|
var PeerMissingError = class extends Error {
|
|
@@ -3259,20 +4413,20 @@ var resolveIntelligenceRuntime = async (config) => {
|
|
|
3259
4413
|
}
|
|
3260
4414
|
return { adapter, embed, provider, ...model ? { model } : {} };
|
|
3261
4415
|
};
|
|
3262
|
-
var defaultVectorStorePath = (root) =>
|
|
4416
|
+
var defaultVectorStorePath = (root) => join10(root, ".doc-bridge", "vectors");
|
|
3263
4417
|
|
|
3264
4418
|
// src/intelligence/rag.ts
|
|
3265
|
-
import { readFileSync as
|
|
3266
|
-
import { join as
|
|
4419
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4420
|
+
import { join as join11 } from "path";
|
|
3267
4421
|
var loadDocuments = (root, index, sources) => {
|
|
3268
4422
|
const includeAgent = sources.includes("agent") || sources.length === 0;
|
|
3269
4423
|
const docs = [];
|
|
3270
4424
|
if (includeAgent) {
|
|
3271
4425
|
for (const entry of index.knowledge) {
|
|
3272
|
-
const abs =
|
|
4426
|
+
const abs = join11(root, entry.path);
|
|
3273
4427
|
let content = "";
|
|
3274
4428
|
try {
|
|
3275
|
-
content =
|
|
4429
|
+
content = readFileSync13(abs, "utf8");
|
|
3276
4430
|
} catch {
|
|
3277
4431
|
content = [entry.title, entry.description].filter(Boolean).join("\n\n");
|
|
3278
4432
|
}
|
|
@@ -3290,7 +4444,7 @@ var createDocBridgeRag = async (root, config, index) => {
|
|
|
3290
4444
|
const { embed } = await resolveIntelligenceRuntime(config);
|
|
3291
4445
|
const ragMod = await importPeer("@agentskit/rag");
|
|
3292
4446
|
const memoryMod = await importPeer("@agentskit/memory");
|
|
3293
|
-
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ?
|
|
4447
|
+
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join11(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
|
|
3294
4448
|
const store = memoryMod.fileVectorMemory({ path: storePath });
|
|
3295
4449
|
const rag = ragMod.createRAG({
|
|
3296
4450
|
embed,
|
|
@@ -3425,15 +4579,15 @@ var startInkChat = async (root, config, index) => {
|
|
|
3425
4579
|
};
|
|
3426
4580
|
|
|
3427
4581
|
// src/memory/ingest.ts
|
|
3428
|
-
import { existsSync as
|
|
3429
|
-
import { join as
|
|
4582
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
4583
|
+
import { join as join12 } from "path";
|
|
3430
4584
|
var memoryFact = (raw, id) => firstParagraph(raw) ?? firstHeading(raw) ?? id;
|
|
3431
|
-
var
|
|
4585
|
+
var relativePath2 = (root, abs) => toPosix(abs).replace(`${toPosix(root)}/`, "");
|
|
3432
4586
|
var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
3433
|
-
if (!
|
|
4587
|
+
if (!existsSync13(dir)) return [];
|
|
3434
4588
|
return walkFiles(dir, { extensions: [".md", ".mdc"] }).map((abs) => {
|
|
3435
|
-
const rel =
|
|
3436
|
-
const raw =
|
|
4589
|
+
const rel = relativePath2(root, abs);
|
|
4590
|
+
const raw = readFileSync14(abs, "utf8");
|
|
3437
4591
|
const id = slugFromPath(rel);
|
|
3438
4592
|
return {
|
|
3439
4593
|
schemaVersion: 1,
|
|
@@ -3448,10 +4602,10 @@ var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
|
3448
4602
|
});
|
|
3449
4603
|
};
|
|
3450
4604
|
var ingestCursorRules = (root) => {
|
|
3451
|
-
const dir =
|
|
4605
|
+
const dir = join12(root, ".cursor", "rules");
|
|
3452
4606
|
return ingestMarkdownDir(root, dir, "cursor", 0.6);
|
|
3453
4607
|
};
|
|
3454
|
-
var ingestAgentMemory = (root) => ingestMarkdownDir(root,
|
|
4608
|
+
var ingestAgentMemory = (root) => ingestMarkdownDir(root, join12(root, ".agent-memory"), "agent-memory", 0.7);
|
|
3455
4609
|
var ingestMemoryCandidates = (root) => [
|
|
3456
4610
|
...ingestAgentMemory(root),
|
|
3457
4611
|
...ingestCursorRules(root)
|
|
@@ -3537,9 +4691,9 @@ var draftMemoryPromotion = (classifications) => {
|
|
|
3537
4691
|
};
|
|
3538
4692
|
|
|
3539
4693
|
// src/memory/github-pr.ts
|
|
3540
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
3541
|
-
import { existsSync as
|
|
3542
|
-
import { join as
|
|
4694
|
+
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
4695
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4696
|
+
import { join as join13 } from "path";
|
|
3543
4697
|
var run = (cmd, args, cwd) => {
|
|
3544
4698
|
const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
|
|
3545
4699
|
const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
@@ -3547,10 +4701,10 @@ var run = (cmd, args, cwd) => {
|
|
|
3547
4701
|
};
|
|
3548
4702
|
var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
|
|
3549
4703
|
var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
3550
|
-
var defaultPromotionDraftPath = (root) =>
|
|
4704
|
+
var defaultPromotionDraftPath = (root) => join13(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
|
|
3551
4705
|
var writePromotionDraft = (root, draft, path) => {
|
|
3552
4706
|
const draftPath = path ?? defaultPromotionDraftPath(root);
|
|
3553
|
-
mkdirSync2(
|
|
4707
|
+
mkdirSync2(join13(root, ".doc-bridge", "drafts"), { recursive: true });
|
|
3554
4708
|
writeFileSync2(draftPath, `${draft.body}
|
|
3555
4709
|
`, "utf8");
|
|
3556
4710
|
return draftPath;
|
|
@@ -3586,7 +4740,7 @@ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
|
|
|
3586
4740
|
message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
|
|
3587
4741
|
};
|
|
3588
4742
|
}
|
|
3589
|
-
if (!
|
|
4743
|
+
if (!existsSync14(join13(root, ".git"))) {
|
|
3590
4744
|
return {
|
|
3591
4745
|
ok: false,
|
|
3592
4746
|
dryRun: false,
|
|
@@ -3664,7 +4818,7 @@ ${auth.out}`
|
|
|
3664
4818
|
];
|
|
3665
4819
|
let prUrl = "";
|
|
3666
4820
|
try {
|
|
3667
|
-
prUrl =
|
|
4821
|
+
prUrl = execFileSync2("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
|
|
3668
4822
|
} catch (error) {
|
|
3669
4823
|
const message = error instanceof Error ? error.message : String(error);
|
|
3670
4824
|
return {
|
|
@@ -3689,23 +4843,23 @@ ${auth.out}`
|
|
|
3689
4843
|
};
|
|
3690
4844
|
|
|
3691
4845
|
// src/index-builder/watch-index.ts
|
|
3692
|
-
import { existsSync as
|
|
3693
|
-
import { dirname as
|
|
4846
|
+
import { existsSync as existsSync15, watch } from "fs";
|
|
4847
|
+
import { dirname as dirname6, resolve as resolve11 } from "path";
|
|
3694
4848
|
var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
|
|
3695
4849
|
var NX_MANIFEST_PATTERN = /(^|[/\\])(project|package)\.json$/i;
|
|
3696
4850
|
var collectWatchRoots = (root, config, configPath) => {
|
|
3697
4851
|
const roots = /* @__PURE__ */ new Set();
|
|
3698
|
-
roots.add(
|
|
4852
|
+
roots.add(resolve11(root, config.corpus.agent.root));
|
|
3699
4853
|
const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
|
|
3700
4854
|
for (const source of humanSources) {
|
|
3701
4855
|
const humanOpts = source.options ?? {};
|
|
3702
4856
|
for (const key of ["contentDir", "docsDir", "root", "srcDir"]) {
|
|
3703
4857
|
const value = humanOpts[key];
|
|
3704
|
-
if (typeof value === "string" && value.length) roots.add(
|
|
4858
|
+
if (typeof value === "string" && value.length) roots.add(resolve11(root, value));
|
|
3705
4859
|
}
|
|
3706
4860
|
}
|
|
3707
|
-
if (configPath) roots.add(
|
|
3708
|
-
return [...roots].filter((dir) =>
|
|
4861
|
+
if (configPath) roots.add(dirname6(resolve11(configPath)));
|
|
4862
|
+
return [...roots].filter((dir) => existsSync15(dir));
|
|
3709
4863
|
};
|
|
3710
4864
|
var watchDocBridgeIndex = (opts) => {
|
|
3711
4865
|
const debounceMs = opts.debounceMs ?? 350;
|
|
@@ -3746,15 +4900,15 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
3746
4900
|
rebuild();
|
|
3747
4901
|
});
|
|
3748
4902
|
}
|
|
3749
|
-
const nxRoot =
|
|
3750
|
-
if (opts.config.routing?.plugin === "nx" &&
|
|
4903
|
+
const nxRoot = resolve11(opts.root);
|
|
4904
|
+
if (opts.config.routing?.plugin === "nx" && existsSync15(nxRoot)) {
|
|
3751
4905
|
watch(nxRoot, { recursive: true }, (_event, filename) => {
|
|
3752
4906
|
if (!filename || !NX_MANIFEST_PATTERN.test(filename)) return;
|
|
3753
4907
|
rebuild();
|
|
3754
4908
|
});
|
|
3755
4909
|
}
|
|
3756
|
-
const configDir =
|
|
3757
|
-
if (
|
|
4910
|
+
const configDir = resolve11(opts.root);
|
|
4911
|
+
if (existsSync15(configDir)) {
|
|
3758
4912
|
watch(configDir, (_event, filename) => {
|
|
3759
4913
|
if (!filename || !/doc-bridge\.config/.test(filename)) return;
|
|
3760
4914
|
rebuild();
|
|
@@ -3771,6 +4925,179 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
3771
4925
|
});
|
|
3772
4926
|
};
|
|
3773
4927
|
|
|
4928
|
+
// src/workflow/engine.ts
|
|
4929
|
+
import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync3, readFileSync as readFileSync15, renameSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
4930
|
+
import { join as join15, relative as relative7, resolve as resolve12 } from "path";
|
|
4931
|
+
var WORKFLOW_STAGES = ["collect", "normalize", "reconcile", "evaluate", "report"];
|
|
4932
|
+
var stageState = {
|
|
4933
|
+
collect: "discovering",
|
|
4934
|
+
normalize: "analyzed",
|
|
4935
|
+
reconcile: "compared",
|
|
4936
|
+
evaluate: "proposed",
|
|
4937
|
+
report: "delivered"
|
|
4938
|
+
};
|
|
4939
|
+
var defaultStateDir = (root) => join15(root, ".doc-bridge", "workflow");
|
|
4940
|
+
var atomicWrite = (path, value) => {
|
|
4941
|
+
const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
4942
|
+
writeFileSync3(temp, `${JSON.stringify(value, null, 2)}
|
|
4943
|
+
`, "utf8");
|
|
4944
|
+
renameSync(temp, path);
|
|
4945
|
+
};
|
|
4946
|
+
var writeManifest = (stateDir, run2) => {
|
|
4947
|
+
atomicWrite(join15(stateDir, "manifest.json"), run2);
|
|
4948
|
+
};
|
|
4949
|
+
var appendTransition = (stateDir, transition2) => {
|
|
4950
|
+
appendFileSync(join15(stateDir, "transitions.jsonl"), `${JSON.stringify(transition2)}
|
|
4951
|
+
`, "utf8");
|
|
4952
|
+
};
|
|
4953
|
+
var transition = (run2, to, reason) => {
|
|
4954
|
+
const item = {
|
|
4955
|
+
from: run2.state,
|
|
4956
|
+
to,
|
|
4957
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4958
|
+
...reason ? { reason } : {}
|
|
4959
|
+
};
|
|
4960
|
+
return { ...run2, state: to, transitions: [...run2.transitions, item] };
|
|
4961
|
+
};
|
|
4962
|
+
var runId = () => `${Date.now()}-${process.pid}`;
|
|
4963
|
+
var stageInputHash = (options, stage, input) => sha256NormalizedV1({ stage, input, sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
4964
|
+
var stageArtifactPath = (stateDir, stage, inputHash) => join15(stateDir, "artifacts", `${stage}-${inputHash}.json`);
|
|
4965
|
+
var readArtifact = (path) => JSON.parse(readFileSync15(path, "utf8"));
|
|
4966
|
+
var stepArtifactPath = (stateDir, step) => resolve12(stateDir, step.artifactRefs?.[0] ?? "");
|
|
4967
|
+
var stepOutput = (stateDir, run2, stage) => {
|
|
4968
|
+
const step = run2.steps.find((item) => item.name === stage);
|
|
4969
|
+
if (!step || step.status !== "completed" || !step.artifactRefs?.[0]) return null;
|
|
4970
|
+
return readArtifact(stepArtifactPath(stateDir, step)).value;
|
|
4971
|
+
};
|
|
4972
|
+
var acquireLock = (stateDir) => {
|
|
4973
|
+
const lock = join15(stateDir, ".lock");
|
|
4974
|
+
try {
|
|
4975
|
+
mkdirSync3(lock);
|
|
4976
|
+
} catch {
|
|
4977
|
+
const ownerPath = join15(lock, "owner.json");
|
|
4978
|
+
try {
|
|
4979
|
+
const owner = JSON.parse(readFileSync15(ownerPath, "utf8"));
|
|
4980
|
+
if (typeof owner.pid === "number") process.kill(owner.pid, 0);
|
|
4981
|
+
throw new Error(`Workflow is already running (pid ${owner.pid ?? "unknown"}).`);
|
|
4982
|
+
} catch (error) {
|
|
4983
|
+
if (error instanceof Error && error.message.startsWith("Workflow is already running")) throw error;
|
|
4984
|
+
rmSync(lock, { recursive: true, force: true });
|
|
4985
|
+
mkdirSync3(lock);
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
writeFileSync3(join15(lock, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
|
|
4989
|
+
return () => rmSync(lock, { recursive: true, force: true });
|
|
4990
|
+
};
|
|
4991
|
+
var loadManifest = (stateDir) => {
|
|
4992
|
+
const path = join15(stateDir, "manifest.json");
|
|
4993
|
+
if (!existsSync16(path)) return void 0;
|
|
4994
|
+
return WorkflowRunV1Schema.parse(JSON.parse(readFileSync15(path, "utf8")));
|
|
4995
|
+
};
|
|
4996
|
+
var baseRun = (options, stateDir, supersedes) => {
|
|
4997
|
+
const inputHash = sha256NormalizedV1({ sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
4998
|
+
return WorkflowRunV1Schema.parse({
|
|
4999
|
+
type: "workflow-run",
|
|
5000
|
+
schemaVersion: 1,
|
|
5001
|
+
contentHash: "0".repeat(64),
|
|
5002
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
5003
|
+
project: { name: resolve12(options.root).split("/").pop() ?? "project", root: "." },
|
|
5004
|
+
sourceRevision: options.sourceRevision,
|
|
5005
|
+
sourceRevisionKind: "content",
|
|
5006
|
+
configurationHash: options.configurationHash,
|
|
5007
|
+
pipelineVersion: "1.0.0",
|
|
5008
|
+
analyzerVersions: { workflow: options.toolVersion ?? "1.0.0" },
|
|
5009
|
+
runId: options.runId ?? runId(),
|
|
5010
|
+
state: "created",
|
|
5011
|
+
steps: WORKFLOW_STAGES.map((name) => ({ name, status: "pending", inputHash })),
|
|
5012
|
+
transitions: [{ from: null, to: "created", at: (/* @__PURE__ */ new Date()).toISOString() }],
|
|
5013
|
+
artifactRefs: [relative7(resolve12(options.root), stateDir), ...supersedes ? [`supersedes:${supersedes}`] : []]
|
|
5014
|
+
});
|
|
5015
|
+
};
|
|
5016
|
+
var withHash = (run2) => WorkflowRunV1Schema.parse({ ...run2, contentHash: contentHashForArtifactV1(run2) });
|
|
5017
|
+
var sameInputs = (run2, options) => run2.sourceRevision === options.sourceRevision && run2.configurationHash === options.configurationHash && run2.analyzerVersions.workflow === (options.toolVersion ?? "1.0.0");
|
|
5018
|
+
var selectedStages = (stage) => stage && stage !== "all" ? [stage] : WORKFLOW_STAGES;
|
|
5019
|
+
var runWorkflow = (options) => {
|
|
5020
|
+
const root = resolve12(options.root);
|
|
5021
|
+
const stateDir = resolve12(root, options.stateDir ?? defaultStateDir(root));
|
|
5022
|
+
mkdirSync3(join15(stateDir, "artifacts"), { recursive: true });
|
|
5023
|
+
const release = acquireLock(stateDir);
|
|
5024
|
+
try {
|
|
5025
|
+
let run2 = loadManifest(stateDir);
|
|
5026
|
+
let supersedes;
|
|
5027
|
+
if (run2 && !sameInputs(run2, options)) {
|
|
5028
|
+
supersedes = run2.runId;
|
|
5029
|
+
run2 = withHash(transition(run2, "stale", "Source revision, configuration hash, or tool version changed."));
|
|
5030
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
5031
|
+
writeManifest(stateDir, run2);
|
|
5032
|
+
run2 = void 0;
|
|
5033
|
+
}
|
|
5034
|
+
if (!run2) {
|
|
5035
|
+
run2 = withHash(baseRun(options, stateDir, supersedes));
|
|
5036
|
+
appendTransition(stateDir, run2.transitions[0]);
|
|
5037
|
+
writeManifest(stateDir, run2);
|
|
5038
|
+
}
|
|
5039
|
+
if (!run2) throw new Error("Workflow manifest was not initialized.");
|
|
5040
|
+
const firstSelectedStage = selectedStages(options.stage)[0];
|
|
5041
|
+
const previousStageIndex = firstSelectedStage ? WORKFLOW_STAGES.indexOf(firstSelectedStage) - 1 : -1;
|
|
5042
|
+
let previousOutput = previousStageIndex >= 0 ? stepOutput(stateDir, run2, WORKFLOW_STAGES[previousStageIndex]) : null;
|
|
5043
|
+
const reusedStages = [];
|
|
5044
|
+
for (const stage of selectedStages(options.stage)) {
|
|
5045
|
+
const input = options.inputs?.[stage] ?? previousOutput;
|
|
5046
|
+
const inputHash = stageInputHash(options, stage, input);
|
|
5047
|
+
const existing = run2.steps.find((step) => step.name === stage);
|
|
5048
|
+
const artifactPath = existing?.artifactRefs?.[0] ? resolve12(stateDir, existing.artifactRefs[0]) : stageArtifactPath(stateDir, stage, inputHash);
|
|
5049
|
+
if (existing?.status === "completed" && existing.inputHash === inputHash && existing.outputHash && existsSync16(artifactPath)) {
|
|
5050
|
+
previousOutput = readArtifact(artifactPath).value;
|
|
5051
|
+
reusedStages.push(stage);
|
|
5052
|
+
continue;
|
|
5053
|
+
}
|
|
5054
|
+
const handler = options.handlers[stage];
|
|
5055
|
+
if (!handler) throw new Error(`No handler configured for workflow stage "${stage}".`);
|
|
5056
|
+
run2 = withHash(transition(run2, stageState[stage]));
|
|
5057
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
5058
|
+
const runningStep = { name: stage, status: "running", inputHash };
|
|
5059
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? runningStep : step) });
|
|
5060
|
+
writeManifest(stateDir, run2);
|
|
5061
|
+
try {
|
|
5062
|
+
const value = handler({ root, stage, input, previousOutput });
|
|
5063
|
+
const outputHash = sha256NormalizedV1(value);
|
|
5064
|
+
const artifact2 = { type: "workflow-step-artifact", stage, inputHash, outputHash, value };
|
|
5065
|
+
mkdirSync3(join15(stateDir, "artifacts"), { recursive: true });
|
|
5066
|
+
if (existsSync16(artifactPath)) {
|
|
5067
|
+
const existingArtifact = readArtifact(artifactPath);
|
|
5068
|
+
if (existingArtifact.outputHash !== outputHash) throw new Error(`Immutable workflow artifact collision for stage "${stage}".`);
|
|
5069
|
+
} else {
|
|
5070
|
+
atomicWrite(artifactPath, artifact2);
|
|
5071
|
+
}
|
|
5072
|
+
const ref = relative7(stateDir, artifactPath);
|
|
5073
|
+
const completedStep = { name: stage, status: "completed", inputHash, outputHash, artifactRefs: [ref] };
|
|
5074
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? completedStep : step) });
|
|
5075
|
+
writeManifest(stateDir, run2);
|
|
5076
|
+
previousOutput = value;
|
|
5077
|
+
} catch (error) {
|
|
5078
|
+
run2 = withHash(transition(run2, "failed", error instanceof Error ? error.message : String(error)));
|
|
5079
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
5080
|
+
writeManifest(stateDir, run2);
|
|
5081
|
+
throw error;
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
if (selectedStages(options.stage).every((stage) => run2.steps.find((step) => step.name === stage)?.status === "completed")) {
|
|
5085
|
+
const complete = selectedStages(options.stage).includes("report") && run2.state !== "delivered" ? withHash(transition(run2, "delivered")) : run2;
|
|
5086
|
+
if (complete !== run2) {
|
|
5087
|
+
appendTransition(stateDir, complete.transitions[complete.transitions.length - 1]);
|
|
5088
|
+
run2 = complete;
|
|
5089
|
+
}
|
|
5090
|
+
writeManifest(stateDir, run2);
|
|
5091
|
+
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] });
|
|
5092
|
+
}
|
|
5093
|
+
return { run: run2, stateDir, reusedStages };
|
|
5094
|
+
} finally {
|
|
5095
|
+
release();
|
|
5096
|
+
}
|
|
5097
|
+
};
|
|
5098
|
+
var loadWorkflowManifest = (stateDir) => WorkflowRunV1Schema.parse(JSON.parse(readFileSync15(join15(resolve12(stateDir), "manifest.json"), "utf8")));
|
|
5099
|
+
var loadWorkflowStepOutput = (stateDir, stage) => stepOutput(resolve12(stateDir), loadWorkflowManifest(stateDir), stage);
|
|
5100
|
+
|
|
3774
5101
|
// src/doctor/badge.ts
|
|
3775
5102
|
var doctorBadgeMetrics = (report) => {
|
|
3776
5103
|
const { packages } = report.coverage;
|
|
@@ -3930,43 +5257,43 @@ var docBridgePatternPayload = () => ({
|
|
|
3930
5257
|
});
|
|
3931
5258
|
|
|
3932
5259
|
// src/cli/demo.ts
|
|
3933
|
-
import { cpSync, existsSync as
|
|
5260
|
+
import { cpSync, existsSync as existsSync18, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync2 } from "fs";
|
|
3934
5261
|
import { tmpdir } from "os";
|
|
3935
|
-
import { dirname as
|
|
5262
|
+
import { dirname as dirname8, join as join17, resolve as resolve14 } from "path";
|
|
3936
5263
|
import { fileURLToPath } from "url";
|
|
3937
5264
|
|
|
3938
5265
|
// src/mcp/install.ts
|
|
3939
|
-
import { existsSync as
|
|
5266
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "fs";
|
|
3940
5267
|
import { homedir } from "os";
|
|
3941
|
-
import { dirname as
|
|
5268
|
+
import { dirname as dirname7, join as join16, resolve as resolve13 } from "path";
|
|
3942
5269
|
var SERVER_NAME = "ak-docs";
|
|
3943
5270
|
var mcpServerEntry = (root) => ({
|
|
3944
5271
|
command: "npx",
|
|
3945
5272
|
args: ["ak-docs", "mcp"],
|
|
3946
5273
|
cwd: root
|
|
3947
5274
|
});
|
|
3948
|
-
var
|
|
3949
|
-
if (!
|
|
5275
|
+
var readJson2 = (path) => {
|
|
5276
|
+
if (!existsSync17(path)) return {};
|
|
3950
5277
|
try {
|
|
3951
|
-
const parsed = JSON.parse(
|
|
5278
|
+
const parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
3952
5279
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
3953
5280
|
} catch {
|
|
3954
5281
|
return {};
|
|
3955
5282
|
}
|
|
3956
5283
|
};
|
|
3957
5284
|
var writeJson = (path, value) => {
|
|
3958
|
-
|
|
3959
|
-
|
|
5285
|
+
mkdirSync4(dirname7(path), { recursive: true });
|
|
5286
|
+
writeFileSync4(path, `${JSON.stringify(value, null, 2)}
|
|
3960
5287
|
`, "utf8");
|
|
3961
5288
|
};
|
|
3962
5289
|
var resolveTargetPath = (target, root) => {
|
|
3963
|
-
if (target === "cursor") return
|
|
3964
|
-
return
|
|
5290
|
+
if (target === "cursor") return resolve13(root, ".cursor", "mcp.json");
|
|
5291
|
+
return join16(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
3965
5292
|
};
|
|
3966
5293
|
var installMcpConfig = (root, target) => {
|
|
3967
5294
|
const configPath = resolveTargetPath(target, root);
|
|
3968
|
-
const created = !
|
|
3969
|
-
const existing =
|
|
5295
|
+
const created = !existsSync17(configPath);
|
|
5296
|
+
const existing = readJson2(configPath);
|
|
3970
5297
|
const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
|
|
3971
5298
|
servers[SERVER_NAME] = mcpServerEntry(root);
|
|
3972
5299
|
writeJson(configPath, { ...existing, mcpServers: servers });
|
|
@@ -3990,12 +5317,12 @@ var installMcpConfig = (root, target) => {
|
|
|
3990
5317
|
var mcpSnippet = (root) => JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpServerEntry(root) } }, null, 2);
|
|
3991
5318
|
|
|
3992
5319
|
// src/cli/demo.ts
|
|
3993
|
-
var packageRoot =
|
|
5320
|
+
var packageRoot = resolve14(dirname8(fileURLToPath(import.meta.url)), "..", "..");
|
|
3994
5321
|
var fixturePath = (fixture) => {
|
|
3995
5322
|
if (fixture === "monorepo") {
|
|
3996
|
-
return
|
|
5323
|
+
return join17(packageRoot, "examples", "demo-monorepo");
|
|
3997
5324
|
}
|
|
3998
|
-
return
|
|
5325
|
+
return join17(packageRoot, "examples", "demo-example");
|
|
3999
5326
|
};
|
|
4000
5327
|
var formatHandoffText = (handoff) => {
|
|
4001
5328
|
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)";
|
|
@@ -4011,11 +5338,11 @@ var formatHandoffText = (handoff) => {
|
|
|
4011
5338
|
var runDemo = (root, config, fixture = "example", options = {}) => {
|
|
4012
5339
|
const targetPackage = fixture === "monorepo" ? "auth" : "example";
|
|
4013
5340
|
const fixtureDir = fixturePath(fixture);
|
|
4014
|
-
if (options.copyFixture &&
|
|
5341
|
+
if (options.copyFixture && existsSync18(fixtureDir)) {
|
|
4015
5342
|
for (const rel of ["doc-bridge.config.json", "docs", "packages", "pnpm-workspace.yaml", "package.json"]) {
|
|
4016
|
-
const src =
|
|
4017
|
-
if (!
|
|
4018
|
-
const dest =
|
|
5343
|
+
const src = join17(fixtureDir, rel);
|
|
5344
|
+
if (!existsSync18(src)) continue;
|
|
5345
|
+
const dest = join17(root, rel);
|
|
4019
5346
|
cpSync(src, dest, { recursive: true });
|
|
4020
5347
|
}
|
|
4021
5348
|
}
|
|
@@ -4070,17 +5397,17 @@ var formatDemoText = (result) => {
|
|
|
4070
5397
|
return lines;
|
|
4071
5398
|
};
|
|
4072
5399
|
var withDemoWorkspace = (fixture, fn) => {
|
|
4073
|
-
const dir = mkdtempSync(
|
|
5400
|
+
const dir = mkdtempSync(join17(tmpdir(), "ak-docs-demo-"));
|
|
4074
5401
|
try {
|
|
4075
5402
|
const fixtureDir = fixturePath(fixture);
|
|
4076
|
-
if (!
|
|
5403
|
+
if (!existsSync18(fixtureDir)) {
|
|
4077
5404
|
throw new Error(`Demo fixture "${fixture}" not found at ${fixtureDir}`);
|
|
4078
5405
|
}
|
|
4079
5406
|
cpSync(fixtureDir, dir, { recursive: true });
|
|
4080
|
-
const config = JSON.parse(
|
|
5407
|
+
const config = JSON.parse(readFileSync17(join17(dir, "doc-bridge.config.json"), "utf8"));
|
|
4081
5408
|
return fn(dir, config);
|
|
4082
5409
|
} finally {
|
|
4083
|
-
|
|
5410
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
4084
5411
|
}
|
|
4085
5412
|
};
|
|
4086
5413
|
|
|
@@ -4288,9 +5615,220 @@ var formatDoctorText = (report) => {
|
|
|
4288
5615
|
};
|
|
4289
5616
|
|
|
4290
5617
|
// src/mcp/server.ts
|
|
4291
|
-
import { readFileSync as
|
|
4292
|
-
import { relative as
|
|
4293
|
-
import { z as
|
|
5618
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync20, realpathSync as realpathSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
5619
|
+
import { join as join20, relative as relative9, resolve as resolve17 } from "path";
|
|
5620
|
+
import { z as z8, ZodError } from "zod";
|
|
5621
|
+
|
|
5622
|
+
// src/fixes/proposals.ts
|
|
5623
|
+
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";
|
|
5624
|
+
import { basename as basename5, dirname as dirname9, extname as extname2, join as join18, relative as relative8, resolve as resolve15, sep as sep8 } from "path";
|
|
5625
|
+
var hash2 = (value) => sha256NormalizedV1(value);
|
|
5626
|
+
var artifactMetadata = (root, options) => ({
|
|
5627
|
+
schemaVersion: 1,
|
|
5628
|
+
contentHash: "0".repeat(64),
|
|
5629
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
5630
|
+
project: { name: options.projectName ?? basename5(resolve15(root)), root: "." },
|
|
5631
|
+
sourceRevision: options.baseRevision,
|
|
5632
|
+
sourceRevisionKind: options.baseRevision.length === 40 ? "git" : "content",
|
|
5633
|
+
configurationHash: options.configurationHash,
|
|
5634
|
+
pipelineVersion: "1.0.0",
|
|
5635
|
+
analyzerVersions: { fixes: options.toolVersion ?? "1.0.0" }
|
|
5636
|
+
});
|
|
5637
|
+
var unifiedDiff = (changes) => changes.map((change) => {
|
|
5638
|
+
const before = change.before.split("\n").map((line) => `-${line}`).join("\n");
|
|
5639
|
+
const after = change.after.split("\n").map((line) => `+${line}`).join("\n");
|
|
5640
|
+
return `--- a/${change.path}
|
|
5641
|
+
+++ b/${change.path}
|
|
5642
|
+
@@
|
|
5643
|
+
${before}
|
|
5644
|
+
${after}`;
|
|
5645
|
+
}).join("\n");
|
|
5646
|
+
var makeProposal = (root, options, changes, preconditions, postconditions) => {
|
|
5647
|
+
const draft = {
|
|
5648
|
+
...artifactMetadata(root, options),
|
|
5649
|
+
type: "fix-proposal",
|
|
5650
|
+
proposalId: `fix-${hash2(changes).slice(0, 20)}`,
|
|
5651
|
+
baseRevision: options.baseRevision,
|
|
5652
|
+
affectedFiles: changes.map(({ path, before }) => ({ path, contentHash: sha256NormalizedV1(before) })),
|
|
5653
|
+
changes,
|
|
5654
|
+
preconditions,
|
|
5655
|
+
diff: unifiedDiff(changes),
|
|
5656
|
+
postconditions,
|
|
5657
|
+
status: "proposed"
|
|
5658
|
+
};
|
|
5659
|
+
return FixProposalV1Schema.parse({ ...draft, contentHash: contentHashForArtifactV1(draft) });
|
|
5660
|
+
};
|
|
5661
|
+
var walkMarkdown = (root, directory = root) => readdirSync4(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
5662
|
+
if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") return [];
|
|
5663
|
+
const path = join18(directory, entry.name);
|
|
5664
|
+
if (entry.isDirectory()) return walkMarkdown(root, path);
|
|
5665
|
+
return entry.isFile() && [".md", ".mdx"].includes(extname2(entry.name).toLowerCase()) ? [relative8(root, path).split(sep8).join("/")] : [];
|
|
5666
|
+
});
|
|
5667
|
+
var localLink = /(!?)\[([^\]]*)\]\(([^)\s]+)(?:\s+["'][^)]*["'])?\)/g;
|
|
5668
|
+
var sortJson = (value) => Array.isArray(value) ? value.map(sortJson) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, item]) => [key, sortJson(item)])) : value;
|
|
5669
|
+
var createMarkdownLinkFixProposal = (root, options) => {
|
|
5670
|
+
const projectRoot = realpathSync8.native(resolve15(root));
|
|
5671
|
+
const paths = walkMarkdown(projectRoot);
|
|
5672
|
+
const changes = [];
|
|
5673
|
+
for (const path of paths) {
|
|
5674
|
+
const content = readFileSync18(join18(projectRoot, path), "utf8");
|
|
5675
|
+
let next = content;
|
|
5676
|
+
for (const match of content.matchAll(localLink)) {
|
|
5677
|
+
const target = match[3];
|
|
5678
|
+
if (!target || match[1] === "!" || /^(?:[a-z]+:|\/|#)/i.test(target)) continue;
|
|
5679
|
+
const targetPath = target.split("#")[0]?.split("?")[0];
|
|
5680
|
+
if (!targetPath || existsSync19(resolve15(projectRoot, dirname9(path), targetPath))) continue;
|
|
5681
|
+
const targetStem = basename5(targetPath, extname2(targetPath)).toLowerCase();
|
|
5682
|
+
const labelStem = (match[2] ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
5683
|
+
const candidates = paths.filter((candidate) => basename5(candidate, extname2(candidate)).toLowerCase() === targetStem || basename5(candidate, extname2(candidate)).toLowerCase().replace(/[^a-z0-9]+/g, "") === labelStem);
|
|
5684
|
+
if (candidates.length !== 1) continue;
|
|
5685
|
+
let replacement = relative8(dirname9(path), candidates[0]).split(sep8).join("/");
|
|
5686
|
+
if (target.startsWith("./") && !replacement.startsWith(".")) replacement = `./${replacement}`;
|
|
5687
|
+
next = next.replace(match[0], match[0].replace(target, replacement));
|
|
5688
|
+
}
|
|
5689
|
+
if (next !== content) changes.push({ path, before: content, after: next });
|
|
5690
|
+
}
|
|
5691
|
+
return changes.length ? makeProposal(projectRoot, options, changes, ["Each replacement has exactly one Markdown target."], ["All corrected local Markdown links resolve."]) : void 0;
|
|
5692
|
+
};
|
|
5693
|
+
var createArtifactNormalizationProposal = (root, artifactPath, options) => {
|
|
5694
|
+
const projectRoot = realpathSync8.native(resolve15(root));
|
|
5695
|
+
const path = artifactPath.split(sep8).join("/");
|
|
5696
|
+
const absolute = containedPath(projectRoot, path);
|
|
5697
|
+
if (!absolute || !existsSync19(absolute) || !statSync5(absolute).isFile()) return void 0;
|
|
5698
|
+
const before = readFileSync18(absolute, "utf8");
|
|
5699
|
+
let after;
|
|
5700
|
+
try {
|
|
5701
|
+
after = `${JSON.stringify(sortJson(JSON.parse(before)), null, 2)}
|
|
5702
|
+
`;
|
|
5703
|
+
} catch {
|
|
5704
|
+
return void 0;
|
|
5705
|
+
}
|
|
5706
|
+
return after === before ? void 0 : makeProposal(projectRoot, options, [{ path: relative8(projectRoot, absolute).split(sep8).join("/"), before, after }], ["The artifact contains valid JSON."], ["The artifact is valid canonical JSON with one trailing newline."]);
|
|
5707
|
+
};
|
|
5708
|
+
var approveFixProposal = (proposalInput, approvedBy, approvedAt = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
5709
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
5710
|
+
if (proposal.status !== "proposed") throw new Error(`Only proposed fixes can be approved; received "${proposal.status}".`);
|
|
5711
|
+
const approved = { ...proposal, status: "approved", approval: { proposalHash: proposal.contentHash, approvedAt, approvedBy }, contentHash: "0".repeat(64) };
|
|
5712
|
+
return FixProposalV1Schema.parse({ ...approved, contentHash: contentHashForArtifactV1(approved) });
|
|
5713
|
+
};
|
|
5714
|
+
var bindingHash = (proposal) => {
|
|
5715
|
+
const { approval: _approval, contentHash: _contentHash, status: _status, ...rest } = proposal;
|
|
5716
|
+
return contentHashForArtifactV1({ ...rest, contentHash: "0".repeat(64), status: "proposed" });
|
|
5717
|
+
};
|
|
5718
|
+
var applyFixProposal = (root, proposalInput, options = {}) => {
|
|
5719
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
5720
|
+
if (proposal.status !== "approved" || !proposal.approval) throw new Error("Only an explicitly approved fix proposal can be applied.");
|
|
5721
|
+
if (proposal.approval.proposalHash !== bindingHash(proposal)) throw new Error("Approval is not bound to the exact proposal content.");
|
|
5722
|
+
if (options.currentRevision && options.currentRevision !== proposal.baseRevision) throw new Error("The repository revision changed since this proposal was created.");
|
|
5723
|
+
if (!proposal.changes?.length) throw new Error("This proposal has no executable changes.");
|
|
5724
|
+
const projectRoot = realpathSync8.native(resolve15(root));
|
|
5725
|
+
const originals = /* @__PURE__ */ new Map();
|
|
5726
|
+
const affected = new Map(proposal.affectedFiles.map((file) => [file.path, file]));
|
|
5727
|
+
if (proposal.changes.some((change) => !affected.has(change.path) || affected.get(change.path)?.contentHash !== sha256NormalizedV1(change.before)) || affected.size !== proposal.changes.length) {
|
|
5728
|
+
throw new Error("Proposal changes do not match its affected-file hashes.");
|
|
5729
|
+
}
|
|
5730
|
+
for (const file of proposal.affectedFiles) {
|
|
5731
|
+
const absolute = containedPath(projectRoot, file.path);
|
|
5732
|
+
if (!absolute || !existsSync19(absolute)) throw new Error(`Affected file is unavailable or escapes the repository root: ${file.path}`);
|
|
5733
|
+
const current = readFileSync18(absolute, "utf8");
|
|
5734
|
+
if (sha256NormalizedV1(current) !== file.contentHash) throw new Error(`Affected file changed since proposal creation: ${file.path}`);
|
|
5735
|
+
originals.set(absolute, current);
|
|
5736
|
+
}
|
|
5737
|
+
try {
|
|
5738
|
+
for (const change of proposal.changes) {
|
|
5739
|
+
const absolute = resolve15(projectRoot, change.path);
|
|
5740
|
+
writeFileSync5(`${absolute}.docbridge-${process.pid}.tmp`, change.after, "utf8");
|
|
5741
|
+
}
|
|
5742
|
+
for (const change of proposal.changes) {
|
|
5743
|
+
const absolute = resolve15(projectRoot, change.path);
|
|
5744
|
+
renameSync2(`${absolute}.docbridge-${process.pid}.tmp`, absolute);
|
|
5745
|
+
}
|
|
5746
|
+
for (const change of proposal.changes) {
|
|
5747
|
+
if (readFileSync18(resolve15(projectRoot, change.path), "utf8") !== change.after) throw new Error(`Postcondition failed for ${change.path}`);
|
|
5748
|
+
}
|
|
5749
|
+
options.verify?.(proposal.changes.map((change) => change.path));
|
|
5750
|
+
} catch (error) {
|
|
5751
|
+
for (const [absolute, content] of originals) writeFileSync5(absolute, content, "utf8");
|
|
5752
|
+
for (const change of proposal.changes) {
|
|
5753
|
+
const temp = `${resolve15(projectRoot, change.path)}.docbridge-${process.pid}.tmp`;
|
|
5754
|
+
if (existsSync19(temp)) unlinkSync(temp);
|
|
5755
|
+
}
|
|
5756
|
+
throw error;
|
|
5757
|
+
}
|
|
5758
|
+
const applied = { ...proposal, status: "applied", contentHash: "0".repeat(64) };
|
|
5759
|
+
return FixProposalV1Schema.parse({ ...applied, contentHash: contentHashForArtifactV1(applied) });
|
|
5760
|
+
};
|
|
5761
|
+
|
|
5762
|
+
// src/agents/registry-adapter.ts
|
|
5763
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
5764
|
+
import { join as join19, resolve as resolve16 } from "path";
|
|
5765
|
+
import { pathToFileURL } from "url";
|
|
5766
|
+
import { z as z7 } from "zod";
|
|
5767
|
+
var DEFAULT_REGISTRY_AGENT_ID = "ecosystem-doc-bridge-corpus-scanner";
|
|
5768
|
+
var RegistryAgentMetadataSchema = z7.object({
|
|
5769
|
+
id: z7.string().min(1).max(256),
|
|
5770
|
+
version: z7.string().min(1).max(64),
|
|
5771
|
+
provider: z7.string().min(1).max(128).optional(),
|
|
5772
|
+
model: z7.string().min(1).max(256).optional(),
|
|
5773
|
+
capabilities: z7.array(z7.string().min(1).max(128)).max(32).default([])
|
|
5774
|
+
}).strict();
|
|
5775
|
+
var deepFreeze = (value) => {
|
|
5776
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
5777
|
+
Object.freeze(value);
|
|
5778
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
5779
|
+
}
|
|
5780
|
+
return value;
|
|
5781
|
+
};
|
|
5782
|
+
var registryConfig = (config) => config.intelligence?.registry;
|
|
5783
|
+
var loadRegistryAgentRunner = async (root, config) => {
|
|
5784
|
+
const metadata = loadRegistryAgentMetadata(root, config);
|
|
5785
|
+
const configured = registryConfig(config)?.runnerModule;
|
|
5786
|
+
const modulePath = configured ? containedPath(root, configured) : containedPath(root, join19(metadata.root, "doc-bridge-adapter.js"));
|
|
5787
|
+
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.`);
|
|
5788
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
5789
|
+
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;
|
|
5790
|
+
if (!runner) throw new Error(`Registry agent runner at ${modulePath} must export a function or { run }. `);
|
|
5791
|
+
return runner;
|
|
5792
|
+
};
|
|
5793
|
+
var loadRegistryAgentMetadata = (root, config) => {
|
|
5794
|
+
const settings = registryConfig(config);
|
|
5795
|
+
const id = settings?.agentId ?? DEFAULT_REGISTRY_AGENT_ID;
|
|
5796
|
+
const agentRoot = settings?.agentRoot ?? "agents";
|
|
5797
|
+
const agentPath = containedPath(root, join19(agentRoot, id));
|
|
5798
|
+
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}`);
|
|
5799
|
+
const metadataPath = [join19(agentPath, "agent.json"), join19(agentPath, "manifest.json")].find(existsSync20);
|
|
5800
|
+
if (!metadataPath) throw new Error(`Registry agent "${id}" is installed but has no agent.json or manifest.json metadata.`);
|
|
5801
|
+
const metadata = RegistryAgentMetadataSchema.parse(JSON.parse(readFileSync19(metadataPath, "utf8")));
|
|
5802
|
+
if (metadata.id !== id) throw new Error(`Installed Registry agent metadata id "${metadata.id}" does not match configured id "${id}".`);
|
|
5803
|
+
return { ...metadata, root: agentPath };
|
|
5804
|
+
};
|
|
5805
|
+
var createRegistryAgentAdapter = (root, config, runner) => {
|
|
5806
|
+
if (!registryConfig(config)?.enabled) throw new Error("Registry agents are disabled. Set intelligence.registry.enabled: true to run an assisted workflow.");
|
|
5807
|
+
const metadata = loadRegistryAgentMetadata(resolve16(root), config);
|
|
5808
|
+
return {
|
|
5809
|
+
metadata,
|
|
5810
|
+
run: async (snapshot, report, evidence2 = report.diagnostics.flatMap((diagnostic2) => diagnostic2.evidence).slice(0, 64)) => {
|
|
5811
|
+
const context = deepFreeze({ snapshot: redactValue(snapshot), report: redactValue(report), evidence: redactValue(evidence2), capabilities: ["snapshot.read", "evidence.read", "proposal.write"], network: false, shell: false });
|
|
5812
|
+
const proposal = AgentProposalV1Schema.parse(await runner(context));
|
|
5813
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Registry agent proposal contentHash does not match its canonical contents.");
|
|
5814
|
+
if (proposal.baseSnapshotHash !== snapshot.contentHash || proposal.baseReportHash !== report.contentHash) throw new Error("Registry agent proposal is not based on the supplied snapshot/report hashes.");
|
|
5815
|
+
if (proposal.origin.kind !== "registry-agent" || proposal.origin.id !== metadata.id) throw new Error(`Registry agent proposal origin must be ${metadata.id}.`);
|
|
5816
|
+
return proposal;
|
|
5817
|
+
}
|
|
5818
|
+
};
|
|
5819
|
+
};
|
|
5820
|
+
var persistRegistryAgentProposal = (stateDir, proposal) => {
|
|
5821
|
+
AgentProposalV1Schema.parse(proposal);
|
|
5822
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Cannot persist a Registry agent proposal with an invalid contentHash.");
|
|
5823
|
+
const safeHash = contentHashForArtifactV1(proposal);
|
|
5824
|
+
mkdirSync6(join19(resolve16(stateDir), "agents"), { recursive: true });
|
|
5825
|
+
const path = join19(resolve16(stateDir), "agents", `${proposal.origin.id}-${safeHash}.json`);
|
|
5826
|
+
writeFileSync6(path, `${JSON.stringify(proposal, null, 2)}
|
|
5827
|
+
`, "utf8");
|
|
5828
|
+
return path;
|
|
5829
|
+
};
|
|
5830
|
+
|
|
5831
|
+
// src/mcp/server.ts
|
|
4294
5832
|
var MCP_TOOLS = [
|
|
4295
5833
|
{
|
|
4296
5834
|
name: "handoff.resolve",
|
|
@@ -4362,25 +5900,70 @@ var MCP_TOOLS = [
|
|
|
4362
5900
|
description: "Return the static Doc Bridge curator and delegate topology.",
|
|
4363
5901
|
annotations: { readOnlyHint: true },
|
|
4364
5902
|
inputSchema: { type: "object", properties: {} }
|
|
5903
|
+
},
|
|
5904
|
+
{
|
|
5905
|
+
name: "docbridge.snapshot",
|
|
5906
|
+
title: "Read the latest discovery snapshot",
|
|
5907
|
+
description: "Read the bounded canonical repository snapshot from the latest workflow run.",
|
|
5908
|
+
annotations: { readOnlyHint: true },
|
|
5909
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5910
|
+
},
|
|
5911
|
+
{
|
|
5912
|
+
name: "docbridge.report",
|
|
5913
|
+
title: "Read the latest reconciliation report",
|
|
5914
|
+
description: "Read the canonical reconciliation report from the latest workflow run.",
|
|
5915
|
+
annotations: { readOnlyHint: true },
|
|
5916
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5917
|
+
},
|
|
5918
|
+
{
|
|
5919
|
+
name: "docbridge.diagnostics",
|
|
5920
|
+
title: "Read reconciliation diagnostics",
|
|
5921
|
+
description: "Read bounded diagnostics from the latest canonical reconciliation report.",
|
|
5922
|
+
annotations: { readOnlyHint: true },
|
|
5923
|
+
inputSchema: { type: "object", properties: { status: { type: "string" }, severity: { type: "string" } } }
|
|
5924
|
+
},
|
|
5925
|
+
{
|
|
5926
|
+
name: "docbridge.relations",
|
|
5927
|
+
title: "Read architecture relations",
|
|
5928
|
+
description: "Read bounded observed and declared relations from the latest canonical snapshot.",
|
|
5929
|
+
annotations: { readOnlyHint: true },
|
|
5930
|
+
inputSchema: { type: "object", properties: { kind: { type: "string" }, limit: { type: "number" } } }
|
|
5931
|
+
},
|
|
5932
|
+
{
|
|
5933
|
+
name: "docbridge.run",
|
|
5934
|
+
title: "Read workflow state",
|
|
5935
|
+
description: "Read the latest resumable workflow state and artifact references.",
|
|
5936
|
+
annotations: { readOnlyHint: true },
|
|
5937
|
+
inputSchema: { type: "object", properties: {} }
|
|
5938
|
+
},
|
|
5939
|
+
{
|
|
5940
|
+
name: "docbridge.proposals",
|
|
5941
|
+
title: "Read or approve proposals",
|
|
5942
|
+
description: "Create, inspect, approve and apply deterministic proposals through the shared human-gated workflow.",
|
|
5943
|
+
inputSchema: { type: "object", properties: { action: { type: "string", enum: ["list", "propose-links", "propose-normalize", "suggest", "approve", "apply"] }, proposalHash: { type: "string" }, artifactPath: { type: "string" }, approvedBy: { type: "string" }, proposal: { type: "object" } } }
|
|
4365
5944
|
}
|
|
4366
5945
|
];
|
|
4367
5946
|
var asRecord = (value) => value && typeof value === "object" ? value : {};
|
|
4368
|
-
var HandoffResolveArgsSchema =
|
|
4369
|
-
id:
|
|
4370
|
-
kind:
|
|
5947
|
+
var HandoffResolveArgsSchema = z8.object({
|
|
5948
|
+
id: z8.string().min(1),
|
|
5949
|
+
kind: z8.enum(["package", "ownership"]).optional()
|
|
4371
5950
|
});
|
|
4372
|
-
var DocSearchArgsSchema =
|
|
4373
|
-
term:
|
|
4374
|
-
limit:
|
|
5951
|
+
var DocSearchArgsSchema = z8.object({
|
|
5952
|
+
term: z8.string().min(1),
|
|
5953
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
4375
5954
|
});
|
|
4376
|
-
var RetrieverQueryArgsSchema =
|
|
4377
|
-
query:
|
|
4378
|
-
limit:
|
|
5955
|
+
var RetrieverQueryArgsSchema = z8.object({
|
|
5956
|
+
query: z8.string().min(1),
|
|
5957
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
4379
5958
|
});
|
|
4380
|
-
var DocGetArgsSchema =
|
|
4381
|
-
id:
|
|
4382
|
-
path:
|
|
5959
|
+
var DocGetArgsSchema = z8.object({
|
|
5960
|
+
id: z8.string().min(1).optional(),
|
|
5961
|
+
path: z8.string().min(1).optional()
|
|
4383
5962
|
}).refine((args) => args.id || args.path, "doc.get requires id or path");
|
|
5963
|
+
var WorkflowRunArgsSchema = z8.object({ runId: z8.string().min(1).optional() });
|
|
5964
|
+
var DiagnosticsArgsSchema = z8.object({ status: z8.string().min(1).optional(), severity: z8.string().min(1).optional() });
|
|
5965
|
+
var RelationsArgsSchema = z8.object({ kind: z8.string().min(1).optional(), limit: z8.number().int().positive().max(500).optional() });
|
|
5966
|
+
var ProposalsArgsSchema = z8.object({ action: z8.enum(["list", "propose-links", "propose-normalize", "suggest", "approve", "apply"]).optional(), proposalHash: z8.string().min(1).optional(), artifactPath: z8.string().min(1).optional(), approvedBy: z8.string().min(1).optional(), proposal: z8.unknown().optional() });
|
|
4384
5967
|
var parseToolArgs = (tool, schema, value) => {
|
|
4385
5968
|
try {
|
|
4386
5969
|
return schema.parse(value);
|
|
@@ -4411,15 +5994,54 @@ var findDocPath = (index, args) => {
|
|
|
4411
5994
|
return doc.path;
|
|
4412
5995
|
};
|
|
4413
5996
|
var resolveDocPath = (root, relPath) => {
|
|
4414
|
-
const rootAbs =
|
|
4415
|
-
const unresolved =
|
|
4416
|
-
const unresolvedRel =
|
|
5997
|
+
const rootAbs = realpathSync9.native(root);
|
|
5998
|
+
const unresolved = resolve17(rootAbs, relPath);
|
|
5999
|
+
const unresolvedRel = relative9(rootAbs, unresolved);
|
|
4417
6000
|
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
4418
|
-
const abs =
|
|
4419
|
-
const rel =
|
|
6001
|
+
const abs = realpathSync9.native(unresolved);
|
|
6002
|
+
const rel = relative9(rootAbs, abs);
|
|
4420
6003
|
if (rel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
4421
6004
|
return abs;
|
|
4422
6005
|
};
|
|
6006
|
+
var workflowStateDir = (ctx) => resolve17(ctx.root, ctx.config.workflow?.stateDir ?? ".doc-bridge/workflow");
|
|
6007
|
+
var workflowRun = (ctx) => loadWorkflowManifest(workflowStateDir(ctx));
|
|
6008
|
+
var ensureLatestRun = (ctx, runId2) => {
|
|
6009
|
+
const run2 = workflowRun(ctx);
|
|
6010
|
+
if (runId2 && run2.runId !== runId2) throw new Error(`Unknown workflow run "${runId2}"`);
|
|
6011
|
+
if (run2.state === "stale" || run2.state === "failed") throw new Error(`Workflow run is ${run2.state}; resume or create a valid run before reading artifacts.`);
|
|
6012
|
+
return run2;
|
|
6013
|
+
};
|
|
6014
|
+
var workflowSnapshot = (ctx, runId2) => {
|
|
6015
|
+
ensureLatestRun(ctx, runId2);
|
|
6016
|
+
return parseDiscoverySnapshot(loadWorkflowStepOutput(workflowStateDir(ctx), "normalize"));
|
|
6017
|
+
};
|
|
6018
|
+
var workflowReport = (ctx, runId2) => {
|
|
6019
|
+
ensureLatestRun(ctx, runId2);
|
|
6020
|
+
return parseReconciliationReport(loadWorkflowStepOutput(workflowStateDir(ctx), "reconcile"));
|
|
6021
|
+
};
|
|
6022
|
+
var proposalPath = (ctx) => join20(ctx.root, ".doc-bridge", "proposal.json");
|
|
6023
|
+
var readSavedProposal = (ctx, input) => {
|
|
6024
|
+
if (input !== void 0) return FixProposalV1Schema.parse(input);
|
|
6025
|
+
try {
|
|
6026
|
+
return FixProposalV1Schema.parse(JSON.parse(readFileSync20(proposalPath(ctx), "utf8")));
|
|
6027
|
+
} catch {
|
|
6028
|
+
throw new Error(`No saved fix proposal at ${proposalPath(ctx)}.`);
|
|
6029
|
+
}
|
|
6030
|
+
};
|
|
6031
|
+
var saveProposal = (ctx, proposal) => {
|
|
6032
|
+
mkdirSync7(join20(ctx.root, ".doc-bridge"), { recursive: true });
|
|
6033
|
+
writeFileSync7(proposalPath(ctx), `${JSON.stringify(proposal, null, 2)}
|
|
6034
|
+
`, "utf8");
|
|
6035
|
+
};
|
|
6036
|
+
var enabledMcpTools = (ctx) => {
|
|
6037
|
+
const configured = ctx.config.surfaces?.mcp?.tools;
|
|
6038
|
+
if (!configured || configured.length === MCP_TOOLS.length && MCP_TOOLS.every((tool) => configured.includes(tool.name))) return MCP_TOOLS;
|
|
6039
|
+
return MCP_TOOLS.filter((tool) => configured.includes(tool.name));
|
|
6040
|
+
};
|
|
6041
|
+
var assertMcpToolEnabled = (ctx, name) => {
|
|
6042
|
+
if (!MCP_TOOLS.some((tool) => tool.name === name)) throw new Error(`Unknown tool "${name}"`);
|
|
6043
|
+
if (!enabledMcpTools(ctx).some((tool) => tool.name === name)) throw new Error(`MCP tool "${name}" is disabled by configuration.`);
|
|
6044
|
+
};
|
|
4423
6045
|
var handleMcpRequest = (ctx, request) => {
|
|
4424
6046
|
if (request.method === "initialize") {
|
|
4425
6047
|
return {
|
|
@@ -4428,11 +6050,13 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
4428
6050
|
serverInfo: { name: "ak-docs", version: PACKAGE_VERSION }
|
|
4429
6051
|
};
|
|
4430
6052
|
}
|
|
4431
|
-
if (request.method === "tools/list") return { tools:
|
|
6053
|
+
if (request.method === "tools/list") return { tools: enabledMcpTools(ctx) };
|
|
4432
6054
|
if (request.method === "tools/call") {
|
|
4433
6055
|
const params = asRecord(request.params);
|
|
4434
6056
|
const name = params.name;
|
|
4435
6057
|
const args = asRecord(params.arguments);
|
|
6058
|
+
if (typeof name !== "string") throw new Error("MCP tools/call requires a tool name.");
|
|
6059
|
+
assertMcpToolEnabled(ctx, name);
|
|
4436
6060
|
const index = () => ctx.loadIndex?.() ?? loadDocBridgeIndex(ctx.root, ctx.config);
|
|
4437
6061
|
if (name === "handoff.resolve") {
|
|
4438
6062
|
const parsed = parseToolArgs("handoff.resolve", HandoffResolveArgsSchema, args);
|
|
@@ -4450,7 +6074,7 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
4450
6074
|
}
|
|
4451
6075
|
if (name === "doc.get") {
|
|
4452
6076
|
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
4453
|
-
return textResult(
|
|
6077
|
+
return textResult(readFileSync20(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
4454
6078
|
}
|
|
4455
6079
|
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
4456
6080
|
if (name === "retriever.query") {
|
|
@@ -4472,6 +6096,81 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
4472
6096
|
mergePolicy: { autoMerge: false, requiresHuman: true }
|
|
4473
6097
|
});
|
|
4474
6098
|
}
|
|
6099
|
+
if (name === "docbridge.snapshot") {
|
|
6100
|
+
const parsed = parseToolArgs("docbridge.snapshot", WorkflowRunArgsSchema, args);
|
|
6101
|
+
return textResult(redactValue(workflowSnapshot(ctx, parsed.runId)));
|
|
6102
|
+
}
|
|
6103
|
+
if (name === "docbridge.report") {
|
|
6104
|
+
const parsed = parseToolArgs("docbridge.report", WorkflowRunArgsSchema, args);
|
|
6105
|
+
return textResult(redactValue(workflowReport(ctx, parsed.runId)));
|
|
6106
|
+
}
|
|
6107
|
+
if (name === "docbridge.diagnostics") {
|
|
6108
|
+
const parsed = parseToolArgs("docbridge.diagnostics", DiagnosticsArgsSchema, args);
|
|
6109
|
+
const diagnostics = workflowReport(ctx).diagnostics.filter(
|
|
6110
|
+
(diagnostic2) => (!parsed.status || diagnostic2.status === parsed.status) && (!parsed.severity || diagnostic2.severity === parsed.severity)
|
|
6111
|
+
);
|
|
6112
|
+
return textResult(redactValue({ reportHash: workflowReport(ctx).contentHash, diagnostics }));
|
|
6113
|
+
}
|
|
6114
|
+
if (name === "docbridge.relations") {
|
|
6115
|
+
const parsed = parseToolArgs("docbridge.relations", RelationsArgsSchema, args);
|
|
6116
|
+
const snapshot = workflowSnapshot(ctx);
|
|
6117
|
+
return textResult({ snapshotHash: snapshot.contentHash, relations: snapshot.relations.filter((relation) => !parsed.kind || relation.kind === parsed.kind).slice(0, parsed.limit ?? 100) });
|
|
6118
|
+
}
|
|
6119
|
+
if (name === "docbridge.run") {
|
|
6120
|
+
parseToolArgs("docbridge.run", z8.object({}), args);
|
|
6121
|
+
return textResult(workflowRun(ctx));
|
|
6122
|
+
}
|
|
6123
|
+
if (name === "docbridge.proposals") {
|
|
6124
|
+
const parsed = parseToolArgs("docbridge.proposals", ProposalsArgsSchema, args);
|
|
6125
|
+
const run2 = (() => {
|
|
6126
|
+
try {
|
|
6127
|
+
return workflowRun(ctx);
|
|
6128
|
+
} catch {
|
|
6129
|
+
return void 0;
|
|
6130
|
+
}
|
|
6131
|
+
})();
|
|
6132
|
+
if (!parsed.action || parsed.action === "list") {
|
|
6133
|
+
let proposal2;
|
|
6134
|
+
try {
|
|
6135
|
+
proposal2 = readSavedProposal(ctx, void 0);
|
|
6136
|
+
} catch {
|
|
6137
|
+
proposal2 = void 0;
|
|
6138
|
+
}
|
|
6139
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposals: proposal2 ? [proposal2] : [] }));
|
|
6140
|
+
}
|
|
6141
|
+
if (parsed.action === "suggest") {
|
|
6142
|
+
return loadRegistryAgentRunner(ctx.root, ctx.config).then(async (runner) => {
|
|
6143
|
+
const snapshot = workflowSnapshot(ctx);
|
|
6144
|
+
const report = workflowReport(ctx);
|
|
6145
|
+
const adapter = createRegistryAgentAdapter(ctx.root, ctx.config, runner);
|
|
6146
|
+
const proposal2 = await adapter.run(snapshot, report);
|
|
6147
|
+
const savedPath = persistRegistryAgentProposal(workflowStateDir(ctx), proposal2);
|
|
6148
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2, proposalPath: savedPath }));
|
|
6149
|
+
});
|
|
6150
|
+
}
|
|
6151
|
+
const discovered = discoverRepository({ root: ctx.root, config: ctx.config });
|
|
6152
|
+
const options = { baseRevision: discovered.sourceRevision, configurationHash: sha256NormalizedV1(ctx.config), ...ctx.config.project?.name ? { projectName: ctx.config.project.name } : {} };
|
|
6153
|
+
if (parsed.action === "propose-links") {
|
|
6154
|
+
const proposal2 = createMarkdownLinkFixProposal(ctx.root, options);
|
|
6155
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
6156
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
6157
|
+
}
|
|
6158
|
+
if (parsed.action === "propose-normalize") {
|
|
6159
|
+
if (!parsed.artifactPath) throw new Error("docbridge.proposals propose-normalize requires artifactPath");
|
|
6160
|
+
const proposal2 = createArtifactNormalizationProposal(ctx.root, parsed.artifactPath, options);
|
|
6161
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
6162
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
6163
|
+
}
|
|
6164
|
+
if (parsed.action === "approve") {
|
|
6165
|
+
const proposal2 = approveFixProposal(readSavedProposal(ctx, parsed.proposal), parsed.approvedBy ?? "human");
|
|
6166
|
+
if (parsed.proposalHash && proposal2.approval?.proposalHash !== parsed.proposalHash) throw new Error("proposalHash does not match the saved proposal");
|
|
6167
|
+
saveProposal(ctx, proposal2);
|
|
6168
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 }));
|
|
6169
|
+
}
|
|
6170
|
+
const proposal = applyFixProposal(ctx.root, readSavedProposal(ctx, parsed.proposal), { currentRevision: discovered.sourceRevision });
|
|
6171
|
+
saveProposal(ctx, proposal);
|
|
6172
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal }));
|
|
6173
|
+
}
|
|
4475
6174
|
throw new Error(`Unknown tool "${String(name)}"`);
|
|
4476
6175
|
}
|
|
4477
6176
|
if (request.method?.startsWith("notifications/")) return void 0;
|
|
@@ -4484,16 +6183,16 @@ var writeFrame = (payload, framing) => {
|
|
|
4484
6183
|
\r
|
|
4485
6184
|
${body}`);
|
|
4486
6185
|
};
|
|
4487
|
-
var
|
|
6186
|
+
var respondMcpRequest = async (ctx, request, framing) => {
|
|
4488
6187
|
if (request.id === void 0) {
|
|
4489
6188
|
try {
|
|
4490
|
-
handleMcpRequest(ctx, request);
|
|
6189
|
+
await handleMcpRequest(ctx, request);
|
|
4491
6190
|
} catch {
|
|
4492
6191
|
}
|
|
4493
6192
|
return;
|
|
4494
6193
|
}
|
|
4495
6194
|
try {
|
|
4496
|
-
const result = handleMcpRequest(ctx, request);
|
|
6195
|
+
const result = await handleMcpRequest(ctx, request);
|
|
4497
6196
|
writeFrame({ jsonrpc: "2.0", id: request.id, result: result ?? {} }, framing);
|
|
4498
6197
|
} catch (error) {
|
|
4499
6198
|
writeFrame({
|
|
@@ -4505,6 +6204,10 @@ var respond = (ctx, request, framing) => {
|
|
|
4505
6204
|
};
|
|
4506
6205
|
var startMcpStdioServer = (ctx) => {
|
|
4507
6206
|
let buffer = Buffer.alloc(0);
|
|
6207
|
+
let responseChain = Promise.resolve();
|
|
6208
|
+
const enqueueResponse = (request, framing) => {
|
|
6209
|
+
responseChain = responseChain.then(() => respondMcpRequest(ctx, request, framing));
|
|
6210
|
+
};
|
|
4508
6211
|
process.stdin.on("data", (chunk) => {
|
|
4509
6212
|
buffer = Buffer.concat([buffer, chunk]);
|
|
4510
6213
|
while (true) {
|
|
@@ -4523,7 +6226,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
4523
6226
|
if (buffer.length < bodyEnd) return;
|
|
4524
6227
|
const raw2 = buffer.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
4525
6228
|
buffer = buffer.subarray(bodyEnd);
|
|
4526
|
-
|
|
6229
|
+
enqueueResponse(JSON.parse(raw2), "content-length");
|
|
4527
6230
|
continue;
|
|
4528
6231
|
}
|
|
4529
6232
|
const lineEnd = buffer.indexOf("\n");
|
|
@@ -4532,7 +6235,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
4532
6235
|
buffer = buffer.subarray(lineEnd + 1);
|
|
4533
6236
|
if (!raw) continue;
|
|
4534
6237
|
try {
|
|
4535
|
-
|
|
6238
|
+
enqueueResponse(JSON.parse(raw), "json-line");
|
|
4536
6239
|
} catch {
|
|
4537
6240
|
writeFrame({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }, "json-line");
|
|
4538
6241
|
}
|
|
@@ -4541,6 +6244,237 @@ var startMcpStdioServer = (ctx) => {
|
|
|
4541
6244
|
process.stdin.resume();
|
|
4542
6245
|
};
|
|
4543
6246
|
|
|
6247
|
+
// src/reconciliation/reconcile.ts
|
|
6248
|
+
var ignoredDocumentationRelations = /* @__PURE__ */ new Set(["covers"]);
|
|
6249
|
+
var metadataDetection = (relation) => {
|
|
6250
|
+
const detection = relation.metadata?.detection;
|
|
6251
|
+
return typeof detection === "string" ? detection : void 0;
|
|
6252
|
+
};
|
|
6253
|
+
var relationDetection = (relation) => relation.discriminator ?? metadataDetection(relation) ?? "static";
|
|
6254
|
+
var entityResolver = (snapshots) => {
|
|
6255
|
+
const references = /* @__PURE__ */ new Map();
|
|
6256
|
+
const entities = snapshots.flatMap((snapshot) => snapshot.entities).sort((a, b) => a.id.localeCompare(b.id));
|
|
6257
|
+
for (const entity of entities) {
|
|
6258
|
+
references.set(entity.id, entity.id);
|
|
6259
|
+
for (const alias of entity.aliases ?? []) {
|
|
6260
|
+
if (!references.has(alias)) references.set(alias, entity.id);
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
6263
|
+
return (reference) => references.get(reference) ?? reference;
|
|
6264
|
+
};
|
|
6265
|
+
var relationBase = (relation, resolveEntity2) => `${resolveEntity2(relation.from)}\0${resolveEntity2(relation.to)}\0${relation.kind}`;
|
|
6266
|
+
var evidenceKey = (item) => `${item.source}:${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.context ?? ""}`;
|
|
6267
|
+
var mergeEvidence = (...relations) => {
|
|
6268
|
+
const merged = /* @__PURE__ */ new Map();
|
|
6269
|
+
for (const relation of relations) {
|
|
6270
|
+
for (const item of relation.evidence) merged.set(evidenceKey(item), item);
|
|
6271
|
+
}
|
|
6272
|
+
return [...merged.values()].sort((a, b) => evidenceKey(a).localeCompare(evidenceKey(b)));
|
|
6273
|
+
};
|
|
6274
|
+
var diagnosticId = (code, value) => `reconciliation:${code}:${sha256NormalizedV1(value).slice(0, 32)}`;
|
|
6275
|
+
var coverageAvailable = (snapshot, relation) => {
|
|
6276
|
+
const status = snapshot.coverage.find(
|
|
6277
|
+
(entry) => entry.scope === "static-imports-and-exports" && (relation.kind === "imports" || relation.kind === "re-exports")
|
|
6278
|
+
)?.status;
|
|
6279
|
+
return status === void 0 || status === "complete";
|
|
6280
|
+
};
|
|
6281
|
+
var entityById = (snapshots) => {
|
|
6282
|
+
const entities = /* @__PURE__ */ new Map();
|
|
6283
|
+
for (const snapshot of snapshots) {
|
|
6284
|
+
for (const entity of snapshot.entities) if (!entities.has(entity.id)) entities.set(entity.id, entity);
|
|
6285
|
+
}
|
|
6286
|
+
return entities;
|
|
6287
|
+
};
|
|
6288
|
+
var isUnresolved = (id, entities) => id.startsWith("unresolved:") || entities.get(id)?.kind === "unresolved-reference";
|
|
6289
|
+
var relationMatches = (observed, declared, resolveEntity2) => {
|
|
6290
|
+
if (relationBase(observed, resolveEntity2) !== relationBase(declared, resolveEntity2)) return false;
|
|
6291
|
+
return relationDetection(declared) === relationDetection(observed);
|
|
6292
|
+
};
|
|
6293
|
+
var reportDiagnostic = (code, status, severity, message, evidence2, value, entityIds, relationIds, remediation) => ({
|
|
6294
|
+
id: diagnosticId(code, value),
|
|
6295
|
+
code,
|
|
6296
|
+
status,
|
|
6297
|
+
severity,
|
|
6298
|
+
message,
|
|
6299
|
+
evidence: [...evidence2],
|
|
6300
|
+
...entityIds?.length ? { entityIds: [...entityIds] } : {},
|
|
6301
|
+
...relationIds?.length ? { relationIds: [...relationIds] } : {},
|
|
6302
|
+
...remediation ? { remediation } : {}
|
|
6303
|
+
});
|
|
6304
|
+
var reconcileKnowledge = (observed, declared) => {
|
|
6305
|
+
const resolveEntity2 = entityResolver([observed, declared]);
|
|
6306
|
+
const entities = entityById([observed, declared]);
|
|
6307
|
+
const observedRelations = observed.relations.filter((relation) => relation.provenance === "observed" && !ignoredDocumentationRelations.has(relation.kind));
|
|
6308
|
+
const declaredRelations = declared.relations.filter((relation) => relation.provenance === "declared" && !ignoredDocumentationRelations.has(relation.kind));
|
|
6309
|
+
const diagnostics = [];
|
|
6310
|
+
for (const entity of declared.entities.filter((item) => isUnresolved(item.id, entities)).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
6311
|
+
diagnostics.push(reportDiagnostic(
|
|
6312
|
+
"UNRESOLVED_ENTITY_REFERENCE",
|
|
6313
|
+
"unresolved",
|
|
6314
|
+
"error",
|
|
6315
|
+
`Declared reference could not be resolved: ${entity.name}.`,
|
|
6316
|
+
entity.evidence,
|
|
6317
|
+
entity.id,
|
|
6318
|
+
[entity.id],
|
|
6319
|
+
void 0,
|
|
6320
|
+
"Resolve the reference to an observed entity ID or configured alias."
|
|
6321
|
+
));
|
|
6322
|
+
}
|
|
6323
|
+
const declaredByBase = /* @__PURE__ */ new Map();
|
|
6324
|
+
for (const relation of declaredRelations) {
|
|
6325
|
+
const base2 = relationBase(relation, resolveEntity2);
|
|
6326
|
+
const group = declaredByBase.get(base2) ?? [];
|
|
6327
|
+
group.push(relation);
|
|
6328
|
+
declaredByBase.set(base2, group);
|
|
6329
|
+
}
|
|
6330
|
+
for (const group of declaredByBase.values()) {
|
|
6331
|
+
const detections = new Set(group.map(relationDetection));
|
|
6332
|
+
if (detections.size < 2) continue;
|
|
6333
|
+
diagnostics.push(reportDiagnostic(
|
|
6334
|
+
"CONFLICTING_DECLARATIONS",
|
|
6335
|
+
"conflict",
|
|
6336
|
+
"error",
|
|
6337
|
+
"Declarations for the same semantic relation disagree on detection.",
|
|
6338
|
+
mergeEvidence(...group),
|
|
6339
|
+
[relationBase(group[0], resolveEntity2), [...detections].sort()],
|
|
6340
|
+
void 0,
|
|
6341
|
+
group.map((relation) => relation.id),
|
|
6342
|
+
"Keep one detection value for this relation or document the intended distinction with a different relation kind."
|
|
6343
|
+
));
|
|
6344
|
+
}
|
|
6345
|
+
for (const relation of observedRelations) {
|
|
6346
|
+
const candidates = declaredByBase.get(relationBase(relation, resolveEntity2)) ?? [];
|
|
6347
|
+
const match = candidates.find((candidate) => relationMatches(relation, candidate, resolveEntity2));
|
|
6348
|
+
if (match) {
|
|
6349
|
+
diagnostics.push(reportDiagnostic(
|
|
6350
|
+
"RELATION_CONFIRMED",
|
|
6351
|
+
"confirmed",
|
|
6352
|
+
"info",
|
|
6353
|
+
"Observed relation is covered by a matching declaration.",
|
|
6354
|
+
mergeEvidence(relation, match),
|
|
6355
|
+
relation.id,
|
|
6356
|
+
void 0,
|
|
6357
|
+
[relation.id, match.id]
|
|
6358
|
+
));
|
|
6359
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
6360
|
+
diagnostics.push(reportDiagnostic(
|
|
6361
|
+
"RELATION_UNDOCUMENTED",
|
|
6362
|
+
"undocumented",
|
|
6363
|
+
"warn",
|
|
6364
|
+
"Observed relation has no matching documentation declaration.",
|
|
6365
|
+
relation.evidence,
|
|
6366
|
+
relation.id,
|
|
6367
|
+
void 0,
|
|
6368
|
+
[relation.id],
|
|
6369
|
+
"Add a matching relation declaration or configure this relation kind as intentionally undocumented."
|
|
6370
|
+
));
|
|
6371
|
+
}
|
|
6372
|
+
}
|
|
6373
|
+
for (const relation of declaredRelations) {
|
|
6374
|
+
const candidates = observedRelations.filter((candidate) => relationBase(candidate, resolveEntity2) === relationBase(relation, resolveEntity2));
|
|
6375
|
+
const detection = relationDetection(relation);
|
|
6376
|
+
if (candidates.some((candidate) => relationMatches(candidate, relation, resolveEntity2))) continue;
|
|
6377
|
+
if (isUnresolved(resolveEntity2(relation.from), entities) || isUnresolved(resolveEntity2(relation.to), entities)) continue;
|
|
6378
|
+
if (detection === "dynamic" || detection === "external") {
|
|
6379
|
+
diagnostics.push(reportDiagnostic(
|
|
6380
|
+
"RELATION_NOT_ANALYZED",
|
|
6381
|
+
"not-analyzed",
|
|
6382
|
+
"info",
|
|
6383
|
+
`Declared ${detection} relation cannot be verified by the current static analyzer.`,
|
|
6384
|
+
relation.evidence,
|
|
6385
|
+
relation.id,
|
|
6386
|
+
void 0,
|
|
6387
|
+
[relation.id],
|
|
6388
|
+
"Enable a compatible analyzer or provide explicit observed evidence before treating this relation as confirmed."
|
|
6389
|
+
));
|
|
6390
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
6391
|
+
diagnostics.push(reportDiagnostic(
|
|
6392
|
+
"DECLARED_RELATION_STALE",
|
|
6393
|
+
"stale-or-unverified",
|
|
6394
|
+
"warn",
|
|
6395
|
+
candidates.length ? "Declared relation has incompatible observed evidence." : "Declared static relation was not observed.",
|
|
6396
|
+
candidates.length ? mergeEvidence(relation, ...candidates) : relation.evidence,
|
|
6397
|
+
relation.id,
|
|
6398
|
+
void 0,
|
|
6399
|
+
[relation.id, ...candidates.map((candidate) => candidate.id)],
|
|
6400
|
+
"Update the declaration or the implementation so both graphs describe the same relation."
|
|
6401
|
+
));
|
|
6402
|
+
}
|
|
6403
|
+
}
|
|
6404
|
+
const sortedDiagnostics = [...diagnostics].sort((a, b) => a.id.localeCompare(b.id));
|
|
6405
|
+
const base = {
|
|
6406
|
+
type: "reconciliation-report",
|
|
6407
|
+
schemaVersion: 1,
|
|
6408
|
+
contentHash: "0".repeat(64),
|
|
6409
|
+
contentHashAlgo: observed.contentHashAlgo,
|
|
6410
|
+
project: observed.project,
|
|
6411
|
+
sourceRevision: observed.sourceRevision,
|
|
6412
|
+
sourceRevisionKind: observed.sourceRevisionKind,
|
|
6413
|
+
configurationHash: observed.configurationHash,
|
|
6414
|
+
pipelineVersion: observed.pipelineVersion,
|
|
6415
|
+
analyzerVersions: observed.analyzerVersions,
|
|
6416
|
+
snapshotHash: observed.contentHash,
|
|
6417
|
+
diagnostics: sortedDiagnostics,
|
|
6418
|
+
summary: {
|
|
6419
|
+
entityCount: observed.entities.length,
|
|
6420
|
+
relationCount: observedRelations.length,
|
|
6421
|
+
diagnosticCount: sortedDiagnostics.length
|
|
6422
|
+
}
|
|
6423
|
+
};
|
|
6424
|
+
return ReconciliationReportV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
6425
|
+
};
|
|
6426
|
+
|
|
6427
|
+
// src/report/html.ts
|
|
6428
|
+
var escapeHtml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
6429
|
+
var anchor = (prefix, value) => `${prefix}-${value.replace(/[^A-Za-z0-9_-]+/g, "-")}`;
|
|
6430
|
+
var evidenceText = (evidence2, includeSnippets) => {
|
|
6431
|
+
const location = `${evidence2.path}${evidence2.lineStart ? `:${evidence2.lineStart}${evidence2.lineEnd && evidence2.lineEnd !== evidence2.lineStart ? `-${evidence2.lineEnd}` : ""}` : ""}`;
|
|
6432
|
+
return `${location}${includeSnippets && evidence2.context ? ` \u2014 ${redactSecrets(evidence2.context)}` : ""}`;
|
|
6433
|
+
};
|
|
6434
|
+
var errorPage = (message) => `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Doc Bridge report error</title><style>body{font:16px system-ui;margin:3rem;color:#311}main{max-width:60rem;margin:auto;border:1px solid #d99;padding:2rem;border-radius:8px;background:#fff8f8}code{white-space:pre-wrap}</style></head><body><main><h1>Doc Bridge report unavailable</h1><p>The saved snapshot/report could not be rendered.</p><code>${escapeHtml(message)}</code><p>Run <code>ak-docs check</code> to regenerate valid artifacts.</p></main></body></html>`;
|
|
6435
|
+
var embeddedJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
6436
|
+
var controls = (statusOptions) => `<section id="controls"><label>Search <input id="search" type="search" placeholder="entity, relation, diagnostic"></label><label>Severity <select id="severity"><option value="">Any</option><option>error</option><option>warn</option><option>info</option></select></label><label>Provenance <select id="provenance"><option value="">Any</option><option>observed</option><option>declared</option><option>proposed</option></select></label><label>Status <select id="status"><option value="">Any</option>${statusOptions.map((value) => `<option>${value}</option>`).join("")}</select></label><label>Analyzer <input id="analyzer" type="search" placeholder="js-ts, report"></label><label>Entity <input id="entity" type="search" placeholder="entity id"></label><label>Relation <input id="relation" type="search" placeholder="relation id"></label><button id="reset">Reset filters</button></section>`;
|
|
6437
|
+
var renderCompact = (snapshot, report, entityAnchors, includeSnippets) => {
|
|
6438
|
+
const entities = snapshot.entities.map((entity) => ({ id: entity.id, name: entity.name, kind: entity.kind, anchor: entityAnchors.get(entity.id) ?? anchor("entity", entity.id) }));
|
|
6439
|
+
const entityIndexes = new Map(entities.map((entity, index) => [entity.id, index]));
|
|
6440
|
+
const relations = snapshot.relations.map((relation) => ({ id: relation.id, from: entityIndexes.get(relation.from), to: entityIndexes.get(relation.to), kind: relation.kind, provenance: relation.provenance }));
|
|
6441
|
+
const diagnostics = report.diagnostics.map((diagnostic2) => ({
|
|
6442
|
+
...diagnostic2,
|
|
6443
|
+
evidence: diagnostic2.evidence.map(({ context, ...evidence2 }) => includeSnippets && context ? { ...evidence2, context: redactSecrets(context) } : evidence2)
|
|
6444
|
+
}));
|
|
6445
|
+
const data = embeddedJson({ entities, relations, diagnostics, coverage: snapshot.coverage });
|
|
6446
|
+
const script = `const data=${data};const esc=(v)=>String(v).replaceAll('&','&').replaceAll('<','<').replaceAll('>','>').replaceAll('"','"').replaceAll("'",''');const entityById=new Map(data.entities.map((e)=>[e.id,e]));const entityLink=(id)=>{const e=entityById.get(id);return '<a href="#'+esc(e?.anchor??'entity-'+id)+'">'+esc(e?.name??id)+'</a>'};const evidenceText=(e)=>esc(e.path+(e.lineStart?':'+e.lineStart+(e.lineEnd&&e.lineEnd!==e.lineStart?'-'+e.lineEnd:''):'')+(e.context?' \u2014 '+e.context:''));const render=()=>{document.querySelector('#map').innerHTML=data.entities.map((e)=>'<div class="node" id="'+esc(e.anchor)+'" data-node="'+esc(e.anchor)+'" data-search="'+esc([e.id,e.name,e.kind].join(' '))+'"><a href="#'+esc(e.anchor)+'">'+esc(e.name)+'</a><br><span class="muted">'+esc(e.kind)+'</span></div>').join('');document.querySelector('#relations').innerHTML=data.relations.map((r)=>{const from=data.entities[r.from],to=data.entities[r.to];return '<tr id="relation-'+esc(r.id)+'" data-relation="'+esc(r.id)+'" data-from="'+esc(from?.anchor??'')+'" data-to="'+esc(to?.anchor??'')+'" data-kind="'+esc(r.kind)+'" data-provenance="'+esc(r.provenance)+'" data-analyzer="snapshot" data-search="'+esc([r.id,from?.id,to?.id,r.kind,r.provenance].join(' '))+'"><td>'+esc(r.kind)+'</td><td>'+entityLink(from?.id??'')+'</td><td>'+entityLink(to?.id??'')+'</td><td>'+esc(r.provenance)+'</td></tr>'}).join('');document.querySelector('#diagnostics').innerHTML=data.diagnostics.length?data.diagnostics.map((d)=>'<article id="diagnostic-'+esc(d.id)+'" class="diagnostic" data-status="'+esc(d.status)+'" data-severity="'+esc(d.severity)+'" data-analyzer="report" data-entity="'+esc((d.entityIds??[]).join(' '))+'" data-relation="'+esc((d.relationIds??[]).join(' '))+'" data-search="'+esc([d.id,d.code,d.message,...(d.entityIds??[]),...(d.relationIds??[])].join(' '))+'"><h3><a href="#diagnostic-'+esc(d.id)+'">'+esc(d.code)+'</a> <span class="badge '+esc(d.severity)+'">'+esc(d.severity)+'</span></h3><p>'+esc(d.message)+'</p><p>Status: <b>'+esc(d.status)+'</b></p><ul>'+d.evidence.map((e)=>'<li>'+evidenceText(e)+'</li>').join('')+'</ul>'+(d.entityIds?.length?'<p>Entities: '+d.entityIds.map(entityLink).join(', ')+'</p>':'')+'</article>').join(''):'<p class="muted">No diagnostics.</p>';document.querySelector('#coverage').innerHTML=data.coverage.length?data.coverage.map((c)=>'<li data-status="'+esc(c.status)+'" data-analyzer="'+esc(c.analyzer)+'" data-search="'+esc([c.analyzer,c.scope,c.status,c.reason??''].join(' '))+'"><b>'+esc(c.analyzer)+'</b> / '+esc(c.scope)+': '+esc(c.status)+(c.reason?' \u2014 '+esc(c.reason):'')+'</li>').join(''):'<li class="muted">No coverage metadata.</li>';document.querySelectorAll('[data-node]').forEach((node)=>node.onclick=()=>{const id=node.dataset.node;document.querySelectorAll('[data-from],[data-to]').forEach((edge)=>edge.classList.toggle('hidden',edge.dataset.from!==id&&edge.dataset.to!==id))});};const apply=()=>{const q=document.querySelector('#search').value.toLowerCase(),severity=document.querySelector('#severity').value,provenance=document.querySelector('#provenance').value,status=document.querySelector('#status').value,analyzer=document.querySelector('#analyzer').value.toLowerCase(),entity=document.querySelector('#entity').value.toLowerCase(),relation=document.querySelector('#relation').value.toLowerCase();document.querySelectorAll('[data-search]').forEach((el)=>el.classList.toggle('hidden',Boolean(q&&!el.dataset.search.toLowerCase().includes(q)||severity&&el.dataset.severity!==severity||provenance&&el.dataset.provenance!==provenance||status&&el.dataset.status!==status||analyzer&&!el.dataset.analyzer?.toLowerCase().includes(analyzer)||entity&&!el.dataset.entity?.toLowerCase().includes(entity)||relation&&!el.dataset.relation?.toLowerCase().includes(relation))));};render();for(const id of ['search','analyzer','entity','relation'])document.querySelector('#'+id).oninput=apply;for(const id of ['severity','provenance','status'])document.querySelector('#'+id).onchange=apply;document.querySelector('#reset').onclick=()=>{for(const id of ['search','analyzer','entity','relation'])document.querySelector('#'+id).value='';for(const id of ['severity','provenance','status'])document.querySelector('#'+id).value='';apply()};`;
|
|
6447
|
+
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(snapshot.project.name)}</title><style>body{font:14px system-ui;margin:0;color:#18202a;background:#f5f7fa}header,main{max-width:1200px;margin:auto;padding:1.25rem}header{background:#18202a;color:white;max-width:none;padding-left:calc((100% - 1200px)/2);padding-right:calc((100% - 1200px)/2)}main{background:white}section{margin:1.5rem 0;border-top:1px solid #d7dde5;padding-top:1rem}table{border-collapse:collapse;width:100%;margin-top:.75rem}td,th{border-bottom:1px solid #e5e9ef;text-align:left;padding:.45rem;vertical-align:top}input,select,button{padding:.45rem;margin:.15rem;border:1px solid #b9c3d0;border-radius:4px;background:white}.badge{border-radius:1rem;padding:.15rem .5rem;background:#dce4ee}.error{background:#ffd9d9}.warn{background:#fff0c2}.info{background:#dcecff}.diagnostic{border:1px solid #d7dde5;border-left:4px solid #9aa7b5;padding:.75rem;margin:.75rem 0}.diagnostic:target,tr:target{background:#fff8cf}.muted{color:#5d6a78}a{color:#0b5cad}#map{display:flex;gap:1rem;flex-wrap:wrap}.node{border:1px solid #9aa7b5;padding:.5rem;border-radius:4px}.hidden{display:none}</style></head><body><header><h1>Doc Bridge: ${escapeHtml(snapshot.project.name)}</h1><p>Offline architecture and documentation reconciliation report</p><p class="muted">Snapshot ${escapeHtml(snapshot.contentHash)} \xB7 Report ${escapeHtml(report.contentHash)} \xB7 Revision ${escapeHtml(snapshot.sourceRevision)}</p></header><main>${controls(["confirmed", "undocumented", "stale-or-unverified", "conflict", "unresolved", "not-analyzed"])}<section id="architecture"><h2>Architecture map</h2><p>Large snapshots are rendered from compact canonical data after load to keep the offline report bounded.</p><div id="map"></div><h3>Relations</h3><table><thead><tr><th>Kind</th><th>From</th><th>To</th><th>Provenance</th></tr></thead><tbody id="relations"></tbody></table></section><section id="diagnostic-lens"><h2>Diagnostic lens</h2><p>Findings: ${report.diagnostics.length}</p><div id="diagnostics"></div></section><section id="coverage"><h2>Coverage and unsupported areas</h2><ul></ul></section><section id="metadata"><h2>Run metadata</h2><dl><dt>Source revision</dt><dd>${escapeHtml(snapshot.sourceRevision)} (${escapeHtml(snapshot.sourceRevisionKind)})</dd><dt>Configuration hash</dt><dd>${escapeHtml(snapshot.configurationHash)}</dd><dt>Pipeline</dt><dd>${escapeHtml(snapshot.pipelineVersion)}</dd></dl></section></main><script>${script}</script></body></html>`;
|
|
6448
|
+
};
|
|
6449
|
+
var render = (input, options) => {
|
|
6450
|
+
const { snapshot, report } = input;
|
|
6451
|
+
const includeSnippets = options.includeSnippets === true;
|
|
6452
|
+
const entityAnchors = new Map(snapshot.entities.map((entity, index) => [entity.id, entity.id.length > 64 ? `entity-n${index}` : anchor("entity", entity.id)]));
|
|
6453
|
+
if (snapshot.entities.length > 500 || report.diagnostics.length > 1e3) return renderCompact(snapshot, report, entityAnchors, includeSnippets);
|
|
6454
|
+
const entityNames = new Map(snapshot.entities.map((entity) => [entity.id, entity.name]));
|
|
6455
|
+
const entityAnchor = (id) => entityAnchors.get(id) ?? anchor("entity", id);
|
|
6456
|
+
const entityLabel = (id) => entityNames.get(id) ?? id;
|
|
6457
|
+
const entityLink = (id) => `<a href="#${entityAnchor(id)}">${escapeHtml(entityLabel(id))}</a>`;
|
|
6458
|
+
const relations = snapshot.relations.map((relation) => `<tr id="${anchor("relation", relation.id)}" data-relation="${escapeHtml(relation.id)}" data-from="${entityAnchor(relation.from)}" data-to="${entityAnchor(relation.to)}" data-kind="${escapeHtml(relation.kind)}" data-provenance="${escapeHtml(relation.provenance)}" data-analyzer="snapshot" data-search="${escapeHtml(`${relation.id} ${relation.from} ${relation.to} ${relation.kind} ${relation.provenance}`)}"><td>${escapeHtml(relation.kind)}</td><td>${entityLink(relation.from)}</td><td>${entityLink(relation.to)}</td><td>${escapeHtml(relation.provenance)}</td></tr>`).join("");
|
|
6459
|
+
const diagnostics = report.diagnostics.map((diagnostic2) => `<article id="${anchor("diagnostic", diagnostic2.id)}" class="diagnostic" data-status="${escapeHtml(diagnostic2.status)}" data-severity="${escapeHtml(diagnostic2.severity)}" data-analyzer="report" data-entity="${escapeHtml(diagnostic2.entityIds?.join(" ") ?? "")}" data-relation="${escapeHtml(diagnostic2.relationIds?.join(" ") ?? "")}" data-search="${escapeHtml(`${diagnostic2.id} ${diagnostic2.code} ${diagnostic2.message} ${diagnostic2.entityIds?.join(" ") ?? ""} ${diagnostic2.relationIds?.join(" ") ?? ""}`)}"><h3><a href="#${anchor("diagnostic", diagnostic2.id)}">${escapeHtml(diagnostic2.code)}</a> <span class="badge ${escapeHtml(diagnostic2.severity)}">${escapeHtml(diagnostic2.severity)}</span></h3><p>${escapeHtml(diagnostic2.message)}</p><p>Status: <b>${escapeHtml(diagnostic2.status)}</b></p><ul>${diagnostic2.evidence.map((item) => `<li>${escapeHtml(evidenceText(item, includeSnippets))}</li>`).join("")}</ul>${diagnostic2.entityIds?.length ? `<p>Entities: ${diagnostic2.entityIds.map((id) => entityLink(id)).join(", ")}</p>` : ""}</article>`).join("");
|
|
6460
|
+
const coverage = snapshot.coverage.map((entry) => `<li data-status="${escapeHtml(entry.status)}" data-analyzer="${escapeHtml(entry.analyzer)}" data-search="${escapeHtml(`${entry.analyzer} ${entry.scope} ${entry.status} ${entry.reason ?? ""}`)}"><b>${escapeHtml(entry.analyzer)}</b> / ${escapeHtml(entry.scope)}: ${escapeHtml(entry.status)}${entry.reason ? ` \u2014 ${escapeHtml(entry.reason)}` : ""}</li>`).join("");
|
|
6461
|
+
const nodes = snapshot.entities.map((entity) => `<div class="node" id="${entityAnchor(entity.id)}" data-node="${entityAnchor(entity.id)}"><a href="#${entityAnchor(entity.id)}">${escapeHtml(entity.name)}</a><br><span class="muted">${escapeHtml(entity.kind)}</span></div>`).join("");
|
|
6462
|
+
const script = `const q=document.querySelector('#search'),severity=document.querySelector('#severity'),provenance=document.querySelector('#provenance'),status=document.querySelector('#status'),analyzer=document.querySelector('#analyzer'),entity=document.querySelector('#entity'),relation=document.querySelector('#relation');function apply(){const term=q.value.toLowerCase(),entityTerm=entity.value.toLowerCase(),relationTerm=relation.value.toLowerCase();document.querySelectorAll('[data-search]').forEach((el)=>{const match=(!term||el.dataset.search.toLowerCase().includes(term))&&(!severity.value||el.dataset.severity===severity.value)&&(!provenance.value||el.dataset.provenance===provenance.value)&&(!status.value||el.dataset.status===status.value)&&(!analyzer.value||el.dataset.analyzer?.toLowerCase().includes(analyzer.value.toLowerCase()))&&(!entityTerm||el.dataset.entity?.toLowerCase().includes(entityTerm))&&(!relationTerm||el.dataset.relation?.toLowerCase().includes(relationTerm));el.classList.toggle('hidden',!match)});}q.oninput=apply;severity.onchange=apply;provenance.onchange=apply;status.onchange=apply;analyzer.oninput=apply;entity.oninput=apply;relation.oninput=apply;document.querySelector('#reset').onclick=()=>{q.value='';severity.value='';provenance.value='';status.value='';analyzer.value='';entity.value='';relation.value='';apply()};document.querySelectorAll('[data-node]').forEach((node)=>node.onclick=()=>{const id=node.dataset.node;document.querySelectorAll('[data-from],[data-to]').forEach((edge)=>edge.classList.toggle('hidden',edge.dataset.from!==id&&edge.dataset.to!==id));});`;
|
|
6463
|
+
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(snapshot.project.name)}</title><style>body{font:14px system-ui;margin:0;color:#18202a;background:#f5f7fa}header,main{max-width:1200px;margin:auto;padding:1.25rem}header{background:#18202a;color:white;max-width:none;padding-left:calc((100% - 1200px)/2);padding-right:calc((100% - 1200px)/2)}main{background:white}section{margin:1.5rem 0;border-top:1px solid #d7dde5;padding-top:1rem}table{border-collapse:collapse;width:100%;margin-top:.75rem}td,th{border-bottom:1px solid #e5e9ef;text-align:left;padding:.45rem;vertical-align:top}input,select,button{padding:.45rem;margin:.15rem;border:1px solid #b9c3d0;border-radius:4px;background:white}.badge{border-radius:1rem;padding:.15rem .5rem;background:#dce4ee}.error{background:#ffd9d9}.warn{background:#fff0c2}.info{background:#dcecff}.diagnostic{border:1px solid #d7dde5;border-left:4px solid #9aa7b5;padding:.75rem;margin:.75rem 0}.diagnostic:target,tr:target{background:#fff8cf}.muted{color:#5d6a78}a{color:#0b5cad}#map{display:flex;gap:1rem;flex-wrap:wrap}.node{border:1px solid #9aa7b5;padding:.5rem;border-radius:4px}.hidden{display:none}</style></head><body><header><h1>Doc Bridge: ${escapeHtml(snapshot.project.name)}</h1><p>Offline architecture and documentation reconciliation report</p><p class="muted">Snapshot ${escapeHtml(snapshot.contentHash)} \xB7 Report ${escapeHtml(report.contentHash)} \xB7 Revision ${escapeHtml(snapshot.sourceRevision)}</p></header><main><section id="controls"><label>Search <input id="search" type="search" placeholder="entity, relation, diagnostic"></label><label>Severity <select id="severity"><option value="">Any</option><option>error</option><option>warn</option><option>info</option></select></label><label>Provenance <select id="provenance"><option value="">Any</option><option>observed</option><option>declared</option><option>proposed</option></select></label><label>Status <select id="status"><option value="">Any</option>${["confirmed", "undocumented", "stale-or-unverified", "conflict", "unresolved", "not-analyzed"].map((value) => `<option>${value}</option>`).join("")}</select></label><label>Analyzer <input id="analyzer" type="search" placeholder="js-ts, report"></label><label>Entity <input id="entity" type="search" placeholder="entity id"></label><label>Relation <input id="relation" type="search" placeholder="relation id"></label><button id="reset">Reset filters</button></section><section id="architecture"><h2>Architecture map</h2><p>Observed and declared relations are rendered from the canonical snapshot; browser code does not infer edges.</p><div id="map">${nodes}</div><h3>Relations</h3><table><thead><tr><th>Kind</th><th>From</th><th>To</th><th>Provenance</th></tr></thead><tbody id="relations">${relations}</tbody></table></section><section id="diagnostic-lens"><h2>Diagnostic lens</h2><p>Findings: ${report.diagnostics.length}</p><div id="diagnostics">${diagnostics || '<p class="muted">No diagnostics.</p>'}</div></section><section id="coverage"><h2>Coverage and unsupported areas</h2><ul>${coverage || "<li>No coverage metadata.</li>"}</ul></section><section id="metadata"><h2>Run metadata</h2><dl><dt>Source revision</dt><dd>${escapeHtml(snapshot.sourceRevision)} (${escapeHtml(snapshot.sourceRevisionKind)})</dd><dt>Configuration hash</dt><dd>${escapeHtml(snapshot.configurationHash)}</dd><dt>Pipeline</dt><dd>${escapeHtml(snapshot.pipelineVersion)}</dd></dl></section></main><script>${script}</script></body></html>`;
|
|
6464
|
+
};
|
|
6465
|
+
var renderOfflineReport = (input, options = {}) => {
|
|
6466
|
+
try {
|
|
6467
|
+
if (!input || typeof input !== "object") throw new Error("Input must contain snapshot and report artifacts.");
|
|
6468
|
+
const value = input;
|
|
6469
|
+
const snapshot = DiscoverySnapshotV1Schema.parse(value.snapshot);
|
|
6470
|
+
const report = ReconciliationReportV1Schema.parse(value.report);
|
|
6471
|
+
if (report.snapshotHash !== snapshot.contentHash) throw new Error("Report snapshotHash does not match snapshot contentHash.");
|
|
6472
|
+
return render({ snapshot, report }, options);
|
|
6473
|
+
} catch (error) {
|
|
6474
|
+
return errorPage(error instanceof Error ? error.message : String(error));
|
|
6475
|
+
}
|
|
6476
|
+
};
|
|
6477
|
+
|
|
4544
6478
|
// src/cli/program.ts
|
|
4545
6479
|
var usage = `ak-docs \u2014 human\u2194agent documentation bridge (@agentskit/doc-bridge)
|
|
4546
6480
|
|
|
@@ -4549,11 +6483,17 @@ Core (no API key):
|
|
|
4549
6483
|
ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
|
|
4550
6484
|
ak-docs doctor [--text] [--badge] [--write-badge]
|
|
4551
6485
|
ak-docs index [--watch]
|
|
6486
|
+
ak-docs discover [--text|--json]
|
|
6487
|
+
ak-docs scan | reconcile | check | map [--text|--json] [--html]
|
|
6488
|
+
ak-docs fix propose links|normalize <artifact> [--output <file>]
|
|
6489
|
+
ak-docs fix approve|apply <proposal.json> [--by <name>]
|
|
6490
|
+
ak-docs suggest [--json|--text] run the configured local Registry agent
|
|
4552
6491
|
ak-docs query [package|ownership|intent|change] <id> [--agent] [--text]
|
|
4553
6492
|
ak-docs search <term> [--agent] [--text]
|
|
4554
6493
|
ak-docs list <packages|intents|changes|knowledge> [--text]
|
|
4555
6494
|
ak-docs ask [question] local consult (no LLM)
|
|
4556
6495
|
ak-docs gate run [gate-id]
|
|
6496
|
+
ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]
|
|
4557
6497
|
ak-docs conformance run documentation-standard-v1 [--text|--json]
|
|
4558
6498
|
ak-docs mcp
|
|
4559
6499
|
ak-docs mcp install --cursor | --claude
|
|
@@ -4613,8 +6553,16 @@ var parseArgs = (argv) => {
|
|
|
4613
6553
|
else if (positional[0] === "memory") command = "memory";
|
|
4614
6554
|
else if (positional[0] === "playbook") command = "playbook";
|
|
4615
6555
|
else if (positional[0] === "registry") command = "registry";
|
|
6556
|
+
else if (positional[0] === "discover") command = "discover";
|
|
6557
|
+
else if (positional[0] === "scan") command = "scan";
|
|
6558
|
+
else if (positional[0] === "reconcile") command = "reconcile";
|
|
6559
|
+
else if (positional[0] === "check") command = "check";
|
|
6560
|
+
else if (positional[0] === "map") command = "map";
|
|
6561
|
+
else if (positional[0] === "fix") command = "fix";
|
|
6562
|
+
else if (positional[0] === "suggest") command = "suggest";
|
|
4616
6563
|
else if (positional[0] === "index") command = "index";
|
|
4617
6564
|
else if (positional[0] === "gate") command = "gate";
|
|
6565
|
+
else if (positional[0] === "rules") command = "rules";
|
|
4618
6566
|
else if (positional[0] === "mcp") command = "mcp";
|
|
4619
6567
|
else if (positional[0] === "doctor") command = "doctor";
|
|
4620
6568
|
else if (positional[0] === "demo") command = "demo";
|
|
@@ -4628,6 +6576,28 @@ var parseArgs = (argv) => {
|
|
|
4628
6576
|
else if (positional[0] === "conformance") command = "conformance";
|
|
4629
6577
|
return { command, flags, configPath, positional };
|
|
4630
6578
|
};
|
|
6579
|
+
var optionValues = (argv, name) => {
|
|
6580
|
+
const values = [];
|
|
6581
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
6582
|
+
const arg = argv[index];
|
|
6583
|
+
if (arg?.startsWith(`${name}=`)) values.push(arg.slice(name.length + 1));
|
|
6584
|
+
else if (arg === name && argv[index + 1] && !argv[index + 1]?.startsWith("-")) {
|
|
6585
|
+
values.push(argv[index + 1]);
|
|
6586
|
+
index += 1;
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
return values;
|
|
6590
|
+
};
|
|
6591
|
+
var parseRuleAssignments = (values, parseValue) => {
|
|
6592
|
+
const result = {};
|
|
6593
|
+
for (const value of values) {
|
|
6594
|
+
const separator = value.indexOf("=");
|
|
6595
|
+
if (separator <= 0 || separator === value.length - 1) throw new Error(`Invalid rule assignment "${value}". Use rule=value.`);
|
|
6596
|
+
const key = value.slice(0, separator);
|
|
6597
|
+
result[key] = parseValue(value.slice(separator + 1));
|
|
6598
|
+
}
|
|
6599
|
+
return result;
|
|
6600
|
+
};
|
|
4631
6601
|
var writeJson2 = (payload) => {
|
|
4632
6602
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
4633
6603
|
`);
|
|
@@ -4719,12 +6689,12 @@ var readIndexedDoc = (root, config, idOrPath) => {
|
|
|
4719
6689
|
const index = loadDocBridgeIndex(root, config);
|
|
4720
6690
|
const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath);
|
|
4721
6691
|
if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`);
|
|
4722
|
-
const abs =
|
|
4723
|
-
const rootAbs =
|
|
6692
|
+
const abs = resolve18(root, entry.path);
|
|
6693
|
+
const rootAbs = resolve18(root);
|
|
4724
6694
|
if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
|
|
4725
6695
|
throw new Error(`Indexed doc escapes project root: ${entry.path}`);
|
|
4726
6696
|
}
|
|
4727
|
-
return
|
|
6697
|
+
return readFileSync21(abs, "utf8");
|
|
4728
6698
|
};
|
|
4729
6699
|
var runAskRepl = async (root, config) => {
|
|
4730
6700
|
const index = loadDocBridgeIndex(root, config);
|
|
@@ -4799,10 +6769,197 @@ var diagnosticNextCommands = (config, result) => {
|
|
|
4799
6769
|
"ak-docs index"
|
|
4800
6770
|
];
|
|
4801
6771
|
};
|
|
6772
|
+
var workflowOptions = (root, config, sourceRevision2, stage, handlers) => ({
|
|
6773
|
+
root,
|
|
6774
|
+
...config.workflow?.stateDir ? { stateDir: config.workflow.stateDir } : {},
|
|
6775
|
+
sourceRevision: sourceRevision2,
|
|
6776
|
+
configurationHash: sha256NormalizedV1(config),
|
|
6777
|
+
stage,
|
|
6778
|
+
handlers
|
|
6779
|
+
});
|
|
6780
|
+
var scanWorkflow = (root, config) => {
|
|
6781
|
+
const discovered = discoverRepository({ root, config });
|
|
6782
|
+
runWorkflow(workflowOptions(root, config, discovered.sourceRevision, "collect", { collect: () => discovered }));
|
|
6783
|
+
return runWorkflow(workflowOptions(root, config, discovered.sourceRevision, "normalize", { normalize: ({ input }) => input }));
|
|
6784
|
+
};
|
|
6785
|
+
var documentationInputs = (root, snapshot) => snapshot.entities.filter((entity) => entity.kind === "document" && entity.path).map((entity) => ({ path: entity.path, content: readFileSync21(resolve18(root, entity.path), "utf8") }));
|
|
6786
|
+
var reconcileWorkflow = (root, config) => {
|
|
6787
|
+
const scanned = scanWorkflow(root, config);
|
|
6788
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(scanned.stateDir, "normalize"));
|
|
6789
|
+
const declared = applyDocumentationDeclarations(snapshot, documentationInputs(root, snapshot)).snapshot;
|
|
6790
|
+
const report = reconcileKnowledge(snapshot, declared);
|
|
6791
|
+
return runWorkflow(workflowOptions(root, config, snapshot.sourceRevision, "reconcile", { reconcile: () => report }));
|
|
6792
|
+
};
|
|
6793
|
+
var checkWorkflow = (root, config) => {
|
|
6794
|
+
const reconciled = reconcileWorkflow(root, config);
|
|
6795
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(reconciled.stateDir, "reconcile"));
|
|
6796
|
+
const evaluated = runWorkflow(workflowOptions(root, config, report.sourceRevision, "evaluate", { evaluate: () => evaluateRules(report, { ...config.rules ? { config: config.rules } : {} }) }));
|
|
6797
|
+
return runWorkflow(workflowOptions(root, config, report.sourceRevision, "report", { report: ({ input }) => input }));
|
|
6798
|
+
};
|
|
6799
|
+
var workflowOutput = (result) => {
|
|
6800
|
+
const snapshot = (() => {
|
|
6801
|
+
try {
|
|
6802
|
+
return parseDiscoverySnapshot(loadWorkflowStepOutput(result.stateDir, "normalize"));
|
|
6803
|
+
} catch {
|
|
6804
|
+
return void 0;
|
|
6805
|
+
}
|
|
6806
|
+
})();
|
|
6807
|
+
const report = (() => {
|
|
6808
|
+
try {
|
|
6809
|
+
return parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, "reconcile"));
|
|
6810
|
+
} catch {
|
|
6811
|
+
return void 0;
|
|
6812
|
+
}
|
|
6813
|
+
})();
|
|
6814
|
+
const rules = (() => {
|
|
6815
|
+
try {
|
|
6816
|
+
const value = loadWorkflowStepOutput(result.stateDir, "evaluate");
|
|
6817
|
+
return value && typeof value === "object" ? value : void 0;
|
|
6818
|
+
} catch {
|
|
6819
|
+
return void 0;
|
|
6820
|
+
}
|
|
6821
|
+
})();
|
|
6822
|
+
return {
|
|
6823
|
+
ok: result.run.state !== "failed" && result.run.state !== "stale",
|
|
6824
|
+
runId: result.run.runId,
|
|
6825
|
+
state: result.run.state,
|
|
6826
|
+
stateDir: result.stateDir,
|
|
6827
|
+
artifactRefs: result.run.artifactRefs,
|
|
6828
|
+
steps: result.run.steps,
|
|
6829
|
+
reusedStages: result.reusedStages,
|
|
6830
|
+
...snapshot ? { snapshotHash: snapshot.contentHash, sourceRevision: snapshot.sourceRevision, configurationHash: snapshot.configurationHash, coverage: snapshot.coverage } : {},
|
|
6831
|
+
...report ? { reportHash: report.contentHash, diagnostics: report.diagnostics } : {},
|
|
6832
|
+
...rules ? { rules } : {}
|
|
6833
|
+
};
|
|
6834
|
+
};
|
|
6835
|
+
var runWorkflowCommand = (command, flags, configPath, argv) => {
|
|
6836
|
+
try {
|
|
6837
|
+
const { config, root } = loadProject(configPath);
|
|
6838
|
+
const result = command === "scan" ? scanWorkflow(root, config) : command === "reconcile" ? reconcileWorkflow(root, config) : checkWorkflow(root, config);
|
|
6839
|
+
const output = workflowOutput(result);
|
|
6840
|
+
if (command === "map") output.kind = "architecture-map";
|
|
6841
|
+
if (command === "map" && flags.has("--html")) {
|
|
6842
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(result.stateDir, "normalize"));
|
|
6843
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, "reconcile"));
|
|
6844
|
+
const outputPath = optionValues(argv, "--output")[0] ?? ".doc-bridge/report.html";
|
|
6845
|
+
const htmlPath = resolve18(root, outputPath);
|
|
6846
|
+
mkdirSync8(dirname10(htmlPath), { recursive: true });
|
|
6847
|
+
writeFileSync8(htmlPath, renderOfflineReport({ snapshot, report }), "utf8");
|
|
6848
|
+
output.htmlPath = htmlPath;
|
|
6849
|
+
}
|
|
6850
|
+
if (wantsTextOutput(flags, config)) {
|
|
6851
|
+
writeLines([`Run: ${String(output.runId)}`, `State: ${String(output.state)}`, ...output.snapshotHash ? [`Snapshot: ${String(output.snapshotHash)}`] : [], ...output.reportHash ? [`Report: ${String(output.reportHash)}`] : [], `Artifacts: ${String(output.artifactRefs.length)}`]);
|
|
6852
|
+
} else writeJson2(output);
|
|
6853
|
+
const ruleExitCode = output.rules && typeof output.rules === "object" && "exitCode" in output.rules && output.rules.exitCode === 1 ? 1 : 0;
|
|
6854
|
+
return result.run.state === "failed" ? 1 : ruleExitCode;
|
|
6855
|
+
} catch (error) {
|
|
6856
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6857
|
+
`);
|
|
6858
|
+
return 2;
|
|
6859
|
+
}
|
|
6860
|
+
};
|
|
6861
|
+
var runRulesCommand = (argv, flags, positional, configPath) => {
|
|
6862
|
+
const action = positional[1];
|
|
6863
|
+
const reportPath = positional[2];
|
|
6864
|
+
if (action !== "run" || !reportPath) {
|
|
6865
|
+
process.stderr.write("Usage: ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]\n");
|
|
6866
|
+
return 2;
|
|
6867
|
+
}
|
|
6868
|
+
try {
|
|
6869
|
+
const { config } = loadProject(configPath);
|
|
6870
|
+
const report = parseReconciliationReport(JSON.parse(readFileSync21(resolve18(reportPath), "utf8")));
|
|
6871
|
+
const presetValue = optionValues(argv, "--preset")[0];
|
|
6872
|
+
const preset = presetValue === void 0 ? void 0 : ["default", "recommended", "strict"].includes(presetValue) ? presetValue : (() => {
|
|
6873
|
+
throw new Error(`Invalid rules preset "${presetValue}".`);
|
|
6874
|
+
})();
|
|
6875
|
+
const severity = {};
|
|
6876
|
+
for (const [rule, level] of Object.entries(parseRuleAssignments(optionValues(argv, "--severity"), parseRuleSeverity))) {
|
|
6877
|
+
severity[parseRuleId(rule)] = level;
|
|
6878
|
+
}
|
|
6879
|
+
const ignore = optionValues(argv, "--ignore").map(parseRuleId);
|
|
6880
|
+
const result = evaluateRules(report, {
|
|
6881
|
+
...config.rules ? { config: config.rules } : {},
|
|
6882
|
+
...preset ? { preset } : {},
|
|
6883
|
+
...Object.keys(severity).length ? { severity } : {},
|
|
6884
|
+
...ignore.length ? { ignore } : {},
|
|
6885
|
+
...optionValues(argv, "--critical-entity").length ? { criticalEntities: optionValues(argv, "--critical-entity") } : {},
|
|
6886
|
+
...optionValues(argv, "--critical-path").length ? { criticalPaths: optionValues(argv, "--critical-path") } : {}
|
|
6887
|
+
});
|
|
6888
|
+
if (wantsTextOutput(flags, config)) {
|
|
6889
|
+
writeLines([
|
|
6890
|
+
`Rules: ${result.mode}`,
|
|
6891
|
+
`Findings: ${result.findings.length}`,
|
|
6892
|
+
...result.findings.length ? result.findings.map((finding) => ` [${finding.severity}] ${finding.code}: ${finding.message}`) : [" (none)"],
|
|
6893
|
+
`Exit code: ${result.exitCode}`
|
|
6894
|
+
]);
|
|
6895
|
+
} else {
|
|
6896
|
+
writeJson2({ ok: result.exitCode === 0, reportHash: report.contentHash, ...result });
|
|
6897
|
+
}
|
|
6898
|
+
return result.exitCode;
|
|
6899
|
+
} catch (error) {
|
|
6900
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6901
|
+
`);
|
|
6902
|
+
return 2;
|
|
6903
|
+
}
|
|
6904
|
+
};
|
|
6905
|
+
var runFixCommand = (argv, positional, configPath) => {
|
|
6906
|
+
try {
|
|
6907
|
+
const { config, root } = loadProject(configPath);
|
|
6908
|
+
const action = positional[1];
|
|
6909
|
+
const proposalPath2 = positional[2];
|
|
6910
|
+
const sourceRevision2 = discoverRepository({ root, config }).sourceRevision;
|
|
6911
|
+
const fixOptions = { baseRevision: sourceRevision2, configurationHash: sha256NormalizedV1(config), ...config.project?.name ? { projectName: config.project.name } : {} };
|
|
6912
|
+
if (action === "propose") {
|
|
6913
|
+
const proposal2 = positional[2] === "links" ? createMarkdownLinkFixProposal(root, fixOptions) : positional[2] === "normalize" && positional[3] ? createArtifactNormalizationProposal(root, positional[3], fixOptions) : void 0;
|
|
6914
|
+
if (!proposal2) {
|
|
6915
|
+
writeJson2({ ok: true, proposal: null });
|
|
6916
|
+
return 0;
|
|
6917
|
+
}
|
|
6918
|
+
const outputPath = optionValues(argv, "--output")[0];
|
|
6919
|
+
if (outputPath) {
|
|
6920
|
+
mkdirSync8(dirname10(resolve18(root, outputPath)), { recursive: true });
|
|
6921
|
+
writeFileSync8(resolve18(root, outputPath), `${JSON.stringify(proposal2, null, 2)}
|
|
6922
|
+
`, "utf8");
|
|
6923
|
+
}
|
|
6924
|
+
writeJson2({ ok: true, proposal: proposal2, ...outputPath ? { proposalPath: resolve18(root, outputPath) } : {} });
|
|
6925
|
+
return 0;
|
|
6926
|
+
}
|
|
6927
|
+
if (!proposalPath2 || !["approve", "apply"].includes(action ?? "")) throw new Error("Usage: ak-docs fix propose links|normalize <artifact> [--output <file>] | fix approve|apply <proposal.json> [--by <name>]");
|
|
6928
|
+
const file = resolve18(root, proposalPath2);
|
|
6929
|
+
const proposal = JSON.parse(readFileSync21(file, "utf8"));
|
|
6930
|
+
const result = action === "approve" ? approveFixProposal(proposal, optionValues(argv, "--by")[0] ?? "human") : applyFixProposal(root, proposal, { currentRevision: sourceRevision2 });
|
|
6931
|
+
writeFileSync8(file, `${JSON.stringify(result, null, 2)}
|
|
6932
|
+
`, "utf8");
|
|
6933
|
+
writeJson2({ ok: true, proposal: result, proposalPath: file });
|
|
6934
|
+
return 0;
|
|
6935
|
+
} catch (error) {
|
|
6936
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6937
|
+
`);
|
|
6938
|
+
return 2;
|
|
6939
|
+
}
|
|
6940
|
+
};
|
|
6941
|
+
var runSuggestCommand = async (flags, configPath) => {
|
|
6942
|
+
try {
|
|
6943
|
+
const { config, root } = loadProject(configPath);
|
|
6944
|
+
const stateDir = resolve18(root, config.workflow?.stateDir ?? ".doc-bridge/workflow");
|
|
6945
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(stateDir, "normalize"));
|
|
6946
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(stateDir, "reconcile"));
|
|
6947
|
+
const adapter = createRegistryAgentAdapter(root, config, await loadRegistryAgentRunner(root, config));
|
|
6948
|
+
const proposal = await adapter.run(snapshot, report);
|
|
6949
|
+
const proposalPath2 = persistRegistryAgentProposal(stateDir, proposal);
|
|
6950
|
+
if (flags.has("--text")) writeLines([`Agent: ${adapter.metadata.id}`, `Proposal: ${proposal.proposalId}`, `Hash: ${proposal.contentHash}`, `Saved: ${proposalPath2}`]);
|
|
6951
|
+
else writeJson2({ ok: true, proposal, proposalPath: proposalPath2 });
|
|
6952
|
+
return 0;
|
|
6953
|
+
} catch (error) {
|
|
6954
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6955
|
+
`);
|
|
6956
|
+
return 2;
|
|
6957
|
+
}
|
|
6958
|
+
};
|
|
4802
6959
|
var writeIfMissing = (path, contents) => {
|
|
4803
|
-
if (
|
|
4804
|
-
|
|
4805
|
-
|
|
6960
|
+
if (existsSync21(path)) return false;
|
|
6961
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
6962
|
+
writeFileSync8(path, contents, "utf8");
|
|
4806
6963
|
return true;
|
|
4807
6964
|
};
|
|
4808
6965
|
var demoOwnership = {
|
|
@@ -4905,7 +7062,7 @@ var scaffoldWorkspaceDocs = (root, config) => {
|
|
|
4905
7062
|
const created = [];
|
|
4906
7063
|
const skipped = [];
|
|
4907
7064
|
for (const pkg of discoverPnpmPackages(root, config)) {
|
|
4908
|
-
const path =
|
|
7065
|
+
const path = resolve18(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
|
|
4909
7066
|
if (writeIfMissing(path, workspaceDocDraft(pkg.id, pkg.path))) created.push(path);
|
|
4910
7067
|
else skipped.push(path);
|
|
4911
7068
|
}
|
|
@@ -4915,11 +7072,11 @@ var bootstrapAgentDocs = (root, config) => {
|
|
|
4915
7072
|
const created = [];
|
|
4916
7073
|
const skipped = [];
|
|
4917
7074
|
for (const doc of scanHumanDocRecords(root, config)) {
|
|
4918
|
-
const raw =
|
|
7075
|
+
const raw = readFileSync21(doc.path, "utf8");
|
|
4919
7076
|
const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
4920
7077
|
const title = firstHeading(body) ?? doc.id;
|
|
4921
7078
|
const description = firstParagraph(body);
|
|
4922
|
-
const draftPath =
|
|
7079
|
+
const draftPath = resolve18(root, config.corpus.agent.root, "human", `${doc.id}.md`);
|
|
4923
7080
|
const draft = [
|
|
4924
7081
|
"---",
|
|
4925
7082
|
"type: knowledge",
|
|
@@ -4983,8 +7140,8 @@ var runCli = (argv) => {
|
|
|
4983
7140
|
return 1;
|
|
4984
7141
|
}
|
|
4985
7142
|
try {
|
|
4986
|
-
const abs =
|
|
4987
|
-
const raw =
|
|
7143
|
+
const abs = resolve18(file);
|
|
7144
|
+
const raw = readFileSync21(abs, "utf8");
|
|
4988
7145
|
const handoff = parseAgentHandoff(JSON.parse(raw));
|
|
4989
7146
|
writeJson2({ ok: true, handoff });
|
|
4990
7147
|
return 0;
|
|
@@ -4994,13 +7151,42 @@ var runCli = (argv) => {
|
|
|
4994
7151
|
return 1;
|
|
4995
7152
|
}
|
|
4996
7153
|
}
|
|
7154
|
+
if (command === "discover") {
|
|
7155
|
+
try {
|
|
7156
|
+
let config;
|
|
7157
|
+
let root = process.cwd();
|
|
7158
|
+
try {
|
|
7159
|
+
const loaded = loadProject(configPath);
|
|
7160
|
+
config = loaded.config;
|
|
7161
|
+
root = loaded.root;
|
|
7162
|
+
} catch (error) {
|
|
7163
|
+
if (!(error instanceof ConfigNotFoundError) || configPath) throw error;
|
|
7164
|
+
}
|
|
7165
|
+
const snapshot = discoverRepository({ root, ...config ? { config } : {} });
|
|
7166
|
+
if (wantsTextOutput(flags, config ?? { surfaces: { cli: { defaultFormat: "json" } } })) {
|
|
7167
|
+
writeLines([`Project: ${snapshot.project.name}`, `Entities: ${snapshot.entities.length}`, `Relations: ${snapshot.relations.length}`, `Source revision: ${snapshot.sourceRevision}`, ...config ? [] : ["No configuration found; discovery used safe defaults."]]);
|
|
7168
|
+
} else {
|
|
7169
|
+
writeJson2({ ok: true, snapshot, ...config ? {} : { proposedConfig: { schemaVersion: 1, corpus: { agent: { root: "docs/for-agents" } } } } });
|
|
7170
|
+
}
|
|
7171
|
+
return 0;
|
|
7172
|
+
} catch (error) {
|
|
7173
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
7174
|
+
`);
|
|
7175
|
+
return 2;
|
|
7176
|
+
}
|
|
7177
|
+
}
|
|
7178
|
+
if (command === "scan" || command === "reconcile" || command === "check" || command === "map") {
|
|
7179
|
+
return runWorkflowCommand(command, flags, configPath, argv);
|
|
7180
|
+
}
|
|
7181
|
+
if (command === "fix") return runFixCommand(argv, positional, configPath);
|
|
7182
|
+
if (command === "suggest") return runSuggestCommand(flags, configPath);
|
|
4997
7183
|
if (command === "init") {
|
|
4998
7184
|
const root = process.cwd();
|
|
4999
7185
|
const withDemo = flags.has("--demo") || !flags.has("--no-demo");
|
|
5000
|
-
const configFile =
|
|
5001
|
-
const docsIndex =
|
|
5002
|
-
const exampleDoc =
|
|
5003
|
-
const agentsMd =
|
|
7186
|
+
const configFile = resolve18(root, configPath ?? "doc-bridge.config.json");
|
|
7187
|
+
const docsIndex = resolve18(root, "docs/for-agents/INDEX.md");
|
|
7188
|
+
const exampleDoc = resolve18(root, "docs/for-agents/packages/example.md");
|
|
7189
|
+
const agentsMd = resolve18(root, "AGENTS.md");
|
|
5004
7190
|
const configWritten = writeIfMissing(configFile, initConfigContents(configFile, withDemo));
|
|
5005
7191
|
const indexWritten = writeIfMissing(
|
|
5006
7192
|
docsIndex,
|
|
@@ -5008,7 +7194,7 @@ var runCli = (argv) => {
|
|
|
5008
7194
|
);
|
|
5009
7195
|
const exampleWritten = withDemo ? writeIfMissing(exampleDoc, exampleAgentDoc) : false;
|
|
5010
7196
|
const agentsWritten = writeIfMissing(agentsMd, agentsMdSnippet);
|
|
5011
|
-
if (withDemo) writeIfMissing(
|
|
7197
|
+
if (withDemo) writeIfMissing(resolve18(root, "src/.gitkeep"), "");
|
|
5012
7198
|
const scaffold = flags.has("--scaffold-workspaces") ? scaffoldWorkspaceDocs(root, loadProject(configFile).config) : void 0;
|
|
5013
7199
|
writeJson2({
|
|
5014
7200
|
ok: true,
|
|
@@ -5187,6 +7373,7 @@ var runCli = (argv) => {
|
|
|
5187
7373
|
return 1;
|
|
5188
7374
|
}
|
|
5189
7375
|
}
|
|
7376
|
+
if (command === "rules") return runRulesCommand(argv, flags, positional, configPath);
|
|
5190
7377
|
if (command === "conformance") {
|
|
5191
7378
|
const action = positional[1];
|
|
5192
7379
|
const profile = positional[2];
|
|
@@ -5251,9 +7438,9 @@ var runCli = (argv) => {
|
|
|
5251
7438
|
const { config, root } = loadProject(configPath);
|
|
5252
7439
|
const report = runDoctor(root, config);
|
|
5253
7440
|
if (flags.has("--write-badge")) {
|
|
5254
|
-
const badgePath =
|
|
5255
|
-
|
|
5256
|
-
|
|
7441
|
+
const badgePath = resolve18(root, ".doc-bridge", "coverage-badge.json");
|
|
7442
|
+
mkdirSync8(dirname10(badgePath), { recursive: true });
|
|
7443
|
+
writeFileSync8(badgePath, `${formatDoctorBadgeJson(report.badge)}
|
|
5257
7444
|
`, "utf8");
|
|
5258
7445
|
}
|
|
5259
7446
|
if (flags.has("--badge")) {
|