@agentskit/doc-bridge 1.4.3 → 1.5.1
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 +12 -0
- package/action.yml +1 -1
- package/dist/cli/program.js +2413 -268
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +74 -3
- package/dist/config/index.js.map +1 -1
- package/dist/{index-DhoAG9Ar.d.ts → index-Di7PkJuf.d.ts} +195 -8
- package/dist/index.d.ts +1942 -4
- package/dist/index.js +2144 -189
- 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 +16 -16
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +84 -127
- 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 +67 -0
- package/src/discovery/documentation.ts +320 -0
- package/src/discovery/repository.ts +515 -0
- package/src/fixes/proposals.ts +165 -0
- package/src/index-builder/content-hash.ts +9 -2
- 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
|
},
|
|
@@ -428,6 +451,39 @@ var GatesConfigSchema = z.object({
|
|
|
428
451
|
).max(16).optional(),
|
|
429
452
|
options: z.record(z.string(), z.unknown()).optional()
|
|
430
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();
|
|
431
487
|
var SurfacesConfigSchema = z.object({
|
|
432
488
|
cli: z.object({
|
|
433
489
|
bin: z.string().min(1).max(64).optional(),
|
|
@@ -445,7 +501,13 @@ var SurfacesConfigSchema = z.object({
|
|
|
445
501
|
"retriever.query",
|
|
446
502
|
"memory.classify",
|
|
447
503
|
"memory.promoteDraft",
|
|
448
|
-
"registry.topology"
|
|
504
|
+
"registry.topology",
|
|
505
|
+
"docbridge.snapshot",
|
|
506
|
+
"docbridge.report",
|
|
507
|
+
"docbridge.diagnostics",
|
|
508
|
+
"docbridge.relations",
|
|
509
|
+
"docbridge.run",
|
|
510
|
+
"docbridge.proposals"
|
|
449
511
|
])
|
|
450
512
|
).max(16).optional(),
|
|
451
513
|
transport: z.enum(["stdio", "http"]).optional(),
|
|
@@ -488,7 +550,13 @@ var IntelligenceConfigSchema = z.object({
|
|
|
488
550
|
}).strict().optional()
|
|
489
551
|
}).strict().optional(),
|
|
490
552
|
runtime: z.enum(["agentskit", "custom"]).optional(),
|
|
491
|
-
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()
|
|
492
560
|
}).strict();
|
|
493
561
|
var FederationConfigSchema = z.object({
|
|
494
562
|
enabled: z.boolean().optional(),
|
|
@@ -578,6 +646,9 @@ var DocBridgeConfigV1Schema = z.object({
|
|
|
578
646
|
index: IndexConfigSchema.optional(),
|
|
579
647
|
routing: RoutingConfigSchema.optional(),
|
|
580
648
|
gates: GatesConfigSchema.optional(),
|
|
649
|
+
rules: RulesConfigSchema.optional(),
|
|
650
|
+
workflow: WorkflowConfigSchema.optional(),
|
|
651
|
+
safety: RepositorySafetyConfigSchema.optional(),
|
|
581
652
|
surfaces: SurfacesConfigSchema.optional(),
|
|
582
653
|
intelligence: IntelligenceConfigSchema.optional(),
|
|
583
654
|
federation: FederationConfigSchema.optional(),
|
|
@@ -1368,7 +1439,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1368
1439
|
};
|
|
1369
1440
|
|
|
1370
1441
|
// src/version.ts
|
|
1371
|
-
var PACKAGE_VERSION = "1.
|
|
1442
|
+
var PACKAGE_VERSION = "1.5.1";
|
|
1372
1443
|
|
|
1373
1444
|
// src/index-builder/capabilities.ts
|
|
1374
1445
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -1413,23 +1484,28 @@ var sortValue = (value) => {
|
|
|
1413
1484
|
}
|
|
1414
1485
|
return value;
|
|
1415
1486
|
};
|
|
1487
|
+
var canonicalJsonV1 = (payload) => JSON.stringify(sortValue(payload));
|
|
1416
1488
|
var sha256NormalizedV1 = (payload) => {
|
|
1417
|
-
const normalized =
|
|
1489
|
+
const normalized = canonicalJsonV1(payload);
|
|
1418
1490
|
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
1419
1491
|
};
|
|
1492
|
+
var contentHashForArtifactV1 = (artifact2) => {
|
|
1493
|
+
const { contentHash: _contentHash, ...payload } = artifact2;
|
|
1494
|
+
return sha256NormalizedV1(payload);
|
|
1495
|
+
};
|
|
1420
1496
|
|
|
1421
1497
|
// src/index-builder/llms-txt.ts
|
|
1422
1498
|
var routePath = (path, pathPrefix) => {
|
|
1423
1499
|
const normalizedPath = path.replaceAll("\\", "/").replace(/\.(?:md|mdx)$/, "");
|
|
1424
1500
|
const normalizedPrefix = pathPrefix?.replaceAll("\\", "/").replace(/\/$/, "");
|
|
1425
|
-
const
|
|
1426
|
-
return
|
|
1501
|
+
const relativePath3 = normalizedPrefix && normalizedPath.startsWith(`${normalizedPrefix}/`) ? normalizedPath.slice(normalizedPrefix.length + 1) : normalizedPath;
|
|
1502
|
+
return relativePath3.replace(/^\/+/, "");
|
|
1427
1503
|
};
|
|
1428
1504
|
var knowledgeUrl = (path, options = {}) => {
|
|
1429
|
-
const
|
|
1505
|
+
const relativePath3 = routePath(path, options.pathPrefix);
|
|
1430
1506
|
if (!options.urlPrefix) return path;
|
|
1431
1507
|
const base = options.urlPrefix.endsWith("/") ? options.urlPrefix : `${options.urlPrefix}/`;
|
|
1432
|
-
return new URL(
|
|
1508
|
+
return new URL(relativePath3, base).toString();
|
|
1433
1509
|
};
|
|
1434
1510
|
var renderLlmsTxt = (config, knowledge, projectName2) => {
|
|
1435
1511
|
const preamble = config.index?.llmsTxt?.preamble ?? `# ${projectName2}
|
|
@@ -2185,26 +2261,26 @@ var llmsRule = (root, config, options) => {
|
|
|
2185
2261
|
if (key !== llmsKey && !rawSources.has(key)) rawSources.set(key, path);
|
|
2186
2262
|
}
|
|
2187
2263
|
const paths = [llmsPath, ...rawSources.values()];
|
|
2188
|
-
const
|
|
2264
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2189
2265
|
const generated = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
2190
2266
|
const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? "project");
|
|
2191
|
-
const llmsIsFresh =
|
|
2192
|
-
if (
|
|
2193
|
-
|
|
2194
|
-
...
|
|
2267
|
+
const llmsIsFresh = evidence2[0]?.content === expectedLlms;
|
|
2268
|
+
if (evidence2[0]?.exists) {
|
|
2269
|
+
evidence2[0] = {
|
|
2270
|
+
...evidence2[0],
|
|
2195
2271
|
evidence: {
|
|
2196
|
-
...
|
|
2272
|
+
...evidence2[0].evidence,
|
|
2197
2273
|
detail: llmsIsFresh ? "File matches the deterministic ak-docs output." : "File is stale or was not generated by the current ak-docs inputs."
|
|
2198
2274
|
}
|
|
2199
2275
|
};
|
|
2200
2276
|
}
|
|
2201
|
-
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);
|
|
2202
2278
|
return {
|
|
2203
2279
|
id: "llms-and-raw-source",
|
|
2204
2280
|
level: "required",
|
|
2205
2281
|
passed,
|
|
2206
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.",
|
|
2207
|
-
evidence:
|
|
2283
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2208
2284
|
remediation: {
|
|
2209
2285
|
command: "ak-docs index",
|
|
2210
2286
|
detail: "Generate llms.txt and configure conformance.documentationStandardV1.rawSources."
|
|
@@ -2223,8 +2299,8 @@ var ecosystemContract = (root, options) => {
|
|
|
2223
2299
|
}
|
|
2224
2300
|
const manifestFile = fileEvidence(root, declaration.manifest);
|
|
2225
2301
|
const claimsFile = fileEvidence(root, declaration.claims);
|
|
2226
|
-
const
|
|
2227
|
-
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 };
|
|
2228
2304
|
try {
|
|
2229
2305
|
const manifest = JSON.parse(manifestFile.content);
|
|
2230
2306
|
const claims = JSON.parse(claimsFile.content);
|
|
@@ -2242,15 +2318,15 @@ var ecosystemContract = (root, options) => {
|
|
|
2242
2318
|
if (typeof value === "string" && /^https:\/\//.test(value)) urls.add(normalizedUrl(value));
|
|
2243
2319
|
}
|
|
2244
2320
|
}
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
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 };
|
|
2248
2324
|
} catch (error) {
|
|
2249
|
-
|
|
2325
|
+
evidence2.push({
|
|
2250
2326
|
path: `${declaration.manifest}, ${declaration.claims}`,
|
|
2251
2327
|
detail: error instanceof Error ? error.message : "Canonical ecosystem contract is invalid."
|
|
2252
2328
|
});
|
|
2253
|
-
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
2329
|
+
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence: evidence2 };
|
|
2254
2330
|
}
|
|
2255
2331
|
};
|
|
2256
2332
|
var handoffsRule = (root, config) => {
|
|
@@ -2276,13 +2352,13 @@ var handoffsRule = (root, config) => {
|
|
|
2276
2352
|
};
|
|
2277
2353
|
var contributionRule = (root, options) => {
|
|
2278
2354
|
const paths = options.contributionPaths?.length ? options.contributionPaths : ["CONTRIBUTING.md"];
|
|
2279
|
-
const
|
|
2355
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2280
2356
|
return {
|
|
2281
2357
|
id: "contribution",
|
|
2282
2358
|
level: "required",
|
|
2283
|
-
passed:
|
|
2284
|
-
message:
|
|
2285
|
-
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),
|
|
2286
2362
|
remediation: {
|
|
2287
2363
|
command: "edit CONTRIBUTING.md",
|
|
2288
2364
|
detail: "Document setup, validation commands, and the pull-request workflow."
|
|
@@ -2291,13 +2367,13 @@ var contributionRule = (root, options) => {
|
|
|
2291
2367
|
};
|
|
2292
2368
|
var markersRule = (root, options, kind, level) => {
|
|
2293
2369
|
const declarations = options[kind === "metadata" ? "metadata" : "diagrams"] ?? [];
|
|
2294
|
-
const
|
|
2370
|
+
const evidence2 = [];
|
|
2295
2371
|
let passed = declarations.length > 0;
|
|
2296
2372
|
for (const declaration of declarations) {
|
|
2297
2373
|
const file = fileEvidence(root, declaration.path);
|
|
2298
2374
|
const missing = declaration.contains.filter((marker) => !file.content.includes(marker));
|
|
2299
2375
|
if (!file.exists || missing.length > 0) passed = false;
|
|
2300
|
-
|
|
2376
|
+
evidence2.push({
|
|
2301
2377
|
path: declaration.path,
|
|
2302
2378
|
detail: !file.exists ? file.evidence.detail : missing.length ? `Missing marker(s): ${missing.join(", ")}` : `Found marker(s): ${declaration.contains.join(", ")}`
|
|
2303
2379
|
});
|
|
@@ -2307,7 +2383,7 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2307
2383
|
level,
|
|
2308
2384
|
passed,
|
|
2309
2385
|
message: passed ? `${kind} evidence is complete.` : `${kind} evidence is incomplete.`,
|
|
2310
|
-
evidence,
|
|
2386
|
+
evidence: evidence2,
|
|
2311
2387
|
remediation: {
|
|
2312
2388
|
command: "edit doc-bridge.config.json",
|
|
2313
2389
|
detail: `Declare ${kind} evidence paths and markers that exist in the repository.`
|
|
@@ -2317,14 +2393,14 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2317
2393
|
var linksRule = (root, options) => {
|
|
2318
2394
|
const links = options.links ?? [];
|
|
2319
2395
|
const contract = ecosystemContract(root, options);
|
|
2320
|
-
const
|
|
2396
|
+
const evidence2 = [...contract.evidence];
|
|
2321
2397
|
let passed = links.length > 0 && contract.passed;
|
|
2322
2398
|
for (const link of links) {
|
|
2323
2399
|
const sources = link.paths.map((path) => ({ path, file: fileEvidence(root, path) }));
|
|
2324
2400
|
const matches = sources.filter(({ file }) => file.exists && file.content.includes(link.url));
|
|
2325
2401
|
const canonical = contract.urls.has(normalizedUrl(link.url));
|
|
2326
2402
|
if (matches.length === 0 || !canonical) passed = false;
|
|
2327
|
-
|
|
2403
|
+
evidence2.push({
|
|
2328
2404
|
path: sources.map((source) => source.path).join(", "),
|
|
2329
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.`
|
|
2330
2406
|
});
|
|
@@ -2334,7 +2410,7 @@ var linksRule = (root, options) => {
|
|
|
2334
2410
|
level: "required",
|
|
2335
2411
|
passed,
|
|
2336
2412
|
message: passed ? `${links.length} required ecosystem link(s) resolve in source.` : "One or more required ecosystem links are missing from source.",
|
|
2337
|
-
evidence,
|
|
2413
|
+
evidence: evidence2,
|
|
2338
2414
|
remediation: {
|
|
2339
2415
|
command: "edit README.md",
|
|
2340
2416
|
detail: "Sync the canonical ecosystem snapshots and add each configured canonical URL to a declared documentation source."
|
|
@@ -2343,14 +2419,14 @@ var linksRule = (root, options) => {
|
|
|
2343
2419
|
};
|
|
2344
2420
|
var quickstartsRule = (root, options) => {
|
|
2345
2421
|
const quickstarts = options.quickstarts ?? [];
|
|
2346
|
-
const
|
|
2422
|
+
const evidence2 = [];
|
|
2347
2423
|
let passed = quickstarts.length > 0;
|
|
2348
2424
|
for (const quickstart of quickstarts) {
|
|
2349
2425
|
const doc = fileEvidence(root, quickstart.doc);
|
|
2350
2426
|
const test = fileEvidence(root, quickstart.test);
|
|
2351
2427
|
const missingMarkers = quickstart.testContains.filter((marker) => !test.content.includes(marker));
|
|
2352
2428
|
if (!doc.exists || !test.exists || missingMarkers.length > 0 || !quickstart.command.trim()) passed = false;
|
|
2353
|
-
|
|
2429
|
+
evidence2.push(
|
|
2354
2430
|
{ path: quickstart.doc, detail: `${quickstart.id}: ${doc.evidence.detail}` },
|
|
2355
2431
|
{
|
|
2356
2432
|
path: quickstart.test,
|
|
@@ -2363,7 +2439,7 @@ var quickstartsRule = (root, options) => {
|
|
|
2363
2439
|
level: "required",
|
|
2364
2440
|
passed,
|
|
2365
2441
|
message: passed ? `${quickstarts.length} quickstart(s) have executable test evidence.` : "Quickstart test evidence is incomplete.",
|
|
2366
|
-
evidence,
|
|
2442
|
+
evidence: evidence2,
|
|
2367
2443
|
remediation: {
|
|
2368
2444
|
command: "pnpm test",
|
|
2369
2445
|
detail: "Map every quickstart to a documentation path, test file, identifying marker, and CI command."
|
|
@@ -2372,13 +2448,13 @@ var quickstartsRule = (root, options) => {
|
|
|
2372
2448
|
};
|
|
2373
2449
|
var visualsRule = (root, options) => {
|
|
2374
2450
|
const visuals = options.visuals ?? [];
|
|
2375
|
-
const
|
|
2451
|
+
const evidence2 = visuals.map((path) => fileEvidence(root, path, { readContent: false }));
|
|
2376
2452
|
return {
|
|
2377
2453
|
id: "visual-explanations",
|
|
2378
2454
|
level: "recommended",
|
|
2379
|
-
passed: visuals.length > 0 &&
|
|
2380
|
-
message: visuals.length > 0 &&
|
|
2381
|
-
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),
|
|
2382
2458
|
remediation: {
|
|
2383
2459
|
command: "edit doc-bridge.config.json",
|
|
2384
2460
|
detail: "Declare the images or animations that explain the product workflow."
|
|
@@ -2427,14 +2503,942 @@ var formatDocumentationStandardText = (report) => [
|
|
|
2427
2503
|
"",
|
|
2428
2504
|
...report.results.flatMap((result) => [
|
|
2429
2505
|
`${result.status === "pass" ? "PASS" : result.status === "excepted" ? "EXCEPTED" : "FAIL"} [${result.level}] ${result.id}: ${result.message}`,
|
|
2430
|
-
...result.evidence.map((
|
|
2506
|
+
...result.evidence.map((evidence2) => ` evidence: ${evidence2.path} \u2014 ${evidence2.detail}`),
|
|
2431
2507
|
...result.exception ? [` exception: ${result.exception.reason} \u2014 ${result.exception.approvedBy} (${result.exception.trackingUrl})`] : result.ok ? [] : [` fix: ${result.remediation.command} \u2014 ${result.remediation.detail}`]
|
|
2432
2508
|
])
|
|
2433
2509
|
];
|
|
2434
2510
|
|
|
2435
|
-
// 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";
|
|
2436
2964
|
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
2437
|
-
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
|
+
if (!existsSync10(manifestPath)) continue;
|
|
3136
|
+
const parsed = readJson(manifestPath);
|
|
3137
|
+
if (!parsed.value) {
|
|
3138
|
+
coverage.push({ status: "partial", reason: `${relativePath(root, manifestPath)}: ${parsed.error ?? "invalid package.json"}` });
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
const path = relativePath(root, absPath);
|
|
3142
|
+
const name = packageName(parsed.value, path);
|
|
3143
|
+
const id = entityId("package", name ?? path);
|
|
3144
|
+
const duplicate = packages.find((pkg) => pkg.id === id);
|
|
3145
|
+
if (duplicate && duplicate.absPath !== absPath) {
|
|
3146
|
+
throw new Error(`Package identity collision for "${id}": "${duplicate.path}" and "${path}".`);
|
|
3147
|
+
}
|
|
3148
|
+
if (!duplicate) packages.push({ id, ...name ? { name } : {}, path, absPath, manifestPath, manifest: parsed.value });
|
|
3149
|
+
}
|
|
3150
|
+
coverage.push({ status: "complete" });
|
|
3151
|
+
return { packages: packages.sort((a, b) => a.id.localeCompare(b.id)), coverage };
|
|
3152
|
+
};
|
|
3153
|
+
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];
|
|
3154
|
+
var readCompilerOptions = (root) => {
|
|
3155
|
+
const configPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
|
|
3156
|
+
if (!configPath) return { options: {} };
|
|
3157
|
+
const parsed = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
3158
|
+
if (parsed.error) return { options: {}, error: ts.flattenDiagnosticMessageText(parsed.error.messageText, "\n") };
|
|
3159
|
+
const config = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname5(configPath));
|
|
3160
|
+
if (config.errors.length) {
|
|
3161
|
+
return {
|
|
3162
|
+
options: config.options,
|
|
3163
|
+
error: ts.flattenDiagnosticMessageText(config.errors[0]?.messageText ?? "Invalid tsconfig", "\n")
|
|
3164
|
+
};
|
|
3165
|
+
}
|
|
3166
|
+
return { options: config.options };
|
|
3167
|
+
};
|
|
3168
|
+
var scriptKind = (path) => {
|
|
3169
|
+
switch (extname(path)) {
|
|
3170
|
+
case ".js":
|
|
3171
|
+
return ts.ScriptKind.JS;
|
|
3172
|
+
case ".jsx":
|
|
3173
|
+
return ts.ScriptKind.JSX;
|
|
3174
|
+
case ".mjs":
|
|
3175
|
+
return ts.ScriptKind.JS;
|
|
3176
|
+
case ".cjs":
|
|
3177
|
+
return ts.ScriptKind.JS;
|
|
3178
|
+
case ".ts":
|
|
3179
|
+
return ts.ScriptKind.TS;
|
|
3180
|
+
case ".tsx":
|
|
3181
|
+
return ts.ScriptKind.TSX;
|
|
3182
|
+
case ".mts":
|
|
3183
|
+
return ts.ScriptKind.TS;
|
|
3184
|
+
case ".cts":
|
|
3185
|
+
return ts.ScriptKind.TS;
|
|
3186
|
+
default:
|
|
3187
|
+
return ts.ScriptKind.Unknown;
|
|
3188
|
+
}
|
|
3189
|
+
};
|
|
3190
|
+
var nodeEvidence = (root, path, sourceFile, node) => {
|
|
3191
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
3192
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
3193
|
+
return lineEvidence("code", root, path, start, end);
|
|
3194
|
+
};
|
|
3195
|
+
var isExported = (node) => {
|
|
3196
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
|
|
3197
|
+
return modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
3198
|
+
};
|
|
3199
|
+
var exportedNames = (sourceFile) => {
|
|
3200
|
+
const names = /* @__PURE__ */ new Set();
|
|
3201
|
+
const addDeclarationName = (node) => {
|
|
3202
|
+
if (!isExported(node)) return;
|
|
3203
|
+
const name = ts.getNameOfDeclaration(node);
|
|
3204
|
+
if (name && ts.isIdentifier(name)) names.add(name.text);
|
|
3205
|
+
};
|
|
3206
|
+
const visit = (node) => {
|
|
3207
|
+
if (ts.isExportDeclaration(node)) {
|
|
3208
|
+
if (!node.exportClause) names.add("*");
|
|
3209
|
+
else if (ts.isNamedExports(node.exportClause)) {
|
|
3210
|
+
for (const element of node.exportClause.elements) names.add(element.name.text);
|
|
3211
|
+
}
|
|
3212
|
+
} else if (ts.isExportAssignment(node)) {
|
|
3213
|
+
names.add("default");
|
|
3214
|
+
} else if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
3215
|
+
addDeclarationName(node);
|
|
3216
|
+
} else if (ts.isVariableStatement(node) && isExported(node)) {
|
|
3217
|
+
for (const declaration of node.declarationList.declarations) {
|
|
3218
|
+
if (ts.isIdentifier(declaration.name)) names.add(declaration.name.text);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
ts.forEachChild(node, visit);
|
|
3222
|
+
};
|
|
3223
|
+
visit(sourceFile);
|
|
3224
|
+
return [...names].sort();
|
|
3225
|
+
};
|
|
3226
|
+
var moduleReferences = (root, path, sourceFile) => {
|
|
3227
|
+
const references = [];
|
|
3228
|
+
let hasDynamic = false;
|
|
3229
|
+
let hasRuntimeWiring = false;
|
|
3230
|
+
const addReference = (specifier, kind, node) => {
|
|
3231
|
+
references.push({ specifier: specifier.text, kind, evidence: nodeEvidence(root, path, sourceFile, node) });
|
|
3232
|
+
};
|
|
3233
|
+
const visit = (node) => {
|
|
3234
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
3235
|
+
addReference(node.moduleSpecifier, "imports", node);
|
|
3236
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
3237
|
+
addReference(node.moduleSpecifier, "re-exports", node);
|
|
3238
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
3239
|
+
addReference(node.moduleReference.expression, "imports", node);
|
|
3240
|
+
} else if (ts.isCallExpression(node)) {
|
|
3241
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
3242
|
+
if (!node.arguments[0] || !ts.isStringLiteralLike(node.arguments[0])) hasDynamic = true;
|
|
3243
|
+
} else if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
|
|
3244
|
+
const argument = node.arguments[0];
|
|
3245
|
+
if (argument && ts.isStringLiteralLike(argument)) addReference(argument, "imports", node);
|
|
3246
|
+
else hasDynamic = true;
|
|
3247
|
+
} else if (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "register") {
|
|
3248
|
+
hasRuntimeWiring = true;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
ts.forEachChild(node, visit);
|
|
3252
|
+
};
|
|
3253
|
+
visit(sourceFile);
|
|
3254
|
+
return {
|
|
3255
|
+
references,
|
|
3256
|
+
exports: exportedNames(sourceFile),
|
|
3257
|
+
hasDynamic,
|
|
3258
|
+
hasRuntimeWiring
|
|
3259
|
+
};
|
|
3260
|
+
};
|
|
3261
|
+
var resolveRelativeModule = (specifier, containingFile, modulePaths) => {
|
|
3262
|
+
const base = resolve8(dirname5(containingFile), specifier);
|
|
3263
|
+
const extension = extname(base);
|
|
3264
|
+
const extensionlessBase = extension ? base.slice(0, -extension.length) : base;
|
|
3265
|
+
const candidates = [
|
|
3266
|
+
base,
|
|
3267
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${base}${extension2}`),
|
|
3268
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(base, `index${extension2}`)),
|
|
3269
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${extensionlessBase}${extension2}`),
|
|
3270
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(extensionlessBase, `index${extension2}`))
|
|
3271
|
+
];
|
|
3272
|
+
return candidates.map((candidate) => modulePaths.get(resolve8(candidate))).find(Boolean);
|
|
3273
|
+
};
|
|
3274
|
+
var resolveReference = (reference, containingFile, modules, packages, compilerOptions) => {
|
|
3275
|
+
if (reference.specifier.startsWith(".") || reference.specifier.startsWith("/")) {
|
|
3276
|
+
const relativeTarget = resolveRelativeModule(reference.specifier, containingFile, modules);
|
|
3277
|
+
return relativeTarget ? { targetId: relativeTarget.entityId } : void 0;
|
|
3278
|
+
}
|
|
3279
|
+
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];
|
|
3280
|
+
if (packageTarget) return { targetId: packageTarget.id };
|
|
3281
|
+
const resolved = ts.resolveModuleName(reference.specifier, containingFile, compilerOptions, ts.sys).resolvedModule?.resolvedFileName;
|
|
3282
|
+
const resolvedTarget = resolved ? modules.get(resolve8(resolved)) : void 0;
|
|
3283
|
+
if (resolvedTarget) return { targetId: resolvedTarget.entityId };
|
|
3284
|
+
return { targetId: entityId("external", reference.specifier) };
|
|
3285
|
+
};
|
|
3286
|
+
var dependencyEntries = (manifest) => {
|
|
3287
|
+
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
3288
|
+
return sections.flatMap((type) => {
|
|
3289
|
+
const value = manifest[type];
|
|
3290
|
+
if (!isRecord2(value)) return [];
|
|
3291
|
+
return Object.keys(value).sort().map((name) => ({ name, type }));
|
|
3292
|
+
});
|
|
3293
|
+
};
|
|
3294
|
+
var sourceRevision = (root, files) => {
|
|
3295
|
+
const contentRevision = () => ({
|
|
3296
|
+
value: sha256NormalizedV1(
|
|
3297
|
+
files.map((path) => ({
|
|
3298
|
+
path: relativePath(root, path),
|
|
3299
|
+
contentHash: sha256NormalizedV1(readFileSync9(path, "utf8"))
|
|
3300
|
+
}))
|
|
3301
|
+
),
|
|
3302
|
+
kind: "content"
|
|
3303
|
+
});
|
|
3304
|
+
try {
|
|
3305
|
+
const status = execFileSync("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
3306
|
+
cwd: root,
|
|
3307
|
+
encoding: "utf8",
|
|
3308
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
3309
|
+
}).trim();
|
|
3310
|
+
if (status) return contentRevision();
|
|
3311
|
+
const value = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
3312
|
+
if (value) return { value, kind: "git" };
|
|
3313
|
+
} catch {
|
|
3314
|
+
}
|
|
3315
|
+
return contentRevision();
|
|
3316
|
+
};
|
|
3317
|
+
var hasPackageManagerMetadata = (root, rootManifest) => Boolean(
|
|
3318
|
+
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"))
|
|
3319
|
+
);
|
|
3320
|
+
var artifact = (root, config, files, entities, relations, coverage) => {
|
|
3321
|
+
const revision = sourceRevision(root, files);
|
|
3322
|
+
const base = {
|
|
3323
|
+
type: "discovery-snapshot",
|
|
3324
|
+
schemaVersion: 1,
|
|
3325
|
+
contentHash: EMPTY_HASH,
|
|
3326
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
3327
|
+
project: { name: entities.find((entity) => entity.kind === "package" && entity.path === ".")?.name ?? basename4(root), root: "." },
|
|
3328
|
+
sourceRevision: revision.value,
|
|
3329
|
+
sourceRevisionKind: revision.kind,
|
|
3330
|
+
configurationHash: sha256NormalizedV1(config ?? {}),
|
|
3331
|
+
pipelineVersion: "1.0.0",
|
|
3332
|
+
analyzerVersions: { repository: "1.0.0", "js-ts": "1.0.0" },
|
|
3333
|
+
entities: [...entities].sort((a, b) => a.id.localeCompare(b.id)),
|
|
3334
|
+
relations: [...relations].sort((a, b) => a.id.localeCompare(b.id)),
|
|
3335
|
+
coverage: [...coverage]
|
|
3336
|
+
};
|
|
3337
|
+
return DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
3338
|
+
};
|
|
3339
|
+
var discoverRepository = (opts = {}) => {
|
|
3340
|
+
const root = resolve8(opts.root ?? process.cwd());
|
|
3341
|
+
const safety = opts.config?.safety;
|
|
3342
|
+
const maxFiles = opts.maxFiles ?? safety?.maxFiles ?? DEFAULT_MAX_FILES2;
|
|
3343
|
+
const maxBytes = opts.maxBytes ?? safety?.maxBytes;
|
|
3344
|
+
const safeOptions = {
|
|
3345
|
+
exclude: [...DEFAULT_SAFETY_EXCLUDES, ...safety?.exclude ?? []],
|
|
3346
|
+
maxFiles,
|
|
3347
|
+
...maxBytes !== void 0 ? { maxBytes } : {},
|
|
3348
|
+
...safety?.maxTimeMs !== void 0 ? { maxTimeMs: safety.maxTimeMs } : {},
|
|
3349
|
+
...safety?.maxMemoryMb !== void 0 ? { maxMemoryMb: safety.maxMemoryMb } : {}
|
|
3350
|
+
};
|
|
3351
|
+
const rootManifestPath = join8(root, "package.json");
|
|
3352
|
+
const rootManifest = readJson(rootManifestPath).value;
|
|
3353
|
+
const packageResult = discoverPackages(root, rootManifest, opts.config);
|
|
3354
|
+
const sourceWalk = safeWalkFiles(root, { extensions: SOURCE_EXTENSIONS, ...safeOptions });
|
|
3355
|
+
const documentWalk = safeWalkFiles(root, { extensions: DOCUMENT_EXTENSIONS, ...safeOptions });
|
|
3356
|
+
const configWalk = safeWalkFiles(root, { extensions: [".json", ".yaml", ".yml", ".js", ".ts"], ...safeOptions });
|
|
3357
|
+
const sourcePaths = sourceWalk.files;
|
|
3358
|
+
const documentPaths = documentWalk.files;
|
|
3359
|
+
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)));
|
|
3360
|
+
const allFiles = [...new Set([rootManifestPath, ...sourcePaths, ...documentPaths, ...configPaths].filter(existsSync10))].sort();
|
|
3361
|
+
const entities = /* @__PURE__ */ new Map();
|
|
3362
|
+
const relations = /* @__PURE__ */ new Map();
|
|
3363
|
+
const addEntity = (entity) => {
|
|
3364
|
+
const existing = entities.get(entity.id);
|
|
3365
|
+
if (existing && (existing.kind !== entity.kind || existing.path !== entity.path)) throw new Error(`Entity identity collision for "${entity.id}".`);
|
|
3366
|
+
entities.set(entity.id, existing ?? entity);
|
|
3367
|
+
};
|
|
3368
|
+
const addRelation = (relation) => {
|
|
3369
|
+
const existing = relations.get(relation.id);
|
|
3370
|
+
if (!existing) {
|
|
3371
|
+
relations.set(relation.id, relation);
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
const evidence2 = new Map(
|
|
3375
|
+
[...existing.evidence, ...relation.evidence].map((item) => [
|
|
3376
|
+
`${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.source}`,
|
|
3377
|
+
item
|
|
3378
|
+
])
|
|
3379
|
+
);
|
|
3380
|
+
relations.set(relation.id, { ...existing, evidence: [...evidence2.values()] });
|
|
3381
|
+
};
|
|
3382
|
+
for (const pkg of packageResult.packages) {
|
|
3383
|
+
const text = readFileSync9(pkg.manifestPath, "utf8");
|
|
3384
|
+
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"'))] });
|
|
3385
|
+
}
|
|
3386
|
+
const modules = /* @__PURE__ */ new Map();
|
|
3387
|
+
for (const absPath of sourcePaths) {
|
|
3388
|
+
const path = relativePath(root, absPath);
|
|
3389
|
+
const pkg = packageForModule(packageResult.packages, absPath);
|
|
3390
|
+
const id = entityId("module", path);
|
|
3391
|
+
const text = readFileSync9(absPath, "utf8");
|
|
3392
|
+
const sourceFile = ts.createSourceFile(absPath, text, ts.ScriptTarget.Latest, true, scriptKind(absPath));
|
|
3393
|
+
const exports = exportedNames(sourceFile);
|
|
3394
|
+
modules.set(resolve8(absPath), { absPath, path, entityId: id, ...pkg ? { packageId: pkg.id } : {} });
|
|
3395
|
+
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) } } : {} });
|
|
3396
|
+
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)] });
|
|
3397
|
+
}
|
|
3398
|
+
for (const absPath of documentPaths) {
|
|
3399
|
+
const path = relativePath(root, absPath);
|
|
3400
|
+
addEntity({ id: entityId("document", path), kind: "document", name: basename4(absPath), path, provenance: "observed", evidence: [lineEvidence("documentation", root, absPath, 1)] });
|
|
3401
|
+
}
|
|
3402
|
+
const compiler = readCompilerOptions(root);
|
|
3403
|
+
const coverage = [
|
|
3404
|
+
...[sourceWalk, documentWalk, configWalk].flatMap((walk, index) => walk.incomplete ? [{ analyzer: "repository", scope: `limits:${["source", "documentation", "configuration"][index]}`, status: "partial", reason: walk.reason }] : []),
|
|
3405
|
+
{ 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)}.` } : {} },
|
|
3406
|
+
{ 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 } : {} },
|
|
3407
|
+
{ analyzer: "js-ts", scope: "static-imports-and-exports", status: compiler.error ? "partial" : "complete", ...compiler.error ? { reason: compiler.error } : {} },
|
|
3408
|
+
{ analyzer: "js-ts", scope: "dynamic-imports", status: "not-analyzed", reason: "Dynamic import expressions and non-literal require calls are not resolved." },
|
|
3409
|
+
{ analyzer: "js-ts", scope: "runtime-wiring", status: "not-analyzed", reason: "Reflection, dependency injection and runtime wiring are not inferred." },
|
|
3410
|
+
{ analyzer: "js-ts", scope: "generated-code", status: "not-analyzed", reason: "Generated code is not interpreted as source architecture." }
|
|
3411
|
+
];
|
|
3412
|
+
for (const pkg of packageResult.packages) {
|
|
3413
|
+
const text = readFileSync9(pkg.manifestPath, "utf8");
|
|
3414
|
+
for (const dependency of dependencyEntries(pkg.manifest)) {
|
|
3415
|
+
const target = packageResult.packages.find((candidate) => candidate.name === dependency.name)?.id ?? entityId("external", dependency.name);
|
|
3416
|
+
if (!entities.has(target)) addEntity({ id: target, kind: "external", name: dependency.name, provenance: "observed", evidence: [lineEvidence("configuration", root, pkg.manifestPath, firstLineContaining(text, `"${dependency.name}"`))] });
|
|
3417
|
+
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 } });
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
for (const module of modules.values()) {
|
|
3421
|
+
const text = readFileSync9(module.absPath, "utf8");
|
|
3422
|
+
const sourceFile = ts.createSourceFile(module.absPath, text, ts.ScriptTarget.Latest, true, scriptKind(module.absPath));
|
|
3423
|
+
const references = moduleReferences(root, module.absPath, sourceFile);
|
|
3424
|
+
for (const reference of references.references) {
|
|
3425
|
+
const target = resolveReference(reference, module.absPath, modules, packageResult.packages, compiler.options);
|
|
3426
|
+
if (!target) continue;
|
|
3427
|
+
if (!entities.has(target.targetId)) {
|
|
3428
|
+
const externalName = target.targetId.replace(/^external:/, "");
|
|
3429
|
+
addEntity({ id: target.targetId, kind: "external", name: externalName, provenance: "observed", evidence: [reference.evidence] });
|
|
3430
|
+
}
|
|
3431
|
+
addRelation({ id: entityId("relation", `${module.entityId}:${reference.kind}:${target.targetId}`), kind: reference.kind, from: module.entityId, to: target.targetId, provenance: "observed", evidence: [reference.evidence] });
|
|
3432
|
+
}
|
|
3433
|
+
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)] });
|
|
3434
|
+
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)] });
|
|
3435
|
+
}
|
|
3436
|
+
return artifact(root, opts.config, allFiles, [...entities.values()], [...relations.values()], coverage);
|
|
3437
|
+
};
|
|
3438
|
+
|
|
3439
|
+
// src/federation/llms.ts
|
|
3440
|
+
import { existsSync as existsSync11, readFileSync as readFileSync10 } from "fs";
|
|
3441
|
+
import { resolve as resolve9 } from "path";
|
|
2438
3442
|
|
|
2439
3443
|
// src/query/search.ts
|
|
2440
3444
|
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
@@ -2563,9 +3567,9 @@ var defaultFetchText = async (url) => {
|
|
|
2563
3567
|
var sourceText = async (root, source, fetchText) => {
|
|
2564
3568
|
try {
|
|
2565
3569
|
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
2566
|
-
const path =
|
|
2567
|
-
if (!
|
|
2568
|
-
return
|
|
3570
|
+
const path = resolve9(root, source);
|
|
3571
|
+
if (!existsSync11(path)) return null;
|
|
3572
|
+
return readFileSync10(path, "utf8");
|
|
2569
3573
|
} catch {
|
|
2570
3574
|
return null;
|
|
2571
3575
|
}
|
|
@@ -2650,16 +3654,16 @@ var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
|
2650
3654
|
};
|
|
2651
3655
|
|
|
2652
3656
|
// src/gates/run-gates.ts
|
|
2653
|
-
import { readFileSync as
|
|
3657
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
2654
3658
|
|
|
2655
3659
|
// src/query/load-index.ts
|
|
2656
|
-
import { existsSync as
|
|
2657
|
-
import { join as
|
|
3660
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
3661
|
+
import { join as join9, resolve as resolve10 } from "path";
|
|
2658
3662
|
|
|
2659
3663
|
// src/schemas/agent-handoff.ts
|
|
2660
|
-
import { z as
|
|
3664
|
+
import { z as z4 } from "zod";
|
|
2661
3665
|
var HANDOFF_SCHEMA_VERSION = 1;
|
|
2662
|
-
var HandoffTargetTypeSchema =
|
|
3666
|
+
var HandoffTargetTypeSchema = z4.enum([
|
|
2663
3667
|
"package",
|
|
2664
3668
|
"module",
|
|
2665
3669
|
"app",
|
|
@@ -2670,133 +3674,133 @@ var HandoffTargetTypeSchema = z3.enum([
|
|
|
2670
3674
|
"change",
|
|
2671
3675
|
"search"
|
|
2672
3676
|
]);
|
|
2673
|
-
var HandoffTargetSchema =
|
|
3677
|
+
var HandoffTargetSchema = z4.object({
|
|
2674
3678
|
type: HandoffTargetTypeSchema,
|
|
2675
|
-
id:
|
|
2676
|
-
path:
|
|
2677
|
-
group:
|
|
2678
|
-
layer:
|
|
3679
|
+
id: z4.string().min(1).max(256),
|
|
3680
|
+
path: z4.string().min(1).max(512).optional(),
|
|
3681
|
+
group: z4.string().min(1).max(128).optional(),
|
|
3682
|
+
layer: z4.string().min(1).max(32).optional()
|
|
2679
3683
|
}).strict();
|
|
2680
|
-
var HandoffBridgeSchema =
|
|
2681
|
-
humanDoc:
|
|
2682
|
-
action:
|
|
2683
|
-
bootstrap:
|
|
3684
|
+
var HandoffBridgeSchema = z4.object({
|
|
3685
|
+
humanDoc: z4.enum(["linked", "missing", "external"]),
|
|
3686
|
+
action: z4.string().min(1).max(256).optional(),
|
|
3687
|
+
bootstrap: z4.string().min(1).max(256).optional()
|
|
2684
3688
|
}).strict();
|
|
2685
|
-
var AgentHandoffV1Schema =
|
|
2686
|
-
type:
|
|
2687
|
-
schemaVersion:
|
|
2688
|
-
source:
|
|
3689
|
+
var AgentHandoffV1Schema = z4.object({
|
|
3690
|
+
type: z4.literal("agent-handoff"),
|
|
3691
|
+
schemaVersion: z4.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
3692
|
+
source: z4.string().min(1).max(512),
|
|
2689
3693
|
target: HandoffTargetSchema,
|
|
2690
|
-
startHere:
|
|
2691
|
-
readBeforeEditing:
|
|
2692
|
-
editRoots:
|
|
2693
|
-
checks:
|
|
2694
|
-
humanDoc:
|
|
3694
|
+
startHere: z4.string().min(1).max(512),
|
|
3695
|
+
readBeforeEditing: z4.array(z4.string().min(1).max(512)).max(64),
|
|
3696
|
+
editRoots: z4.array(z4.string().min(1).max(512)).max(32),
|
|
3697
|
+
checks: z4.array(z4.string().min(1).max(256)).max(32),
|
|
3698
|
+
humanDoc: z4.string().min(1).max(512).nullable().optional(),
|
|
2695
3699
|
bridge: HandoffBridgeSchema.optional(),
|
|
2696
|
-
playbookPatterns:
|
|
2697
|
-
notes:
|
|
3700
|
+
playbookPatterns: z4.array(z4.string().url()).max(16).optional(),
|
|
3701
|
+
notes: z4.array(z4.string().min(1).max(1024)).max(16)
|
|
2698
3702
|
}).strict();
|
|
2699
3703
|
var AgentHandoffLegacySchema = AgentHandoffV1Schema.omit({ schemaVersion: true }).extend({
|
|
2700
|
-
schemaVersion:
|
|
3704
|
+
schemaVersion: z4.literal(HANDOFF_SCHEMA_VERSION).optional()
|
|
2701
3705
|
});
|
|
2702
|
-
var AgentSearchMatchSchema =
|
|
2703
|
-
type:
|
|
2704
|
-
id:
|
|
2705
|
-
path:
|
|
2706
|
-
summary:
|
|
2707
|
-
score:
|
|
2708
|
-
refs:
|
|
3706
|
+
var AgentSearchMatchSchema = z4.object({
|
|
3707
|
+
type: z4.string().min(1).max(64),
|
|
3708
|
+
id: z4.string().min(1).max(256),
|
|
3709
|
+
path: z4.string().min(1).max(512),
|
|
3710
|
+
summary: z4.string().max(2048).optional(),
|
|
3711
|
+
score: z4.number().optional(),
|
|
3712
|
+
refs: z4.number().int().nonnegative().optional()
|
|
2709
3713
|
}).strict();
|
|
2710
|
-
var AgentSearchV1Schema =
|
|
2711
|
-
type:
|
|
2712
|
-
schemaVersion:
|
|
2713
|
-
source:
|
|
2714
|
-
term:
|
|
2715
|
-
count:
|
|
3714
|
+
var AgentSearchV1Schema = z4.object({
|
|
3715
|
+
type: z4.literal("agent-search"),
|
|
3716
|
+
schemaVersion: z4.literal(HANDOFF_SCHEMA_VERSION).default(HANDOFF_SCHEMA_VERSION),
|
|
3717
|
+
source: z4.string().min(1).max(512),
|
|
3718
|
+
term: z4.string().min(1).max(512),
|
|
3719
|
+
count: z4.number().int().nonnegative(),
|
|
2716
3720
|
bestMatch: AgentSearchMatchSchema.nullable(),
|
|
2717
|
-
matches:
|
|
2718
|
-
nextCommands:
|
|
3721
|
+
matches: z4.array(AgentSearchMatchSchema).max(32),
|
|
3722
|
+
nextCommands: z4.array(z4.string().min(1).max(512)).max(16)
|
|
2719
3723
|
}).strict();
|
|
2720
3724
|
var normalizeAgentHandoff = (input) => AgentHandoffV1Schema.parse(AgentHandoffLegacySchema.parse(input));
|
|
2721
3725
|
|
|
2722
3726
|
// src/schemas/doc-bridge-index.ts
|
|
2723
|
-
import { z as
|
|
3727
|
+
import { z as z5 } from "zod";
|
|
2724
3728
|
var INDEX_SCHEMA_VERSION = 1;
|
|
2725
|
-
var ContentHashAlgoSchema =
|
|
2726
|
-
var EcosystemPropertySchema =
|
|
2727
|
-
id:
|
|
2728
|
-
name:
|
|
2729
|
-
url:
|
|
2730
|
-
llms:
|
|
3729
|
+
var ContentHashAlgoSchema = z5.literal("sha256-normalized-v1");
|
|
3730
|
+
var EcosystemPropertySchema = z5.object({
|
|
3731
|
+
id: z5.string().min(1).max(64),
|
|
3732
|
+
name: z5.string().min(1).max(128),
|
|
3733
|
+
url: z5.string().url().optional(),
|
|
3734
|
+
llms: z5.string().url().optional()
|
|
2731
3735
|
}).strict();
|
|
2732
|
-
var KnowledgeEntrySchema =
|
|
2733
|
-
id:
|
|
2734
|
-
type:
|
|
2735
|
-
title:
|
|
2736
|
-
path:
|
|
2737
|
-
description:
|
|
3736
|
+
var KnowledgeEntrySchema = z5.object({
|
|
3737
|
+
id: z5.string().min(1).max(256),
|
|
3738
|
+
type: z5.string().min(1).max(128),
|
|
3739
|
+
title: z5.string().min(1).max(256),
|
|
3740
|
+
path: z5.string().min(1).max(512),
|
|
3741
|
+
description: z5.string().max(2048).optional(),
|
|
2738
3742
|
/** Flattened body excerpt for full-text search (not for display). */
|
|
2739
|
-
body:
|
|
2740
|
-
links:
|
|
2741
|
-
tags:
|
|
3743
|
+
body: z5.string().max(8e3).optional(),
|
|
3744
|
+
links: z5.array(z5.string().min(1).max(512)).max(64).optional(),
|
|
3745
|
+
tags: z5.array(z5.string().min(1).max(64)).max(32).optional()
|
|
2742
3746
|
}).strict();
|
|
2743
|
-
var CapabilityRefSchema =
|
|
2744
|
-
id:
|
|
2745
|
-
kind:
|
|
2746
|
-
description:
|
|
3747
|
+
var CapabilityRefSchema = z5.object({
|
|
3748
|
+
id: z5.string().min(1).max(256),
|
|
3749
|
+
kind: z5.string().min(1).max(64),
|
|
3750
|
+
description: z5.string().max(512).optional()
|
|
2747
3751
|
}).strict();
|
|
2748
|
-
var OwnershipRecordSchema =
|
|
2749
|
-
id:
|
|
2750
|
-
path:
|
|
2751
|
-
group:
|
|
2752
|
-
layer:
|
|
2753
|
-
purpose:
|
|
2754
|
-
checks:
|
|
2755
|
-
agentDoc:
|
|
2756
|
-
humanDoc:
|
|
2757
|
-
readme:
|
|
3752
|
+
var OwnershipRecordSchema = z5.object({
|
|
3753
|
+
id: z5.string().min(1).max(256),
|
|
3754
|
+
path: z5.string().min(1).max(512),
|
|
3755
|
+
group: z5.string().min(1).max(128).optional(),
|
|
3756
|
+
layer: z5.string().min(1).max(32).optional(),
|
|
3757
|
+
purpose: z5.string().max(1024).optional(),
|
|
3758
|
+
checks: z5.array(z5.string().min(1).max(256)).max(32),
|
|
3759
|
+
agentDoc: z5.string().min(1).max(512).optional(),
|
|
3760
|
+
humanDoc: z5.string().min(1).max(512).optional(),
|
|
3761
|
+
readme: z5.string().min(1).max(512).optional()
|
|
2758
3762
|
}).strict();
|
|
2759
|
-
var IndexLookupSchema =
|
|
2760
|
-
packages:
|
|
2761
|
-
ownership:
|
|
2762
|
-
intents:
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
id:
|
|
2766
|
-
title:
|
|
2767
|
-
paths:
|
|
3763
|
+
var IndexLookupSchema = z5.object({
|
|
3764
|
+
packages: z5.array(z5.string().min(1).max(256)).max(2e3),
|
|
3765
|
+
ownership: z5.record(z5.string().min(1).max(256), OwnershipRecordSchema).optional(),
|
|
3766
|
+
intents: z5.record(
|
|
3767
|
+
z5.string().min(1).max(128),
|
|
3768
|
+
z5.object({
|
|
3769
|
+
id: z5.string().min(1).max(128),
|
|
3770
|
+
title: z5.string().min(1).max(256),
|
|
3771
|
+
paths: z5.array(z5.string().min(1).max(512)).max(32)
|
|
2768
3772
|
}).strict()
|
|
2769
3773
|
).optional(),
|
|
2770
|
-
changes:
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
id:
|
|
2774
|
-
title:
|
|
2775
|
-
startHere:
|
|
2776
|
-
relatedPackages:
|
|
3774
|
+
changes: z5.record(
|
|
3775
|
+
z5.string().min(1).max(128),
|
|
3776
|
+
z5.object({
|
|
3777
|
+
id: z5.string().min(1).max(128),
|
|
3778
|
+
title: z5.string().min(1).max(256),
|
|
3779
|
+
startHere: z5.string().min(1).max(512),
|
|
3780
|
+
relatedPackages: z5.array(z5.string().min(1).max(256)).max(32).optional()
|
|
2777
3781
|
}).strict()
|
|
2778
3782
|
).optional()
|
|
2779
3783
|
}).strict();
|
|
2780
|
-
var DocBridgeIndexV1Schema =
|
|
2781
|
-
schemaVersion:
|
|
2782
|
-
contentHash:
|
|
3784
|
+
var DocBridgeIndexV1Schema = z5.object({
|
|
3785
|
+
schemaVersion: z5.literal(INDEX_SCHEMA_VERSION),
|
|
3786
|
+
contentHash: z5.string().regex(/^[a-f0-9]{64}$/),
|
|
2783
3787
|
contentHashAlgo: ContentHashAlgoSchema,
|
|
2784
|
-
generatedAt:
|
|
2785
|
-
project:
|
|
2786
|
-
name:
|
|
2787
|
-
root:
|
|
3788
|
+
generatedAt: z5.string().datetime().optional(),
|
|
3789
|
+
project: z5.object({
|
|
3790
|
+
name: z5.string().min(1).max(128),
|
|
3791
|
+
root: z5.string().min(1).max(512).optional()
|
|
2788
3792
|
}).strict().optional(),
|
|
2789
|
-
properties:
|
|
2790
|
-
knowledge:
|
|
2791
|
-
capabilities:
|
|
2792
|
-
handoffs:
|
|
3793
|
+
properties: z5.array(EcosystemPropertySchema).max(16).optional(),
|
|
3794
|
+
knowledge: z5.array(KnowledgeEntrySchema).max(1e4),
|
|
3795
|
+
capabilities: z5.array(CapabilityRefSchema).max(5e3).optional(),
|
|
3796
|
+
handoffs: z5.record(z5.string().min(1).max(256), AgentHandoffLegacySchema).optional(),
|
|
2793
3797
|
lookup: IndexLookupSchema.optional()
|
|
2794
3798
|
}).strict();
|
|
2795
3799
|
|
|
2796
3800
|
// src/schemas/memory-candidate.ts
|
|
2797
|
-
import { z as
|
|
3801
|
+
import { z as z6 } from "zod";
|
|
2798
3802
|
var MEMORY_CANDIDATE_SCHEMA_VERSION = 1;
|
|
2799
|
-
var MemorySourceSchema =
|
|
3803
|
+
var MemorySourceSchema = z6.enum([
|
|
2800
3804
|
"cursor",
|
|
2801
3805
|
"claude",
|
|
2802
3806
|
"codex",
|
|
@@ -2804,24 +3808,24 @@ var MemorySourceSchema = z5.enum([
|
|
|
2804
3808
|
"agent-memory",
|
|
2805
3809
|
"manual"
|
|
2806
3810
|
]);
|
|
2807
|
-
var MemorySuggestedTypeSchema =
|
|
3811
|
+
var MemorySuggestedTypeSchema = z6.enum([
|
|
2808
3812
|
"user",
|
|
2809
3813
|
"feedback",
|
|
2810
3814
|
"project",
|
|
2811
3815
|
"reference",
|
|
2812
3816
|
"unknown"
|
|
2813
3817
|
]);
|
|
2814
|
-
var MemoryCandidateV1Schema =
|
|
2815
|
-
schemaVersion:
|
|
2816
|
-
id:
|
|
3818
|
+
var MemoryCandidateV1Schema = z6.object({
|
|
3819
|
+
schemaVersion: z6.literal(MEMORY_CANDIDATE_SCHEMA_VERSION),
|
|
3820
|
+
id: z6.string().min(1).max(256),
|
|
2817
3821
|
source: MemorySourceSchema,
|
|
2818
|
-
rawPath:
|
|
2819
|
-
fact:
|
|
2820
|
-
why:
|
|
2821
|
-
howToApply:
|
|
3822
|
+
rawPath: z6.string().min(1).max(512).optional(),
|
|
3823
|
+
fact: z6.string().min(1).max(8e3),
|
|
3824
|
+
why: z6.string().max(4e3).optional(),
|
|
3825
|
+
howToApply: z6.string().max(4e3).optional(),
|
|
2822
3826
|
suggestedType: MemorySuggestedTypeSchema,
|
|
2823
|
-
confidence:
|
|
2824
|
-
references:
|
|
3827
|
+
confidence: z6.number().min(0).max(1),
|
|
3828
|
+
references: z6.array(z6.string().min(1).max(512)).max(64)
|
|
2825
3829
|
}).strict();
|
|
2826
3830
|
|
|
2827
3831
|
// src/validate.ts
|
|
@@ -2847,6 +3851,8 @@ ${result.issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`
|
|
|
2847
3851
|
return result.value;
|
|
2848
3852
|
};
|
|
2849
3853
|
var parseDocBridgeIndex = (input) => DocBridgeIndexV1Schema.parse(input);
|
|
3854
|
+
var parseDiscoverySnapshot = (input) => DiscoverySnapshotV1Schema.parse(input);
|
|
3855
|
+
var parseReconciliationReport = (input) => ReconciliationReportV1Schema.parse(input);
|
|
2850
3856
|
var parseDocBridgeConfig = (input) => {
|
|
2851
3857
|
const result = DocBridgeConfigV1Schema.safeParse(input);
|
|
2852
3858
|
if (!result.success) {
|
|
@@ -2867,11 +3873,11 @@ var IndexNotFoundError = class extends Error {
|
|
|
2867
3873
|
}
|
|
2868
3874
|
path;
|
|
2869
3875
|
};
|
|
2870
|
-
var indexFilePath = (root, config) =>
|
|
3876
|
+
var indexFilePath = (root, config) => join9(root, config.index?.outFile ?? ".doc-bridge/index.json");
|
|
2871
3877
|
var loadDocBridgeIndex = (root, config) => {
|
|
2872
3878
|
const path = indexFilePath(root, config);
|
|
2873
|
-
if (!
|
|
2874
|
-
const raw = JSON.parse(
|
|
3879
|
+
if (!existsSync12(path)) throw new IndexNotFoundError(path);
|
|
3880
|
+
const raw = JSON.parse(readFileSync11(path, "utf8"));
|
|
2875
3881
|
return parseDocBridgeIndex(raw);
|
|
2876
3882
|
};
|
|
2877
3883
|
|
|
@@ -2946,7 +3952,7 @@ var runOkfTypeGate = (root, config) => {
|
|
|
2946
3952
|
const required = config.corpus.agent.okf?.requireType ?? config.gates?.preset === "strict";
|
|
2947
3953
|
if (!required) return { id: "okf-type", ok: true, message: "OKF type frontmatter not required" };
|
|
2948
3954
|
const allowed = config.corpus.agent.okf?.allowedTypes;
|
|
2949
|
-
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(
|
|
3955
|
+
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));
|
|
2950
3956
|
if (bad.length) {
|
|
2951
3957
|
return {
|
|
2952
3958
|
id: "okf-type",
|
|
@@ -2973,10 +3979,10 @@ var DOCS_STYLE_RULES = {
|
|
|
2973
3979
|
"playbook-okf-soft": ["title", "no-stale-wording"],
|
|
2974
3980
|
"title-only": ["title"]
|
|
2975
3981
|
};
|
|
2976
|
-
var
|
|
3982
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2977
3983
|
var docsStyleOptions = (config) => {
|
|
2978
3984
|
const raw = config.gates?.options?.["docs-style"];
|
|
2979
|
-
if (!
|
|
3985
|
+
if (!isRecord3(raw)) {
|
|
2980
3986
|
if (config.gates?.preset === "playbook") {
|
|
2981
3987
|
return { profile: "playbook-okf-soft", required: DOCS_STYLE_RULES["playbook-okf-soft"] };
|
|
2982
3988
|
}
|
|
@@ -3020,7 +4026,7 @@ var runDocsStyleGate = (root, config) => {
|
|
|
3020
4026
|
}
|
|
3021
4027
|
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({
|
|
3022
4028
|
path: doc.path,
|
|
3023
|
-
missing: missingStyleRules(
|
|
4029
|
+
missing: missingStyleRules(readFileSync12(doc.absPath, "utf8"), required)
|
|
3024
4030
|
})).filter((doc) => doc.missing.length > 0);
|
|
3025
4031
|
if (bad.length) {
|
|
3026
4032
|
return {
|
|
@@ -3064,6 +4070,112 @@ var resolveGateIds = (config) => {
|
|
|
3064
4070
|
return [...ids];
|
|
3065
4071
|
};
|
|
3066
4072
|
|
|
4073
|
+
// src/rules/engine.ts
|
|
4074
|
+
import { minimatch as minimatch5 } from "minimatch";
|
|
4075
|
+
var diagnosticRules = {
|
|
4076
|
+
DOCUMENTATION_QUALITY: "documentation-quality",
|
|
4077
|
+
RELATION_UNDOCUMENTED: "graph-undocumented-relation",
|
|
4078
|
+
DECLARED_RELATION_STALE: "declared-unobserved-relation",
|
|
4079
|
+
UNRESOLVED_ENTITY_REFERENCE: "unresolved-reference",
|
|
4080
|
+
CONFLICTING_DECLARATIONS: "conflicting-declaration",
|
|
4081
|
+
RELATION_NOT_ANALYZED: "not-analyzed-coverage",
|
|
4082
|
+
STALE_DOCUMENTATION: "stale-documentation",
|
|
4083
|
+
FRESHNESS_FAILURE: "freshness",
|
|
4084
|
+
OWNERSHIP_GAP: "ownership",
|
|
4085
|
+
CENTRALITY_RISK: "centrality-risk",
|
|
4086
|
+
CRITICAL_PATH_RISK: "critical-path-risk"
|
|
4087
|
+
};
|
|
4088
|
+
var defaultSeverity = (mode, ruleId) => {
|
|
4089
|
+
if (mode === "default") return "info";
|
|
4090
|
+
if (mode === "strict") return ruleId === "not-analyzed-coverage" ? "warn" : "error";
|
|
4091
|
+
return ruleId === "not-analyzed-coverage" ? "info" : "warn";
|
|
4092
|
+
};
|
|
4093
|
+
var resolvedOptions = (options) => {
|
|
4094
|
+
const config = RulesConfigSchema.parse(options.config ?? {});
|
|
4095
|
+
const mode = options.preset ?? config.mode ?? "default";
|
|
4096
|
+
const severity = { ...config.severity, ...options.severity };
|
|
4097
|
+
const ignore = /* @__PURE__ */ new Set([...config.ignore ?? [], ...options.ignore ?? []]);
|
|
4098
|
+
return {
|
|
4099
|
+
mode,
|
|
4100
|
+
severity,
|
|
4101
|
+
ignore,
|
|
4102
|
+
criticalEntities: options.criticalEntities ?? config.criticalEntities ?? [],
|
|
4103
|
+
criticalPaths: options.criticalPaths ?? config.criticalPaths ?? [],
|
|
4104
|
+
warningThresholds: { ...config.warningThresholds, ...options.warningThresholds }
|
|
4105
|
+
};
|
|
4106
|
+
};
|
|
4107
|
+
var severityFor = (ruleId, mode, overrides) => overrides[ruleId] ?? defaultSeverity(mode, ruleId);
|
|
4108
|
+
var findingFromDiagnostic = (diagnostic2, ruleId, severity) => ({
|
|
4109
|
+
id: `${diagnostic2.id}:${ruleId}`,
|
|
4110
|
+
ruleId,
|
|
4111
|
+
code: ruleId,
|
|
4112
|
+
status: diagnostic2.status,
|
|
4113
|
+
severity,
|
|
4114
|
+
message: diagnostic2.message,
|
|
4115
|
+
evidence: diagnostic2.evidence,
|
|
4116
|
+
...diagnostic2.entityIds ? { entityIds: diagnostic2.entityIds } : {},
|
|
4117
|
+
...diagnostic2.relationIds ? { relationIds: diagnostic2.relationIds } : {},
|
|
4118
|
+
...diagnostic2.remediation ? { remediation: diagnostic2.remediation } : {},
|
|
4119
|
+
sourceDiagnosticCode: diagnostic2.code
|
|
4120
|
+
});
|
|
4121
|
+
var criticalFinding = (finding, severity, target) => ({
|
|
4122
|
+
...finding,
|
|
4123
|
+
id: `${finding.id}:critical:${target}`,
|
|
4124
|
+
ruleId: "critical-path-risk",
|
|
4125
|
+
code: "critical-path-risk",
|
|
4126
|
+
severity,
|
|
4127
|
+
message: `Critical path or entity is affected: ${target}. ${finding.message}`
|
|
4128
|
+
});
|
|
4129
|
+
var evaluateRules = (report, options = {}) => {
|
|
4130
|
+
const resolved = resolvedOptions(options);
|
|
4131
|
+
const findings = [];
|
|
4132
|
+
for (const diagnostic2 of [...report.diagnostics].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
4133
|
+
const ruleId = diagnosticRules[diagnostic2.code];
|
|
4134
|
+
if (!ruleId || resolved.ignore.has(ruleId)) continue;
|
|
4135
|
+
const severity = severityFor(ruleId, resolved.mode, resolved.severity);
|
|
4136
|
+
if (severity !== "off") findings.push(findingFromDiagnostic(diagnostic2, ruleId, severity));
|
|
4137
|
+
}
|
|
4138
|
+
const criticalSeverity = severityFor("critical-path-risk", resolved.mode, resolved.severity);
|
|
4139
|
+
const criticalEntitySet = new Set(resolved.criticalEntities);
|
|
4140
|
+
for (const finding of [...findings]) {
|
|
4141
|
+
const matchingEntity = (finding.entityIds ?? []).find((id) => criticalEntitySet.has(id));
|
|
4142
|
+
if (matchingEntity && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
4143
|
+
findings.push(criticalFinding(finding, criticalSeverity, matchingEntity));
|
|
4144
|
+
}
|
|
4145
|
+
for (const path of resolved.criticalPaths) {
|
|
4146
|
+
if (finding.evidence.some((item) => minimatch5(item.path, path, { dot: true })) && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
4147
|
+
findings.push(criticalFinding(finding, criticalSeverity, path));
|
|
4148
|
+
}
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
const centralityThreshold = resolved.warningThresholds["centrality-risk"] ?? 3;
|
|
4152
|
+
const centralitySeverity = severityFor("centrality-risk", resolved.mode, resolved.severity);
|
|
4153
|
+
if (!resolved.ignore.has("centrality-risk") && centralitySeverity !== "off") {
|
|
4154
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4155
|
+
for (const finding of findings.filter((item) => item.ruleId === "graph-undocumented-relation")) {
|
|
4156
|
+
for (const entityId2 of finding.entityIds ?? []) counts.set(entityId2, (counts.get(entityId2) ?? 0) + 1);
|
|
4157
|
+
}
|
|
4158
|
+
for (const [entityId2, count2] of [...counts.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
4159
|
+
if (count2 < centralityThreshold || !criticalEntitySet.has(entityId2)) continue;
|
|
4160
|
+
findings.push({
|
|
4161
|
+
id: `centrality-risk:${entityId2}`,
|
|
4162
|
+
ruleId: "centrality-risk",
|
|
4163
|
+
code: "centrality-risk",
|
|
4164
|
+
status: "unresolved",
|
|
4165
|
+
severity: centralitySeverity,
|
|
4166
|
+
message: `Critical entity has ${count2} undocumented relation finding(s); static centrality is a review signal, not a runtime availability claim.`,
|
|
4167
|
+
evidence: findings.filter((item) => item.ruleId === "graph-undocumented-relation" && item.entityIds?.includes(entityId2)).flatMap((item) => item.evidence),
|
|
4168
|
+
entityIds: [entityId2],
|
|
4169
|
+
remediation: "Review ownership, dependency boundaries, and runtime availability before declaring an SPOF."
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
const sortedFindings = [...findings].sort((a, b) => a.id.localeCompare(b.id));
|
|
4174
|
+
return { mode: resolved.mode, findings: sortedFindings, exitCode: sortedFindings.some((finding) => finding.severity === "error") ? 1 : 0 };
|
|
4175
|
+
};
|
|
4176
|
+
var parseRuleId = (value) => RuleIdSchema.parse(value);
|
|
4177
|
+
var parseRuleSeverity = (value) => RuleSeveritySchema.parse(value);
|
|
4178
|
+
|
|
3067
4179
|
// src/query/query.ts
|
|
3068
4180
|
var handoffForPackage = (index, id, config) => {
|
|
3069
4181
|
const fromIndex = index.handoffs?.[id];
|
|
@@ -3175,7 +4287,7 @@ var runQuery = (index, config, req) => {
|
|
|
3175
4287
|
};
|
|
3176
4288
|
|
|
3177
4289
|
// src/intelligence/adapter.ts
|
|
3178
|
-
import { join as
|
|
4290
|
+
import { join as join10 } from "path";
|
|
3179
4291
|
|
|
3180
4292
|
// src/intelligence/peers.ts
|
|
3181
4293
|
var PeerMissingError = class extends Error {
|
|
@@ -3302,20 +4414,20 @@ var resolveIntelligenceRuntime = async (config) => {
|
|
|
3302
4414
|
}
|
|
3303
4415
|
return { adapter, embed, provider, ...model ? { model } : {} };
|
|
3304
4416
|
};
|
|
3305
|
-
var defaultVectorStorePath = (root) =>
|
|
4417
|
+
var defaultVectorStorePath = (root) => join10(root, ".doc-bridge", "vectors");
|
|
3306
4418
|
|
|
3307
4419
|
// src/intelligence/rag.ts
|
|
3308
|
-
import { readFileSync as
|
|
3309
|
-
import { join as
|
|
4420
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
4421
|
+
import { join as join11 } from "path";
|
|
3310
4422
|
var loadDocuments = (root, index, sources) => {
|
|
3311
4423
|
const includeAgent = sources.includes("agent") || sources.length === 0;
|
|
3312
4424
|
const docs = [];
|
|
3313
4425
|
if (includeAgent) {
|
|
3314
4426
|
for (const entry of index.knowledge) {
|
|
3315
|
-
const abs =
|
|
4427
|
+
const abs = join11(root, entry.path);
|
|
3316
4428
|
let content = "";
|
|
3317
4429
|
try {
|
|
3318
|
-
content =
|
|
4430
|
+
content = readFileSync13(abs, "utf8");
|
|
3319
4431
|
} catch {
|
|
3320
4432
|
content = [entry.title, entry.description].filter(Boolean).join("\n\n");
|
|
3321
4433
|
}
|
|
@@ -3333,7 +4445,7 @@ var createDocBridgeRag = async (root, config, index) => {
|
|
|
3333
4445
|
const { embed } = await resolveIntelligenceRuntime(config);
|
|
3334
4446
|
const ragMod = await importPeer("@agentskit/rag");
|
|
3335
4447
|
const memoryMod = await importPeer("@agentskit/memory");
|
|
3336
|
-
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ?
|
|
4448
|
+
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join11(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
|
|
3337
4449
|
const store = memoryMod.fileVectorMemory({ path: storePath });
|
|
3338
4450
|
const rag = ragMod.createRAG({
|
|
3339
4451
|
embed,
|
|
@@ -3468,15 +4580,15 @@ var startInkChat = async (root, config, index) => {
|
|
|
3468
4580
|
};
|
|
3469
4581
|
|
|
3470
4582
|
// src/memory/ingest.ts
|
|
3471
|
-
import { existsSync as
|
|
3472
|
-
import { join as
|
|
4583
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
4584
|
+
import { join as join12 } from "path";
|
|
3473
4585
|
var memoryFact = (raw, id) => firstParagraph(raw) ?? firstHeading(raw) ?? id;
|
|
3474
|
-
var
|
|
4586
|
+
var relativePath2 = (root, abs) => toPosix(abs).replace(`${toPosix(root)}/`, "");
|
|
3475
4587
|
var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
3476
|
-
if (!
|
|
4588
|
+
if (!existsSync13(dir)) return [];
|
|
3477
4589
|
return walkFiles(dir, { extensions: [".md", ".mdc"] }).map((abs) => {
|
|
3478
|
-
const rel =
|
|
3479
|
-
const raw =
|
|
4590
|
+
const rel = relativePath2(root, abs);
|
|
4591
|
+
const raw = readFileSync14(abs, "utf8");
|
|
3480
4592
|
const id = slugFromPath(rel);
|
|
3481
4593
|
return {
|
|
3482
4594
|
schemaVersion: 1,
|
|
@@ -3491,10 +4603,10 @@ var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
|
3491
4603
|
});
|
|
3492
4604
|
};
|
|
3493
4605
|
var ingestCursorRules = (root) => {
|
|
3494
|
-
const dir =
|
|
4606
|
+
const dir = join12(root, ".cursor", "rules");
|
|
3495
4607
|
return ingestMarkdownDir(root, dir, "cursor", 0.6);
|
|
3496
4608
|
};
|
|
3497
|
-
var ingestAgentMemory = (root) => ingestMarkdownDir(root,
|
|
4609
|
+
var ingestAgentMemory = (root) => ingestMarkdownDir(root, join12(root, ".agent-memory"), "agent-memory", 0.7);
|
|
3498
4610
|
var ingestMemoryCandidates = (root) => [
|
|
3499
4611
|
...ingestAgentMemory(root),
|
|
3500
4612
|
...ingestCursorRules(root)
|
|
@@ -3580,9 +4692,9 @@ var draftMemoryPromotion = (classifications) => {
|
|
|
3580
4692
|
};
|
|
3581
4693
|
|
|
3582
4694
|
// src/memory/github-pr.ts
|
|
3583
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
3584
|
-
import { existsSync as
|
|
3585
|
-
import { join as
|
|
4695
|
+
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
4696
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
4697
|
+
import { join as join13 } from "path";
|
|
3586
4698
|
var run = (cmd, args, cwd) => {
|
|
3587
4699
|
const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
|
|
3588
4700
|
const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
@@ -3590,10 +4702,10 @@ var run = (cmd, args, cwd) => {
|
|
|
3590
4702
|
};
|
|
3591
4703
|
var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
|
|
3592
4704
|
var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
3593
|
-
var defaultPromotionDraftPath = (root) =>
|
|
4705
|
+
var defaultPromotionDraftPath = (root) => join13(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
|
|
3594
4706
|
var writePromotionDraft = (root, draft, path) => {
|
|
3595
4707
|
const draftPath = path ?? defaultPromotionDraftPath(root);
|
|
3596
|
-
mkdirSync2(
|
|
4708
|
+
mkdirSync2(join13(root, ".doc-bridge", "drafts"), { recursive: true });
|
|
3597
4709
|
writeFileSync2(draftPath, `${draft.body}
|
|
3598
4710
|
`, "utf8");
|
|
3599
4711
|
return draftPath;
|
|
@@ -3629,7 +4741,7 @@ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
|
|
|
3629
4741
|
message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
|
|
3630
4742
|
};
|
|
3631
4743
|
}
|
|
3632
|
-
if (!
|
|
4744
|
+
if (!existsSync14(join13(root, ".git"))) {
|
|
3633
4745
|
return {
|
|
3634
4746
|
ok: false,
|
|
3635
4747
|
dryRun: false,
|
|
@@ -3707,7 +4819,7 @@ ${auth.out}`
|
|
|
3707
4819
|
];
|
|
3708
4820
|
let prUrl = "";
|
|
3709
4821
|
try {
|
|
3710
|
-
prUrl =
|
|
4822
|
+
prUrl = execFileSync2("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
|
|
3711
4823
|
} catch (error) {
|
|
3712
4824
|
const message = error instanceof Error ? error.message : String(error);
|
|
3713
4825
|
return {
|
|
@@ -3732,23 +4844,23 @@ ${auth.out}`
|
|
|
3732
4844
|
};
|
|
3733
4845
|
|
|
3734
4846
|
// src/index-builder/watch-index.ts
|
|
3735
|
-
import { existsSync as
|
|
3736
|
-
import { dirname as
|
|
4847
|
+
import { existsSync as existsSync15, watch } from "fs";
|
|
4848
|
+
import { dirname as dirname6, resolve as resolve11 } from "path";
|
|
3737
4849
|
var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
|
|
3738
4850
|
var NX_MANIFEST_PATTERN = /(^|[/\\])(project|package)\.json$/i;
|
|
3739
4851
|
var collectWatchRoots = (root, config, configPath) => {
|
|
3740
4852
|
const roots = /* @__PURE__ */ new Set();
|
|
3741
|
-
roots.add(
|
|
4853
|
+
roots.add(resolve11(root, config.corpus.agent.root));
|
|
3742
4854
|
const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
|
|
3743
4855
|
for (const source of humanSources) {
|
|
3744
4856
|
const humanOpts = source.options ?? {};
|
|
3745
4857
|
for (const key of ["contentDir", "docsDir", "root", "srcDir"]) {
|
|
3746
4858
|
const value = humanOpts[key];
|
|
3747
|
-
if (typeof value === "string" && value.length) roots.add(
|
|
4859
|
+
if (typeof value === "string" && value.length) roots.add(resolve11(root, value));
|
|
3748
4860
|
}
|
|
3749
4861
|
}
|
|
3750
|
-
if (configPath) roots.add(
|
|
3751
|
-
return [...roots].filter((dir) =>
|
|
4862
|
+
if (configPath) roots.add(dirname6(resolve11(configPath)));
|
|
4863
|
+
return [...roots].filter((dir) => existsSync15(dir));
|
|
3752
4864
|
};
|
|
3753
4865
|
var watchDocBridgeIndex = (opts) => {
|
|
3754
4866
|
const debounceMs = opts.debounceMs ?? 350;
|
|
@@ -3789,15 +4901,15 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
3789
4901
|
rebuild();
|
|
3790
4902
|
});
|
|
3791
4903
|
}
|
|
3792
|
-
const nxRoot =
|
|
3793
|
-
if (opts.config.routing?.plugin === "nx" &&
|
|
4904
|
+
const nxRoot = resolve11(opts.root);
|
|
4905
|
+
if (opts.config.routing?.plugin === "nx" && existsSync15(nxRoot)) {
|
|
3794
4906
|
watch(nxRoot, { recursive: true }, (_event, filename) => {
|
|
3795
4907
|
if (!filename || !NX_MANIFEST_PATTERN.test(filename)) return;
|
|
3796
4908
|
rebuild();
|
|
3797
4909
|
});
|
|
3798
4910
|
}
|
|
3799
|
-
const configDir =
|
|
3800
|
-
if (
|
|
4911
|
+
const configDir = resolve11(opts.root);
|
|
4912
|
+
if (existsSync15(configDir)) {
|
|
3801
4913
|
watch(configDir, (_event, filename) => {
|
|
3802
4914
|
if (!filename || !/doc-bridge\.config/.test(filename)) return;
|
|
3803
4915
|
rebuild();
|
|
@@ -3814,6 +4926,179 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
3814
4926
|
});
|
|
3815
4927
|
};
|
|
3816
4928
|
|
|
4929
|
+
// src/workflow/engine.ts
|
|
4930
|
+
import { appendFileSync, existsSync as existsSync16, mkdirSync as mkdirSync3, readFileSync as readFileSync15, renameSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
4931
|
+
import { join as join15, relative as relative7, resolve as resolve12 } from "path";
|
|
4932
|
+
var WORKFLOW_STAGES = ["collect", "normalize", "reconcile", "evaluate", "report"];
|
|
4933
|
+
var stageState = {
|
|
4934
|
+
collect: "discovering",
|
|
4935
|
+
normalize: "analyzed",
|
|
4936
|
+
reconcile: "compared",
|
|
4937
|
+
evaluate: "proposed",
|
|
4938
|
+
report: "delivered"
|
|
4939
|
+
};
|
|
4940
|
+
var defaultStateDir = (root) => join15(root, ".doc-bridge", "workflow");
|
|
4941
|
+
var atomicWrite = (path, value) => {
|
|
4942
|
+
const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
4943
|
+
writeFileSync3(temp, `${JSON.stringify(value, null, 2)}
|
|
4944
|
+
`, "utf8");
|
|
4945
|
+
renameSync(temp, path);
|
|
4946
|
+
};
|
|
4947
|
+
var writeManifest = (stateDir, run2) => {
|
|
4948
|
+
atomicWrite(join15(stateDir, "manifest.json"), run2);
|
|
4949
|
+
};
|
|
4950
|
+
var appendTransition = (stateDir, transition2) => {
|
|
4951
|
+
appendFileSync(join15(stateDir, "transitions.jsonl"), `${JSON.stringify(transition2)}
|
|
4952
|
+
`, "utf8");
|
|
4953
|
+
};
|
|
4954
|
+
var transition = (run2, to, reason) => {
|
|
4955
|
+
const item = {
|
|
4956
|
+
from: run2.state,
|
|
4957
|
+
to,
|
|
4958
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4959
|
+
...reason ? { reason } : {}
|
|
4960
|
+
};
|
|
4961
|
+
return { ...run2, state: to, transitions: [...run2.transitions, item] };
|
|
4962
|
+
};
|
|
4963
|
+
var runId = () => `${Date.now()}-${process.pid}`;
|
|
4964
|
+
var stageInputHash = (options, stage, input) => sha256NormalizedV1({ stage, input, sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
4965
|
+
var stageArtifactPath = (stateDir, stage, inputHash) => join15(stateDir, "artifacts", `${stage}-${inputHash}.json`);
|
|
4966
|
+
var readArtifact = (path) => JSON.parse(readFileSync15(path, "utf8"));
|
|
4967
|
+
var stepArtifactPath = (stateDir, step) => resolve12(stateDir, step.artifactRefs?.[0] ?? "");
|
|
4968
|
+
var stepOutput = (stateDir, run2, stage) => {
|
|
4969
|
+
const step = run2.steps.find((item) => item.name === stage);
|
|
4970
|
+
if (!step || step.status !== "completed" || !step.artifactRefs?.[0]) return null;
|
|
4971
|
+
return readArtifact(stepArtifactPath(stateDir, step)).value;
|
|
4972
|
+
};
|
|
4973
|
+
var acquireLock = (stateDir) => {
|
|
4974
|
+
const lock = join15(stateDir, ".lock");
|
|
4975
|
+
try {
|
|
4976
|
+
mkdirSync3(lock);
|
|
4977
|
+
} catch {
|
|
4978
|
+
const ownerPath = join15(lock, "owner.json");
|
|
4979
|
+
try {
|
|
4980
|
+
const owner = JSON.parse(readFileSync15(ownerPath, "utf8"));
|
|
4981
|
+
if (typeof owner.pid === "number") process.kill(owner.pid, 0);
|
|
4982
|
+
throw new Error(`Workflow is already running (pid ${owner.pid ?? "unknown"}).`);
|
|
4983
|
+
} catch (error) {
|
|
4984
|
+
if (error instanceof Error && error.message.startsWith("Workflow is already running")) throw error;
|
|
4985
|
+
rmSync(lock, { recursive: true, force: true });
|
|
4986
|
+
mkdirSync3(lock);
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
writeFileSync3(join15(lock, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
|
|
4990
|
+
return () => rmSync(lock, { recursive: true, force: true });
|
|
4991
|
+
};
|
|
4992
|
+
var loadManifest = (stateDir) => {
|
|
4993
|
+
const path = join15(stateDir, "manifest.json");
|
|
4994
|
+
if (!existsSync16(path)) return void 0;
|
|
4995
|
+
return WorkflowRunV1Schema.parse(JSON.parse(readFileSync15(path, "utf8")));
|
|
4996
|
+
};
|
|
4997
|
+
var baseRun = (options, stateDir, supersedes) => {
|
|
4998
|
+
const inputHash = sha256NormalizedV1({ sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
4999
|
+
return WorkflowRunV1Schema.parse({
|
|
5000
|
+
type: "workflow-run",
|
|
5001
|
+
schemaVersion: 1,
|
|
5002
|
+
contentHash: "0".repeat(64),
|
|
5003
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
5004
|
+
project: { name: resolve12(options.root).split("/").pop() ?? "project", root: "." },
|
|
5005
|
+
sourceRevision: options.sourceRevision,
|
|
5006
|
+
sourceRevisionKind: "content",
|
|
5007
|
+
configurationHash: options.configurationHash,
|
|
5008
|
+
pipelineVersion: "1.0.0",
|
|
5009
|
+
analyzerVersions: { workflow: options.toolVersion ?? "1.0.0" },
|
|
5010
|
+
runId: options.runId ?? runId(),
|
|
5011
|
+
state: "created",
|
|
5012
|
+
steps: WORKFLOW_STAGES.map((name) => ({ name, status: "pending", inputHash })),
|
|
5013
|
+
transitions: [{ from: null, to: "created", at: (/* @__PURE__ */ new Date()).toISOString() }],
|
|
5014
|
+
artifactRefs: [relative7(resolve12(options.root), stateDir), ...supersedes ? [`supersedes:${supersedes}`] : []]
|
|
5015
|
+
});
|
|
5016
|
+
};
|
|
5017
|
+
var withHash = (run2) => WorkflowRunV1Schema.parse({ ...run2, contentHash: contentHashForArtifactV1(run2) });
|
|
5018
|
+
var sameInputs = (run2, options) => run2.sourceRevision === options.sourceRevision && run2.configurationHash === options.configurationHash && run2.analyzerVersions.workflow === (options.toolVersion ?? "1.0.0");
|
|
5019
|
+
var selectedStages = (stage) => stage && stage !== "all" ? [stage] : WORKFLOW_STAGES;
|
|
5020
|
+
var runWorkflow = (options) => {
|
|
5021
|
+
const root = resolve12(options.root);
|
|
5022
|
+
const stateDir = resolve12(root, options.stateDir ?? defaultStateDir(root));
|
|
5023
|
+
mkdirSync3(join15(stateDir, "artifacts"), { recursive: true });
|
|
5024
|
+
const release = acquireLock(stateDir);
|
|
5025
|
+
try {
|
|
5026
|
+
let run2 = loadManifest(stateDir);
|
|
5027
|
+
let supersedes;
|
|
5028
|
+
if (run2 && !sameInputs(run2, options)) {
|
|
5029
|
+
supersedes = run2.runId;
|
|
5030
|
+
run2 = withHash(transition(run2, "stale", "Source revision, configuration hash, or tool version changed."));
|
|
5031
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
5032
|
+
writeManifest(stateDir, run2);
|
|
5033
|
+
run2 = void 0;
|
|
5034
|
+
}
|
|
5035
|
+
if (!run2) {
|
|
5036
|
+
run2 = withHash(baseRun(options, stateDir, supersedes));
|
|
5037
|
+
appendTransition(stateDir, run2.transitions[0]);
|
|
5038
|
+
writeManifest(stateDir, run2);
|
|
5039
|
+
}
|
|
5040
|
+
if (!run2) throw new Error("Workflow manifest was not initialized.");
|
|
5041
|
+
const firstSelectedStage = selectedStages(options.stage)[0];
|
|
5042
|
+
const previousStageIndex = firstSelectedStage ? WORKFLOW_STAGES.indexOf(firstSelectedStage) - 1 : -1;
|
|
5043
|
+
let previousOutput = previousStageIndex >= 0 ? stepOutput(stateDir, run2, WORKFLOW_STAGES[previousStageIndex]) : null;
|
|
5044
|
+
const reusedStages = [];
|
|
5045
|
+
for (const stage of selectedStages(options.stage)) {
|
|
5046
|
+
const input = options.inputs?.[stage] ?? previousOutput;
|
|
5047
|
+
const inputHash = stageInputHash(options, stage, input);
|
|
5048
|
+
const existing = run2.steps.find((step) => step.name === stage);
|
|
5049
|
+
const artifactPath = existing?.artifactRefs?.[0] ? resolve12(stateDir, existing.artifactRefs[0]) : stageArtifactPath(stateDir, stage, inputHash);
|
|
5050
|
+
if (existing?.status === "completed" && existing.inputHash === inputHash && existing.outputHash && existsSync16(artifactPath)) {
|
|
5051
|
+
previousOutput = readArtifact(artifactPath).value;
|
|
5052
|
+
reusedStages.push(stage);
|
|
5053
|
+
continue;
|
|
5054
|
+
}
|
|
5055
|
+
const handler = options.handlers[stage];
|
|
5056
|
+
if (!handler) throw new Error(`No handler configured for workflow stage "${stage}".`);
|
|
5057
|
+
run2 = withHash(transition(run2, stageState[stage]));
|
|
5058
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
5059
|
+
const runningStep = { name: stage, status: "running", inputHash };
|
|
5060
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? runningStep : step) });
|
|
5061
|
+
writeManifest(stateDir, run2);
|
|
5062
|
+
try {
|
|
5063
|
+
const value = handler({ root, stage, input, previousOutput });
|
|
5064
|
+
const outputHash = sha256NormalizedV1(value);
|
|
5065
|
+
const artifact2 = { type: "workflow-step-artifact", stage, inputHash, outputHash, value };
|
|
5066
|
+
mkdirSync3(join15(stateDir, "artifacts"), { recursive: true });
|
|
5067
|
+
if (existsSync16(artifactPath)) {
|
|
5068
|
+
const existingArtifact = readArtifact(artifactPath);
|
|
5069
|
+
if (existingArtifact.outputHash !== outputHash) throw new Error(`Immutable workflow artifact collision for stage "${stage}".`);
|
|
5070
|
+
} else {
|
|
5071
|
+
atomicWrite(artifactPath, artifact2);
|
|
5072
|
+
}
|
|
5073
|
+
const ref = relative7(stateDir, artifactPath);
|
|
5074
|
+
const completedStep = { name: stage, status: "completed", inputHash, outputHash, artifactRefs: [ref] };
|
|
5075
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? completedStep : step) });
|
|
5076
|
+
writeManifest(stateDir, run2);
|
|
5077
|
+
previousOutput = value;
|
|
5078
|
+
} catch (error) {
|
|
5079
|
+
run2 = withHash(transition(run2, "failed", error instanceof Error ? error.message : String(error)));
|
|
5080
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
5081
|
+
writeManifest(stateDir, run2);
|
|
5082
|
+
throw error;
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
if (selectedStages(options.stage).every((stage) => run2.steps.find((step) => step.name === stage)?.status === "completed")) {
|
|
5086
|
+
const complete = selectedStages(options.stage).includes("report") && run2.state !== "delivered" ? withHash(transition(run2, "delivered")) : run2;
|
|
5087
|
+
if (complete !== run2) {
|
|
5088
|
+
appendTransition(stateDir, complete.transitions[complete.transitions.length - 1]);
|
|
5089
|
+
run2 = complete;
|
|
5090
|
+
}
|
|
5091
|
+
writeManifest(stateDir, run2);
|
|
5092
|
+
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] });
|
|
5093
|
+
}
|
|
5094
|
+
return { run: run2, stateDir, reusedStages };
|
|
5095
|
+
} finally {
|
|
5096
|
+
release();
|
|
5097
|
+
}
|
|
5098
|
+
};
|
|
5099
|
+
var loadWorkflowManifest = (stateDir) => WorkflowRunV1Schema.parse(JSON.parse(readFileSync15(join15(resolve12(stateDir), "manifest.json"), "utf8")));
|
|
5100
|
+
var loadWorkflowStepOutput = (stateDir, stage) => stepOutput(resolve12(stateDir), loadWorkflowManifest(stateDir), stage);
|
|
5101
|
+
|
|
3817
5102
|
// src/doctor/badge.ts
|
|
3818
5103
|
var doctorBadgeMetrics = (report) => {
|
|
3819
5104
|
const { packages } = report.coverage;
|
|
@@ -3973,43 +5258,43 @@ var docBridgePatternPayload = () => ({
|
|
|
3973
5258
|
});
|
|
3974
5259
|
|
|
3975
5260
|
// src/cli/demo.ts
|
|
3976
|
-
import { cpSync, existsSync as
|
|
5261
|
+
import { cpSync, existsSync as existsSync18, mkdtempSync, readFileSync as readFileSync17, rmSync as rmSync2 } from "fs";
|
|
3977
5262
|
import { tmpdir } from "os";
|
|
3978
|
-
import { dirname as
|
|
5263
|
+
import { dirname as dirname8, join as join17, resolve as resolve14 } from "path";
|
|
3979
5264
|
import { fileURLToPath } from "url";
|
|
3980
5265
|
|
|
3981
5266
|
// src/mcp/install.ts
|
|
3982
|
-
import { existsSync as
|
|
5267
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "fs";
|
|
3983
5268
|
import { homedir } from "os";
|
|
3984
|
-
import { dirname as
|
|
5269
|
+
import { dirname as dirname7, join as join16, resolve as resolve13 } from "path";
|
|
3985
5270
|
var SERVER_NAME = "ak-docs";
|
|
3986
5271
|
var mcpServerEntry = (root) => ({
|
|
3987
5272
|
command: "npx",
|
|
3988
5273
|
args: ["ak-docs", "mcp"],
|
|
3989
5274
|
cwd: root
|
|
3990
5275
|
});
|
|
3991
|
-
var
|
|
3992
|
-
if (!
|
|
5276
|
+
var readJson2 = (path) => {
|
|
5277
|
+
if (!existsSync17(path)) return {};
|
|
3993
5278
|
try {
|
|
3994
|
-
const parsed = JSON.parse(
|
|
5279
|
+
const parsed = JSON.parse(readFileSync16(path, "utf8"));
|
|
3995
5280
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
3996
5281
|
} catch {
|
|
3997
5282
|
return {};
|
|
3998
5283
|
}
|
|
3999
5284
|
};
|
|
4000
5285
|
var writeJson = (path, value) => {
|
|
4001
|
-
|
|
4002
|
-
|
|
5286
|
+
mkdirSync4(dirname7(path), { recursive: true });
|
|
5287
|
+
writeFileSync4(path, `${JSON.stringify(value, null, 2)}
|
|
4003
5288
|
`, "utf8");
|
|
4004
5289
|
};
|
|
4005
5290
|
var resolveTargetPath = (target, root) => {
|
|
4006
|
-
if (target === "cursor") return
|
|
4007
|
-
return
|
|
5291
|
+
if (target === "cursor") return resolve13(root, ".cursor", "mcp.json");
|
|
5292
|
+
return join16(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
4008
5293
|
};
|
|
4009
5294
|
var installMcpConfig = (root, target) => {
|
|
4010
5295
|
const configPath = resolveTargetPath(target, root);
|
|
4011
|
-
const created = !
|
|
4012
|
-
const existing =
|
|
5296
|
+
const created = !existsSync17(configPath);
|
|
5297
|
+
const existing = readJson2(configPath);
|
|
4013
5298
|
const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
|
|
4014
5299
|
servers[SERVER_NAME] = mcpServerEntry(root);
|
|
4015
5300
|
writeJson(configPath, { ...existing, mcpServers: servers });
|
|
@@ -4033,12 +5318,12 @@ var installMcpConfig = (root, target) => {
|
|
|
4033
5318
|
var mcpSnippet = (root) => JSON.stringify({ mcpServers: { [SERVER_NAME]: mcpServerEntry(root) } }, null, 2);
|
|
4034
5319
|
|
|
4035
5320
|
// src/cli/demo.ts
|
|
4036
|
-
var packageRoot =
|
|
5321
|
+
var packageRoot = resolve14(dirname8(fileURLToPath(import.meta.url)), "..", "..");
|
|
4037
5322
|
var fixturePath = (fixture) => {
|
|
4038
5323
|
if (fixture === "monorepo") {
|
|
4039
|
-
return
|
|
5324
|
+
return join17(packageRoot, "examples", "demo-monorepo");
|
|
4040
5325
|
}
|
|
4041
|
-
return
|
|
5326
|
+
return join17(packageRoot, "examples", "demo-example");
|
|
4042
5327
|
};
|
|
4043
5328
|
var formatHandoffText = (handoff) => {
|
|
4044
5329
|
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)";
|
|
@@ -4054,11 +5339,11 @@ var formatHandoffText = (handoff) => {
|
|
|
4054
5339
|
var runDemo = (root, config, fixture = "example", options = {}) => {
|
|
4055
5340
|
const targetPackage = fixture === "monorepo" ? "auth" : "example";
|
|
4056
5341
|
const fixtureDir = fixturePath(fixture);
|
|
4057
|
-
if (options.copyFixture &&
|
|
5342
|
+
if (options.copyFixture && existsSync18(fixtureDir)) {
|
|
4058
5343
|
for (const rel of ["doc-bridge.config.json", "docs", "packages", "pnpm-workspace.yaml", "package.json"]) {
|
|
4059
|
-
const src =
|
|
4060
|
-
if (!
|
|
4061
|
-
const dest =
|
|
5344
|
+
const src = join17(fixtureDir, rel);
|
|
5345
|
+
if (!existsSync18(src)) continue;
|
|
5346
|
+
const dest = join17(root, rel);
|
|
4062
5347
|
cpSync(src, dest, { recursive: true });
|
|
4063
5348
|
}
|
|
4064
5349
|
}
|
|
@@ -4113,17 +5398,17 @@ var formatDemoText = (result) => {
|
|
|
4113
5398
|
return lines;
|
|
4114
5399
|
};
|
|
4115
5400
|
var withDemoWorkspace = (fixture, fn) => {
|
|
4116
|
-
const dir = mkdtempSync(
|
|
5401
|
+
const dir = mkdtempSync(join17(tmpdir(), "ak-docs-demo-"));
|
|
4117
5402
|
try {
|
|
4118
5403
|
const fixtureDir = fixturePath(fixture);
|
|
4119
|
-
if (!
|
|
5404
|
+
if (!existsSync18(fixtureDir)) {
|
|
4120
5405
|
throw new Error(`Demo fixture "${fixture}" not found at ${fixtureDir}`);
|
|
4121
5406
|
}
|
|
4122
5407
|
cpSync(fixtureDir, dir, { recursive: true });
|
|
4123
|
-
const config = JSON.parse(
|
|
5408
|
+
const config = JSON.parse(readFileSync17(join17(dir, "doc-bridge.config.json"), "utf8"));
|
|
4124
5409
|
return fn(dir, config);
|
|
4125
5410
|
} finally {
|
|
4126
|
-
|
|
5411
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
4127
5412
|
}
|
|
4128
5413
|
};
|
|
4129
5414
|
|
|
@@ -4331,9 +5616,220 @@ var formatDoctorText = (report) => {
|
|
|
4331
5616
|
};
|
|
4332
5617
|
|
|
4333
5618
|
// src/mcp/server.ts
|
|
4334
|
-
import { readFileSync as
|
|
4335
|
-
import { relative as
|
|
4336
|
-
import { z as
|
|
5619
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync20, realpathSync as realpathSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
5620
|
+
import { join as join20, relative as relative9, resolve as resolve17 } from "path";
|
|
5621
|
+
import { z as z8, ZodError } from "zod";
|
|
5622
|
+
|
|
5623
|
+
// src/fixes/proposals.ts
|
|
5624
|
+
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";
|
|
5625
|
+
import { basename as basename5, dirname as dirname9, extname as extname2, join as join18, relative as relative8, resolve as resolve15, sep as sep8 } from "path";
|
|
5626
|
+
var hash2 = (value) => sha256NormalizedV1(value);
|
|
5627
|
+
var artifactMetadata = (root, options) => ({
|
|
5628
|
+
schemaVersion: 1,
|
|
5629
|
+
contentHash: "0".repeat(64),
|
|
5630
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
5631
|
+
project: { name: options.projectName ?? basename5(resolve15(root)), root: "." },
|
|
5632
|
+
sourceRevision: options.baseRevision,
|
|
5633
|
+
sourceRevisionKind: options.baseRevision.length === 40 ? "git" : "content",
|
|
5634
|
+
configurationHash: options.configurationHash,
|
|
5635
|
+
pipelineVersion: "1.0.0",
|
|
5636
|
+
analyzerVersions: { fixes: options.toolVersion ?? "1.0.0" }
|
|
5637
|
+
});
|
|
5638
|
+
var unifiedDiff = (changes) => changes.map((change) => {
|
|
5639
|
+
const before = change.before.split("\n").map((line) => `-${line}`).join("\n");
|
|
5640
|
+
const after = change.after.split("\n").map((line) => `+${line}`).join("\n");
|
|
5641
|
+
return `--- a/${change.path}
|
|
5642
|
+
+++ b/${change.path}
|
|
5643
|
+
@@
|
|
5644
|
+
${before}
|
|
5645
|
+
${after}`;
|
|
5646
|
+
}).join("\n");
|
|
5647
|
+
var makeProposal = (root, options, changes, preconditions, postconditions) => {
|
|
5648
|
+
const draft = {
|
|
5649
|
+
...artifactMetadata(root, options),
|
|
5650
|
+
type: "fix-proposal",
|
|
5651
|
+
proposalId: `fix-${hash2(changes).slice(0, 20)}`,
|
|
5652
|
+
baseRevision: options.baseRevision,
|
|
5653
|
+
affectedFiles: changes.map(({ path, before }) => ({ path, contentHash: sha256NormalizedV1(before) })),
|
|
5654
|
+
changes,
|
|
5655
|
+
preconditions,
|
|
5656
|
+
diff: unifiedDiff(changes),
|
|
5657
|
+
postconditions,
|
|
5658
|
+
status: "proposed"
|
|
5659
|
+
};
|
|
5660
|
+
return FixProposalV1Schema.parse({ ...draft, contentHash: contentHashForArtifactV1(draft) });
|
|
5661
|
+
};
|
|
5662
|
+
var walkMarkdown = (root, directory = root) => readdirSync4(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
5663
|
+
if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") return [];
|
|
5664
|
+
const path = join18(directory, entry.name);
|
|
5665
|
+
if (entry.isDirectory()) return walkMarkdown(root, path);
|
|
5666
|
+
return entry.isFile() && [".md", ".mdx"].includes(extname2(entry.name).toLowerCase()) ? [relative8(root, path).split(sep8).join("/")] : [];
|
|
5667
|
+
});
|
|
5668
|
+
var localLink = /(!?)\[([^\]]*)\]\(([^)\s]+)(?:\s+["'][^)]*["'])?\)/g;
|
|
5669
|
+
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;
|
|
5670
|
+
var createMarkdownLinkFixProposal = (root, options) => {
|
|
5671
|
+
const projectRoot = realpathSync8.native(resolve15(root));
|
|
5672
|
+
const paths = walkMarkdown(projectRoot);
|
|
5673
|
+
const changes = [];
|
|
5674
|
+
for (const path of paths) {
|
|
5675
|
+
const content = readFileSync18(join18(projectRoot, path), "utf8");
|
|
5676
|
+
let next = content;
|
|
5677
|
+
for (const match of content.matchAll(localLink)) {
|
|
5678
|
+
const target = match[3];
|
|
5679
|
+
if (!target || match[1] === "!" || /^(?:[a-z]+:|\/|#)/i.test(target)) continue;
|
|
5680
|
+
const targetPath = target.split("#")[0]?.split("?")[0];
|
|
5681
|
+
if (!targetPath || existsSync19(resolve15(projectRoot, dirname9(path), targetPath))) continue;
|
|
5682
|
+
const targetStem = basename5(targetPath, extname2(targetPath)).toLowerCase();
|
|
5683
|
+
const labelStem = (match[2] ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
5684
|
+
const candidates = paths.filter((candidate) => basename5(candidate, extname2(candidate)).toLowerCase() === targetStem || basename5(candidate, extname2(candidate)).toLowerCase().replace(/[^a-z0-9]+/g, "") === labelStem);
|
|
5685
|
+
if (candidates.length !== 1) continue;
|
|
5686
|
+
let replacement = relative8(dirname9(path), candidates[0]).split(sep8).join("/");
|
|
5687
|
+
if (target.startsWith("./") && !replacement.startsWith(".")) replacement = `./${replacement}`;
|
|
5688
|
+
next = next.replace(match[0], match[0].replace(target, replacement));
|
|
5689
|
+
}
|
|
5690
|
+
if (next !== content) changes.push({ path, before: content, after: next });
|
|
5691
|
+
}
|
|
5692
|
+
return changes.length ? makeProposal(projectRoot, options, changes, ["Each replacement has exactly one Markdown target."], ["All corrected local Markdown links resolve."]) : void 0;
|
|
5693
|
+
};
|
|
5694
|
+
var createArtifactNormalizationProposal = (root, artifactPath, options) => {
|
|
5695
|
+
const projectRoot = realpathSync8.native(resolve15(root));
|
|
5696
|
+
const path = artifactPath.split(sep8).join("/");
|
|
5697
|
+
const absolute = containedPath(projectRoot, path);
|
|
5698
|
+
if (!absolute || !existsSync19(absolute) || !statSync5(absolute).isFile()) return void 0;
|
|
5699
|
+
const before = readFileSync18(absolute, "utf8");
|
|
5700
|
+
let after;
|
|
5701
|
+
try {
|
|
5702
|
+
after = `${JSON.stringify(sortJson(JSON.parse(before)), null, 2)}
|
|
5703
|
+
`;
|
|
5704
|
+
} catch {
|
|
5705
|
+
return void 0;
|
|
5706
|
+
}
|
|
5707
|
+
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."]);
|
|
5708
|
+
};
|
|
5709
|
+
var approveFixProposal = (proposalInput, approvedBy, approvedAt = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
5710
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
5711
|
+
if (proposal.status !== "proposed") throw new Error(`Only proposed fixes can be approved; received "${proposal.status}".`);
|
|
5712
|
+
const approved = { ...proposal, status: "approved", approval: { proposalHash: proposal.contentHash, approvedAt, approvedBy }, contentHash: "0".repeat(64) };
|
|
5713
|
+
return FixProposalV1Schema.parse({ ...approved, contentHash: contentHashForArtifactV1(approved) });
|
|
5714
|
+
};
|
|
5715
|
+
var bindingHash = (proposal) => {
|
|
5716
|
+
const { approval: _approval, contentHash: _contentHash, status: _status, ...rest } = proposal;
|
|
5717
|
+
return contentHashForArtifactV1({ ...rest, contentHash: "0".repeat(64), status: "proposed" });
|
|
5718
|
+
};
|
|
5719
|
+
var applyFixProposal = (root, proposalInput, options = {}) => {
|
|
5720
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
5721
|
+
if (proposal.status !== "approved" || !proposal.approval) throw new Error("Only an explicitly approved fix proposal can be applied.");
|
|
5722
|
+
if (proposal.approval.proposalHash !== bindingHash(proposal)) throw new Error("Approval is not bound to the exact proposal content.");
|
|
5723
|
+
if (options.currentRevision && options.currentRevision !== proposal.baseRevision) throw new Error("The repository revision changed since this proposal was created.");
|
|
5724
|
+
if (!proposal.changes?.length) throw new Error("This proposal has no executable changes.");
|
|
5725
|
+
const projectRoot = realpathSync8.native(resolve15(root));
|
|
5726
|
+
const originals = /* @__PURE__ */ new Map();
|
|
5727
|
+
const affected = new Map(proposal.affectedFiles.map((file) => [file.path, file]));
|
|
5728
|
+
if (proposal.changes.some((change) => !affected.has(change.path) || affected.get(change.path)?.contentHash !== sha256NormalizedV1(change.before)) || affected.size !== proposal.changes.length) {
|
|
5729
|
+
throw new Error("Proposal changes do not match its affected-file hashes.");
|
|
5730
|
+
}
|
|
5731
|
+
for (const file of proposal.affectedFiles) {
|
|
5732
|
+
const absolute = containedPath(projectRoot, file.path);
|
|
5733
|
+
if (!absolute || !existsSync19(absolute)) throw new Error(`Affected file is unavailable or escapes the repository root: ${file.path}`);
|
|
5734
|
+
const current = readFileSync18(absolute, "utf8");
|
|
5735
|
+
if (sha256NormalizedV1(current) !== file.contentHash) throw new Error(`Affected file changed since proposal creation: ${file.path}`);
|
|
5736
|
+
originals.set(absolute, current);
|
|
5737
|
+
}
|
|
5738
|
+
try {
|
|
5739
|
+
for (const change of proposal.changes) {
|
|
5740
|
+
const absolute = resolve15(projectRoot, change.path);
|
|
5741
|
+
writeFileSync5(`${absolute}.docbridge-${process.pid}.tmp`, change.after, "utf8");
|
|
5742
|
+
}
|
|
5743
|
+
for (const change of proposal.changes) {
|
|
5744
|
+
const absolute = resolve15(projectRoot, change.path);
|
|
5745
|
+
renameSync2(`${absolute}.docbridge-${process.pid}.tmp`, absolute);
|
|
5746
|
+
}
|
|
5747
|
+
for (const change of proposal.changes) {
|
|
5748
|
+
if (readFileSync18(resolve15(projectRoot, change.path), "utf8") !== change.after) throw new Error(`Postcondition failed for ${change.path}`);
|
|
5749
|
+
}
|
|
5750
|
+
options.verify?.(proposal.changes.map((change) => change.path));
|
|
5751
|
+
} catch (error) {
|
|
5752
|
+
for (const [absolute, content] of originals) writeFileSync5(absolute, content, "utf8");
|
|
5753
|
+
for (const change of proposal.changes) {
|
|
5754
|
+
const temp = `${resolve15(projectRoot, change.path)}.docbridge-${process.pid}.tmp`;
|
|
5755
|
+
if (existsSync19(temp)) unlinkSync(temp);
|
|
5756
|
+
}
|
|
5757
|
+
throw error;
|
|
5758
|
+
}
|
|
5759
|
+
const applied = { ...proposal, status: "applied", contentHash: "0".repeat(64) };
|
|
5760
|
+
return FixProposalV1Schema.parse({ ...applied, contentHash: contentHashForArtifactV1(applied) });
|
|
5761
|
+
};
|
|
5762
|
+
|
|
5763
|
+
// src/agents/registry-adapter.ts
|
|
5764
|
+
import { existsSync as existsSync20, readFileSync as readFileSync19, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6 } from "fs";
|
|
5765
|
+
import { join as join19, resolve as resolve16 } from "path";
|
|
5766
|
+
import { pathToFileURL } from "url";
|
|
5767
|
+
import { z as z7 } from "zod";
|
|
5768
|
+
var DEFAULT_REGISTRY_AGENT_ID = "ecosystem-doc-bridge-corpus-scanner";
|
|
5769
|
+
var RegistryAgentMetadataSchema = z7.object({
|
|
5770
|
+
id: z7.string().min(1).max(256),
|
|
5771
|
+
version: z7.string().min(1).max(64),
|
|
5772
|
+
provider: z7.string().min(1).max(128).optional(),
|
|
5773
|
+
model: z7.string().min(1).max(256).optional(),
|
|
5774
|
+
capabilities: z7.array(z7.string().min(1).max(128)).max(32).default([])
|
|
5775
|
+
}).strict();
|
|
5776
|
+
var deepFreeze = (value) => {
|
|
5777
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
5778
|
+
Object.freeze(value);
|
|
5779
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
5780
|
+
}
|
|
5781
|
+
return value;
|
|
5782
|
+
};
|
|
5783
|
+
var registryConfig = (config) => config.intelligence?.registry;
|
|
5784
|
+
var loadRegistryAgentRunner = async (root, config) => {
|
|
5785
|
+
const metadata = loadRegistryAgentMetadata(root, config);
|
|
5786
|
+
const configured = registryConfig(config)?.runnerModule;
|
|
5787
|
+
const modulePath = configured ? containedPath(root, configured) : containedPath(root, join19(metadata.root, "doc-bridge-adapter.js"));
|
|
5788
|
+
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.`);
|
|
5789
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
5790
|
+
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;
|
|
5791
|
+
if (!runner) throw new Error(`Registry agent runner at ${modulePath} must export a function or { run }. `);
|
|
5792
|
+
return runner;
|
|
5793
|
+
};
|
|
5794
|
+
var loadRegistryAgentMetadata = (root, config) => {
|
|
5795
|
+
const settings = registryConfig(config);
|
|
5796
|
+
const id = settings?.agentId ?? DEFAULT_REGISTRY_AGENT_ID;
|
|
5797
|
+
const agentRoot = settings?.agentRoot ?? "agents";
|
|
5798
|
+
const agentPath = containedPath(root, join19(agentRoot, id));
|
|
5799
|
+
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}`);
|
|
5800
|
+
const metadataPath = [join19(agentPath, "agent.json"), join19(agentPath, "manifest.json")].find(existsSync20);
|
|
5801
|
+
if (!metadataPath) throw new Error(`Registry agent "${id}" is installed but has no agent.json or manifest.json metadata.`);
|
|
5802
|
+
const metadata = RegistryAgentMetadataSchema.parse(JSON.parse(readFileSync19(metadataPath, "utf8")));
|
|
5803
|
+
if (metadata.id !== id) throw new Error(`Installed Registry agent metadata id "${metadata.id}" does not match configured id "${id}".`);
|
|
5804
|
+
return { ...metadata, root: agentPath };
|
|
5805
|
+
};
|
|
5806
|
+
var createRegistryAgentAdapter = (root, config, runner) => {
|
|
5807
|
+
if (!registryConfig(config)?.enabled) throw new Error("Registry agents are disabled. Set intelligence.registry.enabled: true to run an assisted workflow.");
|
|
5808
|
+
const metadata = loadRegistryAgentMetadata(resolve16(root), config);
|
|
5809
|
+
return {
|
|
5810
|
+
metadata,
|
|
5811
|
+
run: async (snapshot, report, evidence2 = report.diagnostics.flatMap((diagnostic2) => diagnostic2.evidence).slice(0, 64)) => {
|
|
5812
|
+
const context = deepFreeze({ snapshot: redactValue(snapshot), report: redactValue(report), evidence: redactValue(evidence2), capabilities: ["snapshot.read", "evidence.read", "proposal.write"], network: false, shell: false });
|
|
5813
|
+
const proposal = AgentProposalV1Schema.parse(await runner(context));
|
|
5814
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Registry agent proposal contentHash does not match its canonical contents.");
|
|
5815
|
+
if (proposal.baseSnapshotHash !== snapshot.contentHash || proposal.baseReportHash !== report.contentHash) throw new Error("Registry agent proposal is not based on the supplied snapshot/report hashes.");
|
|
5816
|
+
if (proposal.origin.kind !== "registry-agent" || proposal.origin.id !== metadata.id) throw new Error(`Registry agent proposal origin must be ${metadata.id}.`);
|
|
5817
|
+
return proposal;
|
|
5818
|
+
}
|
|
5819
|
+
};
|
|
5820
|
+
};
|
|
5821
|
+
var persistRegistryAgentProposal = (stateDir, proposal) => {
|
|
5822
|
+
AgentProposalV1Schema.parse(proposal);
|
|
5823
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Cannot persist a Registry agent proposal with an invalid contentHash.");
|
|
5824
|
+
const safeHash = contentHashForArtifactV1(proposal);
|
|
5825
|
+
mkdirSync6(join19(resolve16(stateDir), "agents"), { recursive: true });
|
|
5826
|
+
const path = join19(resolve16(stateDir), "agents", `${proposal.origin.id}-${safeHash}.json`);
|
|
5827
|
+
writeFileSync6(path, `${JSON.stringify(proposal, null, 2)}
|
|
5828
|
+
`, "utf8");
|
|
5829
|
+
return path;
|
|
5830
|
+
};
|
|
5831
|
+
|
|
5832
|
+
// src/mcp/server.ts
|
|
4337
5833
|
var MCP_TOOLS = [
|
|
4338
5834
|
{
|
|
4339
5835
|
name: "handoff.resolve",
|
|
@@ -4405,25 +5901,70 @@ var MCP_TOOLS = [
|
|
|
4405
5901
|
description: "Return the static Doc Bridge curator and delegate topology.",
|
|
4406
5902
|
annotations: { readOnlyHint: true },
|
|
4407
5903
|
inputSchema: { type: "object", properties: {} }
|
|
5904
|
+
},
|
|
5905
|
+
{
|
|
5906
|
+
name: "docbridge.snapshot",
|
|
5907
|
+
title: "Read the latest discovery snapshot",
|
|
5908
|
+
description: "Read the bounded canonical repository snapshot from the latest workflow run.",
|
|
5909
|
+
annotations: { readOnlyHint: true },
|
|
5910
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5911
|
+
},
|
|
5912
|
+
{
|
|
5913
|
+
name: "docbridge.report",
|
|
5914
|
+
title: "Read the latest reconciliation report",
|
|
5915
|
+
description: "Read the canonical reconciliation report from the latest workflow run.",
|
|
5916
|
+
annotations: { readOnlyHint: true },
|
|
5917
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5918
|
+
},
|
|
5919
|
+
{
|
|
5920
|
+
name: "docbridge.diagnostics",
|
|
5921
|
+
title: "Read reconciliation diagnostics",
|
|
5922
|
+
description: "Read bounded diagnostics from the latest canonical reconciliation report.",
|
|
5923
|
+
annotations: { readOnlyHint: true },
|
|
5924
|
+
inputSchema: { type: "object", properties: { status: { type: "string" }, severity: { type: "string" } } }
|
|
5925
|
+
},
|
|
5926
|
+
{
|
|
5927
|
+
name: "docbridge.relations",
|
|
5928
|
+
title: "Read architecture relations",
|
|
5929
|
+
description: "Read bounded observed and declared relations from the latest canonical snapshot.",
|
|
5930
|
+
annotations: { readOnlyHint: true },
|
|
5931
|
+
inputSchema: { type: "object", properties: { kind: { type: "string" }, limit: { type: "number" } } }
|
|
5932
|
+
},
|
|
5933
|
+
{
|
|
5934
|
+
name: "docbridge.run",
|
|
5935
|
+
title: "Read workflow state",
|
|
5936
|
+
description: "Read the latest resumable workflow state and artifact references.",
|
|
5937
|
+
annotations: { readOnlyHint: true },
|
|
5938
|
+
inputSchema: { type: "object", properties: {} }
|
|
5939
|
+
},
|
|
5940
|
+
{
|
|
5941
|
+
name: "docbridge.proposals",
|
|
5942
|
+
title: "Read or approve proposals",
|
|
5943
|
+
description: "Create, inspect, approve and apply deterministic proposals through the shared human-gated workflow.",
|
|
5944
|
+
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" } } }
|
|
4408
5945
|
}
|
|
4409
5946
|
];
|
|
4410
5947
|
var asRecord = (value) => value && typeof value === "object" ? value : {};
|
|
4411
|
-
var HandoffResolveArgsSchema =
|
|
4412
|
-
id:
|
|
4413
|
-
kind:
|
|
5948
|
+
var HandoffResolveArgsSchema = z8.object({
|
|
5949
|
+
id: z8.string().min(1),
|
|
5950
|
+
kind: z8.enum(["package", "ownership"]).optional()
|
|
4414
5951
|
});
|
|
4415
|
-
var DocSearchArgsSchema =
|
|
4416
|
-
term:
|
|
4417
|
-
limit:
|
|
5952
|
+
var DocSearchArgsSchema = z8.object({
|
|
5953
|
+
term: z8.string().min(1),
|
|
5954
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
4418
5955
|
});
|
|
4419
|
-
var RetrieverQueryArgsSchema =
|
|
4420
|
-
query:
|
|
4421
|
-
limit:
|
|
5956
|
+
var RetrieverQueryArgsSchema = z8.object({
|
|
5957
|
+
query: z8.string().min(1),
|
|
5958
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
4422
5959
|
});
|
|
4423
|
-
var DocGetArgsSchema =
|
|
4424
|
-
id:
|
|
4425
|
-
path:
|
|
5960
|
+
var DocGetArgsSchema = z8.object({
|
|
5961
|
+
id: z8.string().min(1).optional(),
|
|
5962
|
+
path: z8.string().min(1).optional()
|
|
4426
5963
|
}).refine((args) => args.id || args.path, "doc.get requires id or path");
|
|
5964
|
+
var WorkflowRunArgsSchema = z8.object({ runId: z8.string().min(1).optional() });
|
|
5965
|
+
var DiagnosticsArgsSchema = z8.object({ status: z8.string().min(1).optional(), severity: z8.string().min(1).optional() });
|
|
5966
|
+
var RelationsArgsSchema = z8.object({ kind: z8.string().min(1).optional(), limit: z8.number().int().positive().max(500).optional() });
|
|
5967
|
+
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() });
|
|
4427
5968
|
var parseToolArgs = (tool, schema, value) => {
|
|
4428
5969
|
try {
|
|
4429
5970
|
return schema.parse(value);
|
|
@@ -4454,15 +5995,54 @@ var findDocPath = (index, args) => {
|
|
|
4454
5995
|
return doc.path;
|
|
4455
5996
|
};
|
|
4456
5997
|
var resolveDocPath = (root, relPath) => {
|
|
4457
|
-
const rootAbs =
|
|
4458
|
-
const unresolved =
|
|
4459
|
-
const unresolvedRel =
|
|
5998
|
+
const rootAbs = realpathSync9.native(root);
|
|
5999
|
+
const unresolved = resolve17(rootAbs, relPath);
|
|
6000
|
+
const unresolvedRel = relative9(rootAbs, unresolved);
|
|
4460
6001
|
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
4461
|
-
const abs =
|
|
4462
|
-
const rel =
|
|
6002
|
+
const abs = realpathSync9.native(unresolved);
|
|
6003
|
+
const rel = relative9(rootAbs, abs);
|
|
4463
6004
|
if (rel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
4464
6005
|
return abs;
|
|
4465
6006
|
};
|
|
6007
|
+
var workflowStateDir = (ctx) => resolve17(ctx.root, ctx.config.workflow?.stateDir ?? ".doc-bridge/workflow");
|
|
6008
|
+
var workflowRun = (ctx) => loadWorkflowManifest(workflowStateDir(ctx));
|
|
6009
|
+
var ensureLatestRun = (ctx, runId2) => {
|
|
6010
|
+
const run2 = workflowRun(ctx);
|
|
6011
|
+
if (runId2 && run2.runId !== runId2) throw new Error(`Unknown workflow run "${runId2}"`);
|
|
6012
|
+
if (run2.state === "stale" || run2.state === "failed") throw new Error(`Workflow run is ${run2.state}; resume or create a valid run before reading artifacts.`);
|
|
6013
|
+
return run2;
|
|
6014
|
+
};
|
|
6015
|
+
var workflowSnapshot = (ctx, runId2) => {
|
|
6016
|
+
ensureLatestRun(ctx, runId2);
|
|
6017
|
+
return parseDiscoverySnapshot(loadWorkflowStepOutput(workflowStateDir(ctx), "normalize"));
|
|
6018
|
+
};
|
|
6019
|
+
var workflowReport = (ctx, runId2) => {
|
|
6020
|
+
ensureLatestRun(ctx, runId2);
|
|
6021
|
+
return parseReconciliationReport(loadWorkflowStepOutput(workflowStateDir(ctx), "reconcile"));
|
|
6022
|
+
};
|
|
6023
|
+
var proposalPath = (ctx) => join20(ctx.root, ".doc-bridge", "proposal.json");
|
|
6024
|
+
var readSavedProposal = (ctx, input) => {
|
|
6025
|
+
if (input !== void 0) return FixProposalV1Schema.parse(input);
|
|
6026
|
+
try {
|
|
6027
|
+
return FixProposalV1Schema.parse(JSON.parse(readFileSync20(proposalPath(ctx), "utf8")));
|
|
6028
|
+
} catch {
|
|
6029
|
+
throw new Error(`No saved fix proposal at ${proposalPath(ctx)}.`);
|
|
6030
|
+
}
|
|
6031
|
+
};
|
|
6032
|
+
var saveProposal = (ctx, proposal) => {
|
|
6033
|
+
mkdirSync7(join20(ctx.root, ".doc-bridge"), { recursive: true });
|
|
6034
|
+
writeFileSync7(proposalPath(ctx), `${JSON.stringify(proposal, null, 2)}
|
|
6035
|
+
`, "utf8");
|
|
6036
|
+
};
|
|
6037
|
+
var enabledMcpTools = (ctx) => {
|
|
6038
|
+
const configured = ctx.config.surfaces?.mcp?.tools;
|
|
6039
|
+
if (!configured || configured.length === MCP_TOOLS.length && MCP_TOOLS.every((tool) => configured.includes(tool.name))) return MCP_TOOLS;
|
|
6040
|
+
return MCP_TOOLS.filter((tool) => configured.includes(tool.name));
|
|
6041
|
+
};
|
|
6042
|
+
var assertMcpToolEnabled = (ctx, name) => {
|
|
6043
|
+
if (!MCP_TOOLS.some((tool) => tool.name === name)) throw new Error(`Unknown tool "${name}"`);
|
|
6044
|
+
if (!enabledMcpTools(ctx).some((tool) => tool.name === name)) throw new Error(`MCP tool "${name}" is disabled by configuration.`);
|
|
6045
|
+
};
|
|
4466
6046
|
var handleMcpRequest = (ctx, request) => {
|
|
4467
6047
|
if (request.method === "initialize") {
|
|
4468
6048
|
return {
|
|
@@ -4471,11 +6051,13 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
4471
6051
|
serverInfo: { name: "ak-docs", version: PACKAGE_VERSION }
|
|
4472
6052
|
};
|
|
4473
6053
|
}
|
|
4474
|
-
if (request.method === "tools/list") return { tools:
|
|
6054
|
+
if (request.method === "tools/list") return { tools: enabledMcpTools(ctx) };
|
|
4475
6055
|
if (request.method === "tools/call") {
|
|
4476
6056
|
const params = asRecord(request.params);
|
|
4477
6057
|
const name = params.name;
|
|
4478
6058
|
const args = asRecord(params.arguments);
|
|
6059
|
+
if (typeof name !== "string") throw new Error("MCP tools/call requires a tool name.");
|
|
6060
|
+
assertMcpToolEnabled(ctx, name);
|
|
4479
6061
|
const index = () => ctx.loadIndex?.() ?? loadDocBridgeIndex(ctx.root, ctx.config);
|
|
4480
6062
|
if (name === "handoff.resolve") {
|
|
4481
6063
|
const parsed = parseToolArgs("handoff.resolve", HandoffResolveArgsSchema, args);
|
|
@@ -4493,7 +6075,7 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
4493
6075
|
}
|
|
4494
6076
|
if (name === "doc.get") {
|
|
4495
6077
|
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
4496
|
-
return textResult(
|
|
6078
|
+
return textResult(readFileSync20(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
4497
6079
|
}
|
|
4498
6080
|
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
4499
6081
|
if (name === "retriever.query") {
|
|
@@ -4515,6 +6097,81 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
4515
6097
|
mergePolicy: { autoMerge: false, requiresHuman: true }
|
|
4516
6098
|
});
|
|
4517
6099
|
}
|
|
6100
|
+
if (name === "docbridge.snapshot") {
|
|
6101
|
+
const parsed = parseToolArgs("docbridge.snapshot", WorkflowRunArgsSchema, args);
|
|
6102
|
+
return textResult(redactValue(workflowSnapshot(ctx, parsed.runId)));
|
|
6103
|
+
}
|
|
6104
|
+
if (name === "docbridge.report") {
|
|
6105
|
+
const parsed = parseToolArgs("docbridge.report", WorkflowRunArgsSchema, args);
|
|
6106
|
+
return textResult(redactValue(workflowReport(ctx, parsed.runId)));
|
|
6107
|
+
}
|
|
6108
|
+
if (name === "docbridge.diagnostics") {
|
|
6109
|
+
const parsed = parseToolArgs("docbridge.diagnostics", DiagnosticsArgsSchema, args);
|
|
6110
|
+
const diagnostics = workflowReport(ctx).diagnostics.filter(
|
|
6111
|
+
(diagnostic2) => (!parsed.status || diagnostic2.status === parsed.status) && (!parsed.severity || diagnostic2.severity === parsed.severity)
|
|
6112
|
+
);
|
|
6113
|
+
return textResult(redactValue({ reportHash: workflowReport(ctx).contentHash, diagnostics }));
|
|
6114
|
+
}
|
|
6115
|
+
if (name === "docbridge.relations") {
|
|
6116
|
+
const parsed = parseToolArgs("docbridge.relations", RelationsArgsSchema, args);
|
|
6117
|
+
const snapshot = workflowSnapshot(ctx);
|
|
6118
|
+
return textResult({ snapshotHash: snapshot.contentHash, relations: snapshot.relations.filter((relation) => !parsed.kind || relation.kind === parsed.kind).slice(0, parsed.limit ?? 100) });
|
|
6119
|
+
}
|
|
6120
|
+
if (name === "docbridge.run") {
|
|
6121
|
+
parseToolArgs("docbridge.run", z8.object({}), args);
|
|
6122
|
+
return textResult(workflowRun(ctx));
|
|
6123
|
+
}
|
|
6124
|
+
if (name === "docbridge.proposals") {
|
|
6125
|
+
const parsed = parseToolArgs("docbridge.proposals", ProposalsArgsSchema, args);
|
|
6126
|
+
const run2 = (() => {
|
|
6127
|
+
try {
|
|
6128
|
+
return workflowRun(ctx);
|
|
6129
|
+
} catch {
|
|
6130
|
+
return void 0;
|
|
6131
|
+
}
|
|
6132
|
+
})();
|
|
6133
|
+
if (!parsed.action || parsed.action === "list") {
|
|
6134
|
+
let proposal2;
|
|
6135
|
+
try {
|
|
6136
|
+
proposal2 = readSavedProposal(ctx, void 0);
|
|
6137
|
+
} catch {
|
|
6138
|
+
proposal2 = void 0;
|
|
6139
|
+
}
|
|
6140
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposals: proposal2 ? [proposal2] : [] }));
|
|
6141
|
+
}
|
|
6142
|
+
if (parsed.action === "suggest") {
|
|
6143
|
+
return loadRegistryAgentRunner(ctx.root, ctx.config).then(async (runner) => {
|
|
6144
|
+
const snapshot = workflowSnapshot(ctx);
|
|
6145
|
+
const report = workflowReport(ctx);
|
|
6146
|
+
const adapter = createRegistryAgentAdapter(ctx.root, ctx.config, runner);
|
|
6147
|
+
const proposal2 = await adapter.run(snapshot, report);
|
|
6148
|
+
const savedPath = persistRegistryAgentProposal(workflowStateDir(ctx), proposal2);
|
|
6149
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2, proposalPath: savedPath }));
|
|
6150
|
+
});
|
|
6151
|
+
}
|
|
6152
|
+
const discovered = discoverRepository({ root: ctx.root, config: ctx.config });
|
|
6153
|
+
const options = { baseRevision: discovered.sourceRevision, configurationHash: sha256NormalizedV1(ctx.config), ...ctx.config.project?.name ? { projectName: ctx.config.project.name } : {} };
|
|
6154
|
+
if (parsed.action === "propose-links") {
|
|
6155
|
+
const proposal2 = createMarkdownLinkFixProposal(ctx.root, options);
|
|
6156
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
6157
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
6158
|
+
}
|
|
6159
|
+
if (parsed.action === "propose-normalize") {
|
|
6160
|
+
if (!parsed.artifactPath) throw new Error("docbridge.proposals propose-normalize requires artifactPath");
|
|
6161
|
+
const proposal2 = createArtifactNormalizationProposal(ctx.root, parsed.artifactPath, options);
|
|
6162
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
6163
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
6164
|
+
}
|
|
6165
|
+
if (parsed.action === "approve") {
|
|
6166
|
+
const proposal2 = approveFixProposal(readSavedProposal(ctx, parsed.proposal), parsed.approvedBy ?? "human");
|
|
6167
|
+
if (parsed.proposalHash && proposal2.approval?.proposalHash !== parsed.proposalHash) throw new Error("proposalHash does not match the saved proposal");
|
|
6168
|
+
saveProposal(ctx, proposal2);
|
|
6169
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 }));
|
|
6170
|
+
}
|
|
6171
|
+
const proposal = applyFixProposal(ctx.root, readSavedProposal(ctx, parsed.proposal), { currentRevision: discovered.sourceRevision });
|
|
6172
|
+
saveProposal(ctx, proposal);
|
|
6173
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal }));
|
|
6174
|
+
}
|
|
4518
6175
|
throw new Error(`Unknown tool "${String(name)}"`);
|
|
4519
6176
|
}
|
|
4520
6177
|
if (request.method?.startsWith("notifications/")) return void 0;
|
|
@@ -4527,16 +6184,16 @@ var writeFrame = (payload, framing) => {
|
|
|
4527
6184
|
\r
|
|
4528
6185
|
${body}`);
|
|
4529
6186
|
};
|
|
4530
|
-
var
|
|
6187
|
+
var respondMcpRequest = async (ctx, request, framing) => {
|
|
4531
6188
|
if (request.id === void 0) {
|
|
4532
6189
|
try {
|
|
4533
|
-
handleMcpRequest(ctx, request);
|
|
6190
|
+
await handleMcpRequest(ctx, request);
|
|
4534
6191
|
} catch {
|
|
4535
6192
|
}
|
|
4536
6193
|
return;
|
|
4537
6194
|
}
|
|
4538
6195
|
try {
|
|
4539
|
-
const result = handleMcpRequest(ctx, request);
|
|
6196
|
+
const result = await handleMcpRequest(ctx, request);
|
|
4540
6197
|
writeFrame({ jsonrpc: "2.0", id: request.id, result: result ?? {} }, framing);
|
|
4541
6198
|
} catch (error) {
|
|
4542
6199
|
writeFrame({
|
|
@@ -4548,6 +6205,10 @@ var respond = (ctx, request, framing) => {
|
|
|
4548
6205
|
};
|
|
4549
6206
|
var startMcpStdioServer = (ctx) => {
|
|
4550
6207
|
let buffer = Buffer.alloc(0);
|
|
6208
|
+
let responseChain = Promise.resolve();
|
|
6209
|
+
const enqueueResponse = (request, framing) => {
|
|
6210
|
+
responseChain = responseChain.then(() => respondMcpRequest(ctx, request, framing));
|
|
6211
|
+
};
|
|
4551
6212
|
process.stdin.on("data", (chunk) => {
|
|
4552
6213
|
buffer = Buffer.concat([buffer, chunk]);
|
|
4553
6214
|
while (true) {
|
|
@@ -4566,7 +6227,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
4566
6227
|
if (buffer.length < bodyEnd) return;
|
|
4567
6228
|
const raw2 = buffer.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
4568
6229
|
buffer = buffer.subarray(bodyEnd);
|
|
4569
|
-
|
|
6230
|
+
enqueueResponse(JSON.parse(raw2), "content-length");
|
|
4570
6231
|
continue;
|
|
4571
6232
|
}
|
|
4572
6233
|
const lineEnd = buffer.indexOf("\n");
|
|
@@ -4575,7 +6236,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
4575
6236
|
buffer = buffer.subarray(lineEnd + 1);
|
|
4576
6237
|
if (!raw) continue;
|
|
4577
6238
|
try {
|
|
4578
|
-
|
|
6239
|
+
enqueueResponse(JSON.parse(raw), "json-line");
|
|
4579
6240
|
} catch {
|
|
4580
6241
|
writeFrame({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }, "json-line");
|
|
4581
6242
|
}
|
|
@@ -4584,6 +6245,237 @@ var startMcpStdioServer = (ctx) => {
|
|
|
4584
6245
|
process.stdin.resume();
|
|
4585
6246
|
};
|
|
4586
6247
|
|
|
6248
|
+
// src/reconciliation/reconcile.ts
|
|
6249
|
+
var ignoredDocumentationRelations = /* @__PURE__ */ new Set(["covers"]);
|
|
6250
|
+
var metadataDetection = (relation) => {
|
|
6251
|
+
const detection = relation.metadata?.detection;
|
|
6252
|
+
return typeof detection === "string" ? detection : void 0;
|
|
6253
|
+
};
|
|
6254
|
+
var relationDetection = (relation) => relation.discriminator ?? metadataDetection(relation) ?? "static";
|
|
6255
|
+
var entityResolver = (snapshots) => {
|
|
6256
|
+
const references = /* @__PURE__ */ new Map();
|
|
6257
|
+
const entities = snapshots.flatMap((snapshot) => snapshot.entities).sort((a, b) => a.id.localeCompare(b.id));
|
|
6258
|
+
for (const entity of entities) {
|
|
6259
|
+
references.set(entity.id, entity.id);
|
|
6260
|
+
for (const alias of entity.aliases ?? []) {
|
|
6261
|
+
if (!references.has(alias)) references.set(alias, entity.id);
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
return (reference) => references.get(reference) ?? reference;
|
|
6265
|
+
};
|
|
6266
|
+
var relationBase = (relation, resolveEntity2) => `${resolveEntity2(relation.from)}\0${resolveEntity2(relation.to)}\0${relation.kind}`;
|
|
6267
|
+
var evidenceKey = (item) => `${item.source}:${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.context ?? ""}`;
|
|
6268
|
+
var mergeEvidence = (...relations) => {
|
|
6269
|
+
const merged = /* @__PURE__ */ new Map();
|
|
6270
|
+
for (const relation of relations) {
|
|
6271
|
+
for (const item of relation.evidence) merged.set(evidenceKey(item), item);
|
|
6272
|
+
}
|
|
6273
|
+
return [...merged.values()].sort((a, b) => evidenceKey(a).localeCompare(evidenceKey(b)));
|
|
6274
|
+
};
|
|
6275
|
+
var diagnosticId = (code, value) => `reconciliation:${code}:${sha256NormalizedV1(value).slice(0, 32)}`;
|
|
6276
|
+
var coverageAvailable = (snapshot, relation) => {
|
|
6277
|
+
const status = snapshot.coverage.find(
|
|
6278
|
+
(entry) => entry.scope === "static-imports-and-exports" && (relation.kind === "imports" || relation.kind === "re-exports")
|
|
6279
|
+
)?.status;
|
|
6280
|
+
return status === void 0 || status === "complete";
|
|
6281
|
+
};
|
|
6282
|
+
var entityById = (snapshots) => {
|
|
6283
|
+
const entities = /* @__PURE__ */ new Map();
|
|
6284
|
+
for (const snapshot of snapshots) {
|
|
6285
|
+
for (const entity of snapshot.entities) if (!entities.has(entity.id)) entities.set(entity.id, entity);
|
|
6286
|
+
}
|
|
6287
|
+
return entities;
|
|
6288
|
+
};
|
|
6289
|
+
var isUnresolved = (id, entities) => id.startsWith("unresolved:") || entities.get(id)?.kind === "unresolved-reference";
|
|
6290
|
+
var relationMatches = (observed, declared, resolveEntity2) => {
|
|
6291
|
+
if (relationBase(observed, resolveEntity2) !== relationBase(declared, resolveEntity2)) return false;
|
|
6292
|
+
return relationDetection(declared) === relationDetection(observed);
|
|
6293
|
+
};
|
|
6294
|
+
var reportDiagnostic = (code, status, severity, message, evidence2, value, entityIds, relationIds, remediation) => ({
|
|
6295
|
+
id: diagnosticId(code, value),
|
|
6296
|
+
code,
|
|
6297
|
+
status,
|
|
6298
|
+
severity,
|
|
6299
|
+
message,
|
|
6300
|
+
evidence: [...evidence2],
|
|
6301
|
+
...entityIds?.length ? { entityIds: [...entityIds] } : {},
|
|
6302
|
+
...relationIds?.length ? { relationIds: [...relationIds] } : {},
|
|
6303
|
+
...remediation ? { remediation } : {}
|
|
6304
|
+
});
|
|
6305
|
+
var reconcileKnowledge = (observed, declared) => {
|
|
6306
|
+
const resolveEntity2 = entityResolver([observed, declared]);
|
|
6307
|
+
const entities = entityById([observed, declared]);
|
|
6308
|
+
const observedRelations = observed.relations.filter((relation) => relation.provenance === "observed" && !ignoredDocumentationRelations.has(relation.kind));
|
|
6309
|
+
const declaredRelations = declared.relations.filter((relation) => relation.provenance === "declared" && !ignoredDocumentationRelations.has(relation.kind));
|
|
6310
|
+
const diagnostics = [];
|
|
6311
|
+
for (const entity of declared.entities.filter((item) => isUnresolved(item.id, entities)).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
6312
|
+
diagnostics.push(reportDiagnostic(
|
|
6313
|
+
"UNRESOLVED_ENTITY_REFERENCE",
|
|
6314
|
+
"unresolved",
|
|
6315
|
+
"error",
|
|
6316
|
+
`Declared reference could not be resolved: ${entity.name}.`,
|
|
6317
|
+
entity.evidence,
|
|
6318
|
+
entity.id,
|
|
6319
|
+
[entity.id],
|
|
6320
|
+
void 0,
|
|
6321
|
+
"Resolve the reference to an observed entity ID or configured alias."
|
|
6322
|
+
));
|
|
6323
|
+
}
|
|
6324
|
+
const declaredByBase = /* @__PURE__ */ new Map();
|
|
6325
|
+
for (const relation of declaredRelations) {
|
|
6326
|
+
const base2 = relationBase(relation, resolveEntity2);
|
|
6327
|
+
const group = declaredByBase.get(base2) ?? [];
|
|
6328
|
+
group.push(relation);
|
|
6329
|
+
declaredByBase.set(base2, group);
|
|
6330
|
+
}
|
|
6331
|
+
for (const group of declaredByBase.values()) {
|
|
6332
|
+
const detections = new Set(group.map(relationDetection));
|
|
6333
|
+
if (detections.size < 2) continue;
|
|
6334
|
+
diagnostics.push(reportDiagnostic(
|
|
6335
|
+
"CONFLICTING_DECLARATIONS",
|
|
6336
|
+
"conflict",
|
|
6337
|
+
"error",
|
|
6338
|
+
"Declarations for the same semantic relation disagree on detection.",
|
|
6339
|
+
mergeEvidence(...group),
|
|
6340
|
+
[relationBase(group[0], resolveEntity2), [...detections].sort()],
|
|
6341
|
+
void 0,
|
|
6342
|
+
group.map((relation) => relation.id),
|
|
6343
|
+
"Keep one detection value for this relation or document the intended distinction with a different relation kind."
|
|
6344
|
+
));
|
|
6345
|
+
}
|
|
6346
|
+
for (const relation of observedRelations) {
|
|
6347
|
+
const candidates = declaredByBase.get(relationBase(relation, resolveEntity2)) ?? [];
|
|
6348
|
+
const match = candidates.find((candidate) => relationMatches(relation, candidate, resolveEntity2));
|
|
6349
|
+
if (match) {
|
|
6350
|
+
diagnostics.push(reportDiagnostic(
|
|
6351
|
+
"RELATION_CONFIRMED",
|
|
6352
|
+
"confirmed",
|
|
6353
|
+
"info",
|
|
6354
|
+
"Observed relation is covered by a matching declaration.",
|
|
6355
|
+
mergeEvidence(relation, match),
|
|
6356
|
+
relation.id,
|
|
6357
|
+
void 0,
|
|
6358
|
+
[relation.id, match.id]
|
|
6359
|
+
));
|
|
6360
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
6361
|
+
diagnostics.push(reportDiagnostic(
|
|
6362
|
+
"RELATION_UNDOCUMENTED",
|
|
6363
|
+
"undocumented",
|
|
6364
|
+
"warn",
|
|
6365
|
+
"Observed relation has no matching documentation declaration.",
|
|
6366
|
+
relation.evidence,
|
|
6367
|
+
relation.id,
|
|
6368
|
+
void 0,
|
|
6369
|
+
[relation.id],
|
|
6370
|
+
"Add a matching relation declaration or configure this relation kind as intentionally undocumented."
|
|
6371
|
+
));
|
|
6372
|
+
}
|
|
6373
|
+
}
|
|
6374
|
+
for (const relation of declaredRelations) {
|
|
6375
|
+
const candidates = observedRelations.filter((candidate) => relationBase(candidate, resolveEntity2) === relationBase(relation, resolveEntity2));
|
|
6376
|
+
const detection = relationDetection(relation);
|
|
6377
|
+
if (candidates.some((candidate) => relationMatches(candidate, relation, resolveEntity2))) continue;
|
|
6378
|
+
if (isUnresolved(resolveEntity2(relation.from), entities) || isUnresolved(resolveEntity2(relation.to), entities)) continue;
|
|
6379
|
+
if (detection === "dynamic" || detection === "external") {
|
|
6380
|
+
diagnostics.push(reportDiagnostic(
|
|
6381
|
+
"RELATION_NOT_ANALYZED",
|
|
6382
|
+
"not-analyzed",
|
|
6383
|
+
"info",
|
|
6384
|
+
`Declared ${detection} relation cannot be verified by the current static analyzer.`,
|
|
6385
|
+
relation.evidence,
|
|
6386
|
+
relation.id,
|
|
6387
|
+
void 0,
|
|
6388
|
+
[relation.id],
|
|
6389
|
+
"Enable a compatible analyzer or provide explicit observed evidence before treating this relation as confirmed."
|
|
6390
|
+
));
|
|
6391
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
6392
|
+
diagnostics.push(reportDiagnostic(
|
|
6393
|
+
"DECLARED_RELATION_STALE",
|
|
6394
|
+
"stale-or-unverified",
|
|
6395
|
+
"warn",
|
|
6396
|
+
candidates.length ? "Declared relation has incompatible observed evidence." : "Declared static relation was not observed.",
|
|
6397
|
+
candidates.length ? mergeEvidence(relation, ...candidates) : relation.evidence,
|
|
6398
|
+
relation.id,
|
|
6399
|
+
void 0,
|
|
6400
|
+
[relation.id, ...candidates.map((candidate) => candidate.id)],
|
|
6401
|
+
"Update the declaration or the implementation so both graphs describe the same relation."
|
|
6402
|
+
));
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
const sortedDiagnostics = [...diagnostics].sort((a, b) => a.id.localeCompare(b.id));
|
|
6406
|
+
const base = {
|
|
6407
|
+
type: "reconciliation-report",
|
|
6408
|
+
schemaVersion: 1,
|
|
6409
|
+
contentHash: "0".repeat(64),
|
|
6410
|
+
contentHashAlgo: observed.contentHashAlgo,
|
|
6411
|
+
project: observed.project,
|
|
6412
|
+
sourceRevision: observed.sourceRevision,
|
|
6413
|
+
sourceRevisionKind: observed.sourceRevisionKind,
|
|
6414
|
+
configurationHash: observed.configurationHash,
|
|
6415
|
+
pipelineVersion: observed.pipelineVersion,
|
|
6416
|
+
analyzerVersions: observed.analyzerVersions,
|
|
6417
|
+
snapshotHash: observed.contentHash,
|
|
6418
|
+
diagnostics: sortedDiagnostics,
|
|
6419
|
+
summary: {
|
|
6420
|
+
entityCount: observed.entities.length,
|
|
6421
|
+
relationCount: observedRelations.length,
|
|
6422
|
+
diagnosticCount: sortedDiagnostics.length
|
|
6423
|
+
}
|
|
6424
|
+
};
|
|
6425
|
+
return ReconciliationReportV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
6426
|
+
};
|
|
6427
|
+
|
|
6428
|
+
// src/report/html.ts
|
|
6429
|
+
var escapeHtml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
6430
|
+
var anchor = (prefix, value) => `${prefix}-${value.replace(/[^A-Za-z0-9_-]+/g, "-")}`;
|
|
6431
|
+
var evidenceText = (evidence2, includeSnippets) => {
|
|
6432
|
+
const location = `${evidence2.path}${evidence2.lineStart ? `:${evidence2.lineStart}${evidence2.lineEnd && evidence2.lineEnd !== evidence2.lineStart ? `-${evidence2.lineEnd}` : ""}` : ""}`;
|
|
6433
|
+
return `${location}${includeSnippets && evidence2.context ? ` \u2014 ${redactSecrets(evidence2.context)}` : ""}`;
|
|
6434
|
+
};
|
|
6435
|
+
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>`;
|
|
6436
|
+
var embeddedJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
6437
|
+
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>`;
|
|
6438
|
+
var renderCompact = (snapshot, report, entityAnchors, includeSnippets) => {
|
|
6439
|
+
const entities = snapshot.entities.map((entity) => ({ id: entity.id, name: entity.name, kind: entity.kind, anchor: entityAnchors.get(entity.id) ?? anchor("entity", entity.id) }));
|
|
6440
|
+
const entityIndexes = new Map(entities.map((entity, index) => [entity.id, index]));
|
|
6441
|
+
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 }));
|
|
6442
|
+
const diagnostics = report.diagnostics.map((diagnostic2) => ({
|
|
6443
|
+
...diagnostic2,
|
|
6444
|
+
evidence: diagnostic2.evidence.map(({ context, ...evidence2 }) => includeSnippets && context ? { ...evidence2, context: redactSecrets(context) } : evidence2)
|
|
6445
|
+
}));
|
|
6446
|
+
const data = embeddedJson({ entities, relations, diagnostics, coverage: snapshot.coverage });
|
|
6447
|
+
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()};`;
|
|
6448
|
+
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>`;
|
|
6449
|
+
};
|
|
6450
|
+
var render = (input, options) => {
|
|
6451
|
+
const { snapshot, report } = input;
|
|
6452
|
+
const includeSnippets = options.includeSnippets === true;
|
|
6453
|
+
const entityAnchors = new Map(snapshot.entities.map((entity, index) => [entity.id, entity.id.length > 64 ? `entity-n${index}` : anchor("entity", entity.id)]));
|
|
6454
|
+
if (snapshot.entities.length > 500 || report.diagnostics.length > 1e3) return renderCompact(snapshot, report, entityAnchors, includeSnippets);
|
|
6455
|
+
const entityNames = new Map(snapshot.entities.map((entity) => [entity.id, entity.name]));
|
|
6456
|
+
const entityAnchor = (id) => entityAnchors.get(id) ?? anchor("entity", id);
|
|
6457
|
+
const entityLabel = (id) => entityNames.get(id) ?? id;
|
|
6458
|
+
const entityLink = (id) => `<a href="#${entityAnchor(id)}">${escapeHtml(entityLabel(id))}</a>`;
|
|
6459
|
+
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("");
|
|
6460
|
+
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("");
|
|
6461
|
+
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("");
|
|
6462
|
+
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("");
|
|
6463
|
+
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));});`;
|
|
6464
|
+
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>`;
|
|
6465
|
+
};
|
|
6466
|
+
var renderOfflineReport = (input, options = {}) => {
|
|
6467
|
+
try {
|
|
6468
|
+
if (!input || typeof input !== "object") throw new Error("Input must contain snapshot and report artifacts.");
|
|
6469
|
+
const value = input;
|
|
6470
|
+
const snapshot = DiscoverySnapshotV1Schema.parse(value.snapshot);
|
|
6471
|
+
const report = ReconciliationReportV1Schema.parse(value.report);
|
|
6472
|
+
if (report.snapshotHash !== snapshot.contentHash) throw new Error("Report snapshotHash does not match snapshot contentHash.");
|
|
6473
|
+
return render({ snapshot, report }, options);
|
|
6474
|
+
} catch (error) {
|
|
6475
|
+
return errorPage(error instanceof Error ? error.message : String(error));
|
|
6476
|
+
}
|
|
6477
|
+
};
|
|
6478
|
+
|
|
4587
6479
|
// src/cli/program.ts
|
|
4588
6480
|
var usage = `ak-docs \u2014 human\u2194agent documentation bridge (@agentskit/doc-bridge)
|
|
4589
6481
|
|
|
@@ -4592,11 +6484,17 @@ Core (no API key):
|
|
|
4592
6484
|
ak-docs demo [--fixture example|monorepo] [--text] [--in-project]
|
|
4593
6485
|
ak-docs doctor [--text] [--badge] [--write-badge]
|
|
4594
6486
|
ak-docs index [--watch]
|
|
6487
|
+
ak-docs discover [--text|--json]
|
|
6488
|
+
ak-docs scan | reconcile | check | map [--text|--json] [--html]
|
|
6489
|
+
ak-docs fix propose links|normalize <artifact> [--output <file>]
|
|
6490
|
+
ak-docs fix approve|apply <proposal.json> [--by <name>]
|
|
6491
|
+
ak-docs suggest [--json|--text] run the configured local Registry agent
|
|
4595
6492
|
ak-docs query [package|ownership|intent|change] <id> [--agent] [--text]
|
|
4596
6493
|
ak-docs search <term> [--agent] [--text]
|
|
4597
6494
|
ak-docs list <packages|intents|changes|knowledge> [--text]
|
|
4598
6495
|
ak-docs ask [question] local consult (no LLM)
|
|
4599
6496
|
ak-docs gate run [gate-id]
|
|
6497
|
+
ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]
|
|
4600
6498
|
ak-docs conformance run documentation-standard-v1 [--text|--json]
|
|
4601
6499
|
ak-docs mcp
|
|
4602
6500
|
ak-docs mcp install --cursor | --claude
|
|
@@ -4656,8 +6554,16 @@ var parseArgs = (argv) => {
|
|
|
4656
6554
|
else if (positional[0] === "memory") command = "memory";
|
|
4657
6555
|
else if (positional[0] === "playbook") command = "playbook";
|
|
4658
6556
|
else if (positional[0] === "registry") command = "registry";
|
|
6557
|
+
else if (positional[0] === "discover") command = "discover";
|
|
6558
|
+
else if (positional[0] === "scan") command = "scan";
|
|
6559
|
+
else if (positional[0] === "reconcile") command = "reconcile";
|
|
6560
|
+
else if (positional[0] === "check") command = "check";
|
|
6561
|
+
else if (positional[0] === "map") command = "map";
|
|
6562
|
+
else if (positional[0] === "fix") command = "fix";
|
|
6563
|
+
else if (positional[0] === "suggest") command = "suggest";
|
|
4659
6564
|
else if (positional[0] === "index") command = "index";
|
|
4660
6565
|
else if (positional[0] === "gate") command = "gate";
|
|
6566
|
+
else if (positional[0] === "rules") command = "rules";
|
|
4661
6567
|
else if (positional[0] === "mcp") command = "mcp";
|
|
4662
6568
|
else if (positional[0] === "doctor") command = "doctor";
|
|
4663
6569
|
else if (positional[0] === "demo") command = "demo";
|
|
@@ -4671,6 +6577,28 @@ var parseArgs = (argv) => {
|
|
|
4671
6577
|
else if (positional[0] === "conformance") command = "conformance";
|
|
4672
6578
|
return { command, flags, configPath, positional };
|
|
4673
6579
|
};
|
|
6580
|
+
var optionValues = (argv, name) => {
|
|
6581
|
+
const values = [];
|
|
6582
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
6583
|
+
const arg = argv[index];
|
|
6584
|
+
if (arg?.startsWith(`${name}=`)) values.push(arg.slice(name.length + 1));
|
|
6585
|
+
else if (arg === name && argv[index + 1] && !argv[index + 1]?.startsWith("-")) {
|
|
6586
|
+
values.push(argv[index + 1]);
|
|
6587
|
+
index += 1;
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
return values;
|
|
6591
|
+
};
|
|
6592
|
+
var parseRuleAssignments = (values, parseValue) => {
|
|
6593
|
+
const result = {};
|
|
6594
|
+
for (const value of values) {
|
|
6595
|
+
const separator = value.indexOf("=");
|
|
6596
|
+
if (separator <= 0 || separator === value.length - 1) throw new Error(`Invalid rule assignment "${value}". Use rule=value.`);
|
|
6597
|
+
const key = value.slice(0, separator);
|
|
6598
|
+
result[key] = parseValue(value.slice(separator + 1));
|
|
6599
|
+
}
|
|
6600
|
+
return result;
|
|
6601
|
+
};
|
|
4674
6602
|
var writeJson2 = (payload) => {
|
|
4675
6603
|
process.stdout.write(`${JSON.stringify(payload, null, 2)}
|
|
4676
6604
|
`);
|
|
@@ -4762,12 +6690,12 @@ var readIndexedDoc = (root, config, idOrPath) => {
|
|
|
4762
6690
|
const index = loadDocBridgeIndex(root, config);
|
|
4763
6691
|
const entry = index.knowledge.find((doc) => doc.id === idOrPath || doc.path === idOrPath);
|
|
4764
6692
|
if (!entry) throw new Error(`Unknown indexed doc "${idOrPath}". Try: search ${idOrPath}`);
|
|
4765
|
-
const abs =
|
|
4766
|
-
const rootAbs =
|
|
6693
|
+
const abs = resolve18(root, entry.path);
|
|
6694
|
+
const rootAbs = resolve18(root);
|
|
4767
6695
|
if (abs !== rootAbs && !abs.startsWith(`${rootAbs}/`)) {
|
|
4768
6696
|
throw new Error(`Indexed doc escapes project root: ${entry.path}`);
|
|
4769
6697
|
}
|
|
4770
|
-
return
|
|
6698
|
+
return readFileSync21(abs, "utf8");
|
|
4771
6699
|
};
|
|
4772
6700
|
var runAskRepl = async (root, config) => {
|
|
4773
6701
|
const index = loadDocBridgeIndex(root, config);
|
|
@@ -4842,10 +6770,197 @@ var diagnosticNextCommands = (config, result) => {
|
|
|
4842
6770
|
"ak-docs index"
|
|
4843
6771
|
];
|
|
4844
6772
|
};
|
|
6773
|
+
var workflowOptions = (root, config, sourceRevision2, stage, handlers) => ({
|
|
6774
|
+
root,
|
|
6775
|
+
...config.workflow?.stateDir ? { stateDir: config.workflow.stateDir } : {},
|
|
6776
|
+
sourceRevision: sourceRevision2,
|
|
6777
|
+
configurationHash: sha256NormalizedV1(config),
|
|
6778
|
+
stage,
|
|
6779
|
+
handlers
|
|
6780
|
+
});
|
|
6781
|
+
var scanWorkflow = (root, config) => {
|
|
6782
|
+
const discovered = discoverRepository({ root, config });
|
|
6783
|
+
runWorkflow(workflowOptions(root, config, discovered.sourceRevision, "collect", { collect: () => discovered }));
|
|
6784
|
+
return runWorkflow(workflowOptions(root, config, discovered.sourceRevision, "normalize", { normalize: ({ input }) => input }));
|
|
6785
|
+
};
|
|
6786
|
+
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") }));
|
|
6787
|
+
var reconcileWorkflow = (root, config) => {
|
|
6788
|
+
const scanned = scanWorkflow(root, config);
|
|
6789
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(scanned.stateDir, "normalize"));
|
|
6790
|
+
const declared = applyDocumentationDeclarations(snapshot, documentationInputs(root, snapshot)).snapshot;
|
|
6791
|
+
const report = reconcileKnowledge(snapshot, declared);
|
|
6792
|
+
return runWorkflow(workflowOptions(root, config, snapshot.sourceRevision, "reconcile", { reconcile: () => report }));
|
|
6793
|
+
};
|
|
6794
|
+
var checkWorkflow = (root, config) => {
|
|
6795
|
+
const reconciled = reconcileWorkflow(root, config);
|
|
6796
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(reconciled.stateDir, "reconcile"));
|
|
6797
|
+
const evaluated = runWorkflow(workflowOptions(root, config, report.sourceRevision, "evaluate", { evaluate: () => evaluateRules(report, { ...config.rules ? { config: config.rules } : {} }) }));
|
|
6798
|
+
return runWorkflow(workflowOptions(root, config, report.sourceRevision, "report", { report: ({ input }) => input }));
|
|
6799
|
+
};
|
|
6800
|
+
var workflowOutput = (result) => {
|
|
6801
|
+
const snapshot = (() => {
|
|
6802
|
+
try {
|
|
6803
|
+
return parseDiscoverySnapshot(loadWorkflowStepOutput(result.stateDir, "normalize"));
|
|
6804
|
+
} catch {
|
|
6805
|
+
return void 0;
|
|
6806
|
+
}
|
|
6807
|
+
})();
|
|
6808
|
+
const report = (() => {
|
|
6809
|
+
try {
|
|
6810
|
+
return parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, "reconcile"));
|
|
6811
|
+
} catch {
|
|
6812
|
+
return void 0;
|
|
6813
|
+
}
|
|
6814
|
+
})();
|
|
6815
|
+
const rules = (() => {
|
|
6816
|
+
try {
|
|
6817
|
+
const value = loadWorkflowStepOutput(result.stateDir, "evaluate");
|
|
6818
|
+
return value && typeof value === "object" ? value : void 0;
|
|
6819
|
+
} catch {
|
|
6820
|
+
return void 0;
|
|
6821
|
+
}
|
|
6822
|
+
})();
|
|
6823
|
+
return {
|
|
6824
|
+
ok: result.run.state !== "failed" && result.run.state !== "stale",
|
|
6825
|
+
runId: result.run.runId,
|
|
6826
|
+
state: result.run.state,
|
|
6827
|
+
stateDir: result.stateDir,
|
|
6828
|
+
artifactRefs: result.run.artifactRefs,
|
|
6829
|
+
steps: result.run.steps,
|
|
6830
|
+
reusedStages: result.reusedStages,
|
|
6831
|
+
...snapshot ? { snapshotHash: snapshot.contentHash, sourceRevision: snapshot.sourceRevision, configurationHash: snapshot.configurationHash, coverage: snapshot.coverage } : {},
|
|
6832
|
+
...report ? { reportHash: report.contentHash, diagnostics: report.diagnostics } : {},
|
|
6833
|
+
...rules ? { rules } : {}
|
|
6834
|
+
};
|
|
6835
|
+
};
|
|
6836
|
+
var runWorkflowCommand = (command, flags, configPath, argv) => {
|
|
6837
|
+
try {
|
|
6838
|
+
const { config, root } = loadProject(configPath);
|
|
6839
|
+
const result = command === "scan" ? scanWorkflow(root, config) : command === "reconcile" ? reconcileWorkflow(root, config) : checkWorkflow(root, config);
|
|
6840
|
+
const output = workflowOutput(result);
|
|
6841
|
+
if (command === "map") output.kind = "architecture-map";
|
|
6842
|
+
if (command === "map" && flags.has("--html")) {
|
|
6843
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(result.stateDir, "normalize"));
|
|
6844
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(result.stateDir, "reconcile"));
|
|
6845
|
+
const outputPath = optionValues(argv, "--output")[0] ?? ".doc-bridge/report.html";
|
|
6846
|
+
const htmlPath = resolve18(root, outputPath);
|
|
6847
|
+
mkdirSync8(dirname10(htmlPath), { recursive: true });
|
|
6848
|
+
writeFileSync8(htmlPath, renderOfflineReport({ snapshot, report }), "utf8");
|
|
6849
|
+
output.htmlPath = htmlPath;
|
|
6850
|
+
}
|
|
6851
|
+
if (wantsTextOutput(flags, config)) {
|
|
6852
|
+
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)}`]);
|
|
6853
|
+
} else writeJson2(output);
|
|
6854
|
+
const ruleExitCode = output.rules && typeof output.rules === "object" && "exitCode" in output.rules && output.rules.exitCode === 1 ? 1 : 0;
|
|
6855
|
+
return result.run.state === "failed" ? 1 : ruleExitCode;
|
|
6856
|
+
} catch (error) {
|
|
6857
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6858
|
+
`);
|
|
6859
|
+
return 2;
|
|
6860
|
+
}
|
|
6861
|
+
};
|
|
6862
|
+
var runRulesCommand = (argv, flags, positional, configPath) => {
|
|
6863
|
+
const action = positional[1];
|
|
6864
|
+
const reportPath = positional[2];
|
|
6865
|
+
if (action !== "run" || !reportPath) {
|
|
6866
|
+
process.stderr.write("Usage: ak-docs rules run <report.json> [--preset default|recommended|strict] [--severity rule=level] [--ignore rule]\n");
|
|
6867
|
+
return 2;
|
|
6868
|
+
}
|
|
6869
|
+
try {
|
|
6870
|
+
const { config } = loadProject(configPath);
|
|
6871
|
+
const report = parseReconciliationReport(JSON.parse(readFileSync21(resolve18(reportPath), "utf8")));
|
|
6872
|
+
const presetValue = optionValues(argv, "--preset")[0];
|
|
6873
|
+
const preset = presetValue === void 0 ? void 0 : ["default", "recommended", "strict"].includes(presetValue) ? presetValue : (() => {
|
|
6874
|
+
throw new Error(`Invalid rules preset "${presetValue}".`);
|
|
6875
|
+
})();
|
|
6876
|
+
const severity = {};
|
|
6877
|
+
for (const [rule, level] of Object.entries(parseRuleAssignments(optionValues(argv, "--severity"), parseRuleSeverity))) {
|
|
6878
|
+
severity[parseRuleId(rule)] = level;
|
|
6879
|
+
}
|
|
6880
|
+
const ignore = optionValues(argv, "--ignore").map(parseRuleId);
|
|
6881
|
+
const result = evaluateRules(report, {
|
|
6882
|
+
...config.rules ? { config: config.rules } : {},
|
|
6883
|
+
...preset ? { preset } : {},
|
|
6884
|
+
...Object.keys(severity).length ? { severity } : {},
|
|
6885
|
+
...ignore.length ? { ignore } : {},
|
|
6886
|
+
...optionValues(argv, "--critical-entity").length ? { criticalEntities: optionValues(argv, "--critical-entity") } : {},
|
|
6887
|
+
...optionValues(argv, "--critical-path").length ? { criticalPaths: optionValues(argv, "--critical-path") } : {}
|
|
6888
|
+
});
|
|
6889
|
+
if (wantsTextOutput(flags, config)) {
|
|
6890
|
+
writeLines([
|
|
6891
|
+
`Rules: ${result.mode}`,
|
|
6892
|
+
`Findings: ${result.findings.length}`,
|
|
6893
|
+
...result.findings.length ? result.findings.map((finding) => ` [${finding.severity}] ${finding.code}: ${finding.message}`) : [" (none)"],
|
|
6894
|
+
`Exit code: ${result.exitCode}`
|
|
6895
|
+
]);
|
|
6896
|
+
} else {
|
|
6897
|
+
writeJson2({ ok: result.exitCode === 0, reportHash: report.contentHash, ...result });
|
|
6898
|
+
}
|
|
6899
|
+
return result.exitCode;
|
|
6900
|
+
} catch (error) {
|
|
6901
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6902
|
+
`);
|
|
6903
|
+
return 2;
|
|
6904
|
+
}
|
|
6905
|
+
};
|
|
6906
|
+
var runFixCommand = (argv, positional, configPath) => {
|
|
6907
|
+
try {
|
|
6908
|
+
const { config, root } = loadProject(configPath);
|
|
6909
|
+
const action = positional[1];
|
|
6910
|
+
const proposalPath2 = positional[2];
|
|
6911
|
+
const sourceRevision2 = discoverRepository({ root, config }).sourceRevision;
|
|
6912
|
+
const fixOptions = { baseRevision: sourceRevision2, configurationHash: sha256NormalizedV1(config), ...config.project?.name ? { projectName: config.project.name } : {} };
|
|
6913
|
+
if (action === "propose") {
|
|
6914
|
+
const proposal2 = positional[2] === "links" ? createMarkdownLinkFixProposal(root, fixOptions) : positional[2] === "normalize" && positional[3] ? createArtifactNormalizationProposal(root, positional[3], fixOptions) : void 0;
|
|
6915
|
+
if (!proposal2) {
|
|
6916
|
+
writeJson2({ ok: true, proposal: null });
|
|
6917
|
+
return 0;
|
|
6918
|
+
}
|
|
6919
|
+
const outputPath = optionValues(argv, "--output")[0];
|
|
6920
|
+
if (outputPath) {
|
|
6921
|
+
mkdirSync8(dirname10(resolve18(root, outputPath)), { recursive: true });
|
|
6922
|
+
writeFileSync8(resolve18(root, outputPath), `${JSON.stringify(proposal2, null, 2)}
|
|
6923
|
+
`, "utf8");
|
|
6924
|
+
}
|
|
6925
|
+
writeJson2({ ok: true, proposal: proposal2, ...outputPath ? { proposalPath: resolve18(root, outputPath) } : {} });
|
|
6926
|
+
return 0;
|
|
6927
|
+
}
|
|
6928
|
+
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>]");
|
|
6929
|
+
const file = resolve18(root, proposalPath2);
|
|
6930
|
+
const proposal = JSON.parse(readFileSync21(file, "utf8"));
|
|
6931
|
+
const result = action === "approve" ? approveFixProposal(proposal, optionValues(argv, "--by")[0] ?? "human") : applyFixProposal(root, proposal, { currentRevision: sourceRevision2 });
|
|
6932
|
+
writeFileSync8(file, `${JSON.stringify(result, null, 2)}
|
|
6933
|
+
`, "utf8");
|
|
6934
|
+
writeJson2({ ok: true, proposal: result, proposalPath: file });
|
|
6935
|
+
return 0;
|
|
6936
|
+
} catch (error) {
|
|
6937
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6938
|
+
`);
|
|
6939
|
+
return 2;
|
|
6940
|
+
}
|
|
6941
|
+
};
|
|
6942
|
+
var runSuggestCommand = async (flags, configPath) => {
|
|
6943
|
+
try {
|
|
6944
|
+
const { config, root } = loadProject(configPath);
|
|
6945
|
+
const stateDir = resolve18(root, config.workflow?.stateDir ?? ".doc-bridge/workflow");
|
|
6946
|
+
const snapshot = parseDiscoverySnapshot(loadWorkflowStepOutput(stateDir, "normalize"));
|
|
6947
|
+
const report = parseReconciliationReport(loadWorkflowStepOutput(stateDir, "reconcile"));
|
|
6948
|
+
const adapter = createRegistryAgentAdapter(root, config, await loadRegistryAgentRunner(root, config));
|
|
6949
|
+
const proposal = await adapter.run(snapshot, report);
|
|
6950
|
+
const proposalPath2 = persistRegistryAgentProposal(stateDir, proposal);
|
|
6951
|
+
if (flags.has("--text")) writeLines([`Agent: ${adapter.metadata.id}`, `Proposal: ${proposal.proposalId}`, `Hash: ${proposal.contentHash}`, `Saved: ${proposalPath2}`]);
|
|
6952
|
+
else writeJson2({ ok: true, proposal, proposalPath: proposalPath2 });
|
|
6953
|
+
return 0;
|
|
6954
|
+
} catch (error) {
|
|
6955
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
6956
|
+
`);
|
|
6957
|
+
return 2;
|
|
6958
|
+
}
|
|
6959
|
+
};
|
|
4845
6960
|
var writeIfMissing = (path, contents) => {
|
|
4846
|
-
if (
|
|
4847
|
-
|
|
4848
|
-
|
|
6961
|
+
if (existsSync21(path)) return false;
|
|
6962
|
+
mkdirSync8(dirname10(path), { recursive: true });
|
|
6963
|
+
writeFileSync8(path, contents, "utf8");
|
|
4849
6964
|
return true;
|
|
4850
6965
|
};
|
|
4851
6966
|
var demoOwnership = {
|
|
@@ -4948,7 +7063,7 @@ var scaffoldWorkspaceDocs = (root, config) => {
|
|
|
4948
7063
|
const created = [];
|
|
4949
7064
|
const skipped = [];
|
|
4950
7065
|
for (const pkg of discoverPnpmPackages(root, config)) {
|
|
4951
|
-
const path =
|
|
7066
|
+
const path = resolve18(root, config.corpus.agent.root, "packages", `${pkg.id}.md`);
|
|
4952
7067
|
if (writeIfMissing(path, workspaceDocDraft(pkg.id, pkg.path))) created.push(path);
|
|
4953
7068
|
else skipped.push(path);
|
|
4954
7069
|
}
|
|
@@ -4958,11 +7073,11 @@ var bootstrapAgentDocs = (root, config) => {
|
|
|
4958
7073
|
const created = [];
|
|
4959
7074
|
const skipped = [];
|
|
4960
7075
|
for (const doc of scanHumanDocRecords(root, config)) {
|
|
4961
|
-
const raw =
|
|
7076
|
+
const raw = readFileSync21(doc.path, "utf8");
|
|
4962
7077
|
const body = raw.replace(/^---\n[\s\S]*?\n---\n?/, "");
|
|
4963
7078
|
const title = firstHeading(body) ?? doc.id;
|
|
4964
7079
|
const description = firstParagraph(body);
|
|
4965
|
-
const draftPath =
|
|
7080
|
+
const draftPath = resolve18(root, config.corpus.agent.root, "human", `${doc.id}.md`);
|
|
4966
7081
|
const draft = [
|
|
4967
7082
|
"---",
|
|
4968
7083
|
"type: knowledge",
|
|
@@ -5026,8 +7141,8 @@ var runCli = (argv) => {
|
|
|
5026
7141
|
return 1;
|
|
5027
7142
|
}
|
|
5028
7143
|
try {
|
|
5029
|
-
const abs =
|
|
5030
|
-
const raw =
|
|
7144
|
+
const abs = resolve18(file);
|
|
7145
|
+
const raw = readFileSync21(abs, "utf8");
|
|
5031
7146
|
const handoff = parseAgentHandoff(JSON.parse(raw));
|
|
5032
7147
|
writeJson2({ ok: true, handoff });
|
|
5033
7148
|
return 0;
|
|
@@ -5037,13 +7152,42 @@ var runCli = (argv) => {
|
|
|
5037
7152
|
return 1;
|
|
5038
7153
|
}
|
|
5039
7154
|
}
|
|
7155
|
+
if (command === "discover") {
|
|
7156
|
+
try {
|
|
7157
|
+
let config;
|
|
7158
|
+
let root = process.cwd();
|
|
7159
|
+
try {
|
|
7160
|
+
const loaded = loadProject(configPath);
|
|
7161
|
+
config = loaded.config;
|
|
7162
|
+
root = loaded.root;
|
|
7163
|
+
} catch (error) {
|
|
7164
|
+
if (!(error instanceof ConfigNotFoundError) || configPath) throw error;
|
|
7165
|
+
}
|
|
7166
|
+
const snapshot = discoverRepository({ root, ...config ? { config } : {} });
|
|
7167
|
+
if (wantsTextOutput(flags, config ?? { surfaces: { cli: { defaultFormat: "json" } } })) {
|
|
7168
|
+
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."]]);
|
|
7169
|
+
} else {
|
|
7170
|
+
writeJson2({ ok: true, snapshot, ...config ? {} : { proposedConfig: { schemaVersion: 1, corpus: { agent: { root: "docs/for-agents" } } } } });
|
|
7171
|
+
}
|
|
7172
|
+
return 0;
|
|
7173
|
+
} catch (error) {
|
|
7174
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
7175
|
+
`);
|
|
7176
|
+
return 2;
|
|
7177
|
+
}
|
|
7178
|
+
}
|
|
7179
|
+
if (command === "scan" || command === "reconcile" || command === "check" || command === "map") {
|
|
7180
|
+
return runWorkflowCommand(command, flags, configPath, argv);
|
|
7181
|
+
}
|
|
7182
|
+
if (command === "fix") return runFixCommand(argv, positional, configPath);
|
|
7183
|
+
if (command === "suggest") return runSuggestCommand(flags, configPath);
|
|
5040
7184
|
if (command === "init") {
|
|
5041
7185
|
const root = process.cwd();
|
|
5042
7186
|
const withDemo = flags.has("--demo") || !flags.has("--no-demo");
|
|
5043
|
-
const configFile =
|
|
5044
|
-
const docsIndex =
|
|
5045
|
-
const exampleDoc =
|
|
5046
|
-
const agentsMd =
|
|
7187
|
+
const configFile = resolve18(root, configPath ?? "doc-bridge.config.json");
|
|
7188
|
+
const docsIndex = resolve18(root, "docs/for-agents/INDEX.md");
|
|
7189
|
+
const exampleDoc = resolve18(root, "docs/for-agents/packages/example.md");
|
|
7190
|
+
const agentsMd = resolve18(root, "AGENTS.md");
|
|
5047
7191
|
const configWritten = writeIfMissing(configFile, initConfigContents(configFile, withDemo));
|
|
5048
7192
|
const indexWritten = writeIfMissing(
|
|
5049
7193
|
docsIndex,
|
|
@@ -5051,7 +7195,7 @@ var runCli = (argv) => {
|
|
|
5051
7195
|
);
|
|
5052
7196
|
const exampleWritten = withDemo ? writeIfMissing(exampleDoc, exampleAgentDoc) : false;
|
|
5053
7197
|
const agentsWritten = writeIfMissing(agentsMd, agentsMdSnippet);
|
|
5054
|
-
if (withDemo) writeIfMissing(
|
|
7198
|
+
if (withDemo) writeIfMissing(resolve18(root, "src/.gitkeep"), "");
|
|
5055
7199
|
const scaffold = flags.has("--scaffold-workspaces") ? scaffoldWorkspaceDocs(root, loadProject(configFile).config) : void 0;
|
|
5056
7200
|
writeJson2({
|
|
5057
7201
|
ok: true,
|
|
@@ -5230,6 +7374,7 @@ var runCli = (argv) => {
|
|
|
5230
7374
|
return 1;
|
|
5231
7375
|
}
|
|
5232
7376
|
}
|
|
7377
|
+
if (command === "rules") return runRulesCommand(argv, flags, positional, configPath);
|
|
5233
7378
|
if (command === "conformance") {
|
|
5234
7379
|
const action = positional[1];
|
|
5235
7380
|
const profile = positional[2];
|
|
@@ -5294,9 +7439,9 @@ var runCli = (argv) => {
|
|
|
5294
7439
|
const { config, root } = loadProject(configPath);
|
|
5295
7440
|
const report = runDoctor(root, config);
|
|
5296
7441
|
if (flags.has("--write-badge")) {
|
|
5297
|
-
const badgePath =
|
|
5298
|
-
|
|
5299
|
-
|
|
7442
|
+
const badgePath = resolve18(root, ".doc-bridge", "coverage-badge.json");
|
|
7443
|
+
mkdirSync8(dirname10(badgePath), { recursive: true });
|
|
7444
|
+
writeFileSync8(badgePath, `${formatDoctorBadgeJson(report.badge)}
|
|
5300
7445
|
`, "utf8");
|
|
5301
7446
|
}
|
|
5302
7447
|
if (flags.has("--badge")) {
|