@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/index.js
CHANGED
|
@@ -33,6 +33,14 @@ var applyConfigDefaults = (config) => {
|
|
|
33
33
|
preset: "minimal",
|
|
34
34
|
...config.gates
|
|
35
35
|
},
|
|
36
|
+
rules: {
|
|
37
|
+
mode: "default",
|
|
38
|
+
...config.rules
|
|
39
|
+
},
|
|
40
|
+
safety: {
|
|
41
|
+
redactSecrets: true,
|
|
42
|
+
...config.safety
|
|
43
|
+
},
|
|
36
44
|
surfaces: {
|
|
37
45
|
cli: {
|
|
38
46
|
bin: "ak-docs",
|
|
@@ -41,7 +49,22 @@ var applyConfigDefaults = (config) => {
|
|
|
41
49
|
},
|
|
42
50
|
mcp: {
|
|
43
51
|
enabled: true,
|
|
44
|
-
tools: [
|
|
52
|
+
tools: [
|
|
53
|
+
"handoff.resolve",
|
|
54
|
+
"doc.search",
|
|
55
|
+
"doc.get",
|
|
56
|
+
"gate.status",
|
|
57
|
+
"retriever.query",
|
|
58
|
+
"memory.classify",
|
|
59
|
+
"memory.promoteDraft",
|
|
60
|
+
"registry.topology",
|
|
61
|
+
"docbridge.snapshot",
|
|
62
|
+
"docbridge.report",
|
|
63
|
+
"docbridge.diagnostics",
|
|
64
|
+
"docbridge.relations",
|
|
65
|
+
"docbridge.run",
|
|
66
|
+
"docbridge.proposals"
|
|
67
|
+
],
|
|
45
68
|
transport: "stdio",
|
|
46
69
|
...config.surfaces?.mcp
|
|
47
70
|
},
|
|
@@ -159,6 +182,39 @@ var GatesConfigSchema = z.object({
|
|
|
159
182
|
).max(16).optional(),
|
|
160
183
|
options: z.record(z.string(), z.unknown()).optional()
|
|
161
184
|
}).strict();
|
|
185
|
+
var RuleIdSchema = z.enum([
|
|
186
|
+
"documentation-quality",
|
|
187
|
+
"graph-undocumented-relation",
|
|
188
|
+
"declared-unobserved-relation",
|
|
189
|
+
"unresolved-reference",
|
|
190
|
+
"conflicting-declaration",
|
|
191
|
+
"not-analyzed-coverage",
|
|
192
|
+
"stale-documentation",
|
|
193
|
+
"centrality-risk",
|
|
194
|
+
"critical-path-risk",
|
|
195
|
+
"freshness",
|
|
196
|
+
"ownership"
|
|
197
|
+
]);
|
|
198
|
+
var RuleSeveritySchema = z.enum(["off", "info", "warn", "error"]);
|
|
199
|
+
var RulesConfigSchema = z.object({
|
|
200
|
+
mode: z.enum(["default", "recommended", "strict"]).optional(),
|
|
201
|
+
severity: z.record(RuleIdSchema, RuleSeveritySchema).optional(),
|
|
202
|
+
ignore: z.array(RuleIdSchema).max(128).optional(),
|
|
203
|
+
criticalEntities: z.array(z.string().min(1).max(256)).max(128).optional(),
|
|
204
|
+
criticalPaths: z.array(z.string().min(1).max(512)).max(128).optional(),
|
|
205
|
+
warningThresholds: z.record(RuleIdSchema, z.number().int().min(1).max(1e5)).optional()
|
|
206
|
+
}).strict();
|
|
207
|
+
var WorkflowConfigSchema = z.object({
|
|
208
|
+
stateDir: z.string().min(1).max(512).optional()
|
|
209
|
+
}).strict();
|
|
210
|
+
var RepositorySafetyConfigSchema = z.object({
|
|
211
|
+
exclude: z.array(z.string().min(1).max(512)).max(128).optional(),
|
|
212
|
+
maxFiles: z.number().int().positive().max(1e6).optional(),
|
|
213
|
+
maxBytes: z.number().int().positive().max(1e10).optional(),
|
|
214
|
+
maxTimeMs: z.number().int().positive().max(864e5).optional(),
|
|
215
|
+
maxMemoryMb: z.number().int().positive().max(1048576).optional(),
|
|
216
|
+
redactSecrets: z.boolean().optional()
|
|
217
|
+
}).strict();
|
|
162
218
|
var SurfacesConfigSchema = z.object({
|
|
163
219
|
cli: z.object({
|
|
164
220
|
bin: z.string().min(1).max(64).optional(),
|
|
@@ -176,7 +232,13 @@ var SurfacesConfigSchema = z.object({
|
|
|
176
232
|
"retriever.query",
|
|
177
233
|
"memory.classify",
|
|
178
234
|
"memory.promoteDraft",
|
|
179
|
-
"registry.topology"
|
|
235
|
+
"registry.topology",
|
|
236
|
+
"docbridge.snapshot",
|
|
237
|
+
"docbridge.report",
|
|
238
|
+
"docbridge.diagnostics",
|
|
239
|
+
"docbridge.relations",
|
|
240
|
+
"docbridge.run",
|
|
241
|
+
"docbridge.proposals"
|
|
180
242
|
])
|
|
181
243
|
).max(16).optional(),
|
|
182
244
|
transport: z.enum(["stdio", "http"]).optional(),
|
|
@@ -219,7 +281,13 @@ var IntelligenceConfigSchema = z.object({
|
|
|
219
281
|
}).strict().optional()
|
|
220
282
|
}).strict().optional(),
|
|
221
283
|
runtime: z.enum(["agentskit", "custom"]).optional(),
|
|
222
|
-
runtimeModule: z.string().min(1).max(512).optional()
|
|
284
|
+
runtimeModule: z.string().min(1).max(512).optional(),
|
|
285
|
+
registry: z.object({
|
|
286
|
+
enabled: z.boolean().optional(),
|
|
287
|
+
agentId: z.string().min(1).max(256).optional(),
|
|
288
|
+
agentRoot: z.string().min(1).max(512).optional(),
|
|
289
|
+
runnerModule: z.string().min(1).max(512).optional()
|
|
290
|
+
}).strict().optional()
|
|
223
291
|
}).strict();
|
|
224
292
|
var FederationConfigSchema = z.object({
|
|
225
293
|
enabled: z.boolean().optional(),
|
|
@@ -309,6 +377,9 @@ var DocBridgeConfigV1Schema = z.object({
|
|
|
309
377
|
index: IndexConfigSchema.optional(),
|
|
310
378
|
routing: RoutingConfigSchema.optional(),
|
|
311
379
|
gates: GatesConfigSchema.optional(),
|
|
380
|
+
rules: RulesConfigSchema.optional(),
|
|
381
|
+
workflow: WorkflowConfigSchema.optional(),
|
|
382
|
+
safety: RepositorySafetyConfigSchema.optional(),
|
|
312
383
|
surfaces: SurfacesConfigSchema.optional(),
|
|
313
384
|
intelligence: IntelligenceConfigSchema.optional(),
|
|
314
385
|
federation: FederationConfigSchema.optional(),
|
|
@@ -995,6 +1066,231 @@ var DocBridgeJsonSchemas = {
|
|
|
995
1066
|
memoryCandidateV1: MemoryCandidateV1JsonSchema
|
|
996
1067
|
};
|
|
997
1068
|
|
|
1069
|
+
// src/schemas/knowledge.ts
|
|
1070
|
+
import { z as z5 } from "zod";
|
|
1071
|
+
var KNOWLEDGE_SCHEMA_VERSION = 1;
|
|
1072
|
+
var KNOWLEDGE_CONTENT_HASH_ALGO = "sha256-normalized-v1";
|
|
1073
|
+
var hash = z5.string().regex(/^[a-f0-9]{64}$/);
|
|
1074
|
+
var boundedString = (max) => z5.string().min(1).max(max);
|
|
1075
|
+
var ProvenanceSchema = z5.enum(["observed", "declared", "proposed"]);
|
|
1076
|
+
var FindingStatusSchema = z5.enum([
|
|
1077
|
+
"confirmed",
|
|
1078
|
+
"undocumented",
|
|
1079
|
+
"stale-or-unverified",
|
|
1080
|
+
"conflict",
|
|
1081
|
+
"unresolved",
|
|
1082
|
+
"not-analyzed"
|
|
1083
|
+
]);
|
|
1084
|
+
var DiagnosticSeveritySchema = z5.enum(["off", "info", "warn", "error"]);
|
|
1085
|
+
var EvidenceSourceSchema = z5.enum([
|
|
1086
|
+
"code",
|
|
1087
|
+
"configuration",
|
|
1088
|
+
"documentation",
|
|
1089
|
+
"agent",
|
|
1090
|
+
"derived"
|
|
1091
|
+
]);
|
|
1092
|
+
var EvidenceSchema = z5.object({
|
|
1093
|
+
source: EvidenceSourceSchema,
|
|
1094
|
+
path: boundedString(512),
|
|
1095
|
+
lineStart: z5.number().int().positive().optional(),
|
|
1096
|
+
lineEnd: z5.number().int().positive().optional(),
|
|
1097
|
+
contentHash: hash.optional(),
|
|
1098
|
+
context: z5.string().max(1024).optional()
|
|
1099
|
+
}).strict().superRefine((value, context) => {
|
|
1100
|
+
if (value.lineStart !== void 0 && value.lineEnd !== void 0 && value.lineEnd < value.lineStart) {
|
|
1101
|
+
context.addIssue({
|
|
1102
|
+
code: z5.ZodIssueCode.custom,
|
|
1103
|
+
path: ["lineEnd"],
|
|
1104
|
+
message: "Must be greater than or equal to lineStart"
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
});
|
|
1108
|
+
var CoverageStatusSchema = z5.enum(["complete", "partial", "not-analyzed"]);
|
|
1109
|
+
var CoverageSchema = z5.object({
|
|
1110
|
+
analyzer: boundedString(128),
|
|
1111
|
+
scope: boundedString(512),
|
|
1112
|
+
status: CoverageStatusSchema,
|
|
1113
|
+
reason: z5.string().max(1024).optional(),
|
|
1114
|
+
evidence: z5.array(EvidenceSchema).max(32).optional()
|
|
1115
|
+
}).strict();
|
|
1116
|
+
var ProjectIdentitySchema = z5.object({
|
|
1117
|
+
name: boundedString(128),
|
|
1118
|
+
root: boundedString(512).optional()
|
|
1119
|
+
}).strict();
|
|
1120
|
+
var ArtifactMetadata = {
|
|
1121
|
+
schemaVersion: z5.literal(KNOWLEDGE_SCHEMA_VERSION),
|
|
1122
|
+
contentHash: hash,
|
|
1123
|
+
contentHashAlgo: z5.literal(KNOWLEDGE_CONTENT_HASH_ALGO),
|
|
1124
|
+
project: ProjectIdentitySchema,
|
|
1125
|
+
sourceRevision: boundedString(128),
|
|
1126
|
+
sourceRevisionKind: z5.enum(["git", "content"]),
|
|
1127
|
+
configurationHash: hash,
|
|
1128
|
+
pipelineVersion: boundedString(64),
|
|
1129
|
+
analyzerVersions: z5.record(boundedString(128), boundedString(64))
|
|
1130
|
+
};
|
|
1131
|
+
var EntitySchema = z5.object({
|
|
1132
|
+
id: boundedString(256),
|
|
1133
|
+
kind: boundedString(128),
|
|
1134
|
+
name: boundedString(256),
|
|
1135
|
+
path: boundedString(512).optional(),
|
|
1136
|
+
aliases: z5.array(boundedString(256)).max(32).optional(),
|
|
1137
|
+
provenance: ProvenanceSchema,
|
|
1138
|
+
evidence: z5.array(EvidenceSchema).max(64),
|
|
1139
|
+
metadata: z5.record(z5.unknown()).optional()
|
|
1140
|
+
}).strict();
|
|
1141
|
+
var RelationSchema = z5.object({
|
|
1142
|
+
id: boundedString(256),
|
|
1143
|
+
kind: boundedString(128),
|
|
1144
|
+
from: boundedString(256),
|
|
1145
|
+
to: boundedString(256),
|
|
1146
|
+
discriminator: boundedString(256).optional(),
|
|
1147
|
+
provenance: ProvenanceSchema,
|
|
1148
|
+
evidence: z5.array(EvidenceSchema).max(64),
|
|
1149
|
+
metadata: z5.record(z5.unknown()).optional()
|
|
1150
|
+
}).strict();
|
|
1151
|
+
var DiscoverySnapshotV1Schema = z5.object({
|
|
1152
|
+
type: z5.literal("discovery-snapshot"),
|
|
1153
|
+
...ArtifactMetadata,
|
|
1154
|
+
entities: z5.array(EntitySchema).max(5e4),
|
|
1155
|
+
relations: z5.array(RelationSchema).max(1e5),
|
|
1156
|
+
coverage: z5.array(CoverageSchema).max(1e3)
|
|
1157
|
+
}).strict().superRefine((value, context) => {
|
|
1158
|
+
const entityIds = /* @__PURE__ */ new Set();
|
|
1159
|
+
for (const [index, entity] of value.entities.entries()) {
|
|
1160
|
+
if (entityIds.has(entity.id)) {
|
|
1161
|
+
context.addIssue({ code: z5.ZodIssueCode.custom, path: ["entities", index, "id"], message: `Duplicate entity id: ${entity.id}` });
|
|
1162
|
+
}
|
|
1163
|
+
entityIds.add(entity.id);
|
|
1164
|
+
}
|
|
1165
|
+
const relationIds = /* @__PURE__ */ new Set();
|
|
1166
|
+
for (const [index, relation] of value.relations.entries()) {
|
|
1167
|
+
if (relationIds.has(relation.id)) {
|
|
1168
|
+
context.addIssue({ code: z5.ZodIssueCode.custom, path: ["relations", index, "id"], message: `Duplicate relation id: ${relation.id}` });
|
|
1169
|
+
}
|
|
1170
|
+
relationIds.add(relation.id);
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
var DiagnosticSchema = z5.object({
|
|
1174
|
+
id: boundedString(256),
|
|
1175
|
+
code: boundedString(128),
|
|
1176
|
+
status: FindingStatusSchema,
|
|
1177
|
+
severity: DiagnosticSeveritySchema,
|
|
1178
|
+
message: boundedString(2048),
|
|
1179
|
+
evidence: z5.array(EvidenceSchema).max(64),
|
|
1180
|
+
entityIds: z5.array(boundedString(256)).max(64).optional(),
|
|
1181
|
+
relationIds: z5.array(boundedString(256)).max(64).optional(),
|
|
1182
|
+
remediation: z5.string().max(2048).optional()
|
|
1183
|
+
}).strict();
|
|
1184
|
+
var ReconciliationReportV1Schema = z5.object({
|
|
1185
|
+
type: z5.literal("reconciliation-report"),
|
|
1186
|
+
...ArtifactMetadata,
|
|
1187
|
+
snapshotHash: hash,
|
|
1188
|
+
diagnostics: z5.array(DiagnosticSchema).max(1e5),
|
|
1189
|
+
summary: z5.object({
|
|
1190
|
+
entityCount: z5.number().int().nonnegative(),
|
|
1191
|
+
relationCount: z5.number().int().nonnegative(),
|
|
1192
|
+
diagnosticCount: z5.number().int().nonnegative()
|
|
1193
|
+
}).strict()
|
|
1194
|
+
}).strict();
|
|
1195
|
+
var WorkflowStateSchema = z5.enum([
|
|
1196
|
+
"created",
|
|
1197
|
+
"discovering",
|
|
1198
|
+
"analyzed",
|
|
1199
|
+
"compared",
|
|
1200
|
+
"awaiting-agent",
|
|
1201
|
+
"proposed",
|
|
1202
|
+
"awaiting-approval",
|
|
1203
|
+
"validating",
|
|
1204
|
+
"delivered",
|
|
1205
|
+
"failed",
|
|
1206
|
+
"cancelled",
|
|
1207
|
+
"stale",
|
|
1208
|
+
"superseded"
|
|
1209
|
+
]);
|
|
1210
|
+
var WorkflowStepSchema = z5.object({
|
|
1211
|
+
name: boundedString(128),
|
|
1212
|
+
status: z5.enum(["pending", "running", "completed", "failed", "skipped"]),
|
|
1213
|
+
inputHash: hash,
|
|
1214
|
+
outputHash: hash.optional(),
|
|
1215
|
+
artifactRefs: z5.array(boundedString(512)).max(32).optional()
|
|
1216
|
+
}).strict();
|
|
1217
|
+
var WorkflowTransitionSchema = z5.object({
|
|
1218
|
+
from: WorkflowStateSchema.nullable(),
|
|
1219
|
+
to: WorkflowStateSchema,
|
|
1220
|
+
at: z5.string().datetime(),
|
|
1221
|
+
reason: z5.string().max(1024).optional()
|
|
1222
|
+
}).strict();
|
|
1223
|
+
var WorkflowRunV1Schema = z5.object({
|
|
1224
|
+
type: z5.literal("workflow-run"),
|
|
1225
|
+
...ArtifactMetadata,
|
|
1226
|
+
runId: boundedString(128),
|
|
1227
|
+
state: WorkflowStateSchema,
|
|
1228
|
+
steps: z5.array(WorkflowStepSchema).max(32),
|
|
1229
|
+
transitions: z5.array(WorkflowTransitionSchema).max(1e3),
|
|
1230
|
+
artifactRefs: z5.array(boundedString(512)).max(128)
|
|
1231
|
+
}).strict();
|
|
1232
|
+
var ProposalOriginSchema = z5.object({
|
|
1233
|
+
kind: z5.enum(["registry-agent", "manual", "deterministic"]),
|
|
1234
|
+
id: boundedString(256).optional(),
|
|
1235
|
+
version: boundedString(64).optional(),
|
|
1236
|
+
provider: boundedString(128).optional(),
|
|
1237
|
+
model: boundedString(256).optional(),
|
|
1238
|
+
capabilities: z5.array(boundedString(128)).max(32).optional()
|
|
1239
|
+
}).strict().superRefine((value, context) => {
|
|
1240
|
+
if (value.kind === "registry-agent" && value.id === void 0) {
|
|
1241
|
+
context.addIssue({ code: z5.ZodIssueCode.custom, path: ["id"], message: "Registry agent origin requires an id" });
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
var AgentProposalV1Schema = z5.object({
|
|
1245
|
+
type: z5.literal("agent-proposal"),
|
|
1246
|
+
...ArtifactMetadata,
|
|
1247
|
+
proposalId: boundedString(128),
|
|
1248
|
+
baseSnapshotHash: hash,
|
|
1249
|
+
baseReportHash: hash,
|
|
1250
|
+
relatedDiagnosticIds: z5.array(boundedString(256)).max(64),
|
|
1251
|
+
rationale: boundedString(4e3),
|
|
1252
|
+
confidence: z5.number().min(0).max(1),
|
|
1253
|
+
evidence: z5.array(EvidenceSchema).max(128),
|
|
1254
|
+
intendedChanges: z5.array(boundedString(4e3)).max(64),
|
|
1255
|
+
origin: ProposalOriginSchema,
|
|
1256
|
+
checks: z5.array(boundedString(512)).max(32)
|
|
1257
|
+
}).strict();
|
|
1258
|
+
var FixProposalStatusSchema = z5.enum(["proposed", "approved", "rejected", "stale", "applied", "failed"]);
|
|
1259
|
+
var AffectedFileSchema = z5.object({
|
|
1260
|
+
path: boundedString(512),
|
|
1261
|
+
contentHash: hash
|
|
1262
|
+
}).strict();
|
|
1263
|
+
var FixChangeSchema = z5.object({
|
|
1264
|
+
path: boundedString(512),
|
|
1265
|
+
before: z5.string().max(1e5),
|
|
1266
|
+
after: z5.string().max(1e5)
|
|
1267
|
+
}).strict();
|
|
1268
|
+
var FixProposalV1Schema = z5.object({
|
|
1269
|
+
type: z5.literal("fix-proposal"),
|
|
1270
|
+
...ArtifactMetadata,
|
|
1271
|
+
proposalId: boundedString(128),
|
|
1272
|
+
baseRevision: boundedString(128),
|
|
1273
|
+
affectedFiles: z5.array(AffectedFileSchema).max(256),
|
|
1274
|
+
changes: z5.array(FixChangeSchema).max(256).optional(),
|
|
1275
|
+
preconditions: z5.array(boundedString(2048)).max(64),
|
|
1276
|
+
diff: boundedString(1e5),
|
|
1277
|
+
postconditions: z5.array(boundedString(2048)).max(64),
|
|
1278
|
+
approval: z5.object({
|
|
1279
|
+
proposalHash: hash,
|
|
1280
|
+
approvedAt: z5.string().datetime(),
|
|
1281
|
+
approvedBy: boundedString(256)
|
|
1282
|
+
}).strict().optional(),
|
|
1283
|
+
status: FixProposalStatusSchema
|
|
1284
|
+
}).strict().superRefine((value, context) => {
|
|
1285
|
+
if ((value.status === "approved" || value.status === "applied") && value.approval === void 0) {
|
|
1286
|
+
context.addIssue({
|
|
1287
|
+
code: z5.ZodIssueCode.custom,
|
|
1288
|
+
path: ["approval"],
|
|
1289
|
+
message: `Fix proposal status ${value.status} requires an approval record`
|
|
1290
|
+
});
|
|
1291
|
+
}
|
|
1292
|
+
});
|
|
1293
|
+
|
|
998
1294
|
// src/validate.ts
|
|
999
1295
|
var zodIssues = (error) => error.issues.map((issue) => ({
|
|
1000
1296
|
path: issue.path.join(".") || "(root)",
|
|
@@ -1020,6 +1316,11 @@ ${result.issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`
|
|
|
1020
1316
|
var parseAgentSearch = (input) => AgentSearchV1Schema.parse(input);
|
|
1021
1317
|
var parseDocBridgeIndex = (input) => DocBridgeIndexV1Schema.parse(input);
|
|
1022
1318
|
var parseMemoryCandidate = (input) => MemoryCandidateV1Schema.parse(input);
|
|
1319
|
+
var parseDiscoverySnapshot = (input) => DiscoverySnapshotV1Schema.parse(input);
|
|
1320
|
+
var parseReconciliationReport = (input) => ReconciliationReportV1Schema.parse(input);
|
|
1321
|
+
var parseWorkflowRun = (input) => WorkflowRunV1Schema.parse(input);
|
|
1322
|
+
var parseAgentProposal = (input) => AgentProposalV1Schema.parse(input);
|
|
1323
|
+
var parseFixProposal = (input) => FixProposalV1Schema.parse(input);
|
|
1023
1324
|
var parseDocBridgeConfig = (input) => {
|
|
1024
1325
|
const result = DocBridgeConfigV1Schema.safeParse(input);
|
|
1025
1326
|
if (!result.success) {
|
|
@@ -1552,7 +1853,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1552
1853
|
};
|
|
1553
1854
|
|
|
1554
1855
|
// src/version.ts
|
|
1555
|
-
var PACKAGE_VERSION = "1.
|
|
1856
|
+
var PACKAGE_VERSION = "1.5.1";
|
|
1556
1857
|
|
|
1557
1858
|
// src/index-builder/capabilities.ts
|
|
1558
1859
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -1597,23 +1898,28 @@ var sortValue = (value) => {
|
|
|
1597
1898
|
}
|
|
1598
1899
|
return value;
|
|
1599
1900
|
};
|
|
1901
|
+
var canonicalJsonV1 = (payload) => JSON.stringify(sortValue(payload));
|
|
1600
1902
|
var sha256NormalizedV1 = (payload) => {
|
|
1601
|
-
const normalized =
|
|
1903
|
+
const normalized = canonicalJsonV1(payload);
|
|
1602
1904
|
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
1603
1905
|
};
|
|
1906
|
+
var contentHashForArtifactV1 = (artifact2) => {
|
|
1907
|
+
const { contentHash: _contentHash, ...payload } = artifact2;
|
|
1908
|
+
return sha256NormalizedV1(payload);
|
|
1909
|
+
};
|
|
1604
1910
|
|
|
1605
1911
|
// src/index-builder/llms-txt.ts
|
|
1606
1912
|
var routePath = (path, pathPrefix) => {
|
|
1607
1913
|
const normalizedPath = path.replaceAll("\\", "/").replace(/\.(?:md|mdx)$/, "");
|
|
1608
1914
|
const normalizedPrefix = pathPrefix?.replaceAll("\\", "/").replace(/\/$/, "");
|
|
1609
|
-
const
|
|
1610
|
-
return
|
|
1915
|
+
const relativePath3 = normalizedPrefix && normalizedPath.startsWith(`${normalizedPrefix}/`) ? normalizedPath.slice(normalizedPrefix.length + 1) : normalizedPath;
|
|
1916
|
+
return relativePath3.replace(/^\/+/, "");
|
|
1611
1917
|
};
|
|
1612
1918
|
var knowledgeUrl = (path, options = {}) => {
|
|
1613
|
-
const
|
|
1919
|
+
const relativePath3 = routePath(path, options.pathPrefix);
|
|
1614
1920
|
if (!options.urlPrefix) return path;
|
|
1615
1921
|
const base = options.urlPrefix.endsWith("/") ? options.urlPrefix : `${options.urlPrefix}/`;
|
|
1616
|
-
return new URL(
|
|
1922
|
+
return new URL(relativePath3, base).toString();
|
|
1617
1923
|
};
|
|
1618
1924
|
var renderLlmsTxt = (config, knowledge, projectName2) => {
|
|
1619
1925
|
const preamble = config.index?.llmsTxt?.preamble ?? `# ${projectName2}
|
|
@@ -2259,6 +2565,1428 @@ var buildDocBridgeIndex = (opts) => {
|
|
|
2259
2565
|
};
|
|
2260
2566
|
};
|
|
2261
2567
|
|
|
2568
|
+
// src/discovery/repository.ts
|
|
2569
|
+
import { execFileSync } from "child_process";
|
|
2570
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
2571
|
+
import { basename as basename4, dirname as dirname5, extname, join as join8, relative as relative5, resolve as resolve7, sep as sep6 } from "path";
|
|
2572
|
+
import * as ts from "typescript";
|
|
2573
|
+
|
|
2574
|
+
// src/safety/repository.ts
|
|
2575
|
+
import { lstatSync as lstatSync3, readdirSync as readdirSync3, realpathSync as realpathSync6, statSync as statSync3 } from "fs";
|
|
2576
|
+
import { isAbsolute as isAbsolute4, relative as relative4, resolve as resolve6, sep as sep5 } from "path";
|
|
2577
|
+
import { minimatch as minimatch4 } from "minimatch";
|
|
2578
|
+
var DEFAULT_SAFETY_EXCLUDES = ["**/.git/**", "**/node_modules/**", "**/dist/**", "**/build/**", "**/coverage/**", "**/.doc-bridge/**", "**/.env", "**/.env.*", "**/*secret*", "**/*credential*", "**/*.pem", "**/*.key"];
|
|
2579
|
+
var containedPath = (root, candidate) => {
|
|
2580
|
+
const projectRoot = realpathSync6.native(resolve6(root));
|
|
2581
|
+
const unresolved = resolve6(projectRoot, candidate);
|
|
2582
|
+
const unresolvedRelative = relative4(projectRoot, unresolved);
|
|
2583
|
+
if (isAbsolute4(unresolvedRelative) || unresolvedRelative === ".." || unresolvedRelative.startsWith(`..${sep5}`)) return void 0;
|
|
2584
|
+
try {
|
|
2585
|
+
const canonical = realpathSync6.native(unresolved);
|
|
2586
|
+
const canonicalRelative = relative4(projectRoot, canonical);
|
|
2587
|
+
return isAbsolute4(canonicalRelative) || canonicalRelative === ".." || canonicalRelative.startsWith(`..${sep5}`) ? void 0 : canonical;
|
|
2588
|
+
} catch {
|
|
2589
|
+
return unresolved;
|
|
2590
|
+
}
|
|
2591
|
+
};
|
|
2592
|
+
var safeWalkFiles = (root, options = {}) => {
|
|
2593
|
+
const projectRoot = resolve6(root);
|
|
2594
|
+
const extensions = options.extensions ?? [];
|
|
2595
|
+
const excludes = options.exclude ?? DEFAULT_SAFETY_EXCLUDES;
|
|
2596
|
+
const files = [];
|
|
2597
|
+
let bytes = 0;
|
|
2598
|
+
let reason;
|
|
2599
|
+
const started = Date.now();
|
|
2600
|
+
const matchesExclude = (path) => excludes.some((pattern) => minimatch4(path, pattern, { dot: true }));
|
|
2601
|
+
const visit = (directory) => {
|
|
2602
|
+
if (reason) return;
|
|
2603
|
+
if (options.maxTimeMs !== void 0 && Date.now() - started >= options.maxTimeMs) {
|
|
2604
|
+
reason = `Repository scan exceeded the ${options.maxTimeMs} ms time limit.`;
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
if (options.maxMemoryMb !== void 0 && process.memoryUsage().heapUsed > options.maxMemoryMb * 1024 * 1024) {
|
|
2608
|
+
reason = `Repository scan exceeded the ${options.maxMemoryMb} MiB memory limit.`;
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
let entries;
|
|
2612
|
+
try {
|
|
2613
|
+
entries = readdirSync3(directory);
|
|
2614
|
+
} catch {
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
for (const name of entries.sort()) {
|
|
2618
|
+
const absolute = resolve6(directory, name);
|
|
2619
|
+
const relativePath3 = relative4(projectRoot, absolute).split(sep5).join("/");
|
|
2620
|
+
if (matchesExclude(relativePath3) || name === ".git") continue;
|
|
2621
|
+
let stats;
|
|
2622
|
+
try {
|
|
2623
|
+
stats = lstatSync3(absolute);
|
|
2624
|
+
} catch {
|
|
2625
|
+
continue;
|
|
2626
|
+
}
|
|
2627
|
+
if (stats.isSymbolicLink()) continue;
|
|
2628
|
+
if (stats.isDirectory()) {
|
|
2629
|
+
visit(absolute);
|
|
2630
|
+
if (reason) return;
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
if (!stats.isFile() || extensions.length > 0 && !extensions.some((extension) => name.endsWith(extension))) continue;
|
|
2634
|
+
if (files.length >= (options.maxFiles ?? 1e4)) {
|
|
2635
|
+
reason = `Repository scan exceeded the ${options.maxFiles ?? 1e4} file limit.`;
|
|
2636
|
+
return;
|
|
2637
|
+
}
|
|
2638
|
+
bytes += statSync3(absolute).size;
|
|
2639
|
+
if (options.maxBytes !== void 0 && bytes > options.maxBytes) {
|
|
2640
|
+
reason = `Repository scan exceeded the ${options.maxBytes} byte limit.`;
|
|
2641
|
+
return;
|
|
2642
|
+
}
|
|
2643
|
+
files.push(absolute);
|
|
2644
|
+
}
|
|
2645
|
+
};
|
|
2646
|
+
visit(projectRoot);
|
|
2647
|
+
return { files: files.sort(), incomplete: reason !== void 0, ...reason ? { reason } : {} };
|
|
2648
|
+
};
|
|
2649
|
+
var SECRET_PATTERNS = [
|
|
2650
|
+
/\b(?:sk|pk)[_-](?:live|test)[_-][A-Za-z0-9_-]{12,}\b/g,
|
|
2651
|
+
/\b(?:ghp|github_pat|xox[baprs])_[A-Za-z0-9_-]{12,}\b/g,
|
|
2652
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
2653
|
+
/(?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*["']?[^\s,"']+/gi
|
|
2654
|
+
];
|
|
2655
|
+
var redactSecrets = (value) => SECRET_PATTERNS.reduce((result, pattern) => result.replace(pattern, "[REDACTED]"), value);
|
|
2656
|
+
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;
|
|
2657
|
+
|
|
2658
|
+
// src/discovery/repository.ts
|
|
2659
|
+
var SOURCE_EXTENSIONS = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"];
|
|
2660
|
+
var DOCUMENT_EXTENSIONS = [".md", ".mdx"];
|
|
2661
|
+
var DEFAULT_MAX_FILES2 = 1e4;
|
|
2662
|
+
var EMPTY_HASH = "0".repeat(64);
|
|
2663
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2664
|
+
var readJson = (path) => {
|
|
2665
|
+
try {
|
|
2666
|
+
const value = JSON.parse(readFileSync8(path, "utf8"));
|
|
2667
|
+
return isRecord2(value) ? { value } : { error: "JSON root is not an object" };
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
2670
|
+
}
|
|
2671
|
+
};
|
|
2672
|
+
var relativePath = (root, path) => toPosix(relative5(root, path)) || ".";
|
|
2673
|
+
var MAX_ID_LENGTH = 256;
|
|
2674
|
+
var ID_HASH_LENGTH = 32;
|
|
2675
|
+
var entityId = (kind, value) => {
|
|
2676
|
+
const fullId = `${kind}:${value}`;
|
|
2677
|
+
if (fullId.length <= MAX_ID_LENGTH) return fullId;
|
|
2678
|
+
const suffix = `:${sha256NormalizedV1(fullId).slice(0, ID_HASH_LENGTH)}`;
|
|
2679
|
+
return `${fullId.slice(0, MAX_ID_LENGTH - suffix.length)}${suffix}`;
|
|
2680
|
+
};
|
|
2681
|
+
var lineEvidence = (source, root, path, lineStart, lineEnd) => ({
|
|
2682
|
+
source,
|
|
2683
|
+
path: relativePath(root, path),
|
|
2684
|
+
...lineStart !== void 0 ? { lineStart } : {},
|
|
2685
|
+
...lineEnd !== void 0 ? { lineEnd } : {}
|
|
2686
|
+
});
|
|
2687
|
+
var firstLineContaining = (text, pattern) => {
|
|
2688
|
+
const line = text.split(/\r?\n/).findIndex((value) => value.includes(pattern));
|
|
2689
|
+
return line >= 0 ? line + 1 : void 0;
|
|
2690
|
+
};
|
|
2691
|
+
var packageName = (manifest, fallback) => typeof manifest.name === "string" && manifest.name.length > 0 ? manifest.name : fallback || void 0;
|
|
2692
|
+
var workspacePatterns = (root, rootManifest) => {
|
|
2693
|
+
const fromPackageJson = rootManifest?.workspaces;
|
|
2694
|
+
if (Array.isArray(fromPackageJson)) return fromPackageJson.filter((value) => typeof value === "string");
|
|
2695
|
+
if (isRecord2(fromPackageJson) && Array.isArray(fromPackageJson.packages)) {
|
|
2696
|
+
return fromPackageJson.packages.filter((value) => typeof value === "string");
|
|
2697
|
+
}
|
|
2698
|
+
const workspacePath = join8(root, "pnpm-workspace.yaml");
|
|
2699
|
+
if (!existsSync9(workspacePath)) return [];
|
|
2700
|
+
const patterns = [];
|
|
2701
|
+
let inPackages = false;
|
|
2702
|
+
for (const line of readFileSync8(workspacePath, "utf8").split(/\r?\n/)) {
|
|
2703
|
+
const trimmed = line.trim();
|
|
2704
|
+
if (trimmed === "packages:") {
|
|
2705
|
+
inPackages = true;
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
if (!inPackages) continue;
|
|
2709
|
+
if (trimmed.startsWith("- ")) {
|
|
2710
|
+
patterns.push(trimmed.slice(2).trim().replace(/^['"]|['"]$/g, ""));
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
if (trimmed && !trimmed.startsWith("#")) inPackages = false;
|
|
2714
|
+
}
|
|
2715
|
+
return patterns;
|
|
2716
|
+
};
|
|
2717
|
+
var discoverPackages = (root, rootManifest, config) => {
|
|
2718
|
+
const packages = [];
|
|
2719
|
+
const coverage = [];
|
|
2720
|
+
const rootManifestPath = join8(root, "package.json");
|
|
2721
|
+
if (rootManifest) {
|
|
2722
|
+
const name = packageName(rootManifest, "");
|
|
2723
|
+
packages.push({
|
|
2724
|
+
id: entityId("package", packageName(rootManifest, "root") ?? "root"),
|
|
2725
|
+
...name ? { name } : {},
|
|
2726
|
+
path: ".",
|
|
2727
|
+
absPath: root,
|
|
2728
|
+
manifestPath: rootManifestPath,
|
|
2729
|
+
manifest: rootManifest
|
|
2730
|
+
});
|
|
2731
|
+
}
|
|
2732
|
+
const configuredPatterns = config?.routing?.options?.packages;
|
|
2733
|
+
const patterns = configuredPatterns?.length ? [...configuredPatterns] : workspacePatterns(root, rootManifest);
|
|
2734
|
+
if (!patterns.length) {
|
|
2735
|
+
coverage.push({ status: "complete" });
|
|
2736
|
+
return { packages, coverage };
|
|
2737
|
+
}
|
|
2738
|
+
const dirs = expandWorkspaceGlobs(root, patterns);
|
|
2739
|
+
for (const absPath of dirs) {
|
|
2740
|
+
const manifestPath = join8(absPath, "package.json");
|
|
2741
|
+
if (!existsSync9(manifestPath)) continue;
|
|
2742
|
+
const parsed = readJson(manifestPath);
|
|
2743
|
+
if (!parsed.value) {
|
|
2744
|
+
coverage.push({ status: "partial", reason: `${relativePath(root, manifestPath)}: ${parsed.error ?? "invalid package.json"}` });
|
|
2745
|
+
continue;
|
|
2746
|
+
}
|
|
2747
|
+
const path = relativePath(root, absPath);
|
|
2748
|
+
const name = packageName(parsed.value, path);
|
|
2749
|
+
const id = entityId("package", name ?? path);
|
|
2750
|
+
const duplicate = packages.find((pkg) => pkg.id === id);
|
|
2751
|
+
if (duplicate && duplicate.absPath !== absPath) {
|
|
2752
|
+
throw new Error(`Package identity collision for "${id}": "${duplicate.path}" and "${path}".`);
|
|
2753
|
+
}
|
|
2754
|
+
if (!duplicate) packages.push({ id, ...name ? { name } : {}, path, absPath, manifestPath, manifest: parsed.value });
|
|
2755
|
+
}
|
|
2756
|
+
coverage.push({ status: "complete" });
|
|
2757
|
+
return { packages: packages.sort((a, b) => a.id.localeCompare(b.id)), coverage };
|
|
2758
|
+
};
|
|
2759
|
+
var packageForModule = (packages, absPath) => [...packages].filter((pkg) => absPath === pkg.absPath || absPath.startsWith(`${pkg.absPath}${sep6}`)).sort((a, b) => b.absPath.length - a.absPath.length)[0];
|
|
2760
|
+
var readCompilerOptions = (root) => {
|
|
2761
|
+
const configPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
|
|
2762
|
+
if (!configPath) return { options: {} };
|
|
2763
|
+
const parsed = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
2764
|
+
if (parsed.error) return { options: {}, error: ts.flattenDiagnosticMessageText(parsed.error.messageText, "\n") };
|
|
2765
|
+
const config = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname5(configPath));
|
|
2766
|
+
if (config.errors.length) {
|
|
2767
|
+
return {
|
|
2768
|
+
options: config.options,
|
|
2769
|
+
error: ts.flattenDiagnosticMessageText(config.errors[0]?.messageText ?? "Invalid tsconfig", "\n")
|
|
2770
|
+
};
|
|
2771
|
+
}
|
|
2772
|
+
return { options: config.options };
|
|
2773
|
+
};
|
|
2774
|
+
var scriptKind = (path) => {
|
|
2775
|
+
switch (extname(path)) {
|
|
2776
|
+
case ".js":
|
|
2777
|
+
return ts.ScriptKind.JS;
|
|
2778
|
+
case ".jsx":
|
|
2779
|
+
return ts.ScriptKind.JSX;
|
|
2780
|
+
case ".mjs":
|
|
2781
|
+
return ts.ScriptKind.JS;
|
|
2782
|
+
case ".cjs":
|
|
2783
|
+
return ts.ScriptKind.JS;
|
|
2784
|
+
case ".ts":
|
|
2785
|
+
return ts.ScriptKind.TS;
|
|
2786
|
+
case ".tsx":
|
|
2787
|
+
return ts.ScriptKind.TSX;
|
|
2788
|
+
case ".mts":
|
|
2789
|
+
return ts.ScriptKind.TS;
|
|
2790
|
+
case ".cts":
|
|
2791
|
+
return ts.ScriptKind.TS;
|
|
2792
|
+
default:
|
|
2793
|
+
return ts.ScriptKind.Unknown;
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
var nodeEvidence = (root, path, sourceFile, node) => {
|
|
2797
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
2798
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
2799
|
+
return lineEvidence("code", root, path, start, end);
|
|
2800
|
+
};
|
|
2801
|
+
var isExported = (node) => {
|
|
2802
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
|
|
2803
|
+
return modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
2804
|
+
};
|
|
2805
|
+
var exportedNames = (sourceFile) => {
|
|
2806
|
+
const names = /* @__PURE__ */ new Set();
|
|
2807
|
+
const addDeclarationName = (node) => {
|
|
2808
|
+
if (!isExported(node)) return;
|
|
2809
|
+
const name = ts.getNameOfDeclaration(node);
|
|
2810
|
+
if (name && ts.isIdentifier(name)) names.add(name.text);
|
|
2811
|
+
};
|
|
2812
|
+
const visit = (node) => {
|
|
2813
|
+
if (ts.isExportDeclaration(node)) {
|
|
2814
|
+
if (!node.exportClause) names.add("*");
|
|
2815
|
+
else if (ts.isNamedExports(node.exportClause)) {
|
|
2816
|
+
for (const element of node.exportClause.elements) names.add(element.name.text);
|
|
2817
|
+
}
|
|
2818
|
+
} else if (ts.isExportAssignment(node)) {
|
|
2819
|
+
names.add("default");
|
|
2820
|
+
} else if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
2821
|
+
addDeclarationName(node);
|
|
2822
|
+
} else if (ts.isVariableStatement(node) && isExported(node)) {
|
|
2823
|
+
for (const declaration of node.declarationList.declarations) {
|
|
2824
|
+
if (ts.isIdentifier(declaration.name)) names.add(declaration.name.text);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
ts.forEachChild(node, visit);
|
|
2828
|
+
};
|
|
2829
|
+
visit(sourceFile);
|
|
2830
|
+
return [...names].sort();
|
|
2831
|
+
};
|
|
2832
|
+
var moduleReferences = (root, path, sourceFile) => {
|
|
2833
|
+
const references = [];
|
|
2834
|
+
let hasDynamic = false;
|
|
2835
|
+
let hasRuntimeWiring = false;
|
|
2836
|
+
const addReference = (specifier, kind, node) => {
|
|
2837
|
+
references.push({ specifier: specifier.text, kind, evidence: nodeEvidence(root, path, sourceFile, node) });
|
|
2838
|
+
};
|
|
2839
|
+
const visit = (node) => {
|
|
2840
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
2841
|
+
addReference(node.moduleSpecifier, "imports", node);
|
|
2842
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
2843
|
+
addReference(node.moduleSpecifier, "re-exports", node);
|
|
2844
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
2845
|
+
addReference(node.moduleReference.expression, "imports", node);
|
|
2846
|
+
} else if (ts.isCallExpression(node)) {
|
|
2847
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
2848
|
+
if (!node.arguments[0] || !ts.isStringLiteralLike(node.arguments[0])) hasDynamic = true;
|
|
2849
|
+
} else if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
|
|
2850
|
+
const argument = node.arguments[0];
|
|
2851
|
+
if (argument && ts.isStringLiteralLike(argument)) addReference(argument, "imports", node);
|
|
2852
|
+
else hasDynamic = true;
|
|
2853
|
+
} else if (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "register") {
|
|
2854
|
+
hasRuntimeWiring = true;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
ts.forEachChild(node, visit);
|
|
2858
|
+
};
|
|
2859
|
+
visit(sourceFile);
|
|
2860
|
+
return {
|
|
2861
|
+
references,
|
|
2862
|
+
exports: exportedNames(sourceFile),
|
|
2863
|
+
hasDynamic,
|
|
2864
|
+
hasRuntimeWiring
|
|
2865
|
+
};
|
|
2866
|
+
};
|
|
2867
|
+
var resolveRelativeModule = (specifier, containingFile, modulePaths) => {
|
|
2868
|
+
const base = resolve7(dirname5(containingFile), specifier);
|
|
2869
|
+
const extension = extname(base);
|
|
2870
|
+
const extensionlessBase = extension ? base.slice(0, -extension.length) : base;
|
|
2871
|
+
const candidates = [
|
|
2872
|
+
base,
|
|
2873
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${base}${extension2}`),
|
|
2874
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(base, `index${extension2}`)),
|
|
2875
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${extensionlessBase}${extension2}`),
|
|
2876
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(extensionlessBase, `index${extension2}`))
|
|
2877
|
+
];
|
|
2878
|
+
return candidates.map((candidate) => modulePaths.get(resolve7(candidate))).find(Boolean);
|
|
2879
|
+
};
|
|
2880
|
+
var resolveReference = (reference, containingFile, modules, packages, compilerOptions) => {
|
|
2881
|
+
if (reference.specifier.startsWith(".") || reference.specifier.startsWith("/")) {
|
|
2882
|
+
const relativeTarget = resolveRelativeModule(reference.specifier, containingFile, modules);
|
|
2883
|
+
return relativeTarget ? { targetId: relativeTarget.entityId } : void 0;
|
|
2884
|
+
}
|
|
2885
|
+
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];
|
|
2886
|
+
if (packageTarget) return { targetId: packageTarget.id };
|
|
2887
|
+
const resolved = ts.resolveModuleName(reference.specifier, containingFile, compilerOptions, ts.sys).resolvedModule?.resolvedFileName;
|
|
2888
|
+
const resolvedTarget = resolved ? modules.get(resolve7(resolved)) : void 0;
|
|
2889
|
+
if (resolvedTarget) return { targetId: resolvedTarget.entityId };
|
|
2890
|
+
return { targetId: entityId("external", reference.specifier) };
|
|
2891
|
+
};
|
|
2892
|
+
var dependencyEntries = (manifest) => {
|
|
2893
|
+
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
2894
|
+
return sections.flatMap((type) => {
|
|
2895
|
+
const value = manifest[type];
|
|
2896
|
+
if (!isRecord2(value)) return [];
|
|
2897
|
+
return Object.keys(value).sort().map((name) => ({ name, type }));
|
|
2898
|
+
});
|
|
2899
|
+
};
|
|
2900
|
+
var sourceRevision = (root, files) => {
|
|
2901
|
+
const contentRevision = () => ({
|
|
2902
|
+
value: sha256NormalizedV1(
|
|
2903
|
+
files.map((path) => ({
|
|
2904
|
+
path: relativePath(root, path),
|
|
2905
|
+
contentHash: sha256NormalizedV1(readFileSync8(path, "utf8"))
|
|
2906
|
+
}))
|
|
2907
|
+
),
|
|
2908
|
+
kind: "content"
|
|
2909
|
+
});
|
|
2910
|
+
try {
|
|
2911
|
+
const status = execFileSync("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
2912
|
+
cwd: root,
|
|
2913
|
+
encoding: "utf8",
|
|
2914
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2915
|
+
}).trim();
|
|
2916
|
+
if (status) return contentRevision();
|
|
2917
|
+
const value = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
2918
|
+
if (value) return { value, kind: "git" };
|
|
2919
|
+
} catch {
|
|
2920
|
+
}
|
|
2921
|
+
return contentRevision();
|
|
2922
|
+
};
|
|
2923
|
+
var hasPackageManagerMetadata = (root, rootManifest) => Boolean(
|
|
2924
|
+
rootManifest?.packageManager || existsSync9(join8(root, "pnpm-lock.yaml")) || existsSync9(join8(root, "pnpm-workspace.yaml")) || existsSync9(join8(root, "yarn.lock")) || existsSync9(join8(root, "bun.lock")) || existsSync9(join8(root, "bun.lockb")) || existsSync9(join8(root, "package-lock.json"))
|
|
2925
|
+
);
|
|
2926
|
+
var artifact = (root, config, files, entities, relations, coverage) => {
|
|
2927
|
+
const revision = sourceRevision(root, files);
|
|
2928
|
+
const base = {
|
|
2929
|
+
type: "discovery-snapshot",
|
|
2930
|
+
schemaVersion: 1,
|
|
2931
|
+
contentHash: EMPTY_HASH,
|
|
2932
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
2933
|
+
project: { name: entities.find((entity) => entity.kind === "package" && entity.path === ".")?.name ?? basename4(root), root: "." },
|
|
2934
|
+
sourceRevision: revision.value,
|
|
2935
|
+
sourceRevisionKind: revision.kind,
|
|
2936
|
+
configurationHash: sha256NormalizedV1(config ?? {}),
|
|
2937
|
+
pipelineVersion: "1.0.0",
|
|
2938
|
+
analyzerVersions: { repository: "1.0.0", "js-ts": "1.0.0" },
|
|
2939
|
+
entities: [...entities].sort((a, b) => a.id.localeCompare(b.id)),
|
|
2940
|
+
relations: [...relations].sort((a, b) => a.id.localeCompare(b.id)),
|
|
2941
|
+
coverage: [...coverage]
|
|
2942
|
+
};
|
|
2943
|
+
return DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
2944
|
+
};
|
|
2945
|
+
var discoverRepository = (opts = {}) => {
|
|
2946
|
+
const root = resolve7(opts.root ?? process.cwd());
|
|
2947
|
+
const safety = opts.config?.safety;
|
|
2948
|
+
const maxFiles = opts.maxFiles ?? safety?.maxFiles ?? DEFAULT_MAX_FILES2;
|
|
2949
|
+
const maxBytes = opts.maxBytes ?? safety?.maxBytes;
|
|
2950
|
+
const safeOptions = {
|
|
2951
|
+
exclude: [...DEFAULT_SAFETY_EXCLUDES, ...safety?.exclude ?? []],
|
|
2952
|
+
maxFiles,
|
|
2953
|
+
...maxBytes !== void 0 ? { maxBytes } : {},
|
|
2954
|
+
...safety?.maxTimeMs !== void 0 ? { maxTimeMs: safety.maxTimeMs } : {},
|
|
2955
|
+
...safety?.maxMemoryMb !== void 0 ? { maxMemoryMb: safety.maxMemoryMb } : {}
|
|
2956
|
+
};
|
|
2957
|
+
const rootManifestPath = join8(root, "package.json");
|
|
2958
|
+
const rootManifest = readJson(rootManifestPath).value;
|
|
2959
|
+
const packageResult = discoverPackages(root, rootManifest, opts.config);
|
|
2960
|
+
const sourceWalk = safeWalkFiles(root, { extensions: SOURCE_EXTENSIONS, ...safeOptions });
|
|
2961
|
+
const documentWalk = safeWalkFiles(root, { extensions: DOCUMENT_EXTENSIONS, ...safeOptions });
|
|
2962
|
+
const configWalk = safeWalkFiles(root, { extensions: [".json", ".yaml", ".yml", ".js", ".ts"], ...safeOptions });
|
|
2963
|
+
const sourcePaths = sourceWalk.files;
|
|
2964
|
+
const documentPaths = documentWalk.files;
|
|
2965
|
+
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)));
|
|
2966
|
+
const allFiles = [...new Set([rootManifestPath, ...sourcePaths, ...documentPaths, ...configPaths].filter(existsSync9))].sort();
|
|
2967
|
+
const entities = /* @__PURE__ */ new Map();
|
|
2968
|
+
const relations = /* @__PURE__ */ new Map();
|
|
2969
|
+
const addEntity = (entity) => {
|
|
2970
|
+
const existing = entities.get(entity.id);
|
|
2971
|
+
if (existing && (existing.kind !== entity.kind || existing.path !== entity.path)) throw new Error(`Entity identity collision for "${entity.id}".`);
|
|
2972
|
+
entities.set(entity.id, existing ?? entity);
|
|
2973
|
+
};
|
|
2974
|
+
const addRelation = (relation) => {
|
|
2975
|
+
const existing = relations.get(relation.id);
|
|
2976
|
+
if (!existing) {
|
|
2977
|
+
relations.set(relation.id, relation);
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
const evidence2 = new Map(
|
|
2981
|
+
[...existing.evidence, ...relation.evidence].map((item) => [
|
|
2982
|
+
`${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.source}`,
|
|
2983
|
+
item
|
|
2984
|
+
])
|
|
2985
|
+
);
|
|
2986
|
+
relations.set(relation.id, { ...existing, evidence: [...evidence2.values()] });
|
|
2987
|
+
};
|
|
2988
|
+
for (const pkg of packageResult.packages) {
|
|
2989
|
+
const text = readFileSync8(pkg.manifestPath, "utf8");
|
|
2990
|
+
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"'))] });
|
|
2991
|
+
}
|
|
2992
|
+
const modules = /* @__PURE__ */ new Map();
|
|
2993
|
+
for (const absPath of sourcePaths) {
|
|
2994
|
+
const path = relativePath(root, absPath);
|
|
2995
|
+
const pkg = packageForModule(packageResult.packages, absPath);
|
|
2996
|
+
const id = entityId("module", path);
|
|
2997
|
+
const text = readFileSync8(absPath, "utf8");
|
|
2998
|
+
const sourceFile = ts.createSourceFile(absPath, text, ts.ScriptTarget.Latest, true, scriptKind(absPath));
|
|
2999
|
+
const exports = exportedNames(sourceFile);
|
|
3000
|
+
modules.set(resolve7(absPath), { absPath, path, entityId: id, ...pkg ? { packageId: pkg.id } : {} });
|
|
3001
|
+
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) } } : {} });
|
|
3002
|
+
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)] });
|
|
3003
|
+
}
|
|
3004
|
+
for (const absPath of documentPaths) {
|
|
3005
|
+
const path = relativePath(root, absPath);
|
|
3006
|
+
addEntity({ id: entityId("document", path), kind: "document", name: basename4(absPath), path, provenance: "observed", evidence: [lineEvidence("documentation", root, absPath, 1)] });
|
|
3007
|
+
}
|
|
3008
|
+
const compiler = readCompilerOptions(root);
|
|
3009
|
+
const coverage = [
|
|
3010
|
+
...[sourceWalk, documentWalk, configWalk].flatMap((walk, index) => walk.incomplete ? [{ analyzer: "repository", scope: `limits:${["source", "documentation", "configuration"][index]}`, status: "partial", reason: walk.reason }] : []),
|
|
3011
|
+
{ 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)}.` } : {} },
|
|
3012
|
+
{ 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 } : {} },
|
|
3013
|
+
{ analyzer: "js-ts", scope: "static-imports-and-exports", status: compiler.error ? "partial" : "complete", ...compiler.error ? { reason: compiler.error } : {} },
|
|
3014
|
+
{ analyzer: "js-ts", scope: "dynamic-imports", status: "not-analyzed", reason: "Dynamic import expressions and non-literal require calls are not resolved." },
|
|
3015
|
+
{ analyzer: "js-ts", scope: "runtime-wiring", status: "not-analyzed", reason: "Reflection, dependency injection and runtime wiring are not inferred." },
|
|
3016
|
+
{ analyzer: "js-ts", scope: "generated-code", status: "not-analyzed", reason: "Generated code is not interpreted as source architecture." }
|
|
3017
|
+
];
|
|
3018
|
+
for (const pkg of packageResult.packages) {
|
|
3019
|
+
const text = readFileSync8(pkg.manifestPath, "utf8");
|
|
3020
|
+
for (const dependency of dependencyEntries(pkg.manifest)) {
|
|
3021
|
+
const target = packageResult.packages.find((candidate) => candidate.name === dependency.name)?.id ?? entityId("external", dependency.name);
|
|
3022
|
+
if (!entities.has(target)) addEntity({ id: target, kind: "external", name: dependency.name, provenance: "observed", evidence: [lineEvidence("configuration", root, pkg.manifestPath, firstLineContaining(text, `"${dependency.name}"`))] });
|
|
3023
|
+
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 } });
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
for (const module of modules.values()) {
|
|
3027
|
+
const text = readFileSync8(module.absPath, "utf8");
|
|
3028
|
+
const sourceFile = ts.createSourceFile(module.absPath, text, ts.ScriptTarget.Latest, true, scriptKind(module.absPath));
|
|
3029
|
+
const references = moduleReferences(root, module.absPath, sourceFile);
|
|
3030
|
+
for (const reference of references.references) {
|
|
3031
|
+
const target = resolveReference(reference, module.absPath, modules, packageResult.packages, compiler.options);
|
|
3032
|
+
if (!target) continue;
|
|
3033
|
+
if (!entities.has(target.targetId)) {
|
|
3034
|
+
const externalName = target.targetId.replace(/^external:/, "");
|
|
3035
|
+
addEntity({ id: target.targetId, kind: "external", name: externalName, provenance: "observed", evidence: [reference.evidence] });
|
|
3036
|
+
}
|
|
3037
|
+
addRelation({ id: entityId("relation", `${module.entityId}:${reference.kind}:${target.targetId}`), kind: reference.kind, from: module.entityId, to: target.targetId, provenance: "observed", evidence: [reference.evidence] });
|
|
3038
|
+
}
|
|
3039
|
+
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)] });
|
|
3040
|
+
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)] });
|
|
3041
|
+
}
|
|
3042
|
+
return artifact(root, opts.config, allFiles, [...entities.values()], [...relations.values()], coverage);
|
|
3043
|
+
};
|
|
3044
|
+
|
|
3045
|
+
// src/agents/registry-adapter.ts
|
|
3046
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3047
|
+
import { join as join9, resolve as resolve8 } from "path";
|
|
3048
|
+
import { pathToFileURL } from "url";
|
|
3049
|
+
import { z as z6 } from "zod";
|
|
3050
|
+
var DEFAULT_REGISTRY_AGENT_ID = "ecosystem-doc-bridge-corpus-scanner";
|
|
3051
|
+
var RegistryAgentMetadataSchema = z6.object({
|
|
3052
|
+
id: z6.string().min(1).max(256),
|
|
3053
|
+
version: z6.string().min(1).max(64),
|
|
3054
|
+
provider: z6.string().min(1).max(128).optional(),
|
|
3055
|
+
model: z6.string().min(1).max(256).optional(),
|
|
3056
|
+
capabilities: z6.array(z6.string().min(1).max(128)).max(32).default([])
|
|
3057
|
+
}).strict();
|
|
3058
|
+
var deepFreeze = (value) => {
|
|
3059
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
3060
|
+
Object.freeze(value);
|
|
3061
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
3062
|
+
}
|
|
3063
|
+
return value;
|
|
3064
|
+
};
|
|
3065
|
+
var registryConfig = (config) => config.intelligence?.registry;
|
|
3066
|
+
var loadRegistryAgentRunner = async (root, config) => {
|
|
3067
|
+
const metadata = loadRegistryAgentMetadata(root, config);
|
|
3068
|
+
const configured = registryConfig(config)?.runnerModule;
|
|
3069
|
+
const modulePath = configured ? containedPath(root, configured) : containedPath(root, join9(metadata.root, "doc-bridge-adapter.js"));
|
|
3070
|
+
if (!modulePath || !existsSync10(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.`);
|
|
3071
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
3072
|
+
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;
|
|
3073
|
+
if (!runner) throw new Error(`Registry agent runner at ${modulePath} must export a function or { run }. `);
|
|
3074
|
+
return runner;
|
|
3075
|
+
};
|
|
3076
|
+
var loadRegistryAgentMetadata = (root, config) => {
|
|
3077
|
+
const settings = registryConfig(config);
|
|
3078
|
+
const id = settings?.agentId ?? DEFAULT_REGISTRY_AGENT_ID;
|
|
3079
|
+
const agentRoot = settings?.agentRoot ?? "agents";
|
|
3080
|
+
const agentPath = containedPath(root, join9(agentRoot, id));
|
|
3081
|
+
if (!agentPath || !existsSync10(agentPath)) throw new Error(`AgentsKit Registry agent "${id}" is not installed at ${join9(agentRoot, id)}. Install it with: npx agentskit add ${id}`);
|
|
3082
|
+
const metadataPath = [join9(agentPath, "agent.json"), join9(agentPath, "manifest.json")].find(existsSync10);
|
|
3083
|
+
if (!metadataPath) throw new Error(`Registry agent "${id}" is installed but has no agent.json or manifest.json metadata.`);
|
|
3084
|
+
const metadata = RegistryAgentMetadataSchema.parse(JSON.parse(readFileSync9(metadataPath, "utf8")));
|
|
3085
|
+
if (metadata.id !== id) throw new Error(`Installed Registry agent metadata id "${metadata.id}" does not match configured id "${id}".`);
|
|
3086
|
+
return { ...metadata, root: agentPath };
|
|
3087
|
+
};
|
|
3088
|
+
var createRegistryAgentAdapter = (root, config, runner) => {
|
|
3089
|
+
if (!registryConfig(config)?.enabled) throw new Error("Registry agents are disabled. Set intelligence.registry.enabled: true to run an assisted workflow.");
|
|
3090
|
+
const metadata = loadRegistryAgentMetadata(resolve8(root), config);
|
|
3091
|
+
return {
|
|
3092
|
+
metadata,
|
|
3093
|
+
run: async (snapshot, report, evidence2 = report.diagnostics.flatMap((diagnostic2) => diagnostic2.evidence).slice(0, 64)) => {
|
|
3094
|
+
const context = deepFreeze({ snapshot: redactValue(snapshot), report: redactValue(report), evidence: redactValue(evidence2), capabilities: ["snapshot.read", "evidence.read", "proposal.write"], network: false, shell: false });
|
|
3095
|
+
const proposal = AgentProposalV1Schema.parse(await runner(context));
|
|
3096
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Registry agent proposal contentHash does not match its canonical contents.");
|
|
3097
|
+
if (proposal.baseSnapshotHash !== snapshot.contentHash || proposal.baseReportHash !== report.contentHash) throw new Error("Registry agent proposal is not based on the supplied snapshot/report hashes.");
|
|
3098
|
+
if (proposal.origin.kind !== "registry-agent" || proposal.origin.id !== metadata.id) throw new Error(`Registry agent proposal origin must be ${metadata.id}.`);
|
|
3099
|
+
return proposal;
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
};
|
|
3103
|
+
var persistRegistryAgentProposal = (stateDir, proposal) => {
|
|
3104
|
+
AgentProposalV1Schema.parse(proposal);
|
|
3105
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Cannot persist a Registry agent proposal with an invalid contentHash.");
|
|
3106
|
+
const safeHash = contentHashForArtifactV1(proposal);
|
|
3107
|
+
mkdirSync2(join9(resolve8(stateDir), "agents"), { recursive: true });
|
|
3108
|
+
const path = join9(resolve8(stateDir), "agents", `${proposal.origin.id}-${safeHash}.json`);
|
|
3109
|
+
writeFileSync2(path, `${JSON.stringify(proposal, null, 2)}
|
|
3110
|
+
`, "utf8");
|
|
3111
|
+
return path;
|
|
3112
|
+
};
|
|
3113
|
+
|
|
3114
|
+
// src/discovery/documentation.ts
|
|
3115
|
+
var detectionValues = /* @__PURE__ */ new Set(["static", "dynamic", "external"]);
|
|
3116
|
+
var evidence = (path, lineStart, lineEnd = lineStart) => ({
|
|
3117
|
+
source: "documentation",
|
|
3118
|
+
path,
|
|
3119
|
+
lineStart,
|
|
3120
|
+
lineEnd
|
|
3121
|
+
});
|
|
3122
|
+
var diagnostic = (path, code, message, lineStart, lineEnd = lineStart) => ({ code, message, path: "docbridge", evidence: evidence(path, lineStart, lineEnd) });
|
|
3123
|
+
var scalar = (value) => {
|
|
3124
|
+
const trimmed = value.trim();
|
|
3125
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
3126
|
+
return trimmed.slice(1, -1);
|
|
3127
|
+
}
|
|
3128
|
+
return trimmed;
|
|
3129
|
+
};
|
|
3130
|
+
var list = (value) => {
|
|
3131
|
+
const trimmed = value.trim();
|
|
3132
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
3133
|
+
const body = trimmed.slice(1, -1).trim();
|
|
3134
|
+
return body ? body.split(",").map(scalar).filter(Boolean) : [];
|
|
3135
|
+
};
|
|
3136
|
+
var findFrontmatter = (content) => {
|
|
3137
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
3138
|
+
if (lines[0] !== "---") return void 0;
|
|
3139
|
+
const end = lines.findIndex((line, index) => index > 0 && line === "---");
|
|
3140
|
+
return end < 0 ? void 0 : { lines, end };
|
|
3141
|
+
};
|
|
3142
|
+
var addDiagnostic = (diagnostics, input, code, message, line, endLine = line) => {
|
|
3143
|
+
diagnostics.push(diagnostic(input.path, code, message, line, endLine));
|
|
3144
|
+
};
|
|
3145
|
+
var resolveEntity = (reference, entities, input, lineStart, unresolved) => {
|
|
3146
|
+
const resolved = entities.find((entity2) => entity2.id === reference || entity2.aliases?.includes(reference));
|
|
3147
|
+
if (resolved) return resolved;
|
|
3148
|
+
const id = `unresolved:${reference}`;
|
|
3149
|
+
const existing = unresolved.get(id);
|
|
3150
|
+
if (existing) return existing;
|
|
3151
|
+
const entity = {
|
|
3152
|
+
id,
|
|
3153
|
+
kind: "unresolved-reference",
|
|
3154
|
+
name: reference,
|
|
3155
|
+
provenance: "declared",
|
|
3156
|
+
evidence: [evidence(input.path, lineStart)]
|
|
3157
|
+
};
|
|
3158
|
+
unresolved.set(id, entity);
|
|
3159
|
+
return entity;
|
|
3160
|
+
};
|
|
3161
|
+
var relationKey = (from, to, kind) => `${from}\0${to}\0${kind}`;
|
|
3162
|
+
var parseBlock = (input) => {
|
|
3163
|
+
const frontmatter = findFrontmatter(input.content);
|
|
3164
|
+
if (!frontmatter) {
|
|
3165
|
+
if (input.content.replace(/^\uFEFF/, "").startsWith("---")) {
|
|
3166
|
+
return {
|
|
3167
|
+
covers: [],
|
|
3168
|
+
relations: [],
|
|
3169
|
+
hasDocbridge: true,
|
|
3170
|
+
diagnostics: [diagnostic(input.path, "DOCBRIDGE_FRONTMATTER_MALFORMED", "Frontmatter must close with a line containing only ---.", 1)]
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
return { covers: [], relations: [], diagnostics: [], hasDocbridge: false };
|
|
3174
|
+
}
|
|
3175
|
+
const { lines, end } = frontmatter;
|
|
3176
|
+
const docbridgeLine = lines.findIndex((line, index) => index > 0 && index < end && /^docbridge\s*:/.test(line));
|
|
3177
|
+
if (docbridgeLine < 0) return { covers: [], relations: [], diagnostics: [], hasDocbridge: false };
|
|
3178
|
+
const diagnostics = [];
|
|
3179
|
+
const covers = [];
|
|
3180
|
+
const relations = [];
|
|
3181
|
+
const inline = lines[docbridgeLine]?.slice("docbridge:".length).trim() ?? "";
|
|
3182
|
+
if (inline && inline !== "{}") addDiagnostic(diagnostics, input, "DOCBRIDGE_BLOCK_MALFORMED", "docbridge must be a nested frontmatter object.", docbridgeLine + 1);
|
|
3183
|
+
let section;
|
|
3184
|
+
let current;
|
|
3185
|
+
const finishRelation = () => {
|
|
3186
|
+
if (current) relations.push(current);
|
|
3187
|
+
current = void 0;
|
|
3188
|
+
};
|
|
3189
|
+
for (let index = docbridgeLine + 1; index < end; index += 1) {
|
|
3190
|
+
const raw = lines[index] ?? "";
|
|
3191
|
+
const trimmed = raw.trim();
|
|
3192
|
+
const line = index + 1;
|
|
3193
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
3194
|
+
if (/\t/.test(raw)) {
|
|
3195
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_INDENTATION_INVALID", "docbridge indentation must use spaces.", line);
|
|
3196
|
+
continue;
|
|
3197
|
+
}
|
|
3198
|
+
if (!raw.startsWith(" ")) {
|
|
3199
|
+
finishRelation();
|
|
3200
|
+
section = void 0;
|
|
3201
|
+
continue;
|
|
3202
|
+
}
|
|
3203
|
+
if (/^ {2}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
|
|
3204
|
+
finishRelation();
|
|
3205
|
+
const match = /^ {2}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
3206
|
+
const key = match?.[1];
|
|
3207
|
+
const value = match?.[2] ?? "";
|
|
3208
|
+
if (key !== "covers" && key !== "relations") {
|
|
3209
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown docbridge field: ${key ?? "(missing)"}.`, line);
|
|
3210
|
+
section = void 0;
|
|
3211
|
+
} else if (key === "covers") {
|
|
3212
|
+
section = "covers";
|
|
3213
|
+
if (value) {
|
|
3214
|
+
const values = list(value);
|
|
3215
|
+
if (!values) addDiagnostic(diagnostics, input, "DOCBRIDGE_COVERS_INVALID", "covers must be a list of entity references.", line);
|
|
3216
|
+
else values.forEach((item) => covers.push({ value: item, line }));
|
|
3217
|
+
}
|
|
3218
|
+
} else {
|
|
3219
|
+
section = "relations";
|
|
3220
|
+
if (value) addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATIONS_INVALID", "relations must be a list of relation objects.", line);
|
|
3221
|
+
}
|
|
3222
|
+
continue;
|
|
3223
|
+
}
|
|
3224
|
+
if (section === "covers" && /^ {4}-\s*/.test(raw)) {
|
|
3225
|
+
const value = scalar(raw.replace(/^ {4}-\s*/, ""));
|
|
3226
|
+
if (!value) addDiagnostic(diagnostics, input, "DOCBRIDGE_REFERENCE_MISSING", "covers entries must not be empty.", line);
|
|
3227
|
+
else covers.push({ value, line });
|
|
3228
|
+
continue;
|
|
3229
|
+
}
|
|
3230
|
+
if (section === "relations" && /^ {4}-\s*/.test(raw)) {
|
|
3231
|
+
finishRelation();
|
|
3232
|
+
const firstField = /^ {4}-\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
3233
|
+
current = { startLine: line, endLine: line, fields: /* @__PURE__ */ new Set() };
|
|
3234
|
+
if (firstField?.[1]) {
|
|
3235
|
+
current.fields.add(firstField[1]);
|
|
3236
|
+
current[firstField[1]] = scalar(firstField[2] ?? "");
|
|
3237
|
+
} else if (raw.replace(/^ {4}-\s*/, "").trim()) {
|
|
3238
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATION_INVALID", "Relation entries must be field mappings.", line);
|
|
3239
|
+
}
|
|
3240
|
+
continue;
|
|
3241
|
+
}
|
|
3242
|
+
if (section === "relations" && current && /^ {6}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
|
|
3243
|
+
const field = /^ {6}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
3244
|
+
const key = field?.[1];
|
|
3245
|
+
current.endLine = line;
|
|
3246
|
+
if (!key || !["from", "to", "kind", "detection"].includes(key)) {
|
|
3247
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown relation field: ${key ?? "(missing)"}.`, line);
|
|
3248
|
+
} else if (current.fields.has(key)) {
|
|
3249
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_DUPLICATE", `Duplicate relation field: ${key}.`, line);
|
|
3250
|
+
} else {
|
|
3251
|
+
current.fields.add(key);
|
|
3252
|
+
current[key] = scalar(field?.[2] ?? "");
|
|
3253
|
+
}
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_STRUCTURE_INVALID", `Invalid docbridge structure at line ${line}.`, line);
|
|
3257
|
+
}
|
|
3258
|
+
finishRelation();
|
|
3259
|
+
return { covers, relations, diagnostics, hasDocbridge: true };
|
|
3260
|
+
};
|
|
3261
|
+
var parseDocumentationDeclarations = (input, options) => {
|
|
3262
|
+
const parsed = parseBlock(input);
|
|
3263
|
+
if (!parsed.hasDocbridge) return { hasDocbridge: false, entities: [], relations: [], diagnostics: [] };
|
|
3264
|
+
const diagnostics = [...parsed.diagnostics];
|
|
3265
|
+
const unresolved = /* @__PURE__ */ new Map();
|
|
3266
|
+
const relations = [];
|
|
3267
|
+
const documentId = options.documentId ?? `document:${input.path}`;
|
|
3268
|
+
const relationClaims = /* @__PURE__ */ new Map();
|
|
3269
|
+
if (!parsed.covers.length && !parsed.relations.length) {
|
|
3270
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_CONTENT_MISSING", "docbridge must declare covers or relations.", 1);
|
|
3271
|
+
}
|
|
3272
|
+
for (const [index, cover] of parsed.covers.entries()) {
|
|
3273
|
+
const target = resolveEntity(cover.value, options.snapshot.entities, input, cover.line, unresolved);
|
|
3274
|
+
relations.push({
|
|
3275
|
+
id: `relation:declared:${input.path}:covers:${index}`,
|
|
3276
|
+
kind: "covers",
|
|
3277
|
+
from: documentId,
|
|
3278
|
+
to: target.id,
|
|
3279
|
+
provenance: "declared",
|
|
3280
|
+
evidence: [evidence(input.path, cover.line)]
|
|
3281
|
+
});
|
|
3282
|
+
}
|
|
3283
|
+
for (const [index, declaration] of parsed.relations.entries()) {
|
|
3284
|
+
const basePath = `relations[${index}]`;
|
|
3285
|
+
const missing = ["from", "to", "kind", "detection"].filter((field) => !declaration[field]);
|
|
3286
|
+
if (missing.length) {
|
|
3287
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATION_FIELD_MISSING", `Relation is missing required field(s): ${missing.join(", ")}.`, declaration.startLine, declaration.endLine);
|
|
3288
|
+
continue;
|
|
3289
|
+
}
|
|
3290
|
+
const from = declaration.from;
|
|
3291
|
+
const to = declaration.to;
|
|
3292
|
+
const kind = declaration.kind;
|
|
3293
|
+
const detection = declaration.detection;
|
|
3294
|
+
if (!detectionValues.has(detection)) {
|
|
3295
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_DETECTION_INVALID", `Invalid relation detection: ${detection}.`, declaration.startLine, declaration.endLine);
|
|
3296
|
+
continue;
|
|
3297
|
+
}
|
|
3298
|
+
const fromEntity = resolveEntity(from, options.snapshot.entities, input, declaration.startLine, unresolved);
|
|
3299
|
+
const toEntity = resolveEntity(to, options.snapshot.entities, input, declaration.startLine, unresolved);
|
|
3300
|
+
const key = relationKey(fromEntity.id, toEntity.id, kind);
|
|
3301
|
+
const previousDetection = relationClaims.get(key);
|
|
3302
|
+
if (previousDetection === detection) addDiagnostic(diagnostics, input, "DOCBRIDGE_DECLARATION_DUPLICATE", "Duplicate relation declaration.", declaration.startLine, declaration.endLine);
|
|
3303
|
+
if (previousDetection && previousDetection !== detection) addDiagnostic(diagnostics, input, "DOCBRIDGE_DECLARATION_CONFLICT", "Conflicting relation declarations use different detection values.", declaration.startLine, declaration.endLine);
|
|
3304
|
+
relationClaims.set(key, previousDetection ?? detection);
|
|
3305
|
+
relations.push({
|
|
3306
|
+
id: `relation:declared:${input.path}:${basePath}`,
|
|
3307
|
+
kind,
|
|
3308
|
+
from: fromEntity.id,
|
|
3309
|
+
to: toEntity.id,
|
|
3310
|
+
discriminator: detection,
|
|
3311
|
+
provenance: "declared",
|
|
3312
|
+
evidence: [evidence(input.path, declaration.startLine, declaration.endLine)],
|
|
3313
|
+
metadata: { detection }
|
|
3314
|
+
});
|
|
3315
|
+
}
|
|
3316
|
+
return {
|
|
3317
|
+
hasDocbridge: true,
|
|
3318
|
+
entities: [...unresolved.values()].sort((a, b) => a.id.localeCompare(b.id)),
|
|
3319
|
+
relations,
|
|
3320
|
+
diagnostics
|
|
3321
|
+
};
|
|
3322
|
+
};
|
|
3323
|
+
var applyDocumentationDeclarations = (snapshot, documents) => {
|
|
3324
|
+
const entities = new Map(snapshot.entities.map((entity) => [entity.id, entity]));
|
|
3325
|
+
const relations = new Map(snapshot.relations.map((relation) => [relation.id, relation]));
|
|
3326
|
+
const diagnostics = [];
|
|
3327
|
+
for (const document of documents) {
|
|
3328
|
+
const result = parseDocumentationDeclarations(document, { snapshot });
|
|
3329
|
+
diagnostics.push(...result.diagnostics);
|
|
3330
|
+
for (const entity of result.entities) entities.set(entity.id, entity);
|
|
3331
|
+
for (const relation of result.relations) relations.set(relation.id, relation);
|
|
3332
|
+
}
|
|
3333
|
+
const base = { ...snapshot, contentHash: "0".repeat(64), entities: [...entities.values()], relations: [...relations.values()] };
|
|
3334
|
+
return {
|
|
3335
|
+
snapshot: DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) }),
|
|
3336
|
+
diagnostics
|
|
3337
|
+
};
|
|
3338
|
+
};
|
|
3339
|
+
|
|
3340
|
+
// src/reconciliation/reconcile.ts
|
|
3341
|
+
var ignoredDocumentationRelations = /* @__PURE__ */ new Set(["covers"]);
|
|
3342
|
+
var metadataDetection = (relation) => {
|
|
3343
|
+
const detection = relation.metadata?.detection;
|
|
3344
|
+
return typeof detection === "string" ? detection : void 0;
|
|
3345
|
+
};
|
|
3346
|
+
var relationDetection = (relation) => relation.discriminator ?? metadataDetection(relation) ?? "static";
|
|
3347
|
+
var entityResolver = (snapshots) => {
|
|
3348
|
+
const references = /* @__PURE__ */ new Map();
|
|
3349
|
+
const entities = snapshots.flatMap((snapshot) => snapshot.entities).sort((a, b) => a.id.localeCompare(b.id));
|
|
3350
|
+
for (const entity of entities) {
|
|
3351
|
+
references.set(entity.id, entity.id);
|
|
3352
|
+
for (const alias of entity.aliases ?? []) {
|
|
3353
|
+
if (!references.has(alias)) references.set(alias, entity.id);
|
|
3354
|
+
}
|
|
3355
|
+
}
|
|
3356
|
+
return (reference) => references.get(reference) ?? reference;
|
|
3357
|
+
};
|
|
3358
|
+
var relationBase = (relation, resolveEntity2) => `${resolveEntity2(relation.from)}\0${resolveEntity2(relation.to)}\0${relation.kind}`;
|
|
3359
|
+
var evidenceKey = (item) => `${item.source}:${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.context ?? ""}`;
|
|
3360
|
+
var mergeEvidence = (...relations) => {
|
|
3361
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3362
|
+
for (const relation of relations) {
|
|
3363
|
+
for (const item of relation.evidence) merged.set(evidenceKey(item), item);
|
|
3364
|
+
}
|
|
3365
|
+
return [...merged.values()].sort((a, b) => evidenceKey(a).localeCompare(evidenceKey(b)));
|
|
3366
|
+
};
|
|
3367
|
+
var diagnosticId = (code, value) => `reconciliation:${code}:${sha256NormalizedV1(value).slice(0, 32)}`;
|
|
3368
|
+
var coverageAvailable = (snapshot, relation) => {
|
|
3369
|
+
const status = snapshot.coverage.find(
|
|
3370
|
+
(entry) => entry.scope === "static-imports-and-exports" && (relation.kind === "imports" || relation.kind === "re-exports")
|
|
3371
|
+
)?.status;
|
|
3372
|
+
return status === void 0 || status === "complete";
|
|
3373
|
+
};
|
|
3374
|
+
var entityById = (snapshots) => {
|
|
3375
|
+
const entities = /* @__PURE__ */ new Map();
|
|
3376
|
+
for (const snapshot of snapshots) {
|
|
3377
|
+
for (const entity of snapshot.entities) if (!entities.has(entity.id)) entities.set(entity.id, entity);
|
|
3378
|
+
}
|
|
3379
|
+
return entities;
|
|
3380
|
+
};
|
|
3381
|
+
var isUnresolved = (id, entities) => id.startsWith("unresolved:") || entities.get(id)?.kind === "unresolved-reference";
|
|
3382
|
+
var relationMatches = (observed, declared, resolveEntity2) => {
|
|
3383
|
+
if (relationBase(observed, resolveEntity2) !== relationBase(declared, resolveEntity2)) return false;
|
|
3384
|
+
return relationDetection(declared) === relationDetection(observed);
|
|
3385
|
+
};
|
|
3386
|
+
var reportDiagnostic = (code, status, severity, message, evidence2, value, entityIds, relationIds, remediation) => ({
|
|
3387
|
+
id: diagnosticId(code, value),
|
|
3388
|
+
code,
|
|
3389
|
+
status,
|
|
3390
|
+
severity,
|
|
3391
|
+
message,
|
|
3392
|
+
evidence: [...evidence2],
|
|
3393
|
+
...entityIds?.length ? { entityIds: [...entityIds] } : {},
|
|
3394
|
+
...relationIds?.length ? { relationIds: [...relationIds] } : {},
|
|
3395
|
+
...remediation ? { remediation } : {}
|
|
3396
|
+
});
|
|
3397
|
+
var reconcileKnowledge = (observed, declared) => {
|
|
3398
|
+
const resolveEntity2 = entityResolver([observed, declared]);
|
|
3399
|
+
const entities = entityById([observed, declared]);
|
|
3400
|
+
const observedRelations = observed.relations.filter((relation) => relation.provenance === "observed" && !ignoredDocumentationRelations.has(relation.kind));
|
|
3401
|
+
const declaredRelations = declared.relations.filter((relation) => relation.provenance === "declared" && !ignoredDocumentationRelations.has(relation.kind));
|
|
3402
|
+
const diagnostics = [];
|
|
3403
|
+
for (const entity of declared.entities.filter((item) => isUnresolved(item.id, entities)).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
3404
|
+
diagnostics.push(reportDiagnostic(
|
|
3405
|
+
"UNRESOLVED_ENTITY_REFERENCE",
|
|
3406
|
+
"unresolved",
|
|
3407
|
+
"error",
|
|
3408
|
+
`Declared reference could not be resolved: ${entity.name}.`,
|
|
3409
|
+
entity.evidence,
|
|
3410
|
+
entity.id,
|
|
3411
|
+
[entity.id],
|
|
3412
|
+
void 0,
|
|
3413
|
+
"Resolve the reference to an observed entity ID or configured alias."
|
|
3414
|
+
));
|
|
3415
|
+
}
|
|
3416
|
+
const declaredByBase = /* @__PURE__ */ new Map();
|
|
3417
|
+
for (const relation of declaredRelations) {
|
|
3418
|
+
const base2 = relationBase(relation, resolveEntity2);
|
|
3419
|
+
const group = declaredByBase.get(base2) ?? [];
|
|
3420
|
+
group.push(relation);
|
|
3421
|
+
declaredByBase.set(base2, group);
|
|
3422
|
+
}
|
|
3423
|
+
for (const group of declaredByBase.values()) {
|
|
3424
|
+
const detections = new Set(group.map(relationDetection));
|
|
3425
|
+
if (detections.size < 2) continue;
|
|
3426
|
+
diagnostics.push(reportDiagnostic(
|
|
3427
|
+
"CONFLICTING_DECLARATIONS",
|
|
3428
|
+
"conflict",
|
|
3429
|
+
"error",
|
|
3430
|
+
"Declarations for the same semantic relation disagree on detection.",
|
|
3431
|
+
mergeEvidence(...group),
|
|
3432
|
+
[relationBase(group[0], resolveEntity2), [...detections].sort()],
|
|
3433
|
+
void 0,
|
|
3434
|
+
group.map((relation) => relation.id),
|
|
3435
|
+
"Keep one detection value for this relation or document the intended distinction with a different relation kind."
|
|
3436
|
+
));
|
|
3437
|
+
}
|
|
3438
|
+
for (const relation of observedRelations) {
|
|
3439
|
+
const candidates = declaredByBase.get(relationBase(relation, resolveEntity2)) ?? [];
|
|
3440
|
+
const match = candidates.find((candidate) => relationMatches(relation, candidate, resolveEntity2));
|
|
3441
|
+
if (match) {
|
|
3442
|
+
diagnostics.push(reportDiagnostic(
|
|
3443
|
+
"RELATION_CONFIRMED",
|
|
3444
|
+
"confirmed",
|
|
3445
|
+
"info",
|
|
3446
|
+
"Observed relation is covered by a matching declaration.",
|
|
3447
|
+
mergeEvidence(relation, match),
|
|
3448
|
+
relation.id,
|
|
3449
|
+
void 0,
|
|
3450
|
+
[relation.id, match.id]
|
|
3451
|
+
));
|
|
3452
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
3453
|
+
diagnostics.push(reportDiagnostic(
|
|
3454
|
+
"RELATION_UNDOCUMENTED",
|
|
3455
|
+
"undocumented",
|
|
3456
|
+
"warn",
|
|
3457
|
+
"Observed relation has no matching documentation declaration.",
|
|
3458
|
+
relation.evidence,
|
|
3459
|
+
relation.id,
|
|
3460
|
+
void 0,
|
|
3461
|
+
[relation.id],
|
|
3462
|
+
"Add a matching relation declaration or configure this relation kind as intentionally undocumented."
|
|
3463
|
+
));
|
|
3464
|
+
}
|
|
3465
|
+
}
|
|
3466
|
+
for (const relation of declaredRelations) {
|
|
3467
|
+
const candidates = observedRelations.filter((candidate) => relationBase(candidate, resolveEntity2) === relationBase(relation, resolveEntity2));
|
|
3468
|
+
const detection = relationDetection(relation);
|
|
3469
|
+
if (candidates.some((candidate) => relationMatches(candidate, relation, resolveEntity2))) continue;
|
|
3470
|
+
if (isUnresolved(resolveEntity2(relation.from), entities) || isUnresolved(resolveEntity2(relation.to), entities)) continue;
|
|
3471
|
+
if (detection === "dynamic" || detection === "external") {
|
|
3472
|
+
diagnostics.push(reportDiagnostic(
|
|
3473
|
+
"RELATION_NOT_ANALYZED",
|
|
3474
|
+
"not-analyzed",
|
|
3475
|
+
"info",
|
|
3476
|
+
`Declared ${detection} relation cannot be verified by the current static analyzer.`,
|
|
3477
|
+
relation.evidence,
|
|
3478
|
+
relation.id,
|
|
3479
|
+
void 0,
|
|
3480
|
+
[relation.id],
|
|
3481
|
+
"Enable a compatible analyzer or provide explicit observed evidence before treating this relation as confirmed."
|
|
3482
|
+
));
|
|
3483
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
3484
|
+
diagnostics.push(reportDiagnostic(
|
|
3485
|
+
"DECLARED_RELATION_STALE",
|
|
3486
|
+
"stale-or-unverified",
|
|
3487
|
+
"warn",
|
|
3488
|
+
candidates.length ? "Declared relation has incompatible observed evidence." : "Declared static relation was not observed.",
|
|
3489
|
+
candidates.length ? mergeEvidence(relation, ...candidates) : relation.evidence,
|
|
3490
|
+
relation.id,
|
|
3491
|
+
void 0,
|
|
3492
|
+
[relation.id, ...candidates.map((candidate) => candidate.id)],
|
|
3493
|
+
"Update the declaration or the implementation so both graphs describe the same relation."
|
|
3494
|
+
));
|
|
3495
|
+
}
|
|
3496
|
+
}
|
|
3497
|
+
const sortedDiagnostics = [...diagnostics].sort((a, b) => a.id.localeCompare(b.id));
|
|
3498
|
+
const base = {
|
|
3499
|
+
type: "reconciliation-report",
|
|
3500
|
+
schemaVersion: 1,
|
|
3501
|
+
contentHash: "0".repeat(64),
|
|
3502
|
+
contentHashAlgo: observed.contentHashAlgo,
|
|
3503
|
+
project: observed.project,
|
|
3504
|
+
sourceRevision: observed.sourceRevision,
|
|
3505
|
+
sourceRevisionKind: observed.sourceRevisionKind,
|
|
3506
|
+
configurationHash: observed.configurationHash,
|
|
3507
|
+
pipelineVersion: observed.pipelineVersion,
|
|
3508
|
+
analyzerVersions: observed.analyzerVersions,
|
|
3509
|
+
snapshotHash: observed.contentHash,
|
|
3510
|
+
diagnostics: sortedDiagnostics,
|
|
3511
|
+
summary: {
|
|
3512
|
+
entityCount: observed.entities.length,
|
|
3513
|
+
relationCount: observedRelations.length,
|
|
3514
|
+
diagnosticCount: sortedDiagnostics.length
|
|
3515
|
+
}
|
|
3516
|
+
};
|
|
3517
|
+
return ReconciliationReportV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
3518
|
+
};
|
|
3519
|
+
|
|
3520
|
+
// src/report/html.ts
|
|
3521
|
+
var escapeHtml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3522
|
+
var anchor = (prefix, value) => `${prefix}-${value.replace(/[^A-Za-z0-9_-]+/g, "-")}`;
|
|
3523
|
+
var evidenceText = (evidence2, includeSnippets) => {
|
|
3524
|
+
const location = `${evidence2.path}${evidence2.lineStart ? `:${evidence2.lineStart}${evidence2.lineEnd && evidence2.lineEnd !== evidence2.lineStart ? `-${evidence2.lineEnd}` : ""}` : ""}`;
|
|
3525
|
+
return `${location}${includeSnippets && evidence2.context ? ` \u2014 ${redactSecrets(evidence2.context)}` : ""}`;
|
|
3526
|
+
};
|
|
3527
|
+
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>`;
|
|
3528
|
+
var embeddedJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
3529
|
+
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>`;
|
|
3530
|
+
var renderCompact = (snapshot, report, entityAnchors, includeSnippets) => {
|
|
3531
|
+
const entities = snapshot.entities.map((entity) => ({ id: entity.id, name: entity.name, kind: entity.kind, anchor: entityAnchors.get(entity.id) ?? anchor("entity", entity.id) }));
|
|
3532
|
+
const entityIndexes = new Map(entities.map((entity, index) => [entity.id, index]));
|
|
3533
|
+
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 }));
|
|
3534
|
+
const diagnostics = report.diagnostics.map((diagnostic2) => ({
|
|
3535
|
+
...diagnostic2,
|
|
3536
|
+
evidence: diagnostic2.evidence.map(({ context, ...evidence2 }) => includeSnippets && context ? { ...evidence2, context: redactSecrets(context) } : evidence2)
|
|
3537
|
+
}));
|
|
3538
|
+
const data = embeddedJson({ entities, relations, diagnostics, coverage: snapshot.coverage });
|
|
3539
|
+
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()};`;
|
|
3540
|
+
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>`;
|
|
3541
|
+
};
|
|
3542
|
+
var render = (input, options) => {
|
|
3543
|
+
const { snapshot, report } = input;
|
|
3544
|
+
const includeSnippets = options.includeSnippets === true;
|
|
3545
|
+
const entityAnchors = new Map(snapshot.entities.map((entity, index) => [entity.id, entity.id.length > 64 ? `entity-n${index}` : anchor("entity", entity.id)]));
|
|
3546
|
+
if (snapshot.entities.length > 500 || report.diagnostics.length > 1e3) return renderCompact(snapshot, report, entityAnchors, includeSnippets);
|
|
3547
|
+
const entityNames = new Map(snapshot.entities.map((entity) => [entity.id, entity.name]));
|
|
3548
|
+
const entityAnchor = (id) => entityAnchors.get(id) ?? anchor("entity", id);
|
|
3549
|
+
const entityLabel = (id) => entityNames.get(id) ?? id;
|
|
3550
|
+
const entityLink = (id) => `<a href="#${entityAnchor(id)}">${escapeHtml(entityLabel(id))}</a>`;
|
|
3551
|
+
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("");
|
|
3552
|
+
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("");
|
|
3553
|
+
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("");
|
|
3554
|
+
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("");
|
|
3555
|
+
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));});`;
|
|
3556
|
+
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>`;
|
|
3557
|
+
};
|
|
3558
|
+
var renderOfflineReport = (input, options = {}) => {
|
|
3559
|
+
try {
|
|
3560
|
+
if (!input || typeof input !== "object") throw new Error("Input must contain snapshot and report artifacts.");
|
|
3561
|
+
const value = input;
|
|
3562
|
+
const snapshot = DiscoverySnapshotV1Schema.parse(value.snapshot);
|
|
3563
|
+
const report = ReconciliationReportV1Schema.parse(value.report);
|
|
3564
|
+
if (report.snapshotHash !== snapshot.contentHash) throw new Error("Report snapshotHash does not match snapshot contentHash.");
|
|
3565
|
+
return render({ snapshot, report }, options);
|
|
3566
|
+
} catch (error) {
|
|
3567
|
+
return errorPage(error instanceof Error ? error.message : String(error));
|
|
3568
|
+
}
|
|
3569
|
+
};
|
|
3570
|
+
|
|
3571
|
+
// src/fixes/proposals.ts
|
|
3572
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync10, realpathSync as realpathSync7, renameSync, statSync as statSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
3573
|
+
import { basename as basename5, dirname as dirname6, extname as extname2, join as join10, relative as relative6, resolve as resolve9, sep as sep7 } from "path";
|
|
3574
|
+
var hash2 = (value) => sha256NormalizedV1(value);
|
|
3575
|
+
var artifactMetadata = (root, options) => ({
|
|
3576
|
+
schemaVersion: 1,
|
|
3577
|
+
contentHash: "0".repeat(64),
|
|
3578
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
3579
|
+
project: { name: options.projectName ?? basename5(resolve9(root)), root: "." },
|
|
3580
|
+
sourceRevision: options.baseRevision,
|
|
3581
|
+
sourceRevisionKind: options.baseRevision.length === 40 ? "git" : "content",
|
|
3582
|
+
configurationHash: options.configurationHash,
|
|
3583
|
+
pipelineVersion: "1.0.0",
|
|
3584
|
+
analyzerVersions: { fixes: options.toolVersion ?? "1.0.0" }
|
|
3585
|
+
});
|
|
3586
|
+
var unifiedDiff = (changes) => changes.map((change) => {
|
|
3587
|
+
const before = change.before.split("\n").map((line) => `-${line}`).join("\n");
|
|
3588
|
+
const after = change.after.split("\n").map((line) => `+${line}`).join("\n");
|
|
3589
|
+
return `--- a/${change.path}
|
|
3590
|
+
+++ b/${change.path}
|
|
3591
|
+
@@
|
|
3592
|
+
${before}
|
|
3593
|
+
${after}`;
|
|
3594
|
+
}).join("\n");
|
|
3595
|
+
var makeProposal = (root, options, changes, preconditions, postconditions) => {
|
|
3596
|
+
const draft = {
|
|
3597
|
+
...artifactMetadata(root, options),
|
|
3598
|
+
type: "fix-proposal",
|
|
3599
|
+
proposalId: `fix-${hash2(changes).slice(0, 20)}`,
|
|
3600
|
+
baseRevision: options.baseRevision,
|
|
3601
|
+
affectedFiles: changes.map(({ path, before }) => ({ path, contentHash: sha256NormalizedV1(before) })),
|
|
3602
|
+
changes,
|
|
3603
|
+
preconditions,
|
|
3604
|
+
diff: unifiedDiff(changes),
|
|
3605
|
+
postconditions,
|
|
3606
|
+
status: "proposed"
|
|
3607
|
+
};
|
|
3608
|
+
return FixProposalV1Schema.parse({ ...draft, contentHash: contentHashForArtifactV1(draft) });
|
|
3609
|
+
};
|
|
3610
|
+
var walkMarkdown = (root, directory = root) => readdirSync4(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
3611
|
+
if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") return [];
|
|
3612
|
+
const path = join10(directory, entry.name);
|
|
3613
|
+
if (entry.isDirectory()) return walkMarkdown(root, path);
|
|
3614
|
+
return entry.isFile() && [".md", ".mdx"].includes(extname2(entry.name).toLowerCase()) ? [relative6(root, path).split(sep7).join("/")] : [];
|
|
3615
|
+
});
|
|
3616
|
+
var localLink = /(!?)\[([^\]]*)\]\(([^)\s]+)(?:\s+["'][^)]*["'])?\)/g;
|
|
3617
|
+
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;
|
|
3618
|
+
var createMarkdownLinkFixProposal = (root, options) => {
|
|
3619
|
+
const projectRoot = realpathSync7.native(resolve9(root));
|
|
3620
|
+
const paths = walkMarkdown(projectRoot);
|
|
3621
|
+
const changes = [];
|
|
3622
|
+
for (const path of paths) {
|
|
3623
|
+
const content = readFileSync10(join10(projectRoot, path), "utf8");
|
|
3624
|
+
let next = content;
|
|
3625
|
+
for (const match of content.matchAll(localLink)) {
|
|
3626
|
+
const target = match[3];
|
|
3627
|
+
if (!target || match[1] === "!" || /^(?:[a-z]+:|\/|#)/i.test(target)) continue;
|
|
3628
|
+
const targetPath = target.split("#")[0]?.split("?")[0];
|
|
3629
|
+
if (!targetPath || existsSync11(resolve9(projectRoot, dirname6(path), targetPath))) continue;
|
|
3630
|
+
const targetStem = basename5(targetPath, extname2(targetPath)).toLowerCase();
|
|
3631
|
+
const labelStem = (match[2] ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3632
|
+
const candidates = paths.filter((candidate) => basename5(candidate, extname2(candidate)).toLowerCase() === targetStem || basename5(candidate, extname2(candidate)).toLowerCase().replace(/[^a-z0-9]+/g, "") === labelStem);
|
|
3633
|
+
if (candidates.length !== 1) continue;
|
|
3634
|
+
let replacement = relative6(dirname6(path), candidates[0]).split(sep7).join("/");
|
|
3635
|
+
if (target.startsWith("./") && !replacement.startsWith(".")) replacement = `./${replacement}`;
|
|
3636
|
+
next = next.replace(match[0], match[0].replace(target, replacement));
|
|
3637
|
+
}
|
|
3638
|
+
if (next !== content) changes.push({ path, before: content, after: next });
|
|
3639
|
+
}
|
|
3640
|
+
return changes.length ? makeProposal(projectRoot, options, changes, ["Each replacement has exactly one Markdown target."], ["All corrected local Markdown links resolve."]) : void 0;
|
|
3641
|
+
};
|
|
3642
|
+
var createArtifactNormalizationProposal = (root, artifactPath, options) => {
|
|
3643
|
+
const projectRoot = realpathSync7.native(resolve9(root));
|
|
3644
|
+
const path = artifactPath.split(sep7).join("/");
|
|
3645
|
+
const absolute = containedPath(projectRoot, path);
|
|
3646
|
+
if (!absolute || !existsSync11(absolute) || !statSync4(absolute).isFile()) return void 0;
|
|
3647
|
+
const before = readFileSync10(absolute, "utf8");
|
|
3648
|
+
let after;
|
|
3649
|
+
try {
|
|
3650
|
+
after = `${JSON.stringify(sortJson(JSON.parse(before)), null, 2)}
|
|
3651
|
+
`;
|
|
3652
|
+
} catch {
|
|
3653
|
+
return void 0;
|
|
3654
|
+
}
|
|
3655
|
+
return after === before ? void 0 : makeProposal(projectRoot, options, [{ path: relative6(projectRoot, absolute).split(sep7).join("/"), before, after }], ["The artifact contains valid JSON."], ["The artifact is valid canonical JSON with one trailing newline."]);
|
|
3656
|
+
};
|
|
3657
|
+
var approveFixProposal = (proposalInput, approvedBy, approvedAt = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
3658
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
3659
|
+
if (proposal.status !== "proposed") throw new Error(`Only proposed fixes can be approved; received "${proposal.status}".`);
|
|
3660
|
+
const approved = { ...proposal, status: "approved", approval: { proposalHash: proposal.contentHash, approvedAt, approvedBy }, contentHash: "0".repeat(64) };
|
|
3661
|
+
return FixProposalV1Schema.parse({ ...approved, contentHash: contentHashForArtifactV1(approved) });
|
|
3662
|
+
};
|
|
3663
|
+
var bindingHash = (proposal) => {
|
|
3664
|
+
const { approval: _approval, contentHash: _contentHash, status: _status, ...rest } = proposal;
|
|
3665
|
+
return contentHashForArtifactV1({ ...rest, contentHash: "0".repeat(64), status: "proposed" });
|
|
3666
|
+
};
|
|
3667
|
+
var applyFixProposal = (root, proposalInput, options = {}) => {
|
|
3668
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
3669
|
+
if (proposal.status !== "approved" || !proposal.approval) throw new Error("Only an explicitly approved fix proposal can be applied.");
|
|
3670
|
+
if (proposal.approval.proposalHash !== bindingHash(proposal)) throw new Error("Approval is not bound to the exact proposal content.");
|
|
3671
|
+
if (options.currentRevision && options.currentRevision !== proposal.baseRevision) throw new Error("The repository revision changed since this proposal was created.");
|
|
3672
|
+
if (!proposal.changes?.length) throw new Error("This proposal has no executable changes.");
|
|
3673
|
+
const projectRoot = realpathSync7.native(resolve9(root));
|
|
3674
|
+
const originals = /* @__PURE__ */ new Map();
|
|
3675
|
+
const affected = new Map(proposal.affectedFiles.map((file) => [file.path, file]));
|
|
3676
|
+
if (proposal.changes.some((change) => !affected.has(change.path) || affected.get(change.path)?.contentHash !== sha256NormalizedV1(change.before)) || affected.size !== proposal.changes.length) {
|
|
3677
|
+
throw new Error("Proposal changes do not match its affected-file hashes.");
|
|
3678
|
+
}
|
|
3679
|
+
for (const file of proposal.affectedFiles) {
|
|
3680
|
+
const absolute = containedPath(projectRoot, file.path);
|
|
3681
|
+
if (!absolute || !existsSync11(absolute)) throw new Error(`Affected file is unavailable or escapes the repository root: ${file.path}`);
|
|
3682
|
+
const current = readFileSync10(absolute, "utf8");
|
|
3683
|
+
if (sha256NormalizedV1(current) !== file.contentHash) throw new Error(`Affected file changed since proposal creation: ${file.path}`);
|
|
3684
|
+
originals.set(absolute, current);
|
|
3685
|
+
}
|
|
3686
|
+
try {
|
|
3687
|
+
for (const change of proposal.changes) {
|
|
3688
|
+
const absolute = resolve9(projectRoot, change.path);
|
|
3689
|
+
writeFileSync3(`${absolute}.docbridge-${process.pid}.tmp`, change.after, "utf8");
|
|
3690
|
+
}
|
|
3691
|
+
for (const change of proposal.changes) {
|
|
3692
|
+
const absolute = resolve9(projectRoot, change.path);
|
|
3693
|
+
renameSync(`${absolute}.docbridge-${process.pid}.tmp`, absolute);
|
|
3694
|
+
}
|
|
3695
|
+
for (const change of proposal.changes) {
|
|
3696
|
+
if (readFileSync10(resolve9(projectRoot, change.path), "utf8") !== change.after) throw new Error(`Postcondition failed for ${change.path}`);
|
|
3697
|
+
}
|
|
3698
|
+
options.verify?.(proposal.changes.map((change) => change.path));
|
|
3699
|
+
} catch (error) {
|
|
3700
|
+
for (const [absolute, content] of originals) writeFileSync3(absolute, content, "utf8");
|
|
3701
|
+
for (const change of proposal.changes) {
|
|
3702
|
+
const temp = `${resolve9(projectRoot, change.path)}.docbridge-${process.pid}.tmp`;
|
|
3703
|
+
if (existsSync11(temp)) unlinkSync(temp);
|
|
3704
|
+
}
|
|
3705
|
+
throw error;
|
|
3706
|
+
}
|
|
3707
|
+
const applied = { ...proposal, status: "applied", contentHash: "0".repeat(64) };
|
|
3708
|
+
return FixProposalV1Schema.parse({ ...applied, contentHash: contentHashForArtifactV1(applied) });
|
|
3709
|
+
};
|
|
3710
|
+
|
|
3711
|
+
// src/workflow/engine.ts
|
|
3712
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync11, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3713
|
+
import { join as join11, relative as relative7, resolve as resolve10 } from "path";
|
|
3714
|
+
var WORKFLOW_STAGES = ["collect", "normalize", "reconcile", "evaluate", "report"];
|
|
3715
|
+
var stageState = {
|
|
3716
|
+
collect: "discovering",
|
|
3717
|
+
normalize: "analyzed",
|
|
3718
|
+
reconcile: "compared",
|
|
3719
|
+
evaluate: "proposed",
|
|
3720
|
+
report: "delivered"
|
|
3721
|
+
};
|
|
3722
|
+
var defaultStateDir = (root) => join11(root, ".doc-bridge", "workflow");
|
|
3723
|
+
var atomicWrite = (path, value) => {
|
|
3724
|
+
const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
3725
|
+
writeFileSync4(temp, `${JSON.stringify(value, null, 2)}
|
|
3726
|
+
`, "utf8");
|
|
3727
|
+
renameSync2(temp, path);
|
|
3728
|
+
};
|
|
3729
|
+
var writeManifest = (stateDir, run2) => {
|
|
3730
|
+
atomicWrite(join11(stateDir, "manifest.json"), run2);
|
|
3731
|
+
};
|
|
3732
|
+
var appendTransition = (stateDir, transition2) => {
|
|
3733
|
+
appendFileSync(join11(stateDir, "transitions.jsonl"), `${JSON.stringify(transition2)}
|
|
3734
|
+
`, "utf8");
|
|
3735
|
+
};
|
|
3736
|
+
var transition = (run2, to, reason) => {
|
|
3737
|
+
const item = {
|
|
3738
|
+
from: run2.state,
|
|
3739
|
+
to,
|
|
3740
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3741
|
+
...reason ? { reason } : {}
|
|
3742
|
+
};
|
|
3743
|
+
return { ...run2, state: to, transitions: [...run2.transitions, item] };
|
|
3744
|
+
};
|
|
3745
|
+
var runId = () => `${Date.now()}-${process.pid}`;
|
|
3746
|
+
var stageInputHash = (options, stage, input) => sha256NormalizedV1({ stage, input, sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
3747
|
+
var stageArtifactPath = (stateDir, stage, inputHash) => join11(stateDir, "artifacts", `${stage}-${inputHash}.json`);
|
|
3748
|
+
var readArtifact = (path) => JSON.parse(readFileSync11(path, "utf8"));
|
|
3749
|
+
var stepArtifactPath = (stateDir, step) => resolve10(stateDir, step.artifactRefs?.[0] ?? "");
|
|
3750
|
+
var stepOutput = (stateDir, run2, stage) => {
|
|
3751
|
+
const step = run2.steps.find((item) => item.name === stage);
|
|
3752
|
+
if (!step || step.status !== "completed" || !step.artifactRefs?.[0]) return null;
|
|
3753
|
+
return readArtifact(stepArtifactPath(stateDir, step)).value;
|
|
3754
|
+
};
|
|
3755
|
+
var acquireLock = (stateDir) => {
|
|
3756
|
+
const lock = join11(stateDir, ".lock");
|
|
3757
|
+
try {
|
|
3758
|
+
mkdirSync3(lock);
|
|
3759
|
+
} catch {
|
|
3760
|
+
const ownerPath = join11(lock, "owner.json");
|
|
3761
|
+
try {
|
|
3762
|
+
const owner = JSON.parse(readFileSync11(ownerPath, "utf8"));
|
|
3763
|
+
if (typeof owner.pid === "number") process.kill(owner.pid, 0);
|
|
3764
|
+
throw new Error(`Workflow is already running (pid ${owner.pid ?? "unknown"}).`);
|
|
3765
|
+
} catch (error) {
|
|
3766
|
+
if (error instanceof Error && error.message.startsWith("Workflow is already running")) throw error;
|
|
3767
|
+
rmSync(lock, { recursive: true, force: true });
|
|
3768
|
+
mkdirSync3(lock);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
writeFileSync4(join11(lock, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
|
|
3772
|
+
return () => rmSync(lock, { recursive: true, force: true });
|
|
3773
|
+
};
|
|
3774
|
+
var loadManifest = (stateDir) => {
|
|
3775
|
+
const path = join11(stateDir, "manifest.json");
|
|
3776
|
+
if (!existsSync12(path)) return void 0;
|
|
3777
|
+
return WorkflowRunV1Schema.parse(JSON.parse(readFileSync11(path, "utf8")));
|
|
3778
|
+
};
|
|
3779
|
+
var baseRun = (options, stateDir, supersedes) => {
|
|
3780
|
+
const inputHash = sha256NormalizedV1({ sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
3781
|
+
return WorkflowRunV1Schema.parse({
|
|
3782
|
+
type: "workflow-run",
|
|
3783
|
+
schemaVersion: 1,
|
|
3784
|
+
contentHash: "0".repeat(64),
|
|
3785
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
3786
|
+
project: { name: resolve10(options.root).split("/").pop() ?? "project", root: "." },
|
|
3787
|
+
sourceRevision: options.sourceRevision,
|
|
3788
|
+
sourceRevisionKind: "content",
|
|
3789
|
+
configurationHash: options.configurationHash,
|
|
3790
|
+
pipelineVersion: "1.0.0",
|
|
3791
|
+
analyzerVersions: { workflow: options.toolVersion ?? "1.0.0" },
|
|
3792
|
+
runId: options.runId ?? runId(),
|
|
3793
|
+
state: "created",
|
|
3794
|
+
steps: WORKFLOW_STAGES.map((name) => ({ name, status: "pending", inputHash })),
|
|
3795
|
+
transitions: [{ from: null, to: "created", at: (/* @__PURE__ */ new Date()).toISOString() }],
|
|
3796
|
+
artifactRefs: [relative7(resolve10(options.root), stateDir), ...supersedes ? [`supersedes:${supersedes}`] : []]
|
|
3797
|
+
});
|
|
3798
|
+
};
|
|
3799
|
+
var withHash = (run2) => WorkflowRunV1Schema.parse({ ...run2, contentHash: contentHashForArtifactV1(run2) });
|
|
3800
|
+
var sameInputs = (run2, options) => run2.sourceRevision === options.sourceRevision && run2.configurationHash === options.configurationHash && run2.analyzerVersions.workflow === (options.toolVersion ?? "1.0.0");
|
|
3801
|
+
var selectedStages = (stage) => stage && stage !== "all" ? [stage] : WORKFLOW_STAGES;
|
|
3802
|
+
var runWorkflow = (options) => {
|
|
3803
|
+
const root = resolve10(options.root);
|
|
3804
|
+
const stateDir = resolve10(root, options.stateDir ?? defaultStateDir(root));
|
|
3805
|
+
mkdirSync3(join11(stateDir, "artifacts"), { recursive: true });
|
|
3806
|
+
const release = acquireLock(stateDir);
|
|
3807
|
+
try {
|
|
3808
|
+
let run2 = loadManifest(stateDir);
|
|
3809
|
+
let supersedes;
|
|
3810
|
+
if (run2 && !sameInputs(run2, options)) {
|
|
3811
|
+
supersedes = run2.runId;
|
|
3812
|
+
run2 = withHash(transition(run2, "stale", "Source revision, configuration hash, or tool version changed."));
|
|
3813
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
3814
|
+
writeManifest(stateDir, run2);
|
|
3815
|
+
run2 = void 0;
|
|
3816
|
+
}
|
|
3817
|
+
if (!run2) {
|
|
3818
|
+
run2 = withHash(baseRun(options, stateDir, supersedes));
|
|
3819
|
+
appendTransition(stateDir, run2.transitions[0]);
|
|
3820
|
+
writeManifest(stateDir, run2);
|
|
3821
|
+
}
|
|
3822
|
+
if (!run2) throw new Error("Workflow manifest was not initialized.");
|
|
3823
|
+
const firstSelectedStage = selectedStages(options.stage)[0];
|
|
3824
|
+
const previousStageIndex = firstSelectedStage ? WORKFLOW_STAGES.indexOf(firstSelectedStage) - 1 : -1;
|
|
3825
|
+
let previousOutput = previousStageIndex >= 0 ? stepOutput(stateDir, run2, WORKFLOW_STAGES[previousStageIndex]) : null;
|
|
3826
|
+
const reusedStages = [];
|
|
3827
|
+
for (const stage of selectedStages(options.stage)) {
|
|
3828
|
+
const input = options.inputs?.[stage] ?? previousOutput;
|
|
3829
|
+
const inputHash = stageInputHash(options, stage, input);
|
|
3830
|
+
const existing = run2.steps.find((step) => step.name === stage);
|
|
3831
|
+
const artifactPath = existing?.artifactRefs?.[0] ? resolve10(stateDir, existing.artifactRefs[0]) : stageArtifactPath(stateDir, stage, inputHash);
|
|
3832
|
+
if (existing?.status === "completed" && existing.inputHash === inputHash && existing.outputHash && existsSync12(artifactPath)) {
|
|
3833
|
+
previousOutput = readArtifact(artifactPath).value;
|
|
3834
|
+
reusedStages.push(stage);
|
|
3835
|
+
continue;
|
|
3836
|
+
}
|
|
3837
|
+
const handler = options.handlers[stage];
|
|
3838
|
+
if (!handler) throw new Error(`No handler configured for workflow stage "${stage}".`);
|
|
3839
|
+
run2 = withHash(transition(run2, stageState[stage]));
|
|
3840
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
3841
|
+
const runningStep = { name: stage, status: "running", inputHash };
|
|
3842
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? runningStep : step) });
|
|
3843
|
+
writeManifest(stateDir, run2);
|
|
3844
|
+
try {
|
|
3845
|
+
const value = handler({ root, stage, input, previousOutput });
|
|
3846
|
+
const outputHash = sha256NormalizedV1(value);
|
|
3847
|
+
const artifact2 = { type: "workflow-step-artifact", stage, inputHash, outputHash, value };
|
|
3848
|
+
mkdirSync3(join11(stateDir, "artifacts"), { recursive: true });
|
|
3849
|
+
if (existsSync12(artifactPath)) {
|
|
3850
|
+
const existingArtifact = readArtifact(artifactPath);
|
|
3851
|
+
if (existingArtifact.outputHash !== outputHash) throw new Error(`Immutable workflow artifact collision for stage "${stage}".`);
|
|
3852
|
+
} else {
|
|
3853
|
+
atomicWrite(artifactPath, artifact2);
|
|
3854
|
+
}
|
|
3855
|
+
const ref = relative7(stateDir, artifactPath);
|
|
3856
|
+
const completedStep = { name: stage, status: "completed", inputHash, outputHash, artifactRefs: [ref] };
|
|
3857
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? completedStep : step) });
|
|
3858
|
+
writeManifest(stateDir, run2);
|
|
3859
|
+
previousOutput = value;
|
|
3860
|
+
} catch (error) {
|
|
3861
|
+
run2 = withHash(transition(run2, "failed", error instanceof Error ? error.message : String(error)));
|
|
3862
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
3863
|
+
writeManifest(stateDir, run2);
|
|
3864
|
+
throw error;
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
if (selectedStages(options.stage).every((stage) => run2.steps.find((step) => step.name === stage)?.status === "completed")) {
|
|
3868
|
+
const complete = selectedStages(options.stage).includes("report") && run2.state !== "delivered" ? withHash(transition(run2, "delivered")) : run2;
|
|
3869
|
+
if (complete !== run2) {
|
|
3870
|
+
appendTransition(stateDir, complete.transitions[complete.transitions.length - 1]);
|
|
3871
|
+
run2 = complete;
|
|
3872
|
+
}
|
|
3873
|
+
writeManifest(stateDir, run2);
|
|
3874
|
+
if (run2.state === "delivered") atomicWrite(join11(stateDir, "last-known-good.json"), { runId: run2.runId, manifestHash: run2.contentHash, report: run2.steps.find((step) => step.name === "report")?.artifactRefs?.[0] });
|
|
3875
|
+
}
|
|
3876
|
+
return { run: run2, stateDir, reusedStages };
|
|
3877
|
+
} finally {
|
|
3878
|
+
release();
|
|
3879
|
+
}
|
|
3880
|
+
};
|
|
3881
|
+
var loadWorkflowManifest = (stateDir) => WorkflowRunV1Schema.parse(JSON.parse(readFileSync11(join11(resolve10(stateDir), "manifest.json"), "utf8")));
|
|
3882
|
+
var loadWorkflowStepOutput = (stateDir, stage) => stepOutput(resolve10(stateDir), loadWorkflowManifest(stateDir), stage);
|
|
3883
|
+
|
|
3884
|
+
// src/rules/engine.ts
|
|
3885
|
+
import { minimatch as minimatch5 } from "minimatch";
|
|
3886
|
+
var diagnosticRules = {
|
|
3887
|
+
DOCUMENTATION_QUALITY: "documentation-quality",
|
|
3888
|
+
RELATION_UNDOCUMENTED: "graph-undocumented-relation",
|
|
3889
|
+
DECLARED_RELATION_STALE: "declared-unobserved-relation",
|
|
3890
|
+
UNRESOLVED_ENTITY_REFERENCE: "unresolved-reference",
|
|
3891
|
+
CONFLICTING_DECLARATIONS: "conflicting-declaration",
|
|
3892
|
+
RELATION_NOT_ANALYZED: "not-analyzed-coverage",
|
|
3893
|
+
STALE_DOCUMENTATION: "stale-documentation",
|
|
3894
|
+
FRESHNESS_FAILURE: "freshness",
|
|
3895
|
+
OWNERSHIP_GAP: "ownership",
|
|
3896
|
+
CENTRALITY_RISK: "centrality-risk",
|
|
3897
|
+
CRITICAL_PATH_RISK: "critical-path-risk"
|
|
3898
|
+
};
|
|
3899
|
+
var defaultSeverity = (mode, ruleId) => {
|
|
3900
|
+
if (mode === "default") return "info";
|
|
3901
|
+
if (mode === "strict") return ruleId === "not-analyzed-coverage" ? "warn" : "error";
|
|
3902
|
+
return ruleId === "not-analyzed-coverage" ? "info" : "warn";
|
|
3903
|
+
};
|
|
3904
|
+
var resolvedOptions = (options) => {
|
|
3905
|
+
const config = RulesConfigSchema.parse(options.config ?? {});
|
|
3906
|
+
const mode = options.preset ?? config.mode ?? "default";
|
|
3907
|
+
const severity = { ...config.severity, ...options.severity };
|
|
3908
|
+
const ignore = /* @__PURE__ */ new Set([...config.ignore ?? [], ...options.ignore ?? []]);
|
|
3909
|
+
return {
|
|
3910
|
+
mode,
|
|
3911
|
+
severity,
|
|
3912
|
+
ignore,
|
|
3913
|
+
criticalEntities: options.criticalEntities ?? config.criticalEntities ?? [],
|
|
3914
|
+
criticalPaths: options.criticalPaths ?? config.criticalPaths ?? [],
|
|
3915
|
+
warningThresholds: { ...config.warningThresholds, ...options.warningThresholds }
|
|
3916
|
+
};
|
|
3917
|
+
};
|
|
3918
|
+
var severityFor = (ruleId, mode, overrides) => overrides[ruleId] ?? defaultSeverity(mode, ruleId);
|
|
3919
|
+
var findingFromDiagnostic = (diagnostic2, ruleId, severity) => ({
|
|
3920
|
+
id: `${diagnostic2.id}:${ruleId}`,
|
|
3921
|
+
ruleId,
|
|
3922
|
+
code: ruleId,
|
|
3923
|
+
status: diagnostic2.status,
|
|
3924
|
+
severity,
|
|
3925
|
+
message: diagnostic2.message,
|
|
3926
|
+
evidence: diagnostic2.evidence,
|
|
3927
|
+
...diagnostic2.entityIds ? { entityIds: diagnostic2.entityIds } : {},
|
|
3928
|
+
...diagnostic2.relationIds ? { relationIds: diagnostic2.relationIds } : {},
|
|
3929
|
+
...diagnostic2.remediation ? { remediation: diagnostic2.remediation } : {},
|
|
3930
|
+
sourceDiagnosticCode: diagnostic2.code
|
|
3931
|
+
});
|
|
3932
|
+
var criticalFinding = (finding, severity, target) => ({
|
|
3933
|
+
...finding,
|
|
3934
|
+
id: `${finding.id}:critical:${target}`,
|
|
3935
|
+
ruleId: "critical-path-risk",
|
|
3936
|
+
code: "critical-path-risk",
|
|
3937
|
+
severity,
|
|
3938
|
+
message: `Critical path or entity is affected: ${target}. ${finding.message}`
|
|
3939
|
+
});
|
|
3940
|
+
var evaluateRules = (report, options = {}) => {
|
|
3941
|
+
const resolved = resolvedOptions(options);
|
|
3942
|
+
const findings = [];
|
|
3943
|
+
for (const diagnostic2 of [...report.diagnostics].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
3944
|
+
const ruleId = diagnosticRules[diagnostic2.code];
|
|
3945
|
+
if (!ruleId || resolved.ignore.has(ruleId)) continue;
|
|
3946
|
+
const severity = severityFor(ruleId, resolved.mode, resolved.severity);
|
|
3947
|
+
if (severity !== "off") findings.push(findingFromDiagnostic(diagnostic2, ruleId, severity));
|
|
3948
|
+
}
|
|
3949
|
+
const criticalSeverity = severityFor("critical-path-risk", resolved.mode, resolved.severity);
|
|
3950
|
+
const criticalEntitySet = new Set(resolved.criticalEntities);
|
|
3951
|
+
for (const finding of [...findings]) {
|
|
3952
|
+
const matchingEntity = (finding.entityIds ?? []).find((id) => criticalEntitySet.has(id));
|
|
3953
|
+
if (matchingEntity && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
3954
|
+
findings.push(criticalFinding(finding, criticalSeverity, matchingEntity));
|
|
3955
|
+
}
|
|
3956
|
+
for (const path of resolved.criticalPaths) {
|
|
3957
|
+
if (finding.evidence.some((item) => minimatch5(item.path, path, { dot: true })) && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
3958
|
+
findings.push(criticalFinding(finding, criticalSeverity, path));
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
const centralityThreshold = resolved.warningThresholds["centrality-risk"] ?? 3;
|
|
3963
|
+
const centralitySeverity = severityFor("centrality-risk", resolved.mode, resolved.severity);
|
|
3964
|
+
if (!resolved.ignore.has("centrality-risk") && centralitySeverity !== "off") {
|
|
3965
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3966
|
+
for (const finding of findings.filter((item) => item.ruleId === "graph-undocumented-relation")) {
|
|
3967
|
+
for (const entityId2 of finding.entityIds ?? []) counts.set(entityId2, (counts.get(entityId2) ?? 0) + 1);
|
|
3968
|
+
}
|
|
3969
|
+
for (const [entityId2, count2] of [...counts.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
3970
|
+
if (count2 < centralityThreshold || !criticalEntitySet.has(entityId2)) continue;
|
|
3971
|
+
findings.push({
|
|
3972
|
+
id: `centrality-risk:${entityId2}`,
|
|
3973
|
+
ruleId: "centrality-risk",
|
|
3974
|
+
code: "centrality-risk",
|
|
3975
|
+
status: "unresolved",
|
|
3976
|
+
severity: centralitySeverity,
|
|
3977
|
+
message: `Critical entity has ${count2} undocumented relation finding(s); static centrality is a review signal, not a runtime availability claim.`,
|
|
3978
|
+
evidence: findings.filter((item) => item.ruleId === "graph-undocumented-relation" && item.entityIds?.includes(entityId2)).flatMap((item) => item.evidence),
|
|
3979
|
+
entityIds: [entityId2],
|
|
3980
|
+
remediation: "Review ownership, dependency boundaries, and runtime availability before declaring an SPOF."
|
|
3981
|
+
});
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
const sortedFindings = [...findings].sort((a, b) => a.id.localeCompare(b.id));
|
|
3985
|
+
return { mode: resolved.mode, findings: sortedFindings, exitCode: sortedFindings.some((finding) => finding.severity === "error") ? 1 : 0 };
|
|
3986
|
+
};
|
|
3987
|
+
var parseRuleId = (value) => RuleIdSchema.parse(value);
|
|
3988
|
+
var parseRuleSeverity = (value) => RuleSeveritySchema.parse(value);
|
|
3989
|
+
|
|
2262
3990
|
// src/federation/ecosystem-llms.ts
|
|
2263
3991
|
function formatEcosystemLlmsBlock(options) {
|
|
2264
3992
|
const heading = options.heading ?? "AgentsKit ecosystem";
|
|
@@ -2286,44 +4014,44 @@ function formatEcosystemLlmsSection(options) {
|
|
|
2286
4014
|
}
|
|
2287
4015
|
|
|
2288
4016
|
// src/gates/run-gates.ts
|
|
2289
|
-
import { readFileSync as
|
|
4017
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
2290
4018
|
|
|
2291
4019
|
// src/conformance/documentation-standard-v1.ts
|
|
2292
|
-
import { existsSync as
|
|
2293
|
-
import { isAbsolute as
|
|
4020
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, realpathSync as realpathSync8, statSync as statSync5 } from "fs";
|
|
4021
|
+
import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve11, sep as sep8 } from "path";
|
|
2294
4022
|
|
|
2295
4023
|
// src/conformance/ecosystem-contract.ts
|
|
2296
|
-
import { z as
|
|
2297
|
-
var NonEmptyStringSchema =
|
|
2298
|
-
var HttpsUrlSchema =
|
|
2299
|
-
var RepoSchema =
|
|
2300
|
-
var SlugSchema =
|
|
2301
|
-
var SurfaceSchema =
|
|
4024
|
+
import { z as z7 } from "zod";
|
|
4025
|
+
var NonEmptyStringSchema = z7.string().refine((value) => value.trim().length > 0, "must be non-empty");
|
|
4026
|
+
var HttpsUrlSchema = z7.string().url().refine((value) => value.startsWith("https://"), "must use https");
|
|
4027
|
+
var RepoSchema = z7.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/);
|
|
4028
|
+
var SlugSchema = z7.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
4029
|
+
var SurfaceSchema = z7.object({
|
|
2302
4030
|
home: HttpsUrlSchema.optional(),
|
|
2303
4031
|
docs: HttpsUrlSchema.optional(),
|
|
2304
4032
|
llms: HttpsUrlSchema.optional(),
|
|
2305
4033
|
stats: HttpsUrlSchema.optional(),
|
|
2306
|
-
documentation:
|
|
2307
|
-
chat:
|
|
4034
|
+
documentation: z7.enum(["fumadocs", "repository"]),
|
|
4035
|
+
chat: z7.enum(["agentschat", "custom", "none"])
|
|
2308
4036
|
}).passthrough();
|
|
2309
|
-
var ProductSchema =
|
|
4037
|
+
var ProductSchema = z7.object({
|
|
2310
4038
|
id: SlugSchema,
|
|
2311
4039
|
name: NonEmptyStringSchema,
|
|
2312
4040
|
shortName: NonEmptyStringSchema,
|
|
2313
4041
|
kind: NonEmptyStringSchema,
|
|
2314
4042
|
role: NonEmptyStringSchema,
|
|
2315
4043
|
promise: NonEmptyStringSchema,
|
|
2316
|
-
maturity:
|
|
4044
|
+
maturity: z7.enum(["planning", "alpha", "beta", "stable", "deprecated"]),
|
|
2317
4045
|
repo: RepoSchema.nullable(),
|
|
2318
|
-
accent:
|
|
4046
|
+
accent: z7.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
2319
4047
|
surfaces: SurfaceSchema,
|
|
2320
|
-
navigation:
|
|
2321
|
-
showInBar:
|
|
2322
|
-
order:
|
|
2323
|
-
next:
|
|
4048
|
+
navigation: z7.object({
|
|
4049
|
+
showInBar: z7.boolean(),
|
|
4050
|
+
order: z7.number().int().nonnegative().optional(),
|
|
4051
|
+
next: z7.array(SlugSchema)
|
|
2324
4052
|
}).passthrough()
|
|
2325
4053
|
}).passthrough();
|
|
2326
|
-
var LegacyPropertySchema =
|
|
4054
|
+
var LegacyPropertySchema = z7.object({
|
|
2327
4055
|
id: SlugSchema,
|
|
2328
4056
|
name: NonEmptyStringSchema,
|
|
2329
4057
|
barLabel: NonEmptyStringSchema,
|
|
@@ -2332,50 +4060,50 @@ var LegacyPropertySchema = z5.object({
|
|
|
2332
4060
|
repo: RepoSchema.nullable(),
|
|
2333
4061
|
tagline: NonEmptyStringSchema,
|
|
2334
4062
|
kind: NonEmptyStringSchema,
|
|
2335
|
-
accent:
|
|
4063
|
+
accent: z7.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
2336
4064
|
llms: HttpsUrlSchema.optional(),
|
|
2337
4065
|
stats: HttpsUrlSchema.optional()
|
|
2338
4066
|
}).passthrough();
|
|
2339
|
-
var ManifestSchema =
|
|
2340
|
-
schemaVersion:
|
|
2341
|
-
parentBrand:
|
|
2342
|
-
products:
|
|
4067
|
+
var ManifestSchema = z7.object({
|
|
4068
|
+
schemaVersion: z7.literal(2),
|
|
4069
|
+
parentBrand: z7.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema }).passthrough(),
|
|
4070
|
+
products: z7.array(ProductSchema).min(1),
|
|
2343
4071
|
// Historical four-product shim or full seven-product projection of products[].
|
|
2344
|
-
properties:
|
|
4072
|
+
properties: z7.array(LegacyPropertySchema).refine((value) => value.length === 4 || value.length === 7, {
|
|
2345
4073
|
message: "must project either the legacy four products or the full seven-product catalog"
|
|
2346
4074
|
}),
|
|
2347
|
-
builder:
|
|
4075
|
+
builder: z7.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema, url: HttpsUrlSchema }).passthrough().optional()
|
|
2348
4076
|
}).passthrough();
|
|
2349
|
-
var
|
|
2350
|
-
|
|
2351
|
-
type:
|
|
4077
|
+
var EvidenceSchema2 = z7.discriminatedUnion("type", [
|
|
4078
|
+
z7.object({
|
|
4079
|
+
type: z7.literal("repository-derivation"),
|
|
2352
4080
|
repo: RepoSchema,
|
|
2353
4081
|
path: NonEmptyStringSchema,
|
|
2354
4082
|
summary: NonEmptyStringSchema
|
|
2355
4083
|
}).passthrough(),
|
|
2356
|
-
|
|
4084
|
+
z7.object({ type: z7.literal("endpoint"), url: HttpsUrlSchema, summary: NonEmptyStringSchema }).passthrough()
|
|
2357
4085
|
]);
|
|
2358
|
-
var ClaimSchema =
|
|
4086
|
+
var ClaimSchema = z7.object({
|
|
2359
4087
|
id: NonEmptyStringSchema,
|
|
2360
|
-
value:
|
|
4088
|
+
value: z7.number().finite().nonnegative(),
|
|
2361
4089
|
noun: NonEmptyStringSchema,
|
|
2362
|
-
conservativeFloor:
|
|
2363
|
-
evidence:
|
|
4090
|
+
conservativeFloor: z7.number().int().nonnegative().optional(),
|
|
4091
|
+
evidence: EvidenceSchema2
|
|
2364
4092
|
}).passthrough();
|
|
2365
|
-
var ClaimProductSchema =
|
|
4093
|
+
var ClaimProductSchema = z7.object({
|
|
2366
4094
|
productId: SlugSchema,
|
|
2367
|
-
source:
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
4095
|
+
source: z7.discriminatedUnion("type", [
|
|
4096
|
+
z7.object({ type: z7.literal("endpoint"), url: HttpsUrlSchema }).passthrough(),
|
|
4097
|
+
z7.object({ type: z7.literal("repository"), repo: RepoSchema }).passthrough(),
|
|
4098
|
+
z7.object({ type: z7.literal("declaration"), summary: NonEmptyStringSchema }).passthrough()
|
|
2371
4099
|
]),
|
|
2372
|
-
verification:
|
|
2373
|
-
claims:
|
|
4100
|
+
verification: z7.enum(["verified", "declared"]),
|
|
4101
|
+
claims: z7.array(ClaimSchema)
|
|
2374
4102
|
}).passthrough();
|
|
2375
|
-
var ClaimsSchema =
|
|
2376
|
-
schemaVersion:
|
|
2377
|
-
manifestSchemaVersion:
|
|
2378
|
-
products:
|
|
4103
|
+
var ClaimsSchema = z7.object({
|
|
4104
|
+
schemaVersion: z7.literal(1),
|
|
4105
|
+
manifestSchemaVersion: z7.literal(2),
|
|
4106
|
+
products: z7.array(ClaimProductSchema)
|
|
2379
4107
|
}).passthrough();
|
|
2380
4108
|
var LEGACY_FOUR_PRODUCT_IDS = ["agentskit", "akos", "playbook", "registry"];
|
|
2381
4109
|
var FULL_SEVEN_PRODUCT_IDS = [
|
|
@@ -2472,15 +4200,15 @@ var DOCUMENTATION_STANDARD_V1_ID = "documentation-standard-v1";
|
|
|
2472
4200
|
var DOCUMENTATION_STANDARD_V1_STATUS = "stable";
|
|
2473
4201
|
var MAX_TEXT_EVIDENCE_BYTES = 4 * 1024 * 1024;
|
|
2474
4202
|
var safePath = (root, path) => {
|
|
2475
|
-
const rootAbs =
|
|
2476
|
-
const unresolved =
|
|
2477
|
-
const unresolvedRel =
|
|
2478
|
-
if (
|
|
2479
|
-
if (!
|
|
4203
|
+
const rootAbs = realpathSync8.native(resolve11(root));
|
|
4204
|
+
const unresolved = resolve11(rootAbs, path);
|
|
4205
|
+
const unresolvedRel = relative8(rootAbs, unresolved);
|
|
4206
|
+
if (isAbsolute5(unresolvedRel) || unresolvedRel === ".." || unresolvedRel.startsWith(`..${sep8}`)) return void 0;
|
|
4207
|
+
if (!existsSync13(unresolved)) return unresolved;
|
|
2480
4208
|
try {
|
|
2481
|
-
const abs =
|
|
2482
|
-
const rel =
|
|
2483
|
-
return !
|
|
4209
|
+
const abs = realpathSync8.native(unresolved);
|
|
4210
|
+
const rel = relative8(rootAbs, abs);
|
|
4211
|
+
return !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep8}`) ? abs : void 0;
|
|
2484
4212
|
} catch {
|
|
2485
4213
|
return void 0;
|
|
2486
4214
|
}
|
|
@@ -2494,11 +4222,11 @@ var fileEvidence = (root, path, options) => {
|
|
|
2494
4222
|
evidence: { path, detail: "Path escapes the project root." }
|
|
2495
4223
|
};
|
|
2496
4224
|
}
|
|
2497
|
-
if (!
|
|
4225
|
+
if (!existsSync13(abs)) {
|
|
2498
4226
|
return { exists: false, content: "", evidence: { path, detail: "File does not exist." } };
|
|
2499
4227
|
}
|
|
2500
4228
|
try {
|
|
2501
|
-
const stat =
|
|
4229
|
+
const stat = statSync5(abs);
|
|
2502
4230
|
if (!stat.isFile()) {
|
|
2503
4231
|
return { exists: false, content: "", evidence: { path, detail: "Path is not a regular file." } };
|
|
2504
4232
|
}
|
|
@@ -2509,7 +4237,7 @@ var fileEvidence = (root, path, options) => {
|
|
|
2509
4237
|
return {
|
|
2510
4238
|
exists: true,
|
|
2511
4239
|
content: "",
|
|
2512
|
-
evidence: { path: toPosix(
|
|
4240
|
+
evidence: { path: toPosix(relative8(resolve11(root), abs)) || ".", detail: "File exists and is non-empty." }
|
|
2513
4241
|
};
|
|
2514
4242
|
}
|
|
2515
4243
|
if (stat.size > MAX_TEXT_EVIDENCE_BYTES) {
|
|
@@ -2519,12 +4247,12 @@ var fileEvidence = (root, path, options) => {
|
|
|
2519
4247
|
evidence: { path, detail: `Text evidence exceeds ${MAX_TEXT_EVIDENCE_BYTES} bytes.` }
|
|
2520
4248
|
};
|
|
2521
4249
|
}
|
|
2522
|
-
const content =
|
|
4250
|
+
const content = readFileSync12(abs, "utf8");
|
|
2523
4251
|
return {
|
|
2524
4252
|
exists: content.trim().length > 0,
|
|
2525
4253
|
content,
|
|
2526
4254
|
evidence: {
|
|
2527
|
-
path: toPosix(
|
|
4255
|
+
path: toPosix(relative8(resolve11(root), abs)) || ".",
|
|
2528
4256
|
detail: content.trim().length > 0 ? "File exists and is non-empty." : "File is empty."
|
|
2529
4257
|
}
|
|
2530
4258
|
};
|
|
@@ -2559,7 +4287,7 @@ var humanDocsRule = (root, config) => {
|
|
|
2559
4287
|
passed: docs.length > 0,
|
|
2560
4288
|
message: docs.length > 0 ? `Found ${docs.length} human document(s).` : "No human documentation was discovered.",
|
|
2561
4289
|
evidence: docs.slice(0, 10).map((doc) => ({
|
|
2562
|
-
path: toPosix(
|
|
4290
|
+
path: toPosix(relative8(resolve11(root), doc.path)),
|
|
2563
4291
|
detail: `Human route: ${doc.url}`
|
|
2564
4292
|
})),
|
|
2565
4293
|
remediation: {
|
|
@@ -2570,33 +4298,33 @@ var humanDocsRule = (root, config) => {
|
|
|
2570
4298
|
};
|
|
2571
4299
|
var llmsRule = (root, config, options) => {
|
|
2572
4300
|
const llmsPath = config.index?.llmsTxt?.outFile ?? "llms.txt";
|
|
2573
|
-
const llmsKey = safePath(root, llmsPath) ??
|
|
4301
|
+
const llmsKey = safePath(root, llmsPath) ?? resolve11(root, llmsPath);
|
|
2574
4302
|
const rawSources = /* @__PURE__ */ new Map();
|
|
2575
4303
|
for (const path of options.rawSources ?? []) {
|
|
2576
|
-
const key = safePath(root, path) ??
|
|
4304
|
+
const key = safePath(root, path) ?? resolve11(root, path);
|
|
2577
4305
|
if (key !== llmsKey && !rawSources.has(key)) rawSources.set(key, path);
|
|
2578
4306
|
}
|
|
2579
4307
|
const paths = [llmsPath, ...rawSources.values()];
|
|
2580
|
-
const
|
|
4308
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2581
4309
|
const generated = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
2582
4310
|
const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? "project");
|
|
2583
|
-
const llmsIsFresh =
|
|
2584
|
-
if (
|
|
2585
|
-
|
|
2586
|
-
...
|
|
4311
|
+
const llmsIsFresh = evidence2[0]?.content === expectedLlms;
|
|
4312
|
+
if (evidence2[0]?.exists) {
|
|
4313
|
+
evidence2[0] = {
|
|
4314
|
+
...evidence2[0],
|
|
2587
4315
|
evidence: {
|
|
2588
|
-
...
|
|
4316
|
+
...evidence2[0].evidence,
|
|
2589
4317
|
detail: llmsIsFresh ? "File matches the deterministic ak-docs output." : "File is stale or was not generated by the current ak-docs inputs."
|
|
2590
4318
|
}
|
|
2591
4319
|
};
|
|
2592
4320
|
}
|
|
2593
|
-
const passed = config.index?.llmsTxt?.enabled !== false && paths.length > 1 && llmsIsFresh &&
|
|
4321
|
+
const passed = config.index?.llmsTxt?.enabled !== false && paths.length > 1 && llmsIsFresh && evidence2.every((item) => item.exists);
|
|
2594
4322
|
return {
|
|
2595
4323
|
id: "llms-and-raw-source",
|
|
2596
4324
|
level: "required",
|
|
2597
4325
|
passed,
|
|
2598
4326
|
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.",
|
|
2599
|
-
evidence:
|
|
4327
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2600
4328
|
remediation: {
|
|
2601
4329
|
command: "ak-docs index",
|
|
2602
4330
|
detail: "Generate llms.txt and configure conformance.documentationStandardV1.rawSources."
|
|
@@ -2615,8 +4343,8 @@ var ecosystemContract = (root, options) => {
|
|
|
2615
4343
|
}
|
|
2616
4344
|
const manifestFile = fileEvidence(root, declaration.manifest);
|
|
2617
4345
|
const claimsFile = fileEvidence(root, declaration.claims);
|
|
2618
|
-
const
|
|
2619
|
-
if (!manifestFile.exists || !claimsFile.exists) return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
4346
|
+
const evidence2 = [manifestFile.evidence, claimsFile.evidence];
|
|
4347
|
+
if (!manifestFile.exists || !claimsFile.exists) return { passed: false, urls: /* @__PURE__ */ new Set(), evidence: evidence2 };
|
|
2620
4348
|
try {
|
|
2621
4349
|
const manifest = JSON.parse(manifestFile.content);
|
|
2622
4350
|
const claims = JSON.parse(claimsFile.content);
|
|
@@ -2634,15 +4362,15 @@ var ecosystemContract = (root, options) => {
|
|
|
2634
4362
|
if (typeof value === "string" && /^https:\/\//.test(value)) urls.add(normalizedUrl(value));
|
|
2635
4363
|
}
|
|
2636
4364
|
}
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
return { passed: urls.size > 0, urls, evidence };
|
|
4365
|
+
evidence2[0] = { path: declaration.manifest, detail: `Validated ${productIds.length} canonical product(s), including ${declaration.productId}.` };
|
|
4366
|
+
evidence2[1] = { path: declaration.claims, detail: `Validated claim-ledger identity for ${contract.claims.products.length} product(s).` };
|
|
4367
|
+
return { passed: urls.size > 0, urls, evidence: evidence2 };
|
|
2640
4368
|
} catch (error) {
|
|
2641
|
-
|
|
4369
|
+
evidence2.push({
|
|
2642
4370
|
path: `${declaration.manifest}, ${declaration.claims}`,
|
|
2643
4371
|
detail: error instanceof Error ? error.message : "Canonical ecosystem contract is invalid."
|
|
2644
4372
|
});
|
|
2645
|
-
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
4373
|
+
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence: evidence2 };
|
|
2646
4374
|
}
|
|
2647
4375
|
};
|
|
2648
4376
|
var handoffsRule = (root, config) => {
|
|
@@ -2668,13 +4396,13 @@ var handoffsRule = (root, config) => {
|
|
|
2668
4396
|
};
|
|
2669
4397
|
var contributionRule = (root, options) => {
|
|
2670
4398
|
const paths = options.contributionPaths?.length ? options.contributionPaths : ["CONTRIBUTING.md"];
|
|
2671
|
-
const
|
|
4399
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2672
4400
|
return {
|
|
2673
4401
|
id: "contribution",
|
|
2674
4402
|
level: "required",
|
|
2675
|
-
passed:
|
|
2676
|
-
message:
|
|
2677
|
-
evidence:
|
|
4403
|
+
passed: evidence2.some((item) => item.exists),
|
|
4404
|
+
message: evidence2.some((item) => item.exists) ? "Contribution guidance is available." : "Contribution guidance is missing.",
|
|
4405
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2678
4406
|
remediation: {
|
|
2679
4407
|
command: "edit CONTRIBUTING.md",
|
|
2680
4408
|
detail: "Document setup, validation commands, and the pull-request workflow."
|
|
@@ -2683,13 +4411,13 @@ var contributionRule = (root, options) => {
|
|
|
2683
4411
|
};
|
|
2684
4412
|
var markersRule = (root, options, kind, level) => {
|
|
2685
4413
|
const declarations = options[kind === "metadata" ? "metadata" : "diagrams"] ?? [];
|
|
2686
|
-
const
|
|
4414
|
+
const evidence2 = [];
|
|
2687
4415
|
let passed = declarations.length > 0;
|
|
2688
4416
|
for (const declaration of declarations) {
|
|
2689
4417
|
const file = fileEvidence(root, declaration.path);
|
|
2690
4418
|
const missing = declaration.contains.filter((marker) => !file.content.includes(marker));
|
|
2691
4419
|
if (!file.exists || missing.length > 0) passed = false;
|
|
2692
|
-
|
|
4420
|
+
evidence2.push({
|
|
2693
4421
|
path: declaration.path,
|
|
2694
4422
|
detail: !file.exists ? file.evidence.detail : missing.length ? `Missing marker(s): ${missing.join(", ")}` : `Found marker(s): ${declaration.contains.join(", ")}`
|
|
2695
4423
|
});
|
|
@@ -2699,7 +4427,7 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2699
4427
|
level,
|
|
2700
4428
|
passed,
|
|
2701
4429
|
message: passed ? `${kind} evidence is complete.` : `${kind} evidence is incomplete.`,
|
|
2702
|
-
evidence,
|
|
4430
|
+
evidence: evidence2,
|
|
2703
4431
|
remediation: {
|
|
2704
4432
|
command: "edit doc-bridge.config.json",
|
|
2705
4433
|
detail: `Declare ${kind} evidence paths and markers that exist in the repository.`
|
|
@@ -2709,14 +4437,14 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2709
4437
|
var linksRule = (root, options) => {
|
|
2710
4438
|
const links = options.links ?? [];
|
|
2711
4439
|
const contract = ecosystemContract(root, options);
|
|
2712
|
-
const
|
|
4440
|
+
const evidence2 = [...contract.evidence];
|
|
2713
4441
|
let passed = links.length > 0 && contract.passed;
|
|
2714
4442
|
for (const link of links) {
|
|
2715
4443
|
const sources = link.paths.map((path) => ({ path, file: fileEvidence(root, path) }));
|
|
2716
4444
|
const matches = sources.filter(({ file }) => file.exists && file.content.includes(link.url));
|
|
2717
4445
|
const canonical = contract.urls.has(normalizedUrl(link.url));
|
|
2718
4446
|
if (matches.length === 0 || !canonical) passed = false;
|
|
2719
|
-
|
|
4447
|
+
evidence2.push({
|
|
2720
4448
|
path: sources.map((source) => source.path).join(", "),
|
|
2721
4449
|
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.`
|
|
2722
4450
|
});
|
|
@@ -2726,7 +4454,7 @@ var linksRule = (root, options) => {
|
|
|
2726
4454
|
level: "required",
|
|
2727
4455
|
passed,
|
|
2728
4456
|
message: passed ? `${links.length} required ecosystem link(s) resolve in source.` : "One or more required ecosystem links are missing from source.",
|
|
2729
|
-
evidence,
|
|
4457
|
+
evidence: evidence2,
|
|
2730
4458
|
remediation: {
|
|
2731
4459
|
command: "edit README.md",
|
|
2732
4460
|
detail: "Sync the canonical ecosystem snapshots and add each configured canonical URL to a declared documentation source."
|
|
@@ -2735,14 +4463,14 @@ var linksRule = (root, options) => {
|
|
|
2735
4463
|
};
|
|
2736
4464
|
var quickstartsRule = (root, options) => {
|
|
2737
4465
|
const quickstarts = options.quickstarts ?? [];
|
|
2738
|
-
const
|
|
4466
|
+
const evidence2 = [];
|
|
2739
4467
|
let passed = quickstarts.length > 0;
|
|
2740
4468
|
for (const quickstart of quickstarts) {
|
|
2741
4469
|
const doc = fileEvidence(root, quickstart.doc);
|
|
2742
4470
|
const test = fileEvidence(root, quickstart.test);
|
|
2743
4471
|
const missingMarkers = quickstart.testContains.filter((marker) => !test.content.includes(marker));
|
|
2744
4472
|
if (!doc.exists || !test.exists || missingMarkers.length > 0 || !quickstart.command.trim()) passed = false;
|
|
2745
|
-
|
|
4473
|
+
evidence2.push(
|
|
2746
4474
|
{ path: quickstart.doc, detail: `${quickstart.id}: ${doc.evidence.detail}` },
|
|
2747
4475
|
{
|
|
2748
4476
|
path: quickstart.test,
|
|
@@ -2755,7 +4483,7 @@ var quickstartsRule = (root, options) => {
|
|
|
2755
4483
|
level: "required",
|
|
2756
4484
|
passed,
|
|
2757
4485
|
message: passed ? `${quickstarts.length} quickstart(s) have executable test evidence.` : "Quickstart test evidence is incomplete.",
|
|
2758
|
-
evidence,
|
|
4486
|
+
evidence: evidence2,
|
|
2759
4487
|
remediation: {
|
|
2760
4488
|
command: "pnpm test",
|
|
2761
4489
|
detail: "Map every quickstart to a documentation path, test file, identifying marker, and CI command."
|
|
@@ -2764,13 +4492,13 @@ var quickstartsRule = (root, options) => {
|
|
|
2764
4492
|
};
|
|
2765
4493
|
var visualsRule = (root, options) => {
|
|
2766
4494
|
const visuals = options.visuals ?? [];
|
|
2767
|
-
const
|
|
4495
|
+
const evidence2 = visuals.map((path) => fileEvidence(root, path, { readContent: false }));
|
|
2768
4496
|
return {
|
|
2769
4497
|
id: "visual-explanations",
|
|
2770
4498
|
level: "recommended",
|
|
2771
|
-
passed: visuals.length > 0 &&
|
|
2772
|
-
message: visuals.length > 0 &&
|
|
2773
|
-
evidence:
|
|
4499
|
+
passed: visuals.length > 0 && evidence2.every((item) => item.exists),
|
|
4500
|
+
message: visuals.length > 0 && evidence2.every((item) => item.exists) ? `${visuals.length} visual asset(s) found.` : "Visual explanation evidence is incomplete.",
|
|
4501
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2774
4502
|
remediation: {
|
|
2775
4503
|
command: "edit doc-bridge.config.json",
|
|
2776
4504
|
detail: "Declare the images or animations that explain the product workflow."
|
|
@@ -2819,14 +4547,14 @@ var formatDocumentationStandardText = (report) => [
|
|
|
2819
4547
|
"",
|
|
2820
4548
|
...report.results.flatMap((result) => [
|
|
2821
4549
|
`${result.status === "pass" ? "PASS" : result.status === "excepted" ? "EXCEPTED" : "FAIL"} [${result.level}] ${result.id}: ${result.message}`,
|
|
2822
|
-
...result.evidence.map((
|
|
4550
|
+
...result.evidence.map((evidence2) => ` evidence: ${evidence2.path} \u2014 ${evidence2.detail}`),
|
|
2823
4551
|
...result.exception ? [` exception: ${result.exception.reason} \u2014 ${result.exception.approvedBy} (${result.exception.trackingUrl})`] : result.ok ? [] : [` fix: ${result.remediation.command} \u2014 ${result.remediation.detail}`]
|
|
2824
4552
|
])
|
|
2825
4553
|
];
|
|
2826
4554
|
|
|
2827
4555
|
// src/query/load-index.ts
|
|
2828
|
-
import { existsSync as
|
|
2829
|
-
import { join as
|
|
4556
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
4557
|
+
import { join as join12, resolve as resolve12 } from "path";
|
|
2830
4558
|
var IndexNotFoundError = class extends Error {
|
|
2831
4559
|
constructor(path) {
|
|
2832
4560
|
super(`Missing index at ${path}. Run: ak-docs index`);
|
|
@@ -2835,14 +4563,14 @@ var IndexNotFoundError = class extends Error {
|
|
|
2835
4563
|
}
|
|
2836
4564
|
path;
|
|
2837
4565
|
};
|
|
2838
|
-
var indexFilePath = (root, config) =>
|
|
4566
|
+
var indexFilePath = (root, config) => join12(root, config.index?.outFile ?? ".doc-bridge/index.json");
|
|
2839
4567
|
var loadDocBridgeIndex = (root, config) => {
|
|
2840
4568
|
const path = indexFilePath(root, config);
|
|
2841
|
-
if (!
|
|
2842
|
-
const raw = JSON.parse(
|
|
4569
|
+
if (!existsSync14(path)) throw new IndexNotFoundError(path);
|
|
4570
|
+
const raw = JSON.parse(readFileSync13(path, "utf8"));
|
|
2843
4571
|
return parseDocBridgeIndex(raw);
|
|
2844
4572
|
};
|
|
2845
|
-
var resolveRoot = (cwd) =>
|
|
4573
|
+
var resolveRoot = (cwd) => resolve12(cwd ?? process.cwd());
|
|
2846
4574
|
|
|
2847
4575
|
// src/gates/run-gates.ts
|
|
2848
4576
|
var RESERVED_GATE_IDS = /* @__PURE__ */ new Set(["link-rot", "routing-currency", "bootstrap-size"]);
|
|
@@ -2915,7 +4643,7 @@ var runOkfTypeGate = (root, config) => {
|
|
|
2915
4643
|
const required = config.corpus.agent.okf?.requireType ?? config.gates?.preset === "strict";
|
|
2916
4644
|
if (!required) return { id: "okf-type", ok: true, message: "OKF type frontmatter not required" };
|
|
2917
4645
|
const allowed = config.corpus.agent.okf?.allowedTypes;
|
|
2918
|
-
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(
|
|
4646
|
+
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(readFileSync14(doc.absPath, "utf8")) })).filter((doc) => !doc.type || allowed && !allowed.includes(doc.type));
|
|
2919
4647
|
if (bad.length) {
|
|
2920
4648
|
return {
|
|
2921
4649
|
id: "okf-type",
|
|
@@ -2942,10 +4670,10 @@ var DOCS_STYLE_RULES = {
|
|
|
2942
4670
|
"playbook-okf-soft": ["title", "no-stale-wording"],
|
|
2943
4671
|
"title-only": ["title"]
|
|
2944
4672
|
};
|
|
2945
|
-
var
|
|
4673
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2946
4674
|
var docsStyleOptions = (config) => {
|
|
2947
4675
|
const raw = config.gates?.options?.["docs-style"];
|
|
2948
|
-
if (!
|
|
4676
|
+
if (!isRecord3(raw)) {
|
|
2949
4677
|
if (config.gates?.preset === "playbook") {
|
|
2950
4678
|
return { profile: "playbook-okf-soft", required: DOCS_STYLE_RULES["playbook-okf-soft"] };
|
|
2951
4679
|
}
|
|
@@ -2989,7 +4717,7 @@ var runDocsStyleGate = (root, config) => {
|
|
|
2989
4717
|
}
|
|
2990
4718
|
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({
|
|
2991
4719
|
path: doc.path,
|
|
2992
|
-
missing: missingStyleRules(
|
|
4720
|
+
missing: missingStyleRules(readFileSync14(doc.absPath, "utf8"), required)
|
|
2993
4721
|
})).filter((doc) => doc.missing.length > 0);
|
|
2994
4722
|
if (bad.length) {
|
|
2995
4723
|
return {
|
|
@@ -3034,9 +4762,9 @@ var resolveGateIds = (config) => {
|
|
|
3034
4762
|
};
|
|
3035
4763
|
|
|
3036
4764
|
// src/mcp/server.ts
|
|
3037
|
-
import { readFileSync as
|
|
3038
|
-
import { relative as
|
|
3039
|
-
import { z as
|
|
4765
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync16, realpathSync as realpathSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
4766
|
+
import { join as join14, relative as relative9, resolve as resolve13 } from "path";
|
|
4767
|
+
import { z as z8, ZodError } from "zod";
|
|
3040
4768
|
|
|
3041
4769
|
// src/query/search.ts
|
|
3042
4770
|
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
@@ -3154,15 +4882,15 @@ var createDocBridgeRetriever = (index, options = {}) => ({
|
|
|
3154
4882
|
});
|
|
3155
4883
|
|
|
3156
4884
|
// src/memory/ingest.ts
|
|
3157
|
-
import { existsSync as
|
|
3158
|
-
import { join as
|
|
4885
|
+
import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
|
|
4886
|
+
import { join as join13 } from "path";
|
|
3159
4887
|
var memoryFact = (raw, id) => firstParagraph(raw) ?? firstHeading(raw) ?? id;
|
|
3160
|
-
var
|
|
4888
|
+
var relativePath2 = (root, abs) => toPosix(abs).replace(`${toPosix(root)}/`, "");
|
|
3161
4889
|
var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
3162
|
-
if (!
|
|
4890
|
+
if (!existsSync15(dir)) return [];
|
|
3163
4891
|
return walkFiles(dir, { extensions: [".md", ".mdc"] }).map((abs) => {
|
|
3164
|
-
const rel =
|
|
3165
|
-
const raw =
|
|
4892
|
+
const rel = relativePath2(root, abs);
|
|
4893
|
+
const raw = readFileSync15(abs, "utf8");
|
|
3166
4894
|
const id = slugFromPath(rel);
|
|
3167
4895
|
return {
|
|
3168
4896
|
schemaVersion: 1,
|
|
@@ -3177,10 +4905,10 @@ var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
|
3177
4905
|
});
|
|
3178
4906
|
};
|
|
3179
4907
|
var ingestCursorRules = (root) => {
|
|
3180
|
-
const dir =
|
|
4908
|
+
const dir = join13(root, ".cursor", "rules");
|
|
3181
4909
|
return ingestMarkdownDir(root, dir, "cursor", 0.6);
|
|
3182
4910
|
};
|
|
3183
|
-
var ingestAgentMemory = (root) => ingestMarkdownDir(root,
|
|
4911
|
+
var ingestAgentMemory = (root) => ingestMarkdownDir(root, join13(root, ".agent-memory"), "agent-memory", 0.7);
|
|
3184
4912
|
var ingestMemoryCandidates = (root) => [
|
|
3185
4913
|
...ingestAgentMemory(root),
|
|
3186
4914
|
...ingestCursorRules(root)
|
|
@@ -3447,25 +5175,70 @@ var MCP_TOOLS = [
|
|
|
3447
5175
|
description: "Return the static Doc Bridge curator and delegate topology.",
|
|
3448
5176
|
annotations: { readOnlyHint: true },
|
|
3449
5177
|
inputSchema: { type: "object", properties: {} }
|
|
5178
|
+
},
|
|
5179
|
+
{
|
|
5180
|
+
name: "docbridge.snapshot",
|
|
5181
|
+
title: "Read the latest discovery snapshot",
|
|
5182
|
+
description: "Read the bounded canonical repository snapshot from the latest workflow run.",
|
|
5183
|
+
annotations: { readOnlyHint: true },
|
|
5184
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5185
|
+
},
|
|
5186
|
+
{
|
|
5187
|
+
name: "docbridge.report",
|
|
5188
|
+
title: "Read the latest reconciliation report",
|
|
5189
|
+
description: "Read the canonical reconciliation report from the latest workflow run.",
|
|
5190
|
+
annotations: { readOnlyHint: true },
|
|
5191
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5192
|
+
},
|
|
5193
|
+
{
|
|
5194
|
+
name: "docbridge.diagnostics",
|
|
5195
|
+
title: "Read reconciliation diagnostics",
|
|
5196
|
+
description: "Read bounded diagnostics from the latest canonical reconciliation report.",
|
|
5197
|
+
annotations: { readOnlyHint: true },
|
|
5198
|
+
inputSchema: { type: "object", properties: { status: { type: "string" }, severity: { type: "string" } } }
|
|
5199
|
+
},
|
|
5200
|
+
{
|
|
5201
|
+
name: "docbridge.relations",
|
|
5202
|
+
title: "Read architecture relations",
|
|
5203
|
+
description: "Read bounded observed and declared relations from the latest canonical snapshot.",
|
|
5204
|
+
annotations: { readOnlyHint: true },
|
|
5205
|
+
inputSchema: { type: "object", properties: { kind: { type: "string" }, limit: { type: "number" } } }
|
|
5206
|
+
},
|
|
5207
|
+
{
|
|
5208
|
+
name: "docbridge.run",
|
|
5209
|
+
title: "Read workflow state",
|
|
5210
|
+
description: "Read the latest resumable workflow state and artifact references.",
|
|
5211
|
+
annotations: { readOnlyHint: true },
|
|
5212
|
+
inputSchema: { type: "object", properties: {} }
|
|
5213
|
+
},
|
|
5214
|
+
{
|
|
5215
|
+
name: "docbridge.proposals",
|
|
5216
|
+
title: "Read or approve proposals",
|
|
5217
|
+
description: "Create, inspect, approve and apply deterministic proposals through the shared human-gated workflow.",
|
|
5218
|
+
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" } } }
|
|
3450
5219
|
}
|
|
3451
5220
|
];
|
|
3452
5221
|
var asRecord = (value) => value && typeof value === "object" ? value : {};
|
|
3453
|
-
var HandoffResolveArgsSchema =
|
|
3454
|
-
id:
|
|
3455
|
-
kind:
|
|
5222
|
+
var HandoffResolveArgsSchema = z8.object({
|
|
5223
|
+
id: z8.string().min(1),
|
|
5224
|
+
kind: z8.enum(["package", "ownership"]).optional()
|
|
3456
5225
|
});
|
|
3457
|
-
var DocSearchArgsSchema =
|
|
3458
|
-
term:
|
|
3459
|
-
limit:
|
|
5226
|
+
var DocSearchArgsSchema = z8.object({
|
|
5227
|
+
term: z8.string().min(1),
|
|
5228
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
3460
5229
|
});
|
|
3461
|
-
var RetrieverQueryArgsSchema =
|
|
3462
|
-
query:
|
|
3463
|
-
limit:
|
|
5230
|
+
var RetrieverQueryArgsSchema = z8.object({
|
|
5231
|
+
query: z8.string().min(1),
|
|
5232
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
3464
5233
|
});
|
|
3465
|
-
var DocGetArgsSchema =
|
|
3466
|
-
id:
|
|
3467
|
-
path:
|
|
5234
|
+
var DocGetArgsSchema = z8.object({
|
|
5235
|
+
id: z8.string().min(1).optional(),
|
|
5236
|
+
path: z8.string().min(1).optional()
|
|
3468
5237
|
}).refine((args) => args.id || args.path, "doc.get requires id or path");
|
|
5238
|
+
var WorkflowRunArgsSchema = z8.object({ runId: z8.string().min(1).optional() });
|
|
5239
|
+
var DiagnosticsArgsSchema = z8.object({ status: z8.string().min(1).optional(), severity: z8.string().min(1).optional() });
|
|
5240
|
+
var RelationsArgsSchema = z8.object({ kind: z8.string().min(1).optional(), limit: z8.number().int().positive().max(500).optional() });
|
|
5241
|
+
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() });
|
|
3469
5242
|
var parseToolArgs = (tool, schema, value) => {
|
|
3470
5243
|
try {
|
|
3471
5244
|
return schema.parse(value);
|
|
@@ -3496,15 +5269,54 @@ var findDocPath = (index, args) => {
|
|
|
3496
5269
|
return doc.path;
|
|
3497
5270
|
};
|
|
3498
5271
|
var resolveDocPath = (root, relPath) => {
|
|
3499
|
-
const rootAbs =
|
|
3500
|
-
const unresolved =
|
|
3501
|
-
const unresolvedRel =
|
|
5272
|
+
const rootAbs = realpathSync9.native(root);
|
|
5273
|
+
const unresolved = resolve13(rootAbs, relPath);
|
|
5274
|
+
const unresolvedRel = relative9(rootAbs, unresolved);
|
|
3502
5275
|
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
3503
|
-
const abs =
|
|
3504
|
-
const rel =
|
|
5276
|
+
const abs = realpathSync9.native(unresolved);
|
|
5277
|
+
const rel = relative9(rootAbs, abs);
|
|
3505
5278
|
if (rel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
3506
5279
|
return abs;
|
|
3507
5280
|
};
|
|
5281
|
+
var workflowStateDir = (ctx) => resolve13(ctx.root, ctx.config.workflow?.stateDir ?? ".doc-bridge/workflow");
|
|
5282
|
+
var workflowRun = (ctx) => loadWorkflowManifest(workflowStateDir(ctx));
|
|
5283
|
+
var ensureLatestRun = (ctx, runId2) => {
|
|
5284
|
+
const run2 = workflowRun(ctx);
|
|
5285
|
+
if (runId2 && run2.runId !== runId2) throw new Error(`Unknown workflow run "${runId2}"`);
|
|
5286
|
+
if (run2.state === "stale" || run2.state === "failed") throw new Error(`Workflow run is ${run2.state}; resume or create a valid run before reading artifacts.`);
|
|
5287
|
+
return run2;
|
|
5288
|
+
};
|
|
5289
|
+
var workflowSnapshot = (ctx, runId2) => {
|
|
5290
|
+
ensureLatestRun(ctx, runId2);
|
|
5291
|
+
return parseDiscoverySnapshot(loadWorkflowStepOutput(workflowStateDir(ctx), "normalize"));
|
|
5292
|
+
};
|
|
5293
|
+
var workflowReport = (ctx, runId2) => {
|
|
5294
|
+
ensureLatestRun(ctx, runId2);
|
|
5295
|
+
return parseReconciliationReport(loadWorkflowStepOutput(workflowStateDir(ctx), "reconcile"));
|
|
5296
|
+
};
|
|
5297
|
+
var proposalPath = (ctx) => join14(ctx.root, ".doc-bridge", "proposal.json");
|
|
5298
|
+
var readSavedProposal = (ctx, input) => {
|
|
5299
|
+
if (input !== void 0) return FixProposalV1Schema.parse(input);
|
|
5300
|
+
try {
|
|
5301
|
+
return FixProposalV1Schema.parse(JSON.parse(readFileSync16(proposalPath(ctx), "utf8")));
|
|
5302
|
+
} catch {
|
|
5303
|
+
throw new Error(`No saved fix proposal at ${proposalPath(ctx)}.`);
|
|
5304
|
+
}
|
|
5305
|
+
};
|
|
5306
|
+
var saveProposal = (ctx, proposal) => {
|
|
5307
|
+
mkdirSync4(join14(ctx.root, ".doc-bridge"), { recursive: true });
|
|
5308
|
+
writeFileSync5(proposalPath(ctx), `${JSON.stringify(proposal, null, 2)}
|
|
5309
|
+
`, "utf8");
|
|
5310
|
+
};
|
|
5311
|
+
var enabledMcpTools = (ctx) => {
|
|
5312
|
+
const configured = ctx.config.surfaces?.mcp?.tools;
|
|
5313
|
+
if (!configured || configured.length === MCP_TOOLS.length && MCP_TOOLS.every((tool) => configured.includes(tool.name))) return MCP_TOOLS;
|
|
5314
|
+
return MCP_TOOLS.filter((tool) => configured.includes(tool.name));
|
|
5315
|
+
};
|
|
5316
|
+
var assertMcpToolEnabled = (ctx, name) => {
|
|
5317
|
+
if (!MCP_TOOLS.some((tool) => tool.name === name)) throw new Error(`Unknown tool "${name}"`);
|
|
5318
|
+
if (!enabledMcpTools(ctx).some((tool) => tool.name === name)) throw new Error(`MCP tool "${name}" is disabled by configuration.`);
|
|
5319
|
+
};
|
|
3508
5320
|
var handleMcpRequest = (ctx, request) => {
|
|
3509
5321
|
if (request.method === "initialize") {
|
|
3510
5322
|
return {
|
|
@@ -3513,11 +5325,13 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
3513
5325
|
serverInfo: { name: "ak-docs", version: PACKAGE_VERSION }
|
|
3514
5326
|
};
|
|
3515
5327
|
}
|
|
3516
|
-
if (request.method === "tools/list") return { tools:
|
|
5328
|
+
if (request.method === "tools/list") return { tools: enabledMcpTools(ctx) };
|
|
3517
5329
|
if (request.method === "tools/call") {
|
|
3518
5330
|
const params = asRecord(request.params);
|
|
3519
5331
|
const name = params.name;
|
|
3520
5332
|
const args = asRecord(params.arguments);
|
|
5333
|
+
if (typeof name !== "string") throw new Error("MCP tools/call requires a tool name.");
|
|
5334
|
+
assertMcpToolEnabled(ctx, name);
|
|
3521
5335
|
const index = () => ctx.loadIndex?.() ?? loadDocBridgeIndex(ctx.root, ctx.config);
|
|
3522
5336
|
if (name === "handoff.resolve") {
|
|
3523
5337
|
const parsed = parseToolArgs("handoff.resolve", HandoffResolveArgsSchema, args);
|
|
@@ -3535,7 +5349,7 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
3535
5349
|
}
|
|
3536
5350
|
if (name === "doc.get") {
|
|
3537
5351
|
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
3538
|
-
return textResult(
|
|
5352
|
+
return textResult(readFileSync16(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
3539
5353
|
}
|
|
3540
5354
|
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
3541
5355
|
if (name === "retriever.query") {
|
|
@@ -3557,6 +5371,81 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
3557
5371
|
mergePolicy: { autoMerge: false, requiresHuman: true }
|
|
3558
5372
|
});
|
|
3559
5373
|
}
|
|
5374
|
+
if (name === "docbridge.snapshot") {
|
|
5375
|
+
const parsed = parseToolArgs("docbridge.snapshot", WorkflowRunArgsSchema, args);
|
|
5376
|
+
return textResult(redactValue(workflowSnapshot(ctx, parsed.runId)));
|
|
5377
|
+
}
|
|
5378
|
+
if (name === "docbridge.report") {
|
|
5379
|
+
const parsed = parseToolArgs("docbridge.report", WorkflowRunArgsSchema, args);
|
|
5380
|
+
return textResult(redactValue(workflowReport(ctx, parsed.runId)));
|
|
5381
|
+
}
|
|
5382
|
+
if (name === "docbridge.diagnostics") {
|
|
5383
|
+
const parsed = parseToolArgs("docbridge.diagnostics", DiagnosticsArgsSchema, args);
|
|
5384
|
+
const diagnostics = workflowReport(ctx).diagnostics.filter(
|
|
5385
|
+
(diagnostic2) => (!parsed.status || diagnostic2.status === parsed.status) && (!parsed.severity || diagnostic2.severity === parsed.severity)
|
|
5386
|
+
);
|
|
5387
|
+
return textResult(redactValue({ reportHash: workflowReport(ctx).contentHash, diagnostics }));
|
|
5388
|
+
}
|
|
5389
|
+
if (name === "docbridge.relations") {
|
|
5390
|
+
const parsed = parseToolArgs("docbridge.relations", RelationsArgsSchema, args);
|
|
5391
|
+
const snapshot = workflowSnapshot(ctx);
|
|
5392
|
+
return textResult({ snapshotHash: snapshot.contentHash, relations: snapshot.relations.filter((relation) => !parsed.kind || relation.kind === parsed.kind).slice(0, parsed.limit ?? 100) });
|
|
5393
|
+
}
|
|
5394
|
+
if (name === "docbridge.run") {
|
|
5395
|
+
parseToolArgs("docbridge.run", z8.object({}), args);
|
|
5396
|
+
return textResult(workflowRun(ctx));
|
|
5397
|
+
}
|
|
5398
|
+
if (name === "docbridge.proposals") {
|
|
5399
|
+
const parsed = parseToolArgs("docbridge.proposals", ProposalsArgsSchema, args);
|
|
5400
|
+
const run2 = (() => {
|
|
5401
|
+
try {
|
|
5402
|
+
return workflowRun(ctx);
|
|
5403
|
+
} catch {
|
|
5404
|
+
return void 0;
|
|
5405
|
+
}
|
|
5406
|
+
})();
|
|
5407
|
+
if (!parsed.action || parsed.action === "list") {
|
|
5408
|
+
let proposal2;
|
|
5409
|
+
try {
|
|
5410
|
+
proposal2 = readSavedProposal(ctx, void 0);
|
|
5411
|
+
} catch {
|
|
5412
|
+
proposal2 = void 0;
|
|
5413
|
+
}
|
|
5414
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposals: proposal2 ? [proposal2] : [] }));
|
|
5415
|
+
}
|
|
5416
|
+
if (parsed.action === "suggest") {
|
|
5417
|
+
return loadRegistryAgentRunner(ctx.root, ctx.config).then(async (runner) => {
|
|
5418
|
+
const snapshot = workflowSnapshot(ctx);
|
|
5419
|
+
const report = workflowReport(ctx);
|
|
5420
|
+
const adapter = createRegistryAgentAdapter(ctx.root, ctx.config, runner);
|
|
5421
|
+
const proposal2 = await adapter.run(snapshot, report);
|
|
5422
|
+
const savedPath = persistRegistryAgentProposal(workflowStateDir(ctx), proposal2);
|
|
5423
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2, proposalPath: savedPath }));
|
|
5424
|
+
});
|
|
5425
|
+
}
|
|
5426
|
+
const discovered = discoverRepository({ root: ctx.root, config: ctx.config });
|
|
5427
|
+
const options = { baseRevision: discovered.sourceRevision, configurationHash: sha256NormalizedV1(ctx.config), ...ctx.config.project?.name ? { projectName: ctx.config.project.name } : {} };
|
|
5428
|
+
if (parsed.action === "propose-links") {
|
|
5429
|
+
const proposal2 = createMarkdownLinkFixProposal(ctx.root, options);
|
|
5430
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
5431
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
5432
|
+
}
|
|
5433
|
+
if (parsed.action === "propose-normalize") {
|
|
5434
|
+
if (!parsed.artifactPath) throw new Error("docbridge.proposals propose-normalize requires artifactPath");
|
|
5435
|
+
const proposal2 = createArtifactNormalizationProposal(ctx.root, parsed.artifactPath, options);
|
|
5436
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
5437
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
5438
|
+
}
|
|
5439
|
+
if (parsed.action === "approve") {
|
|
5440
|
+
const proposal2 = approveFixProposal(readSavedProposal(ctx, parsed.proposal), parsed.approvedBy ?? "human");
|
|
5441
|
+
if (parsed.proposalHash && proposal2.approval?.proposalHash !== parsed.proposalHash) throw new Error("proposalHash does not match the saved proposal");
|
|
5442
|
+
saveProposal(ctx, proposal2);
|
|
5443
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 }));
|
|
5444
|
+
}
|
|
5445
|
+
const proposal = applyFixProposal(ctx.root, readSavedProposal(ctx, parsed.proposal), { currentRevision: discovered.sourceRevision });
|
|
5446
|
+
saveProposal(ctx, proposal);
|
|
5447
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal }));
|
|
5448
|
+
}
|
|
3560
5449
|
throw new Error(`Unknown tool "${String(name)}"`);
|
|
3561
5450
|
}
|
|
3562
5451
|
if (request.method?.startsWith("notifications/")) return void 0;
|
|
@@ -3569,16 +5458,16 @@ var writeFrame = (payload, framing) => {
|
|
|
3569
5458
|
\r
|
|
3570
5459
|
${body}`);
|
|
3571
5460
|
};
|
|
3572
|
-
var
|
|
5461
|
+
var respondMcpRequest = async (ctx, request, framing) => {
|
|
3573
5462
|
if (request.id === void 0) {
|
|
3574
5463
|
try {
|
|
3575
|
-
handleMcpRequest(ctx, request);
|
|
5464
|
+
await handleMcpRequest(ctx, request);
|
|
3576
5465
|
} catch {
|
|
3577
5466
|
}
|
|
3578
5467
|
return;
|
|
3579
5468
|
}
|
|
3580
5469
|
try {
|
|
3581
|
-
const result = handleMcpRequest(ctx, request);
|
|
5470
|
+
const result = await handleMcpRequest(ctx, request);
|
|
3582
5471
|
writeFrame({ jsonrpc: "2.0", id: request.id, result: result ?? {} }, framing);
|
|
3583
5472
|
} catch (error) {
|
|
3584
5473
|
writeFrame({
|
|
@@ -3590,6 +5479,10 @@ var respond = (ctx, request, framing) => {
|
|
|
3590
5479
|
};
|
|
3591
5480
|
var startMcpStdioServer = (ctx) => {
|
|
3592
5481
|
let buffer = Buffer.alloc(0);
|
|
5482
|
+
let responseChain = Promise.resolve();
|
|
5483
|
+
const enqueueResponse = (request, framing) => {
|
|
5484
|
+
responseChain = responseChain.then(() => respondMcpRequest(ctx, request, framing));
|
|
5485
|
+
};
|
|
3593
5486
|
process.stdin.on("data", (chunk) => {
|
|
3594
5487
|
buffer = Buffer.concat([buffer, chunk]);
|
|
3595
5488
|
while (true) {
|
|
@@ -3608,7 +5501,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
3608
5501
|
if (buffer.length < bodyEnd) return;
|
|
3609
5502
|
const raw2 = buffer.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
3610
5503
|
buffer = buffer.subarray(bodyEnd);
|
|
3611
|
-
|
|
5504
|
+
enqueueResponse(JSON.parse(raw2), "content-length");
|
|
3612
5505
|
continue;
|
|
3613
5506
|
}
|
|
3614
5507
|
const lineEnd = buffer.indexOf("\n");
|
|
@@ -3617,7 +5510,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
3617
5510
|
buffer = buffer.subarray(lineEnd + 1);
|
|
3618
5511
|
if (!raw) continue;
|
|
3619
5512
|
try {
|
|
3620
|
-
|
|
5513
|
+
enqueueResponse(JSON.parse(raw), "json-line");
|
|
3621
5514
|
} catch {
|
|
3622
5515
|
writeFrame({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }, "json-line");
|
|
3623
5516
|
}
|
|
@@ -3627,37 +5520,37 @@ var startMcpStdioServer = (ctx) => {
|
|
|
3627
5520
|
};
|
|
3628
5521
|
|
|
3629
5522
|
// src/mcp/install.ts
|
|
3630
|
-
import { existsSync as
|
|
5523
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "fs";
|
|
3631
5524
|
import { homedir } from "os";
|
|
3632
|
-
import { dirname as
|
|
5525
|
+
import { dirname as dirname7, join as join15, resolve as resolve14 } from "path";
|
|
3633
5526
|
var SERVER_NAME = "ak-docs";
|
|
3634
5527
|
var mcpServerEntry = (root) => ({
|
|
3635
5528
|
command: "npx",
|
|
3636
5529
|
args: ["ak-docs", "mcp"],
|
|
3637
5530
|
cwd: root
|
|
3638
5531
|
});
|
|
3639
|
-
var
|
|
3640
|
-
if (!
|
|
5532
|
+
var readJson2 = (path) => {
|
|
5533
|
+
if (!existsSync16(path)) return {};
|
|
3641
5534
|
try {
|
|
3642
|
-
const parsed = JSON.parse(
|
|
5535
|
+
const parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
3643
5536
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
3644
5537
|
} catch {
|
|
3645
5538
|
return {};
|
|
3646
5539
|
}
|
|
3647
5540
|
};
|
|
3648
5541
|
var writeJson = (path, value) => {
|
|
3649
|
-
|
|
3650
|
-
|
|
5542
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
5543
|
+
writeFileSync6(path, `${JSON.stringify(value, null, 2)}
|
|
3651
5544
|
`, "utf8");
|
|
3652
5545
|
};
|
|
3653
5546
|
var resolveTargetPath = (target, root) => {
|
|
3654
|
-
if (target === "cursor") return
|
|
3655
|
-
return
|
|
5547
|
+
if (target === "cursor") return resolve14(root, ".cursor", "mcp.json");
|
|
5548
|
+
return join15(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
3656
5549
|
};
|
|
3657
5550
|
var installMcpConfig = (root, target) => {
|
|
3658
5551
|
const configPath = resolveTargetPath(target, root);
|
|
3659
|
-
const created = !
|
|
3660
|
-
const existing =
|
|
5552
|
+
const created = !existsSync16(configPath);
|
|
5553
|
+
const existing = readJson2(configPath);
|
|
3661
5554
|
const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
|
|
3662
5555
|
servers[SERVER_NAME] = mcpServerEntry(root);
|
|
3663
5556
|
writeJson(configPath, { ...existing, mcpServers: servers });
|
|
@@ -3922,23 +5815,23 @@ var formatDoctorText = (report) => {
|
|
|
3922
5815
|
};
|
|
3923
5816
|
|
|
3924
5817
|
// src/index-builder/watch-index.ts
|
|
3925
|
-
import { existsSync as
|
|
3926
|
-
import { dirname as
|
|
5818
|
+
import { existsSync as existsSync17, watch } from "fs";
|
|
5819
|
+
import { dirname as dirname8, resolve as resolve15 } from "path";
|
|
3927
5820
|
var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
|
|
3928
5821
|
var NX_MANIFEST_PATTERN = /(^|[/\\])(project|package)\.json$/i;
|
|
3929
5822
|
var collectWatchRoots = (root, config, configPath) => {
|
|
3930
5823
|
const roots = /* @__PURE__ */ new Set();
|
|
3931
|
-
roots.add(
|
|
5824
|
+
roots.add(resolve15(root, config.corpus.agent.root));
|
|
3932
5825
|
const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
|
|
3933
5826
|
for (const source of humanSources) {
|
|
3934
5827
|
const humanOpts = source.options ?? {};
|
|
3935
5828
|
for (const key of ["contentDir", "docsDir", "root", "srcDir"]) {
|
|
3936
5829
|
const value = humanOpts[key];
|
|
3937
|
-
if (typeof value === "string" && value.length) roots.add(
|
|
5830
|
+
if (typeof value === "string" && value.length) roots.add(resolve15(root, value));
|
|
3938
5831
|
}
|
|
3939
5832
|
}
|
|
3940
|
-
if (configPath) roots.add(
|
|
3941
|
-
return [...roots].filter((dir) =>
|
|
5833
|
+
if (configPath) roots.add(dirname8(resolve15(configPath)));
|
|
5834
|
+
return [...roots].filter((dir) => existsSync17(dir));
|
|
3942
5835
|
};
|
|
3943
5836
|
var watchDocBridgeIndex = (opts) => {
|
|
3944
5837
|
const debounceMs = opts.debounceMs ?? 350;
|
|
@@ -3979,15 +5872,15 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
3979
5872
|
rebuild();
|
|
3980
5873
|
});
|
|
3981
5874
|
}
|
|
3982
|
-
const nxRoot =
|
|
3983
|
-
if (opts.config.routing?.plugin === "nx" &&
|
|
5875
|
+
const nxRoot = resolve15(opts.root);
|
|
5876
|
+
if (opts.config.routing?.plugin === "nx" && existsSync17(nxRoot)) {
|
|
3984
5877
|
watch(nxRoot, { recursive: true }, (_event, filename) => {
|
|
3985
5878
|
if (!filename || !NX_MANIFEST_PATTERN.test(filename)) return;
|
|
3986
5879
|
rebuild();
|
|
3987
5880
|
});
|
|
3988
5881
|
}
|
|
3989
|
-
const configDir =
|
|
3990
|
-
if (
|
|
5882
|
+
const configDir = resolve15(opts.root);
|
|
5883
|
+
if (existsSync17(configDir)) {
|
|
3991
5884
|
watch(configDir, (_event, filename) => {
|
|
3992
5885
|
if (!filename || !/doc-bridge\.config/.test(filename)) return;
|
|
3993
5886
|
rebuild();
|
|
@@ -4005,9 +5898,9 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
4005
5898
|
};
|
|
4006
5899
|
|
|
4007
5900
|
// src/memory/github-pr.ts
|
|
4008
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
4009
|
-
import { existsSync as
|
|
4010
|
-
import { join as
|
|
5901
|
+
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
5902
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
5903
|
+
import { join as join17 } from "path";
|
|
4011
5904
|
var run = (cmd, args, cwd) => {
|
|
4012
5905
|
const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
|
|
4013
5906
|
const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
@@ -4015,11 +5908,11 @@ var run = (cmd, args, cwd) => {
|
|
|
4015
5908
|
};
|
|
4016
5909
|
var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
|
|
4017
5910
|
var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
4018
|
-
var defaultPromotionDraftPath = (root) =>
|
|
5911
|
+
var defaultPromotionDraftPath = (root) => join17(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
|
|
4019
5912
|
var writePromotionDraft = (root, draft, path) => {
|
|
4020
5913
|
const draftPath = path ?? defaultPromotionDraftPath(root);
|
|
4021
|
-
|
|
4022
|
-
|
|
5914
|
+
mkdirSync6(join17(root, ".doc-bridge", "drafts"), { recursive: true });
|
|
5915
|
+
writeFileSync7(draftPath, `${draft.body}
|
|
4023
5916
|
`, "utf8");
|
|
4024
5917
|
return draftPath;
|
|
4025
5918
|
};
|
|
@@ -4054,7 +5947,7 @@ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
|
|
|
4054
5947
|
message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
|
|
4055
5948
|
};
|
|
4056
5949
|
}
|
|
4057
|
-
if (!
|
|
5950
|
+
if (!existsSync18(join17(root, ".git"))) {
|
|
4058
5951
|
return {
|
|
4059
5952
|
ok: false,
|
|
4060
5953
|
dryRun: false,
|
|
@@ -4132,7 +6025,7 @@ ${auth.out}`
|
|
|
4132
6025
|
];
|
|
4133
6026
|
let prUrl = "";
|
|
4134
6027
|
try {
|
|
4135
|
-
prUrl =
|
|
6028
|
+
prUrl = execFileSync2("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
|
|
4136
6029
|
} catch (error) {
|
|
4137
6030
|
const message = error instanceof Error ? error.message : String(error);
|
|
4138
6031
|
return {
|
|
@@ -4157,8 +6050,8 @@ ${auth.out}`
|
|
|
4157
6050
|
};
|
|
4158
6051
|
|
|
4159
6052
|
// src/federation/llms.ts
|
|
4160
|
-
import { existsSync as
|
|
4161
|
-
import { resolve as
|
|
6053
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
6054
|
+
import { resolve as resolve16 } from "path";
|
|
4162
6055
|
var tokenize2 = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 2);
|
|
4163
6056
|
var scoreText = (query, text) => {
|
|
4164
6057
|
const hay = text.toLowerCase();
|
|
@@ -4173,9 +6066,9 @@ var defaultFetchText = async (url) => {
|
|
|
4173
6066
|
var sourceText = async (root, source, fetchText) => {
|
|
4174
6067
|
try {
|
|
4175
6068
|
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
4176
|
-
const path =
|
|
4177
|
-
if (!
|
|
4178
|
-
return
|
|
6069
|
+
const path = resolve16(root, source);
|
|
6070
|
+
if (!existsSync19(path)) return null;
|
|
6071
|
+
return readFileSync18(path, "utf8");
|
|
4179
6072
|
} catch {
|
|
4180
6073
|
return null;
|
|
4181
6074
|
}
|
|
@@ -4260,11 +6153,11 @@ var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
|
4260
6153
|
};
|
|
4261
6154
|
|
|
4262
6155
|
// src/intelligence/rag.ts
|
|
4263
|
-
import { readFileSync as
|
|
4264
|
-
import { join as
|
|
6156
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
6157
|
+
import { join as join19 } from "path";
|
|
4265
6158
|
|
|
4266
6159
|
// src/intelligence/adapter.ts
|
|
4267
|
-
import { join as
|
|
6160
|
+
import { join as join18 } from "path";
|
|
4268
6161
|
|
|
4269
6162
|
// src/intelligence/peers.ts
|
|
4270
6163
|
var PeerMissingError = class extends Error {
|
|
@@ -4391,7 +6284,7 @@ var resolveIntelligenceRuntime = async (config) => {
|
|
|
4391
6284
|
}
|
|
4392
6285
|
return { adapter, embed, provider, ...model ? { model } : {} };
|
|
4393
6286
|
};
|
|
4394
|
-
var defaultVectorStorePath = (root) =>
|
|
6287
|
+
var defaultVectorStorePath = (root) => join18(root, ".doc-bridge", "vectors");
|
|
4395
6288
|
|
|
4396
6289
|
// src/intelligence/rag.ts
|
|
4397
6290
|
var loadDocuments = (root, index, sources) => {
|
|
@@ -4399,10 +6292,10 @@ var loadDocuments = (root, index, sources) => {
|
|
|
4399
6292
|
const docs = [];
|
|
4400
6293
|
if (includeAgent) {
|
|
4401
6294
|
for (const entry of index.knowledge) {
|
|
4402
|
-
const abs =
|
|
6295
|
+
const abs = join19(root, entry.path);
|
|
4403
6296
|
let content = "";
|
|
4404
6297
|
try {
|
|
4405
|
-
content =
|
|
6298
|
+
content = readFileSync19(abs, "utf8");
|
|
4406
6299
|
} catch {
|
|
4407
6300
|
content = [entry.title, entry.description].filter(Boolean).join("\n\n");
|
|
4408
6301
|
}
|
|
@@ -4420,7 +6313,7 @@ var createDocBridgeRag = async (root, config, index) => {
|
|
|
4420
6313
|
const { embed } = await resolveIntelligenceRuntime(config);
|
|
4421
6314
|
const ragMod = await importPeer("@agentskit/rag");
|
|
4422
6315
|
const memoryMod = await importPeer("@agentskit/memory");
|
|
4423
|
-
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ?
|
|
6316
|
+
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join19(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
|
|
4424
6317
|
const store = memoryMod.fileVectorMemory({ path: storePath });
|
|
4425
6318
|
const rag = ragMod.createRAG({
|
|
4426
6319
|
embed,
|
|
@@ -4674,15 +6567,22 @@ var docBridgePatternPayload = () => ({
|
|
|
4674
6567
|
body: docBridgePatternMarkdown()
|
|
4675
6568
|
});
|
|
4676
6569
|
export {
|
|
6570
|
+
AffectedFileSchema,
|
|
4677
6571
|
AgentHandoffLegacySchema,
|
|
4678
6572
|
AgentHandoffV1JsonSchema,
|
|
4679
6573
|
AgentHandoffV1Schema,
|
|
6574
|
+
AgentProposalV1Schema,
|
|
4680
6575
|
AgentSearchV1Schema,
|
|
4681
6576
|
ConfigNotFoundError,
|
|
6577
|
+
CoverageSchema,
|
|
6578
|
+
DEFAULT_REGISTRY_AGENT_ID,
|
|
6579
|
+
DEFAULT_SAFETY_EXCLUDES,
|
|
4682
6580
|
DOCUMENTATION_STANDARD_V1_ID,
|
|
4683
6581
|
DOCUMENTATION_STANDARD_V1_STATUS,
|
|
4684
6582
|
DOC_BRIDGE_PATTERN_ID,
|
|
4685
6583
|
DOC_BRIDGE_PATTERN_META,
|
|
6584
|
+
DiagnosticSeveritySchema,
|
|
6585
|
+
DiscoverySnapshotV1Schema,
|
|
4686
6586
|
DocBridgeConfigV1Schema,
|
|
4687
6587
|
DocBridgeIndexV1JsonSchema,
|
|
4688
6588
|
DocBridgeIndexV1Schema,
|
|
@@ -4690,11 +6590,20 @@ export {
|
|
|
4690
6590
|
DocumentationStandardRuleIdSchema,
|
|
4691
6591
|
DocumentationStandardV1ConfigSchema,
|
|
4692
6592
|
EcosystemContractEvidenceSchema,
|
|
6593
|
+
EntitySchema,
|
|
6594
|
+
EvidenceSchema,
|
|
6595
|
+
EvidenceSourceSchema,
|
|
6596
|
+
FindingStatusSchema,
|
|
6597
|
+
FixChangeSchema,
|
|
6598
|
+
FixProposalStatusSchema,
|
|
6599
|
+
FixProposalV1Schema,
|
|
4693
6600
|
HANDOFF_SCHEMA_VERSION,
|
|
4694
6601
|
HandoffBridgeSchema,
|
|
4695
6602
|
HandoffTargetTypeSchema,
|
|
4696
6603
|
INDEX_SCHEMA_VERSION,
|
|
4697
6604
|
IndexNotFoundError,
|
|
6605
|
+
KNOWLEDGE_CONTENT_HASH_ALGO,
|
|
6606
|
+
KNOWLEDGE_SCHEMA_VERSION,
|
|
4698
6607
|
KnowledgeEntrySchema,
|
|
4699
6608
|
MCP_TOOLS,
|
|
4700
6609
|
MEMORY_CANDIDATE_SCHEMA_VERSION,
|
|
@@ -4702,21 +6611,47 @@ export {
|
|
|
4702
6611
|
MemoryCandidateV1Schema,
|
|
4703
6612
|
PACKAGE_VERSION,
|
|
4704
6613
|
PeerMissingError,
|
|
6614
|
+
ProjectIdentitySchema,
|
|
6615
|
+
ProposalOriginSchema,
|
|
6616
|
+
ProvenanceSchema,
|
|
6617
|
+
ReconciliationReportV1Schema,
|
|
6618
|
+
RelationSchema,
|
|
6619
|
+
RepositorySafetyConfigSchema,
|
|
6620
|
+
RuleIdSchema,
|
|
6621
|
+
RuleSeveritySchema,
|
|
6622
|
+
RulesConfigSchema,
|
|
6623
|
+
WORKFLOW_STAGES,
|
|
6624
|
+
WorkflowConfigSchema,
|
|
6625
|
+
WorkflowRunV1Schema,
|
|
6626
|
+
WorkflowStateSchema,
|
|
6627
|
+
WorkflowStepSchema,
|
|
6628
|
+
WorkflowTransitionSchema,
|
|
4705
6629
|
applyConfigDefaults,
|
|
6630
|
+
applyDocumentationDeclarations,
|
|
6631
|
+
applyFixProposal,
|
|
6632
|
+
approveFixProposal,
|
|
4706
6633
|
buildDocBridgeIndex,
|
|
4707
6634
|
buildLookup,
|
|
6635
|
+
canonicalJsonV1,
|
|
4708
6636
|
chunksFromMarkdown,
|
|
4709
6637
|
classifyMemoryCandidates,
|
|
4710
6638
|
collectPackages,
|
|
6639
|
+
containedPath,
|
|
6640
|
+
contentHashForArtifactV1,
|
|
6641
|
+
createArtifactNormalizationProposal,
|
|
4711
6642
|
createDocBridgeRag,
|
|
4712
6643
|
createDocBridgeRetriever,
|
|
6644
|
+
createMarkdownLinkFixProposal,
|
|
6645
|
+
createRegistryAgentAdapter,
|
|
4713
6646
|
defaultPromotionDraftPath,
|
|
4714
6647
|
defineConfig,
|
|
4715
6648
|
discoverNxProjects,
|
|
6649
|
+
discoverRepository,
|
|
4716
6650
|
docBridgePatternMarkdown,
|
|
4717
6651
|
docBridgePatternPayload,
|
|
4718
6652
|
doctorBadgeMetrics,
|
|
4719
6653
|
draftMemoryPromotion,
|
|
6654
|
+
evaluateRules,
|
|
4720
6655
|
formatDoctorBadgeJson,
|
|
4721
6656
|
formatDoctorBadgeMarkdown,
|
|
4722
6657
|
formatDoctorText,
|
|
@@ -4733,19 +6668,37 @@ export {
|
|
|
4733
6668
|
loadConfig,
|
|
4734
6669
|
loadDocBridgeIndex,
|
|
4735
6670
|
loadFederatedChunks,
|
|
6671
|
+
loadRegistryAgentMetadata,
|
|
6672
|
+
loadRegistryAgentRunner,
|
|
6673
|
+
loadWorkflowManifest,
|
|
6674
|
+
loadWorkflowStepOutput,
|
|
4736
6675
|
mcpSnippet,
|
|
4737
6676
|
normalizeAgentHandoff,
|
|
4738
6677
|
parseAgentHandoff,
|
|
6678
|
+
parseAgentProposal,
|
|
4739
6679
|
parseAgentSearch,
|
|
6680
|
+
parseDiscoverySnapshot,
|
|
4740
6681
|
parseDocBridgeConfig,
|
|
4741
6682
|
parseDocBridgeIndex,
|
|
6683
|
+
parseDocumentationDeclarations,
|
|
6684
|
+
parseFixProposal,
|
|
4742
6685
|
parseLlmsTxtLinks,
|
|
4743
6686
|
parseMemoryCandidate,
|
|
6687
|
+
parseReconciliationReport,
|
|
6688
|
+
parseRuleId,
|
|
6689
|
+
parseRuleSeverity,
|
|
6690
|
+
parseWorkflowRun,
|
|
6691
|
+
persistRegistryAgentProposal,
|
|
4744
6692
|
projectRootFromConfigPath,
|
|
4745
6693
|
promoteMemoryToGithubPr,
|
|
6694
|
+
reconcileKnowledge,
|
|
6695
|
+
redactSecrets,
|
|
6696
|
+
redactValue,
|
|
6697
|
+
renderOfflineReport,
|
|
4746
6698
|
resolveGateIds,
|
|
4747
6699
|
resolveProjectRoot,
|
|
4748
6700
|
resolveRoot,
|
|
6701
|
+
respondMcpRequest,
|
|
4749
6702
|
retrieveDocBridgeChunks,
|
|
4750
6703
|
retrieveHybridChunks,
|
|
4751
6704
|
runChatOnce,
|
|
@@ -4754,7 +6707,9 @@ export {
|
|
|
4754
6707
|
runGate,
|
|
4755
6708
|
runGates,
|
|
4756
6709
|
runQuery,
|
|
6710
|
+
runWorkflow,
|
|
4757
6711
|
safeParseAgentHandoff,
|
|
6712
|
+
safeWalkFiles,
|
|
4758
6713
|
scanHumanDocRecords,
|
|
4759
6714
|
scanHumanDocs,
|
|
4760
6715
|
scanMemorySafety,
|