@agentskit/doc-bridge 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/action.yml +1 -1
- package/dist/cli/program.js +2461 -274
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +77 -4
- package/dist/config/index.js.map +1 -1
- package/dist/{index-DAeq_OIi.d.ts → index-Di7PkJuf.d.ts} +209 -8
- package/dist/index.d.ts +1942 -4
- package/dist/index.js +2190 -193
- package/dist/index.js.map +1 -1
- package/docs/PRD-doc-bridge-knowledge-engine.md +338 -0
- package/docs/knowledge-engine-runbook.md +44 -0
- package/ecosystem-claims.json +19 -19
- package/ecosystem-upstream.json +2 -2
- package/ecosystem.json +117 -109
- package/mcpb/manifest.json +25 -1
- package/package.json +2 -2
- package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
- package/src/agents/registry-adapter.ts +97 -0
- package/src/cli/program.ts +285 -3
- package/src/config/defaults.ts +14 -1
- package/src/config/schema.ts +69 -0
- package/src/conformance/ecosystem-contract.ts +6 -3
- package/src/discovery/documentation.ts +320 -0
- package/src/discovery/repository.ts +514 -0
- package/src/fixes/proposals.ts +165 -0
- package/src/index-builder/content-hash.ts +9 -2
- package/src/index-builder/human-adapters/plain-markdown.ts +17 -1
- package/src/index-builder/llms-txt.ts +22 -2
- package/src/index-builder/scan-corpus.ts +17 -1
- package/src/index.ts +99 -2
- package/src/mcp/server.ts +178 -8
- package/src/reconciliation/reconcile.ts +227 -0
- package/src/report/html.ts +74 -0
- package/src/rules/engine.ts +180 -0
- package/src/safety/repository.ts +84 -0
- package/src/schemas/knowledge.ts +315 -0
- package/src/validate.ts +24 -0
- package/src/version.ts +1 -1
- package/src/workflow/engine.ts +238 -0
- package/tsup.config.ts +2 -1
package/dist/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
|
},
|
|
@@ -87,7 +110,9 @@ var IndexConfigSchema = z.object({
|
|
|
87
110
|
llmsTxt: z.object({
|
|
88
111
|
enabled: z.boolean().optional(),
|
|
89
112
|
outFile: z.string().min(1).max(512).optional(),
|
|
90
|
-
preamble: z.string().max(4e3).optional()
|
|
113
|
+
preamble: z.string().max(4e3).optional(),
|
|
114
|
+
urlPrefix: z.string().url().optional(),
|
|
115
|
+
pathPrefix: z.string().min(1).max(512).optional()
|
|
91
116
|
}).strict().optional(),
|
|
92
117
|
capabilities: z.object({
|
|
93
118
|
enabled: z.boolean().optional(),
|
|
@@ -157,6 +182,39 @@ var GatesConfigSchema = z.object({
|
|
|
157
182
|
).max(16).optional(),
|
|
158
183
|
options: z.record(z.string(), z.unknown()).optional()
|
|
159
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();
|
|
160
218
|
var SurfacesConfigSchema = z.object({
|
|
161
219
|
cli: z.object({
|
|
162
220
|
bin: z.string().min(1).max(64).optional(),
|
|
@@ -174,7 +232,13 @@ var SurfacesConfigSchema = z.object({
|
|
|
174
232
|
"retriever.query",
|
|
175
233
|
"memory.classify",
|
|
176
234
|
"memory.promoteDraft",
|
|
177
|
-
"registry.topology"
|
|
235
|
+
"registry.topology",
|
|
236
|
+
"docbridge.snapshot",
|
|
237
|
+
"docbridge.report",
|
|
238
|
+
"docbridge.diagnostics",
|
|
239
|
+
"docbridge.relations",
|
|
240
|
+
"docbridge.run",
|
|
241
|
+
"docbridge.proposals"
|
|
178
242
|
])
|
|
179
243
|
).max(16).optional(),
|
|
180
244
|
transport: z.enum(["stdio", "http"]).optional(),
|
|
@@ -217,7 +281,13 @@ var IntelligenceConfigSchema = z.object({
|
|
|
217
281
|
}).strict().optional()
|
|
218
282
|
}).strict().optional(),
|
|
219
283
|
runtime: z.enum(["agentskit", "custom"]).optional(),
|
|
220
|
-
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()
|
|
221
291
|
}).strict();
|
|
222
292
|
var FederationConfigSchema = z.object({
|
|
223
293
|
enabled: z.boolean().optional(),
|
|
@@ -307,6 +377,9 @@ var DocBridgeConfigV1Schema = z.object({
|
|
|
307
377
|
index: IndexConfigSchema.optional(),
|
|
308
378
|
routing: RoutingConfigSchema.optional(),
|
|
309
379
|
gates: GatesConfigSchema.optional(),
|
|
380
|
+
rules: RulesConfigSchema.optional(),
|
|
381
|
+
workflow: WorkflowConfigSchema.optional(),
|
|
382
|
+
safety: RepositorySafetyConfigSchema.optional(),
|
|
310
383
|
surfaces: SurfacesConfigSchema.optional(),
|
|
311
384
|
intelligence: IntelligenceConfigSchema.optional(),
|
|
312
385
|
federation: FederationConfigSchema.optional(),
|
|
@@ -993,6 +1066,231 @@ var DocBridgeJsonSchemas = {
|
|
|
993
1066
|
memoryCandidateV1: MemoryCandidateV1JsonSchema
|
|
994
1067
|
};
|
|
995
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
|
+
|
|
996
1294
|
// src/validate.ts
|
|
997
1295
|
var zodIssues = (error) => error.issues.map((issue) => ({
|
|
998
1296
|
path: issue.path.join(".") || "(root)",
|
|
@@ -1018,6 +1316,11 @@ ${result.issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`
|
|
|
1018
1316
|
var parseAgentSearch = (input) => AgentSearchV1Schema.parse(input);
|
|
1019
1317
|
var parseDocBridgeIndex = (input) => DocBridgeIndexV1Schema.parse(input);
|
|
1020
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);
|
|
1021
1324
|
var parseDocBridgeConfig = (input) => {
|
|
1022
1325
|
const result = DocBridgeConfigV1Schema.safeParse(input);
|
|
1023
1326
|
if (!result.success) {
|
|
@@ -1111,6 +1414,9 @@ var defaultChecksForTarget = (root, opts) => {
|
|
|
1111
1414
|
return opts.strict ? ["npm test", "npm run lint"] : ["npm test"];
|
|
1112
1415
|
};
|
|
1113
1416
|
|
|
1417
|
+
// src/index-builder/scan-corpus.ts
|
|
1418
|
+
import { minimatch } from "minimatch";
|
|
1419
|
+
|
|
1114
1420
|
// src/lib/bounded-text.ts
|
|
1115
1421
|
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
1116
1422
|
var MAX_DOCUMENT_BYTES = 4 * 1024 * 1024;
|
|
@@ -1271,10 +1577,20 @@ var walkFiles = (root, opts) => {
|
|
|
1271
1577
|
};
|
|
1272
1578
|
|
|
1273
1579
|
// src/index-builder/scan-corpus.ts
|
|
1580
|
+
var configuredPathMatches = (relPath, include, exclude) => {
|
|
1581
|
+
const normalize = (pattern) => toPosix(pattern).replace(/^\.\//, "");
|
|
1582
|
+
const included = include?.filter(Boolean).map(normalize) ?? [];
|
|
1583
|
+
const excluded = exclude?.filter(Boolean).map(normalize) ?? [];
|
|
1584
|
+
if (excluded.some((pattern) => minimatch(relPath, pattern, { dot: true }))) return false;
|
|
1585
|
+
return included.length === 0 || included.some((pattern) => minimatch(relPath, pattern, { dot: true }));
|
|
1586
|
+
};
|
|
1274
1587
|
var scanAgentCorpus = (root, config) => {
|
|
1275
1588
|
const agentRoot = containedProjectPath(root, config.corpus.agent.root);
|
|
1276
1589
|
if (!agentRoot) throw new Error("Agent corpus root escapes the project root.");
|
|
1277
|
-
const files = walkFiles(agentRoot, { extensions: [".md", ".mdx"] })
|
|
1590
|
+
const files = walkFiles(agentRoot, { extensions: [".md", ".mdx"] }).filter((abs) => {
|
|
1591
|
+
const relToCorpus = toPosix(abs.replace(`${toPosix(agentRoot)}/`, ""));
|
|
1592
|
+
return configuredPathMatches(relToCorpus, config.corpus.agent.include, config.corpus.agent.exclude);
|
|
1593
|
+
});
|
|
1278
1594
|
const corpusRelRoot = toPosix(config.corpus.agent.root);
|
|
1279
1595
|
const budget = { used: 0 };
|
|
1280
1596
|
return files.map((abs) => {
|
|
@@ -1537,7 +1853,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1537
1853
|
};
|
|
1538
1854
|
|
|
1539
1855
|
// src/version.ts
|
|
1540
|
-
var PACKAGE_VERSION = "1.
|
|
1856
|
+
var PACKAGE_VERSION = "1.5.0";
|
|
1541
1857
|
|
|
1542
1858
|
// src/index-builder/capabilities.ts
|
|
1543
1859
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -1582,12 +1898,29 @@ var sortValue = (value) => {
|
|
|
1582
1898
|
}
|
|
1583
1899
|
return value;
|
|
1584
1900
|
};
|
|
1901
|
+
var canonicalJsonV1 = (payload) => JSON.stringify(sortValue(payload));
|
|
1585
1902
|
var sha256NormalizedV1 = (payload) => {
|
|
1586
|
-
const normalized =
|
|
1903
|
+
const normalized = canonicalJsonV1(payload);
|
|
1587
1904
|
return createHash("sha256").update(normalized, "utf8").digest("hex");
|
|
1588
1905
|
};
|
|
1906
|
+
var contentHashForArtifactV1 = (artifact2) => {
|
|
1907
|
+
const { contentHash: _contentHash, ...payload } = artifact2;
|
|
1908
|
+
return sha256NormalizedV1(payload);
|
|
1909
|
+
};
|
|
1589
1910
|
|
|
1590
1911
|
// src/index-builder/llms-txt.ts
|
|
1912
|
+
var routePath = (path, pathPrefix) => {
|
|
1913
|
+
const normalizedPath = path.replaceAll("\\", "/").replace(/\.(?:md|mdx)$/, "");
|
|
1914
|
+
const normalizedPrefix = pathPrefix?.replaceAll("\\", "/").replace(/\/$/, "");
|
|
1915
|
+
const relativePath3 = normalizedPrefix && normalizedPath.startsWith(`${normalizedPrefix}/`) ? normalizedPath.slice(normalizedPrefix.length + 1) : normalizedPath;
|
|
1916
|
+
return relativePath3.replace(/^\/+/, "");
|
|
1917
|
+
};
|
|
1918
|
+
var knowledgeUrl = (path, options = {}) => {
|
|
1919
|
+
const relativePath3 = routePath(path, options.pathPrefix);
|
|
1920
|
+
if (!options.urlPrefix) return path;
|
|
1921
|
+
const base = options.urlPrefix.endsWith("/") ? options.urlPrefix : `${options.urlPrefix}/`;
|
|
1922
|
+
return new URL(relativePath3, base).toString();
|
|
1923
|
+
};
|
|
1591
1924
|
var renderLlmsTxt = (config, knowledge, projectName2) => {
|
|
1592
1925
|
const preamble = config.index?.llmsTxt?.preamble ?? `# ${projectName2}
|
|
1593
1926
|
|
|
@@ -1595,7 +1928,8 @@ var renderLlmsTxt = (config, knowledge, projectName2) => {
|
|
|
1595
1928
|
`;
|
|
1596
1929
|
const lines = knowledge.slice(0, 500).map((entry) => {
|
|
1597
1930
|
const desc = entry.description ? `: ${entry.description}` : "";
|
|
1598
|
-
|
|
1931
|
+
const url = knowledgeUrl(entry.path, config.index?.llmsTxt);
|
|
1932
|
+
return `- [${entry.title}](${url})${desc}`;
|
|
1599
1933
|
});
|
|
1600
1934
|
return `${preamble.trim()}
|
|
1601
1935
|
|
|
@@ -1827,11 +2161,23 @@ var nextraAdapter = {
|
|
|
1827
2161
|
};
|
|
1828
2162
|
|
|
1829
2163
|
// src/index-builder/human-adapters/plain-markdown.ts
|
|
2164
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
2165
|
+
var stringPatterns = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
|
|
2166
|
+
var matchesConfiguredPath = (relPath, options) => {
|
|
2167
|
+
const normalize = (pattern) => pattern.replaceAll("\\", "/").replace(/^\.\//, "");
|
|
2168
|
+
const include = stringPatterns(options?.include).map(normalize);
|
|
2169
|
+
const exclude = stringPatterns(options?.exclude).map(normalize);
|
|
2170
|
+
if (exclude.some((pattern) => minimatch2(relPath, pattern, { dot: true }))) return false;
|
|
2171
|
+
return include.length === 0 || include.some((pattern) => minimatch2(relPath, pattern, { dot: true }));
|
|
2172
|
+
};
|
|
1830
2173
|
var plainMarkdownAdapter = {
|
|
1831
2174
|
plugin: "plain-markdown",
|
|
1832
2175
|
scan: ({ root, config }) => {
|
|
1833
2176
|
const humanRoot = optionString(config.options, ["contentDir", "root", "docsDir"]) ?? "docs";
|
|
1834
|
-
return scanMarkdownDocs(root, humanRoot, {
|
|
2177
|
+
return scanMarkdownDocs(root, humanRoot, {
|
|
2178
|
+
urlPrefix: config.options?.urlPrefix,
|
|
2179
|
+
includeRelPath: (relPath) => matchesConfiguredPath(relPath, config.options)
|
|
2180
|
+
});
|
|
1835
2181
|
}
|
|
1836
2182
|
};
|
|
1837
2183
|
|
|
@@ -1860,7 +2206,7 @@ var starlightAdapter = {
|
|
|
1860
2206
|
};
|
|
1861
2207
|
|
|
1862
2208
|
// src/index-builder/human-adapters/vitepress.ts
|
|
1863
|
-
import { minimatch } from "minimatch";
|
|
2209
|
+
import { minimatch as minimatch3 } from "minimatch";
|
|
1864
2210
|
var isVitePressPage = (relPath) => !relPath.split("/").some((part) => part === ".vitepress" || part.startsWith("."));
|
|
1865
2211
|
var srcExcludePatterns = (value) => {
|
|
1866
2212
|
if (value === void 0) return [];
|
|
@@ -1874,7 +2220,7 @@ var srcExcludePatterns = (value) => {
|
|
|
1874
2220
|
return pattern;
|
|
1875
2221
|
});
|
|
1876
2222
|
};
|
|
1877
|
-
var isExcluded = (relPath, patterns) => patterns.some((pattern) =>
|
|
2223
|
+
var isExcluded = (relPath, patterns) => patterns.some((pattern) => minimatch3(relPath, pattern, { dot: true }));
|
|
1878
2224
|
var vitepressSlug = (relPath, cleanUrls) => {
|
|
1879
2225
|
const slug2 = routeSlug(relPath);
|
|
1880
2226
|
if (cleanUrls || /(?:^|\/)index\.mdx?$/.test(relPath)) return slug2;
|
|
@@ -2219,6 +2565,1427 @@ var buildDocBridgeIndex = (opts) => {
|
|
|
2219
2565
|
};
|
|
2220
2566
|
};
|
|
2221
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
|
+
const parsed = readJson(manifestPath);
|
|
2742
|
+
if (!parsed.value) {
|
|
2743
|
+
coverage.push({ status: "partial", reason: `${relativePath(root, manifestPath)}: ${parsed.error ?? "invalid package.json"}` });
|
|
2744
|
+
continue;
|
|
2745
|
+
}
|
|
2746
|
+
const path = relativePath(root, absPath);
|
|
2747
|
+
const name = packageName(parsed.value, path);
|
|
2748
|
+
const id = entityId("package", name ?? path);
|
|
2749
|
+
const duplicate = packages.find((pkg) => pkg.id === id);
|
|
2750
|
+
if (duplicate && duplicate.absPath !== absPath) {
|
|
2751
|
+
throw new Error(`Package identity collision for "${id}": "${duplicate.path}" and "${path}".`);
|
|
2752
|
+
}
|
|
2753
|
+
if (!duplicate) packages.push({ id, ...name ? { name } : {}, path, absPath, manifestPath, manifest: parsed.value });
|
|
2754
|
+
}
|
|
2755
|
+
coverage.push({ status: "complete" });
|
|
2756
|
+
return { packages: packages.sort((a, b) => a.id.localeCompare(b.id)), coverage };
|
|
2757
|
+
};
|
|
2758
|
+
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];
|
|
2759
|
+
var readCompilerOptions = (root) => {
|
|
2760
|
+
const configPath = ts.findConfigFile(root, ts.sys.fileExists, "tsconfig.json");
|
|
2761
|
+
if (!configPath) return { options: {} };
|
|
2762
|
+
const parsed = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
2763
|
+
if (parsed.error) return { options: {}, error: ts.flattenDiagnosticMessageText(parsed.error.messageText, "\n") };
|
|
2764
|
+
const config = ts.parseJsonConfigFileContent(parsed.config, ts.sys, dirname5(configPath));
|
|
2765
|
+
if (config.errors.length) {
|
|
2766
|
+
return {
|
|
2767
|
+
options: config.options,
|
|
2768
|
+
error: ts.flattenDiagnosticMessageText(config.errors[0]?.messageText ?? "Invalid tsconfig", "\n")
|
|
2769
|
+
};
|
|
2770
|
+
}
|
|
2771
|
+
return { options: config.options };
|
|
2772
|
+
};
|
|
2773
|
+
var scriptKind = (path) => {
|
|
2774
|
+
switch (extname(path)) {
|
|
2775
|
+
case ".js":
|
|
2776
|
+
return ts.ScriptKind.JS;
|
|
2777
|
+
case ".jsx":
|
|
2778
|
+
return ts.ScriptKind.JSX;
|
|
2779
|
+
case ".mjs":
|
|
2780
|
+
return ts.ScriptKind.JS;
|
|
2781
|
+
case ".cjs":
|
|
2782
|
+
return ts.ScriptKind.JS;
|
|
2783
|
+
case ".ts":
|
|
2784
|
+
return ts.ScriptKind.TS;
|
|
2785
|
+
case ".tsx":
|
|
2786
|
+
return ts.ScriptKind.TSX;
|
|
2787
|
+
case ".mts":
|
|
2788
|
+
return ts.ScriptKind.TS;
|
|
2789
|
+
case ".cts":
|
|
2790
|
+
return ts.ScriptKind.TS;
|
|
2791
|
+
default:
|
|
2792
|
+
return ts.ScriptKind.Unknown;
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
var nodeEvidence = (root, path, sourceFile, node) => {
|
|
2796
|
+
const start = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
2797
|
+
const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
|
|
2798
|
+
return lineEvidence("code", root, path, start, end);
|
|
2799
|
+
};
|
|
2800
|
+
var isExported = (node) => {
|
|
2801
|
+
const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : void 0;
|
|
2802
|
+
return modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
2803
|
+
};
|
|
2804
|
+
var exportedNames = (sourceFile) => {
|
|
2805
|
+
const names = /* @__PURE__ */ new Set();
|
|
2806
|
+
const addDeclarationName = (node) => {
|
|
2807
|
+
if (!isExported(node)) return;
|
|
2808
|
+
const name = ts.getNameOfDeclaration(node);
|
|
2809
|
+
if (name && ts.isIdentifier(name)) names.add(name.text);
|
|
2810
|
+
};
|
|
2811
|
+
const visit = (node) => {
|
|
2812
|
+
if (ts.isExportDeclaration(node)) {
|
|
2813
|
+
if (!node.exportClause) names.add("*");
|
|
2814
|
+
else if (ts.isNamedExports(node.exportClause)) {
|
|
2815
|
+
for (const element of node.exportClause.elements) names.add(element.name.text);
|
|
2816
|
+
}
|
|
2817
|
+
} else if (ts.isExportAssignment(node)) {
|
|
2818
|
+
names.add("default");
|
|
2819
|
+
} else if (ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) {
|
|
2820
|
+
addDeclarationName(node);
|
|
2821
|
+
} else if (ts.isVariableStatement(node) && isExported(node)) {
|
|
2822
|
+
for (const declaration of node.declarationList.declarations) {
|
|
2823
|
+
if (ts.isIdentifier(declaration.name)) names.add(declaration.name.text);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
ts.forEachChild(node, visit);
|
|
2827
|
+
};
|
|
2828
|
+
visit(sourceFile);
|
|
2829
|
+
return [...names].sort();
|
|
2830
|
+
};
|
|
2831
|
+
var moduleReferences = (root, path, sourceFile) => {
|
|
2832
|
+
const references = [];
|
|
2833
|
+
let hasDynamic = false;
|
|
2834
|
+
let hasRuntimeWiring = false;
|
|
2835
|
+
const addReference = (specifier, kind, node) => {
|
|
2836
|
+
references.push({ specifier: specifier.text, kind, evidence: nodeEvidence(root, path, sourceFile, node) });
|
|
2837
|
+
};
|
|
2838
|
+
const visit = (node) => {
|
|
2839
|
+
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
2840
|
+
addReference(node.moduleSpecifier, "imports", node);
|
|
2841
|
+
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
2842
|
+
addReference(node.moduleSpecifier, "re-exports", node);
|
|
2843
|
+
} else if (ts.isImportEqualsDeclaration(node) && ts.isExternalModuleReference(node.moduleReference) && ts.isStringLiteral(node.moduleReference.expression)) {
|
|
2844
|
+
addReference(node.moduleReference.expression, "imports", node);
|
|
2845
|
+
} else if (ts.isCallExpression(node)) {
|
|
2846
|
+
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
2847
|
+
if (!node.arguments[0] || !ts.isStringLiteralLike(node.arguments[0])) hasDynamic = true;
|
|
2848
|
+
} else if (ts.isIdentifier(node.expression) && node.expression.text === "require") {
|
|
2849
|
+
const argument = node.arguments[0];
|
|
2850
|
+
if (argument && ts.isStringLiteralLike(argument)) addReference(argument, "imports", node);
|
|
2851
|
+
else hasDynamic = true;
|
|
2852
|
+
} else if (ts.isPropertyAccessExpression(node.expression) && node.expression.name.text === "register") {
|
|
2853
|
+
hasRuntimeWiring = true;
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
ts.forEachChild(node, visit);
|
|
2857
|
+
};
|
|
2858
|
+
visit(sourceFile);
|
|
2859
|
+
return {
|
|
2860
|
+
references,
|
|
2861
|
+
exports: exportedNames(sourceFile),
|
|
2862
|
+
hasDynamic,
|
|
2863
|
+
hasRuntimeWiring
|
|
2864
|
+
};
|
|
2865
|
+
};
|
|
2866
|
+
var resolveRelativeModule = (specifier, containingFile, modulePaths) => {
|
|
2867
|
+
const base = resolve7(dirname5(containingFile), specifier);
|
|
2868
|
+
const extension = extname(base);
|
|
2869
|
+
const extensionlessBase = extension ? base.slice(0, -extension.length) : base;
|
|
2870
|
+
const candidates = [
|
|
2871
|
+
base,
|
|
2872
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${base}${extension2}`),
|
|
2873
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(base, `index${extension2}`)),
|
|
2874
|
+
...SOURCE_EXTENSIONS.map((extension2) => `${extensionlessBase}${extension2}`),
|
|
2875
|
+
...SOURCE_EXTENSIONS.map((extension2) => join8(extensionlessBase, `index${extension2}`))
|
|
2876
|
+
];
|
|
2877
|
+
return candidates.map((candidate) => modulePaths.get(resolve7(candidate))).find(Boolean);
|
|
2878
|
+
};
|
|
2879
|
+
var resolveReference = (reference, containingFile, modules, packages, compilerOptions) => {
|
|
2880
|
+
if (reference.specifier.startsWith(".") || reference.specifier.startsWith("/")) {
|
|
2881
|
+
const relativeTarget = resolveRelativeModule(reference.specifier, containingFile, modules);
|
|
2882
|
+
return relativeTarget ? { targetId: relativeTarget.entityId } : void 0;
|
|
2883
|
+
}
|
|
2884
|
+
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];
|
|
2885
|
+
if (packageTarget) return { targetId: packageTarget.id };
|
|
2886
|
+
const resolved = ts.resolveModuleName(reference.specifier, containingFile, compilerOptions, ts.sys).resolvedModule?.resolvedFileName;
|
|
2887
|
+
const resolvedTarget = resolved ? modules.get(resolve7(resolved)) : void 0;
|
|
2888
|
+
if (resolvedTarget) return { targetId: resolvedTarget.entityId };
|
|
2889
|
+
return { targetId: entityId("external", reference.specifier) };
|
|
2890
|
+
};
|
|
2891
|
+
var dependencyEntries = (manifest) => {
|
|
2892
|
+
const sections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
|
|
2893
|
+
return sections.flatMap((type) => {
|
|
2894
|
+
const value = manifest[type];
|
|
2895
|
+
if (!isRecord2(value)) return [];
|
|
2896
|
+
return Object.keys(value).sort().map((name) => ({ name, type }));
|
|
2897
|
+
});
|
|
2898
|
+
};
|
|
2899
|
+
var sourceRevision = (root, files) => {
|
|
2900
|
+
const contentRevision = () => ({
|
|
2901
|
+
value: sha256NormalizedV1(
|
|
2902
|
+
files.map((path) => ({
|
|
2903
|
+
path: relativePath(root, path),
|
|
2904
|
+
contentHash: sha256NormalizedV1(readFileSync8(path, "utf8"))
|
|
2905
|
+
}))
|
|
2906
|
+
),
|
|
2907
|
+
kind: "content"
|
|
2908
|
+
});
|
|
2909
|
+
try {
|
|
2910
|
+
const status = execFileSync("git", ["status", "--porcelain", "--untracked-files=all"], {
|
|
2911
|
+
cwd: root,
|
|
2912
|
+
encoding: "utf8",
|
|
2913
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2914
|
+
}).trim();
|
|
2915
|
+
if (status) return contentRevision();
|
|
2916
|
+
const value = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
2917
|
+
if (value) return { value, kind: "git" };
|
|
2918
|
+
} catch {
|
|
2919
|
+
}
|
|
2920
|
+
return contentRevision();
|
|
2921
|
+
};
|
|
2922
|
+
var hasPackageManagerMetadata = (root, rootManifest) => Boolean(
|
|
2923
|
+
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"))
|
|
2924
|
+
);
|
|
2925
|
+
var artifact = (root, config, files, entities, relations, coverage) => {
|
|
2926
|
+
const revision = sourceRevision(root, files);
|
|
2927
|
+
const base = {
|
|
2928
|
+
type: "discovery-snapshot",
|
|
2929
|
+
schemaVersion: 1,
|
|
2930
|
+
contentHash: EMPTY_HASH,
|
|
2931
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
2932
|
+
project: { name: entities.find((entity) => entity.kind === "package" && entity.path === ".")?.name ?? basename4(root), root: "." },
|
|
2933
|
+
sourceRevision: revision.value,
|
|
2934
|
+
sourceRevisionKind: revision.kind,
|
|
2935
|
+
configurationHash: sha256NormalizedV1(config ?? {}),
|
|
2936
|
+
pipelineVersion: "1.0.0",
|
|
2937
|
+
analyzerVersions: { repository: "1.0.0", "js-ts": "1.0.0" },
|
|
2938
|
+
entities: [...entities].sort((a, b) => a.id.localeCompare(b.id)),
|
|
2939
|
+
relations: [...relations].sort((a, b) => a.id.localeCompare(b.id)),
|
|
2940
|
+
coverage: [...coverage]
|
|
2941
|
+
};
|
|
2942
|
+
return DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
2943
|
+
};
|
|
2944
|
+
var discoverRepository = (opts = {}) => {
|
|
2945
|
+
const root = resolve7(opts.root ?? process.cwd());
|
|
2946
|
+
const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES2;
|
|
2947
|
+
const safety = opts.config?.safety;
|
|
2948
|
+
const maxBytes = opts.maxBytes ?? safety?.maxBytes;
|
|
2949
|
+
const safeOptions = {
|
|
2950
|
+
exclude: [...DEFAULT_SAFETY_EXCLUDES, ...safety?.exclude ?? []],
|
|
2951
|
+
maxFiles,
|
|
2952
|
+
...maxBytes !== void 0 ? { maxBytes } : {},
|
|
2953
|
+
...safety?.maxTimeMs !== void 0 ? { maxTimeMs: safety.maxTimeMs } : {},
|
|
2954
|
+
...safety?.maxMemoryMb !== void 0 ? { maxMemoryMb: safety.maxMemoryMb } : {}
|
|
2955
|
+
};
|
|
2956
|
+
const rootManifestPath = join8(root, "package.json");
|
|
2957
|
+
const rootManifest = readJson(rootManifestPath).value;
|
|
2958
|
+
const packageResult = discoverPackages(root, rootManifest, opts.config);
|
|
2959
|
+
const sourceWalk = safeWalkFiles(root, { extensions: SOURCE_EXTENSIONS, ...safeOptions });
|
|
2960
|
+
const documentWalk = safeWalkFiles(root, { extensions: DOCUMENT_EXTENSIONS, ...safeOptions });
|
|
2961
|
+
const configWalk = safeWalkFiles(root, { extensions: [".json", ".yaml", ".yml", ".js", ".ts"], ...safeOptions });
|
|
2962
|
+
const sourcePaths = sourceWalk.files;
|
|
2963
|
+
const documentPaths = documentWalk.files;
|
|
2964
|
+
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)));
|
|
2965
|
+
const allFiles = [...new Set([rootManifestPath, ...sourcePaths, ...documentPaths, ...configPaths].filter(existsSync9))].sort();
|
|
2966
|
+
const entities = /* @__PURE__ */ new Map();
|
|
2967
|
+
const relations = /* @__PURE__ */ new Map();
|
|
2968
|
+
const addEntity = (entity) => {
|
|
2969
|
+
const existing = entities.get(entity.id);
|
|
2970
|
+
if (existing && (existing.kind !== entity.kind || existing.path !== entity.path)) throw new Error(`Entity identity collision for "${entity.id}".`);
|
|
2971
|
+
entities.set(entity.id, existing ?? entity);
|
|
2972
|
+
};
|
|
2973
|
+
const addRelation = (relation) => {
|
|
2974
|
+
const existing = relations.get(relation.id);
|
|
2975
|
+
if (!existing) {
|
|
2976
|
+
relations.set(relation.id, relation);
|
|
2977
|
+
return;
|
|
2978
|
+
}
|
|
2979
|
+
const evidence2 = new Map(
|
|
2980
|
+
[...existing.evidence, ...relation.evidence].map((item) => [
|
|
2981
|
+
`${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.source}`,
|
|
2982
|
+
item
|
|
2983
|
+
])
|
|
2984
|
+
);
|
|
2985
|
+
relations.set(relation.id, { ...existing, evidence: [...evidence2.values()] });
|
|
2986
|
+
};
|
|
2987
|
+
for (const pkg of packageResult.packages) {
|
|
2988
|
+
const text = readFileSync8(pkg.manifestPath, "utf8");
|
|
2989
|
+
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"'))] });
|
|
2990
|
+
}
|
|
2991
|
+
const modules = /* @__PURE__ */ new Map();
|
|
2992
|
+
for (const absPath of sourcePaths) {
|
|
2993
|
+
const path = relativePath(root, absPath);
|
|
2994
|
+
const pkg = packageForModule(packageResult.packages, absPath);
|
|
2995
|
+
const id = entityId("module", path);
|
|
2996
|
+
const text = readFileSync8(absPath, "utf8");
|
|
2997
|
+
const sourceFile = ts.createSourceFile(absPath, text, ts.ScriptTarget.Latest, true, scriptKind(absPath));
|
|
2998
|
+
const exports = exportedNames(sourceFile);
|
|
2999
|
+
modules.set(resolve7(absPath), { absPath, path, entityId: id, ...pkg ? { packageId: pkg.id } : {} });
|
|
3000
|
+
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) } } : {} });
|
|
3001
|
+
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)] });
|
|
3002
|
+
}
|
|
3003
|
+
for (const absPath of documentPaths) {
|
|
3004
|
+
const path = relativePath(root, absPath);
|
|
3005
|
+
addEntity({ id: entityId("document", path), kind: "document", name: basename4(absPath), path, provenance: "observed", evidence: [lineEvidence("documentation", root, absPath, 1)] });
|
|
3006
|
+
}
|
|
3007
|
+
const compiler = readCompilerOptions(root);
|
|
3008
|
+
const coverage = [
|
|
3009
|
+
...[sourceWalk, documentWalk, configWalk].flatMap((walk, index) => walk.incomplete ? [{ analyzer: "repository", scope: `limits:${["source", "documentation", "configuration"][index]}`, status: "partial", reason: walk.reason }] : []),
|
|
3010
|
+
{ 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)}.` } : {} },
|
|
3011
|
+
{ 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 } : {} },
|
|
3012
|
+
{ analyzer: "js-ts", scope: "static-imports-and-exports", status: compiler.error ? "partial" : "complete", ...compiler.error ? { reason: compiler.error } : {} },
|
|
3013
|
+
{ analyzer: "js-ts", scope: "dynamic-imports", status: "not-analyzed", reason: "Dynamic import expressions and non-literal require calls are not resolved." },
|
|
3014
|
+
{ analyzer: "js-ts", scope: "runtime-wiring", status: "not-analyzed", reason: "Reflection, dependency injection and runtime wiring are not inferred." },
|
|
3015
|
+
{ analyzer: "js-ts", scope: "generated-code", status: "not-analyzed", reason: "Generated code is not interpreted as source architecture." }
|
|
3016
|
+
];
|
|
3017
|
+
for (const pkg of packageResult.packages) {
|
|
3018
|
+
const text = readFileSync8(pkg.manifestPath, "utf8");
|
|
3019
|
+
for (const dependency of dependencyEntries(pkg.manifest)) {
|
|
3020
|
+
const target = packageResult.packages.find((candidate) => candidate.name === dependency.name)?.id ?? entityId("external", dependency.name);
|
|
3021
|
+
if (!entities.has(target)) addEntity({ id: target, kind: "external", name: dependency.name, provenance: "observed", evidence: [lineEvidence("configuration", root, pkg.manifestPath, firstLineContaining(text, `"${dependency.name}"`))] });
|
|
3022
|
+
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 } });
|
|
3023
|
+
}
|
|
3024
|
+
}
|
|
3025
|
+
for (const module of modules.values()) {
|
|
3026
|
+
const text = readFileSync8(module.absPath, "utf8");
|
|
3027
|
+
const sourceFile = ts.createSourceFile(module.absPath, text, ts.ScriptTarget.Latest, true, scriptKind(module.absPath));
|
|
3028
|
+
const references = moduleReferences(root, module.absPath, sourceFile);
|
|
3029
|
+
for (const reference of references.references) {
|
|
3030
|
+
const target = resolveReference(reference, module.absPath, modules, packageResult.packages, compiler.options);
|
|
3031
|
+
if (!target) continue;
|
|
3032
|
+
if (!entities.has(target.targetId)) {
|
|
3033
|
+
const externalName = target.targetId.replace(/^external:/, "");
|
|
3034
|
+
addEntity({ id: target.targetId, kind: "external", name: externalName, provenance: "observed", evidence: [reference.evidence] });
|
|
3035
|
+
}
|
|
3036
|
+
addRelation({ id: entityId("relation", `${module.entityId}:${reference.kind}:${target.targetId}`), kind: reference.kind, from: module.entityId, to: target.targetId, provenance: "observed", evidence: [reference.evidence] });
|
|
3037
|
+
}
|
|
3038
|
+
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)] });
|
|
3039
|
+
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)] });
|
|
3040
|
+
}
|
|
3041
|
+
return artifact(root, opts.config, allFiles, [...entities.values()], [...relations.values()], coverage);
|
|
3042
|
+
};
|
|
3043
|
+
|
|
3044
|
+
// src/agents/registry-adapter.ts
|
|
3045
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3046
|
+
import { join as join9, resolve as resolve8 } from "path";
|
|
3047
|
+
import { pathToFileURL } from "url";
|
|
3048
|
+
import { z as z6 } from "zod";
|
|
3049
|
+
var DEFAULT_REGISTRY_AGENT_ID = "ecosystem-doc-bridge-corpus-scanner";
|
|
3050
|
+
var RegistryAgentMetadataSchema = z6.object({
|
|
3051
|
+
id: z6.string().min(1).max(256),
|
|
3052
|
+
version: z6.string().min(1).max(64),
|
|
3053
|
+
provider: z6.string().min(1).max(128).optional(),
|
|
3054
|
+
model: z6.string().min(1).max(256).optional(),
|
|
3055
|
+
capabilities: z6.array(z6.string().min(1).max(128)).max(32).default([])
|
|
3056
|
+
}).strict();
|
|
3057
|
+
var deepFreeze = (value) => {
|
|
3058
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
3059
|
+
Object.freeze(value);
|
|
3060
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
3061
|
+
}
|
|
3062
|
+
return value;
|
|
3063
|
+
};
|
|
3064
|
+
var registryConfig = (config) => config.intelligence?.registry;
|
|
3065
|
+
var loadRegistryAgentRunner = async (root, config) => {
|
|
3066
|
+
const metadata = loadRegistryAgentMetadata(root, config);
|
|
3067
|
+
const configured = registryConfig(config)?.runnerModule;
|
|
3068
|
+
const modulePath = configured ? containedPath(root, configured) : containedPath(root, join9(metadata.root, "doc-bridge-adapter.js"));
|
|
3069
|
+
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.`);
|
|
3070
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
3071
|
+
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;
|
|
3072
|
+
if (!runner) throw new Error(`Registry agent runner at ${modulePath} must export a function or { run }. `);
|
|
3073
|
+
return runner;
|
|
3074
|
+
};
|
|
3075
|
+
var loadRegistryAgentMetadata = (root, config) => {
|
|
3076
|
+
const settings = registryConfig(config);
|
|
3077
|
+
const id = settings?.agentId ?? DEFAULT_REGISTRY_AGENT_ID;
|
|
3078
|
+
const agentRoot = settings?.agentRoot ?? "agents";
|
|
3079
|
+
const agentPath = containedPath(root, join9(agentRoot, id));
|
|
3080
|
+
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}`);
|
|
3081
|
+
const metadataPath = [join9(agentPath, "agent.json"), join9(agentPath, "manifest.json")].find(existsSync10);
|
|
3082
|
+
if (!metadataPath) throw new Error(`Registry agent "${id}" is installed but has no agent.json or manifest.json metadata.`);
|
|
3083
|
+
const metadata = RegistryAgentMetadataSchema.parse(JSON.parse(readFileSync9(metadataPath, "utf8")));
|
|
3084
|
+
if (metadata.id !== id) throw new Error(`Installed Registry agent metadata id "${metadata.id}" does not match configured id "${id}".`);
|
|
3085
|
+
return { ...metadata, root: agentPath };
|
|
3086
|
+
};
|
|
3087
|
+
var createRegistryAgentAdapter = (root, config, runner) => {
|
|
3088
|
+
if (!registryConfig(config)?.enabled) throw new Error("Registry agents are disabled. Set intelligence.registry.enabled: true to run an assisted workflow.");
|
|
3089
|
+
const metadata = loadRegistryAgentMetadata(resolve8(root), config);
|
|
3090
|
+
return {
|
|
3091
|
+
metadata,
|
|
3092
|
+
run: async (snapshot, report, evidence2 = report.diagnostics.flatMap((diagnostic2) => diagnostic2.evidence).slice(0, 64)) => {
|
|
3093
|
+
const context = deepFreeze({ snapshot: redactValue(snapshot), report: redactValue(report), evidence: redactValue(evidence2), capabilities: ["snapshot.read", "evidence.read", "proposal.write"], network: false, shell: false });
|
|
3094
|
+
const proposal = AgentProposalV1Schema.parse(await runner(context));
|
|
3095
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Registry agent proposal contentHash does not match its canonical contents.");
|
|
3096
|
+
if (proposal.baseSnapshotHash !== snapshot.contentHash || proposal.baseReportHash !== report.contentHash) throw new Error("Registry agent proposal is not based on the supplied snapshot/report hashes.");
|
|
3097
|
+
if (proposal.origin.kind !== "registry-agent" || proposal.origin.id !== metadata.id) throw new Error(`Registry agent proposal origin must be ${metadata.id}.`);
|
|
3098
|
+
return proposal;
|
|
3099
|
+
}
|
|
3100
|
+
};
|
|
3101
|
+
};
|
|
3102
|
+
var persistRegistryAgentProposal = (stateDir, proposal) => {
|
|
3103
|
+
AgentProposalV1Schema.parse(proposal);
|
|
3104
|
+
if (proposal.contentHash !== contentHashForArtifactV1(proposal)) throw new Error("Cannot persist a Registry agent proposal with an invalid contentHash.");
|
|
3105
|
+
const safeHash = contentHashForArtifactV1(proposal);
|
|
3106
|
+
mkdirSync2(join9(resolve8(stateDir), "agents"), { recursive: true });
|
|
3107
|
+
const path = join9(resolve8(stateDir), "agents", `${proposal.origin.id}-${safeHash}.json`);
|
|
3108
|
+
writeFileSync2(path, `${JSON.stringify(proposal, null, 2)}
|
|
3109
|
+
`, "utf8");
|
|
3110
|
+
return path;
|
|
3111
|
+
};
|
|
3112
|
+
|
|
3113
|
+
// src/discovery/documentation.ts
|
|
3114
|
+
var detectionValues = /* @__PURE__ */ new Set(["static", "dynamic", "external"]);
|
|
3115
|
+
var evidence = (path, lineStart, lineEnd = lineStart) => ({
|
|
3116
|
+
source: "documentation",
|
|
3117
|
+
path,
|
|
3118
|
+
lineStart,
|
|
3119
|
+
lineEnd
|
|
3120
|
+
});
|
|
3121
|
+
var diagnostic = (path, code, message, lineStart, lineEnd = lineStart) => ({ code, message, path: "docbridge", evidence: evidence(path, lineStart, lineEnd) });
|
|
3122
|
+
var scalar = (value) => {
|
|
3123
|
+
const trimmed = value.trim();
|
|
3124
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
3125
|
+
return trimmed.slice(1, -1);
|
|
3126
|
+
}
|
|
3127
|
+
return trimmed;
|
|
3128
|
+
};
|
|
3129
|
+
var list = (value) => {
|
|
3130
|
+
const trimmed = value.trim();
|
|
3131
|
+
if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return void 0;
|
|
3132
|
+
const body = trimmed.slice(1, -1).trim();
|
|
3133
|
+
return body ? body.split(",").map(scalar).filter(Boolean) : [];
|
|
3134
|
+
};
|
|
3135
|
+
var findFrontmatter = (content) => {
|
|
3136
|
+
const lines = content.replace(/^\uFEFF/, "").split(/\r?\n/);
|
|
3137
|
+
if (lines[0] !== "---") return void 0;
|
|
3138
|
+
const end = lines.findIndex((line, index) => index > 0 && line === "---");
|
|
3139
|
+
return end < 0 ? void 0 : { lines, end };
|
|
3140
|
+
};
|
|
3141
|
+
var addDiagnostic = (diagnostics, input, code, message, line, endLine = line) => {
|
|
3142
|
+
diagnostics.push(diagnostic(input.path, code, message, line, endLine));
|
|
3143
|
+
};
|
|
3144
|
+
var resolveEntity = (reference, entities, input, lineStart, unresolved) => {
|
|
3145
|
+
const resolved = entities.find((entity2) => entity2.id === reference || entity2.aliases?.includes(reference));
|
|
3146
|
+
if (resolved) return resolved;
|
|
3147
|
+
const id = `unresolved:${reference}`;
|
|
3148
|
+
const existing = unresolved.get(id);
|
|
3149
|
+
if (existing) return existing;
|
|
3150
|
+
const entity = {
|
|
3151
|
+
id,
|
|
3152
|
+
kind: "unresolved-reference",
|
|
3153
|
+
name: reference,
|
|
3154
|
+
provenance: "declared",
|
|
3155
|
+
evidence: [evidence(input.path, lineStart)]
|
|
3156
|
+
};
|
|
3157
|
+
unresolved.set(id, entity);
|
|
3158
|
+
return entity;
|
|
3159
|
+
};
|
|
3160
|
+
var relationKey = (from, to, kind) => `${from}\0${to}\0${kind}`;
|
|
3161
|
+
var parseBlock = (input) => {
|
|
3162
|
+
const frontmatter = findFrontmatter(input.content);
|
|
3163
|
+
if (!frontmatter) {
|
|
3164
|
+
if (input.content.replace(/^\uFEFF/, "").startsWith("---")) {
|
|
3165
|
+
return {
|
|
3166
|
+
covers: [],
|
|
3167
|
+
relations: [],
|
|
3168
|
+
hasDocbridge: true,
|
|
3169
|
+
diagnostics: [diagnostic(input.path, "DOCBRIDGE_FRONTMATTER_MALFORMED", "Frontmatter must close with a line containing only ---.", 1)]
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
return { covers: [], relations: [], diagnostics: [], hasDocbridge: false };
|
|
3173
|
+
}
|
|
3174
|
+
const { lines, end } = frontmatter;
|
|
3175
|
+
const docbridgeLine = lines.findIndex((line, index) => index > 0 && index < end && /^docbridge\s*:/.test(line));
|
|
3176
|
+
if (docbridgeLine < 0) return { covers: [], relations: [], diagnostics: [], hasDocbridge: false };
|
|
3177
|
+
const diagnostics = [];
|
|
3178
|
+
const covers = [];
|
|
3179
|
+
const relations = [];
|
|
3180
|
+
const inline = lines[docbridgeLine]?.slice("docbridge:".length).trim() ?? "";
|
|
3181
|
+
if (inline && inline !== "{}") addDiagnostic(diagnostics, input, "DOCBRIDGE_BLOCK_MALFORMED", "docbridge must be a nested frontmatter object.", docbridgeLine + 1);
|
|
3182
|
+
let section;
|
|
3183
|
+
let current;
|
|
3184
|
+
const finishRelation = () => {
|
|
3185
|
+
if (current) relations.push(current);
|
|
3186
|
+
current = void 0;
|
|
3187
|
+
};
|
|
3188
|
+
for (let index = docbridgeLine + 1; index < end; index += 1) {
|
|
3189
|
+
const raw = lines[index] ?? "";
|
|
3190
|
+
const trimmed = raw.trim();
|
|
3191
|
+
const line = index + 1;
|
|
3192
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
3193
|
+
if (/\t/.test(raw)) {
|
|
3194
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_INDENTATION_INVALID", "docbridge indentation must use spaces.", line);
|
|
3195
|
+
continue;
|
|
3196
|
+
}
|
|
3197
|
+
if (!raw.startsWith(" ")) {
|
|
3198
|
+
finishRelation();
|
|
3199
|
+
section = void 0;
|
|
3200
|
+
continue;
|
|
3201
|
+
}
|
|
3202
|
+
if (/^ {2}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
|
|
3203
|
+
finishRelation();
|
|
3204
|
+
const match = /^ {2}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
3205
|
+
const key = match?.[1];
|
|
3206
|
+
const value = match?.[2] ?? "";
|
|
3207
|
+
if (key !== "covers" && key !== "relations") {
|
|
3208
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown docbridge field: ${key ?? "(missing)"}.`, line);
|
|
3209
|
+
section = void 0;
|
|
3210
|
+
} else if (key === "covers") {
|
|
3211
|
+
section = "covers";
|
|
3212
|
+
if (value) {
|
|
3213
|
+
const values = list(value);
|
|
3214
|
+
if (!values) addDiagnostic(diagnostics, input, "DOCBRIDGE_COVERS_INVALID", "covers must be a list of entity references.", line);
|
|
3215
|
+
else values.forEach((item) => covers.push({ value: item, line }));
|
|
3216
|
+
}
|
|
3217
|
+
} else {
|
|
3218
|
+
section = "relations";
|
|
3219
|
+
if (value) addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATIONS_INVALID", "relations must be a list of relation objects.", line);
|
|
3220
|
+
}
|
|
3221
|
+
continue;
|
|
3222
|
+
}
|
|
3223
|
+
if (section === "covers" && /^ {4}-\s*/.test(raw)) {
|
|
3224
|
+
const value = scalar(raw.replace(/^ {4}-\s*/, ""));
|
|
3225
|
+
if (!value) addDiagnostic(diagnostics, input, "DOCBRIDGE_REFERENCE_MISSING", "covers entries must not be empty.", line);
|
|
3226
|
+
else covers.push({ value, line });
|
|
3227
|
+
continue;
|
|
3228
|
+
}
|
|
3229
|
+
if (section === "relations" && /^ {4}-\s*/.test(raw)) {
|
|
3230
|
+
finishRelation();
|
|
3231
|
+
const firstField = /^ {4}-\s*([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
3232
|
+
current = { startLine: line, endLine: line, fields: /* @__PURE__ */ new Set() };
|
|
3233
|
+
if (firstField?.[1]) {
|
|
3234
|
+
current.fields.add(firstField[1]);
|
|
3235
|
+
current[firstField[1]] = scalar(firstField[2] ?? "");
|
|
3236
|
+
} else if (raw.replace(/^ {4}-\s*/, "").trim()) {
|
|
3237
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATION_INVALID", "Relation entries must be field mappings.", line);
|
|
3238
|
+
}
|
|
3239
|
+
continue;
|
|
3240
|
+
}
|
|
3241
|
+
if (section === "relations" && current && /^ {6}[A-Za-z][A-Za-z0-9_-]*\s*:/.test(raw)) {
|
|
3242
|
+
const field = /^ {6}([A-Za-z][A-Za-z0-9_-]*)\s*:\s*(.*)$/.exec(raw);
|
|
3243
|
+
const key = field?.[1];
|
|
3244
|
+
current.endLine = line;
|
|
3245
|
+
if (!key || !["from", "to", "kind", "detection"].includes(key)) {
|
|
3246
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_UNKNOWN", `Unknown relation field: ${key ?? "(missing)"}.`, line);
|
|
3247
|
+
} else if (current.fields.has(key)) {
|
|
3248
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_FIELD_DUPLICATE", `Duplicate relation field: ${key}.`, line);
|
|
3249
|
+
} else {
|
|
3250
|
+
current.fields.add(key);
|
|
3251
|
+
current[key] = scalar(field?.[2] ?? "");
|
|
3252
|
+
}
|
|
3253
|
+
continue;
|
|
3254
|
+
}
|
|
3255
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_STRUCTURE_INVALID", `Invalid docbridge structure at line ${line}.`, line);
|
|
3256
|
+
}
|
|
3257
|
+
finishRelation();
|
|
3258
|
+
return { covers, relations, diagnostics, hasDocbridge: true };
|
|
3259
|
+
};
|
|
3260
|
+
var parseDocumentationDeclarations = (input, options) => {
|
|
3261
|
+
const parsed = parseBlock(input);
|
|
3262
|
+
if (!parsed.hasDocbridge) return { hasDocbridge: false, entities: [], relations: [], diagnostics: [] };
|
|
3263
|
+
const diagnostics = [...parsed.diagnostics];
|
|
3264
|
+
const unresolved = /* @__PURE__ */ new Map();
|
|
3265
|
+
const relations = [];
|
|
3266
|
+
const documentId = options.documentId ?? `document:${input.path}`;
|
|
3267
|
+
const relationClaims = /* @__PURE__ */ new Map();
|
|
3268
|
+
if (!parsed.covers.length && !parsed.relations.length) {
|
|
3269
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_CONTENT_MISSING", "docbridge must declare covers or relations.", 1);
|
|
3270
|
+
}
|
|
3271
|
+
for (const [index, cover] of parsed.covers.entries()) {
|
|
3272
|
+
const target = resolveEntity(cover.value, options.snapshot.entities, input, cover.line, unresolved);
|
|
3273
|
+
relations.push({
|
|
3274
|
+
id: `relation:declared:${input.path}:covers:${index}`,
|
|
3275
|
+
kind: "covers",
|
|
3276
|
+
from: documentId,
|
|
3277
|
+
to: target.id,
|
|
3278
|
+
provenance: "declared",
|
|
3279
|
+
evidence: [evidence(input.path, cover.line)]
|
|
3280
|
+
});
|
|
3281
|
+
}
|
|
3282
|
+
for (const [index, declaration] of parsed.relations.entries()) {
|
|
3283
|
+
const basePath = `relations[${index}]`;
|
|
3284
|
+
const missing = ["from", "to", "kind", "detection"].filter((field) => !declaration[field]);
|
|
3285
|
+
if (missing.length) {
|
|
3286
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_RELATION_FIELD_MISSING", `Relation is missing required field(s): ${missing.join(", ")}.`, declaration.startLine, declaration.endLine);
|
|
3287
|
+
continue;
|
|
3288
|
+
}
|
|
3289
|
+
const from = declaration.from;
|
|
3290
|
+
const to = declaration.to;
|
|
3291
|
+
const kind = declaration.kind;
|
|
3292
|
+
const detection = declaration.detection;
|
|
3293
|
+
if (!detectionValues.has(detection)) {
|
|
3294
|
+
addDiagnostic(diagnostics, input, "DOCBRIDGE_DETECTION_INVALID", `Invalid relation detection: ${detection}.`, declaration.startLine, declaration.endLine);
|
|
3295
|
+
continue;
|
|
3296
|
+
}
|
|
3297
|
+
const fromEntity = resolveEntity(from, options.snapshot.entities, input, declaration.startLine, unresolved);
|
|
3298
|
+
const toEntity = resolveEntity(to, options.snapshot.entities, input, declaration.startLine, unresolved);
|
|
3299
|
+
const key = relationKey(fromEntity.id, toEntity.id, kind);
|
|
3300
|
+
const previousDetection = relationClaims.get(key);
|
|
3301
|
+
if (previousDetection === detection) addDiagnostic(diagnostics, input, "DOCBRIDGE_DECLARATION_DUPLICATE", "Duplicate relation declaration.", declaration.startLine, declaration.endLine);
|
|
3302
|
+
if (previousDetection && previousDetection !== detection) addDiagnostic(diagnostics, input, "DOCBRIDGE_DECLARATION_CONFLICT", "Conflicting relation declarations use different detection values.", declaration.startLine, declaration.endLine);
|
|
3303
|
+
relationClaims.set(key, previousDetection ?? detection);
|
|
3304
|
+
relations.push({
|
|
3305
|
+
id: `relation:declared:${input.path}:${basePath}`,
|
|
3306
|
+
kind,
|
|
3307
|
+
from: fromEntity.id,
|
|
3308
|
+
to: toEntity.id,
|
|
3309
|
+
discriminator: detection,
|
|
3310
|
+
provenance: "declared",
|
|
3311
|
+
evidence: [evidence(input.path, declaration.startLine, declaration.endLine)],
|
|
3312
|
+
metadata: { detection }
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
return {
|
|
3316
|
+
hasDocbridge: true,
|
|
3317
|
+
entities: [...unresolved.values()].sort((a, b) => a.id.localeCompare(b.id)),
|
|
3318
|
+
relations,
|
|
3319
|
+
diagnostics
|
|
3320
|
+
};
|
|
3321
|
+
};
|
|
3322
|
+
var applyDocumentationDeclarations = (snapshot, documents) => {
|
|
3323
|
+
const entities = new Map(snapshot.entities.map((entity) => [entity.id, entity]));
|
|
3324
|
+
const relations = new Map(snapshot.relations.map((relation) => [relation.id, relation]));
|
|
3325
|
+
const diagnostics = [];
|
|
3326
|
+
for (const document of documents) {
|
|
3327
|
+
const result = parseDocumentationDeclarations(document, { snapshot });
|
|
3328
|
+
diagnostics.push(...result.diagnostics);
|
|
3329
|
+
for (const entity of result.entities) entities.set(entity.id, entity);
|
|
3330
|
+
for (const relation of result.relations) relations.set(relation.id, relation);
|
|
3331
|
+
}
|
|
3332
|
+
const base = { ...snapshot, contentHash: "0".repeat(64), entities: [...entities.values()], relations: [...relations.values()] };
|
|
3333
|
+
return {
|
|
3334
|
+
snapshot: DiscoverySnapshotV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) }),
|
|
3335
|
+
diagnostics
|
|
3336
|
+
};
|
|
3337
|
+
};
|
|
3338
|
+
|
|
3339
|
+
// src/reconciliation/reconcile.ts
|
|
3340
|
+
var ignoredDocumentationRelations = /* @__PURE__ */ new Set(["covers"]);
|
|
3341
|
+
var metadataDetection = (relation) => {
|
|
3342
|
+
const detection = relation.metadata?.detection;
|
|
3343
|
+
return typeof detection === "string" ? detection : void 0;
|
|
3344
|
+
};
|
|
3345
|
+
var relationDetection = (relation) => relation.discriminator ?? metadataDetection(relation) ?? "static";
|
|
3346
|
+
var entityResolver = (snapshots) => {
|
|
3347
|
+
const references = /* @__PURE__ */ new Map();
|
|
3348
|
+
const entities = snapshots.flatMap((snapshot) => snapshot.entities).sort((a, b) => a.id.localeCompare(b.id));
|
|
3349
|
+
for (const entity of entities) {
|
|
3350
|
+
references.set(entity.id, entity.id);
|
|
3351
|
+
for (const alias of entity.aliases ?? []) {
|
|
3352
|
+
if (!references.has(alias)) references.set(alias, entity.id);
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3355
|
+
return (reference) => references.get(reference) ?? reference;
|
|
3356
|
+
};
|
|
3357
|
+
var relationBase = (relation, resolveEntity2) => `${resolveEntity2(relation.from)}\0${resolveEntity2(relation.to)}\0${relation.kind}`;
|
|
3358
|
+
var evidenceKey = (item) => `${item.source}:${item.path}:${item.lineStart ?? ""}:${item.lineEnd ?? ""}:${item.context ?? ""}`;
|
|
3359
|
+
var mergeEvidence = (...relations) => {
|
|
3360
|
+
const merged = /* @__PURE__ */ new Map();
|
|
3361
|
+
for (const relation of relations) {
|
|
3362
|
+
for (const item of relation.evidence) merged.set(evidenceKey(item), item);
|
|
3363
|
+
}
|
|
3364
|
+
return [...merged.values()].sort((a, b) => evidenceKey(a).localeCompare(evidenceKey(b)));
|
|
3365
|
+
};
|
|
3366
|
+
var diagnosticId = (code, value) => `reconciliation:${code}:${sha256NormalizedV1(value).slice(0, 32)}`;
|
|
3367
|
+
var coverageAvailable = (snapshot, relation) => {
|
|
3368
|
+
const status = snapshot.coverage.find(
|
|
3369
|
+
(entry) => entry.scope === "static-imports-and-exports" && (relation.kind === "imports" || relation.kind === "re-exports")
|
|
3370
|
+
)?.status;
|
|
3371
|
+
return status === void 0 || status === "complete";
|
|
3372
|
+
};
|
|
3373
|
+
var entityById = (snapshots) => {
|
|
3374
|
+
const entities = /* @__PURE__ */ new Map();
|
|
3375
|
+
for (const snapshot of snapshots) {
|
|
3376
|
+
for (const entity of snapshot.entities) if (!entities.has(entity.id)) entities.set(entity.id, entity);
|
|
3377
|
+
}
|
|
3378
|
+
return entities;
|
|
3379
|
+
};
|
|
3380
|
+
var isUnresolved = (id, entities) => id.startsWith("unresolved:") || entities.get(id)?.kind === "unresolved-reference";
|
|
3381
|
+
var relationMatches = (observed, declared, resolveEntity2) => {
|
|
3382
|
+
if (relationBase(observed, resolveEntity2) !== relationBase(declared, resolveEntity2)) return false;
|
|
3383
|
+
return relationDetection(declared) === relationDetection(observed);
|
|
3384
|
+
};
|
|
3385
|
+
var reportDiagnostic = (code, status, severity, message, evidence2, value, entityIds, relationIds, remediation) => ({
|
|
3386
|
+
id: diagnosticId(code, value),
|
|
3387
|
+
code,
|
|
3388
|
+
status,
|
|
3389
|
+
severity,
|
|
3390
|
+
message,
|
|
3391
|
+
evidence: [...evidence2],
|
|
3392
|
+
...entityIds?.length ? { entityIds: [...entityIds] } : {},
|
|
3393
|
+
...relationIds?.length ? { relationIds: [...relationIds] } : {},
|
|
3394
|
+
...remediation ? { remediation } : {}
|
|
3395
|
+
});
|
|
3396
|
+
var reconcileKnowledge = (observed, declared) => {
|
|
3397
|
+
const resolveEntity2 = entityResolver([observed, declared]);
|
|
3398
|
+
const entities = entityById([observed, declared]);
|
|
3399
|
+
const observedRelations = observed.relations.filter((relation) => relation.provenance === "observed" && !ignoredDocumentationRelations.has(relation.kind));
|
|
3400
|
+
const declaredRelations = declared.relations.filter((relation) => relation.provenance === "declared" && !ignoredDocumentationRelations.has(relation.kind));
|
|
3401
|
+
const diagnostics = [];
|
|
3402
|
+
for (const entity of declared.entities.filter((item) => isUnresolved(item.id, entities)).sort((a, b) => a.id.localeCompare(b.id))) {
|
|
3403
|
+
diagnostics.push(reportDiagnostic(
|
|
3404
|
+
"UNRESOLVED_ENTITY_REFERENCE",
|
|
3405
|
+
"unresolved",
|
|
3406
|
+
"error",
|
|
3407
|
+
`Declared reference could not be resolved: ${entity.name}.`,
|
|
3408
|
+
entity.evidence,
|
|
3409
|
+
entity.id,
|
|
3410
|
+
[entity.id],
|
|
3411
|
+
void 0,
|
|
3412
|
+
"Resolve the reference to an observed entity ID or configured alias."
|
|
3413
|
+
));
|
|
3414
|
+
}
|
|
3415
|
+
const declaredByBase = /* @__PURE__ */ new Map();
|
|
3416
|
+
for (const relation of declaredRelations) {
|
|
3417
|
+
const base2 = relationBase(relation, resolveEntity2);
|
|
3418
|
+
const group = declaredByBase.get(base2) ?? [];
|
|
3419
|
+
group.push(relation);
|
|
3420
|
+
declaredByBase.set(base2, group);
|
|
3421
|
+
}
|
|
3422
|
+
for (const group of declaredByBase.values()) {
|
|
3423
|
+
const detections = new Set(group.map(relationDetection));
|
|
3424
|
+
if (detections.size < 2) continue;
|
|
3425
|
+
diagnostics.push(reportDiagnostic(
|
|
3426
|
+
"CONFLICTING_DECLARATIONS",
|
|
3427
|
+
"conflict",
|
|
3428
|
+
"error",
|
|
3429
|
+
"Declarations for the same semantic relation disagree on detection.",
|
|
3430
|
+
mergeEvidence(...group),
|
|
3431
|
+
[relationBase(group[0], resolveEntity2), [...detections].sort()],
|
|
3432
|
+
void 0,
|
|
3433
|
+
group.map((relation) => relation.id),
|
|
3434
|
+
"Keep one detection value for this relation or document the intended distinction with a different relation kind."
|
|
3435
|
+
));
|
|
3436
|
+
}
|
|
3437
|
+
for (const relation of observedRelations) {
|
|
3438
|
+
const candidates = declaredByBase.get(relationBase(relation, resolveEntity2)) ?? [];
|
|
3439
|
+
const match = candidates.find((candidate) => relationMatches(relation, candidate, resolveEntity2));
|
|
3440
|
+
if (match) {
|
|
3441
|
+
diagnostics.push(reportDiagnostic(
|
|
3442
|
+
"RELATION_CONFIRMED",
|
|
3443
|
+
"confirmed",
|
|
3444
|
+
"info",
|
|
3445
|
+
"Observed relation is covered by a matching declaration.",
|
|
3446
|
+
mergeEvidence(relation, match),
|
|
3447
|
+
relation.id,
|
|
3448
|
+
void 0,
|
|
3449
|
+
[relation.id, match.id]
|
|
3450
|
+
));
|
|
3451
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
3452
|
+
diagnostics.push(reportDiagnostic(
|
|
3453
|
+
"RELATION_UNDOCUMENTED",
|
|
3454
|
+
"undocumented",
|
|
3455
|
+
"warn",
|
|
3456
|
+
"Observed relation has no matching documentation declaration.",
|
|
3457
|
+
relation.evidence,
|
|
3458
|
+
relation.id,
|
|
3459
|
+
void 0,
|
|
3460
|
+
[relation.id],
|
|
3461
|
+
"Add a matching relation declaration or configure this relation kind as intentionally undocumented."
|
|
3462
|
+
));
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
for (const relation of declaredRelations) {
|
|
3466
|
+
const candidates = observedRelations.filter((candidate) => relationBase(candidate, resolveEntity2) === relationBase(relation, resolveEntity2));
|
|
3467
|
+
const detection = relationDetection(relation);
|
|
3468
|
+
if (candidates.some((candidate) => relationMatches(candidate, relation, resolveEntity2))) continue;
|
|
3469
|
+
if (isUnresolved(resolveEntity2(relation.from), entities) || isUnresolved(resolveEntity2(relation.to), entities)) continue;
|
|
3470
|
+
if (detection === "dynamic" || detection === "external") {
|
|
3471
|
+
diagnostics.push(reportDiagnostic(
|
|
3472
|
+
"RELATION_NOT_ANALYZED",
|
|
3473
|
+
"not-analyzed",
|
|
3474
|
+
"info",
|
|
3475
|
+
`Declared ${detection} relation cannot be verified by the current static analyzer.`,
|
|
3476
|
+
relation.evidence,
|
|
3477
|
+
relation.id,
|
|
3478
|
+
void 0,
|
|
3479
|
+
[relation.id],
|
|
3480
|
+
"Enable a compatible analyzer or provide explicit observed evidence before treating this relation as confirmed."
|
|
3481
|
+
));
|
|
3482
|
+
} else if (coverageAvailable(observed, relation)) {
|
|
3483
|
+
diagnostics.push(reportDiagnostic(
|
|
3484
|
+
"DECLARED_RELATION_STALE",
|
|
3485
|
+
"stale-or-unverified",
|
|
3486
|
+
"warn",
|
|
3487
|
+
candidates.length ? "Declared relation has incompatible observed evidence." : "Declared static relation was not observed.",
|
|
3488
|
+
candidates.length ? mergeEvidence(relation, ...candidates) : relation.evidence,
|
|
3489
|
+
relation.id,
|
|
3490
|
+
void 0,
|
|
3491
|
+
[relation.id, ...candidates.map((candidate) => candidate.id)],
|
|
3492
|
+
"Update the declaration or the implementation so both graphs describe the same relation."
|
|
3493
|
+
));
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
const sortedDiagnostics = [...diagnostics].sort((a, b) => a.id.localeCompare(b.id));
|
|
3497
|
+
const base = {
|
|
3498
|
+
type: "reconciliation-report",
|
|
3499
|
+
schemaVersion: 1,
|
|
3500
|
+
contentHash: "0".repeat(64),
|
|
3501
|
+
contentHashAlgo: observed.contentHashAlgo,
|
|
3502
|
+
project: observed.project,
|
|
3503
|
+
sourceRevision: observed.sourceRevision,
|
|
3504
|
+
sourceRevisionKind: observed.sourceRevisionKind,
|
|
3505
|
+
configurationHash: observed.configurationHash,
|
|
3506
|
+
pipelineVersion: observed.pipelineVersion,
|
|
3507
|
+
analyzerVersions: observed.analyzerVersions,
|
|
3508
|
+
snapshotHash: observed.contentHash,
|
|
3509
|
+
diagnostics: sortedDiagnostics,
|
|
3510
|
+
summary: {
|
|
3511
|
+
entityCount: observed.entities.length,
|
|
3512
|
+
relationCount: observedRelations.length,
|
|
3513
|
+
diagnosticCount: sortedDiagnostics.length
|
|
3514
|
+
}
|
|
3515
|
+
};
|
|
3516
|
+
return ReconciliationReportV1Schema.parse({ ...base, contentHash: contentHashForArtifactV1(base) });
|
|
3517
|
+
};
|
|
3518
|
+
|
|
3519
|
+
// src/report/html.ts
|
|
3520
|
+
var escapeHtml = (value) => String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3521
|
+
var anchor = (prefix, value) => `${prefix}-${value.replace(/[^A-Za-z0-9_-]+/g, "-")}`;
|
|
3522
|
+
var evidenceText = (evidence2, includeSnippets) => {
|
|
3523
|
+
const location = `${evidence2.path}${evidence2.lineStart ? `:${evidence2.lineStart}${evidence2.lineEnd && evidence2.lineEnd !== evidence2.lineStart ? `-${evidence2.lineEnd}` : ""}` : ""}`;
|
|
3524
|
+
return `${location}${includeSnippets && evidence2.context ? ` \u2014 ${redactSecrets(evidence2.context)}` : ""}`;
|
|
3525
|
+
};
|
|
3526
|
+
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>`;
|
|
3527
|
+
var embeddedJson = (value) => JSON.stringify(value).replaceAll("<", "\\u003c");
|
|
3528
|
+
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>`;
|
|
3529
|
+
var renderCompact = (snapshot, report, entityAnchors, includeSnippets) => {
|
|
3530
|
+
const entities = snapshot.entities.map((entity) => ({ id: entity.id, name: entity.name, kind: entity.kind, anchor: entityAnchors.get(entity.id) ?? anchor("entity", entity.id) }));
|
|
3531
|
+
const entityIndexes = new Map(entities.map((entity, index) => [entity.id, index]));
|
|
3532
|
+
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 }));
|
|
3533
|
+
const diagnostics = report.diagnostics.map((diagnostic2) => ({
|
|
3534
|
+
...diagnostic2,
|
|
3535
|
+
evidence: diagnostic2.evidence.map(({ context, ...evidence2 }) => includeSnippets && context ? { ...evidence2, context: redactSecrets(context) } : evidence2)
|
|
3536
|
+
}));
|
|
3537
|
+
const data = embeddedJson({ entities, relations, diagnostics, coverage: snapshot.coverage });
|
|
3538
|
+
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()};`;
|
|
3539
|
+
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>`;
|
|
3540
|
+
};
|
|
3541
|
+
var render = (input, options) => {
|
|
3542
|
+
const { snapshot, report } = input;
|
|
3543
|
+
const includeSnippets = options.includeSnippets === true;
|
|
3544
|
+
const entityAnchors = new Map(snapshot.entities.map((entity, index) => [entity.id, entity.id.length > 64 ? `entity-n${index}` : anchor("entity", entity.id)]));
|
|
3545
|
+
if (snapshot.entities.length > 500 || report.diagnostics.length > 1e3) return renderCompact(snapshot, report, entityAnchors, includeSnippets);
|
|
3546
|
+
const entityNames = new Map(snapshot.entities.map((entity) => [entity.id, entity.name]));
|
|
3547
|
+
const entityAnchor = (id) => entityAnchors.get(id) ?? anchor("entity", id);
|
|
3548
|
+
const entityLabel = (id) => entityNames.get(id) ?? id;
|
|
3549
|
+
const entityLink = (id) => `<a href="#${entityAnchor(id)}">${escapeHtml(entityLabel(id))}</a>`;
|
|
3550
|
+
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("");
|
|
3551
|
+
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("");
|
|
3552
|
+
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("");
|
|
3553
|
+
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("");
|
|
3554
|
+
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));});`;
|
|
3555
|
+
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>`;
|
|
3556
|
+
};
|
|
3557
|
+
var renderOfflineReport = (input, options = {}) => {
|
|
3558
|
+
try {
|
|
3559
|
+
if (!input || typeof input !== "object") throw new Error("Input must contain snapshot and report artifacts.");
|
|
3560
|
+
const value = input;
|
|
3561
|
+
const snapshot = DiscoverySnapshotV1Schema.parse(value.snapshot);
|
|
3562
|
+
const report = ReconciliationReportV1Schema.parse(value.report);
|
|
3563
|
+
if (report.snapshotHash !== snapshot.contentHash) throw new Error("Report snapshotHash does not match snapshot contentHash.");
|
|
3564
|
+
return render({ snapshot, report }, options);
|
|
3565
|
+
} catch (error) {
|
|
3566
|
+
return errorPage(error instanceof Error ? error.message : String(error));
|
|
3567
|
+
}
|
|
3568
|
+
};
|
|
3569
|
+
|
|
3570
|
+
// src/fixes/proposals.ts
|
|
3571
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4, readFileSync as readFileSync10, realpathSync as realpathSync7, renameSync, statSync as statSync4, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
|
|
3572
|
+
import { basename as basename5, dirname as dirname6, extname as extname2, join as join10, relative as relative6, resolve as resolve9, sep as sep7 } from "path";
|
|
3573
|
+
var hash2 = (value) => sha256NormalizedV1(value);
|
|
3574
|
+
var artifactMetadata = (root, options) => ({
|
|
3575
|
+
schemaVersion: 1,
|
|
3576
|
+
contentHash: "0".repeat(64),
|
|
3577
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
3578
|
+
project: { name: options.projectName ?? basename5(resolve9(root)), root: "." },
|
|
3579
|
+
sourceRevision: options.baseRevision,
|
|
3580
|
+
sourceRevisionKind: options.baseRevision.length === 40 ? "git" : "content",
|
|
3581
|
+
configurationHash: options.configurationHash,
|
|
3582
|
+
pipelineVersion: "1.0.0",
|
|
3583
|
+
analyzerVersions: { fixes: options.toolVersion ?? "1.0.0" }
|
|
3584
|
+
});
|
|
3585
|
+
var unifiedDiff = (changes) => changes.map((change) => {
|
|
3586
|
+
const before = change.before.split("\n").map((line) => `-${line}`).join("\n");
|
|
3587
|
+
const after = change.after.split("\n").map((line) => `+${line}`).join("\n");
|
|
3588
|
+
return `--- a/${change.path}
|
|
3589
|
+
+++ b/${change.path}
|
|
3590
|
+
@@
|
|
3591
|
+
${before}
|
|
3592
|
+
${after}`;
|
|
3593
|
+
}).join("\n");
|
|
3594
|
+
var makeProposal = (root, options, changes, preconditions, postconditions) => {
|
|
3595
|
+
const draft = {
|
|
3596
|
+
...artifactMetadata(root, options),
|
|
3597
|
+
type: "fix-proposal",
|
|
3598
|
+
proposalId: `fix-${hash2(changes).slice(0, 20)}`,
|
|
3599
|
+
baseRevision: options.baseRevision,
|
|
3600
|
+
affectedFiles: changes.map(({ path, before }) => ({ path, contentHash: sha256NormalizedV1(before) })),
|
|
3601
|
+
changes,
|
|
3602
|
+
preconditions,
|
|
3603
|
+
diff: unifiedDiff(changes),
|
|
3604
|
+
postconditions,
|
|
3605
|
+
status: "proposed"
|
|
3606
|
+
};
|
|
3607
|
+
return FixProposalV1Schema.parse({ ...draft, contentHash: contentHashForArtifactV1(draft) });
|
|
3608
|
+
};
|
|
3609
|
+
var walkMarkdown = (root, directory = root) => readdirSync4(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
3610
|
+
if (entry.name === ".git" || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build") return [];
|
|
3611
|
+
const path = join10(directory, entry.name);
|
|
3612
|
+
if (entry.isDirectory()) return walkMarkdown(root, path);
|
|
3613
|
+
return entry.isFile() && [".md", ".mdx"].includes(extname2(entry.name).toLowerCase()) ? [relative6(root, path).split(sep7).join("/")] : [];
|
|
3614
|
+
});
|
|
3615
|
+
var localLink = /(!?)\[([^\]]*)\]\(([^)\s]+)(?:\s+["'][^)]*["'])?\)/g;
|
|
3616
|
+
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;
|
|
3617
|
+
var createMarkdownLinkFixProposal = (root, options) => {
|
|
3618
|
+
const projectRoot = realpathSync7.native(resolve9(root));
|
|
3619
|
+
const paths = walkMarkdown(projectRoot);
|
|
3620
|
+
const changes = [];
|
|
3621
|
+
for (const path of paths) {
|
|
3622
|
+
const content = readFileSync10(join10(projectRoot, path), "utf8");
|
|
3623
|
+
let next = content;
|
|
3624
|
+
for (const match of content.matchAll(localLink)) {
|
|
3625
|
+
const target = match[3];
|
|
3626
|
+
if (!target || match[1] === "!" || /^(?:[a-z]+:|\/|#)/i.test(target)) continue;
|
|
3627
|
+
const targetPath = target.split("#")[0]?.split("?")[0];
|
|
3628
|
+
if (!targetPath || existsSync11(resolve9(projectRoot, dirname6(path), targetPath))) continue;
|
|
3629
|
+
const targetStem = basename5(targetPath, extname2(targetPath)).toLowerCase();
|
|
3630
|
+
const labelStem = (match[2] ?? "").trim().toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
3631
|
+
const candidates = paths.filter((candidate) => basename5(candidate, extname2(candidate)).toLowerCase() === targetStem || basename5(candidate, extname2(candidate)).toLowerCase().replace(/[^a-z0-9]+/g, "") === labelStem);
|
|
3632
|
+
if (candidates.length !== 1) continue;
|
|
3633
|
+
let replacement = relative6(dirname6(path), candidates[0]).split(sep7).join("/");
|
|
3634
|
+
if (target.startsWith("./") && !replacement.startsWith(".")) replacement = `./${replacement}`;
|
|
3635
|
+
next = next.replace(match[0], match[0].replace(target, replacement));
|
|
3636
|
+
}
|
|
3637
|
+
if (next !== content) changes.push({ path, before: content, after: next });
|
|
3638
|
+
}
|
|
3639
|
+
return changes.length ? makeProposal(projectRoot, options, changes, ["Each replacement has exactly one Markdown target."], ["All corrected local Markdown links resolve."]) : void 0;
|
|
3640
|
+
};
|
|
3641
|
+
var createArtifactNormalizationProposal = (root, artifactPath, options) => {
|
|
3642
|
+
const projectRoot = realpathSync7.native(resolve9(root));
|
|
3643
|
+
const path = artifactPath.split(sep7).join("/");
|
|
3644
|
+
const absolute = containedPath(projectRoot, path);
|
|
3645
|
+
if (!absolute || !existsSync11(absolute) || !statSync4(absolute).isFile()) return void 0;
|
|
3646
|
+
const before = readFileSync10(absolute, "utf8");
|
|
3647
|
+
let after;
|
|
3648
|
+
try {
|
|
3649
|
+
after = `${JSON.stringify(sortJson(JSON.parse(before)), null, 2)}
|
|
3650
|
+
`;
|
|
3651
|
+
} catch {
|
|
3652
|
+
return void 0;
|
|
3653
|
+
}
|
|
3654
|
+
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."]);
|
|
3655
|
+
};
|
|
3656
|
+
var approveFixProposal = (proposalInput, approvedBy, approvedAt = (/* @__PURE__ */ new Date()).toISOString()) => {
|
|
3657
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
3658
|
+
if (proposal.status !== "proposed") throw new Error(`Only proposed fixes can be approved; received "${proposal.status}".`);
|
|
3659
|
+
const approved = { ...proposal, status: "approved", approval: { proposalHash: proposal.contentHash, approvedAt, approvedBy }, contentHash: "0".repeat(64) };
|
|
3660
|
+
return FixProposalV1Schema.parse({ ...approved, contentHash: contentHashForArtifactV1(approved) });
|
|
3661
|
+
};
|
|
3662
|
+
var bindingHash = (proposal) => {
|
|
3663
|
+
const { approval: _approval, contentHash: _contentHash, status: _status, ...rest } = proposal;
|
|
3664
|
+
return contentHashForArtifactV1({ ...rest, contentHash: "0".repeat(64), status: "proposed" });
|
|
3665
|
+
};
|
|
3666
|
+
var applyFixProposal = (root, proposalInput, options = {}) => {
|
|
3667
|
+
const proposal = FixProposalV1Schema.parse(proposalInput);
|
|
3668
|
+
if (proposal.status !== "approved" || !proposal.approval) throw new Error("Only an explicitly approved fix proposal can be applied.");
|
|
3669
|
+
if (proposal.approval.proposalHash !== bindingHash(proposal)) throw new Error("Approval is not bound to the exact proposal content.");
|
|
3670
|
+
if (options.currentRevision && options.currentRevision !== proposal.baseRevision) throw new Error("The repository revision changed since this proposal was created.");
|
|
3671
|
+
if (!proposal.changes?.length) throw new Error("This proposal has no executable changes.");
|
|
3672
|
+
const projectRoot = realpathSync7.native(resolve9(root));
|
|
3673
|
+
const originals = /* @__PURE__ */ new Map();
|
|
3674
|
+
const affected = new Map(proposal.affectedFiles.map((file) => [file.path, file]));
|
|
3675
|
+
if (proposal.changes.some((change) => !affected.has(change.path) || affected.get(change.path)?.contentHash !== sha256NormalizedV1(change.before)) || affected.size !== proposal.changes.length) {
|
|
3676
|
+
throw new Error("Proposal changes do not match its affected-file hashes.");
|
|
3677
|
+
}
|
|
3678
|
+
for (const file of proposal.affectedFiles) {
|
|
3679
|
+
const absolute = containedPath(projectRoot, file.path);
|
|
3680
|
+
if (!absolute || !existsSync11(absolute)) throw new Error(`Affected file is unavailable or escapes the repository root: ${file.path}`);
|
|
3681
|
+
const current = readFileSync10(absolute, "utf8");
|
|
3682
|
+
if (sha256NormalizedV1(current) !== file.contentHash) throw new Error(`Affected file changed since proposal creation: ${file.path}`);
|
|
3683
|
+
originals.set(absolute, current);
|
|
3684
|
+
}
|
|
3685
|
+
try {
|
|
3686
|
+
for (const change of proposal.changes) {
|
|
3687
|
+
const absolute = resolve9(projectRoot, change.path);
|
|
3688
|
+
writeFileSync3(`${absolute}.docbridge-${process.pid}.tmp`, change.after, "utf8");
|
|
3689
|
+
}
|
|
3690
|
+
for (const change of proposal.changes) {
|
|
3691
|
+
const absolute = resolve9(projectRoot, change.path);
|
|
3692
|
+
renameSync(`${absolute}.docbridge-${process.pid}.tmp`, absolute);
|
|
3693
|
+
}
|
|
3694
|
+
for (const change of proposal.changes) {
|
|
3695
|
+
if (readFileSync10(resolve9(projectRoot, change.path), "utf8") !== change.after) throw new Error(`Postcondition failed for ${change.path}`);
|
|
3696
|
+
}
|
|
3697
|
+
options.verify?.(proposal.changes.map((change) => change.path));
|
|
3698
|
+
} catch (error) {
|
|
3699
|
+
for (const [absolute, content] of originals) writeFileSync3(absolute, content, "utf8");
|
|
3700
|
+
for (const change of proposal.changes) {
|
|
3701
|
+
const temp = `${resolve9(projectRoot, change.path)}.docbridge-${process.pid}.tmp`;
|
|
3702
|
+
if (existsSync11(temp)) unlinkSync(temp);
|
|
3703
|
+
}
|
|
3704
|
+
throw error;
|
|
3705
|
+
}
|
|
3706
|
+
const applied = { ...proposal, status: "applied", contentHash: "0".repeat(64) };
|
|
3707
|
+
return FixProposalV1Schema.parse({ ...applied, contentHash: contentHashForArtifactV1(applied) });
|
|
3708
|
+
};
|
|
3709
|
+
|
|
3710
|
+
// src/workflow/engine.ts
|
|
3711
|
+
import { appendFileSync, existsSync as existsSync12, mkdirSync as mkdirSync3, readFileSync as readFileSync11, renameSync as renameSync2, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3712
|
+
import { join as join11, relative as relative7, resolve as resolve10 } from "path";
|
|
3713
|
+
var WORKFLOW_STAGES = ["collect", "normalize", "reconcile", "evaluate", "report"];
|
|
3714
|
+
var stageState = {
|
|
3715
|
+
collect: "discovering",
|
|
3716
|
+
normalize: "analyzed",
|
|
3717
|
+
reconcile: "compared",
|
|
3718
|
+
evaluate: "proposed",
|
|
3719
|
+
report: "delivered"
|
|
3720
|
+
};
|
|
3721
|
+
var defaultStateDir = (root) => join11(root, ".doc-bridge", "workflow");
|
|
3722
|
+
var atomicWrite = (path, value) => {
|
|
3723
|
+
const temp = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
3724
|
+
writeFileSync4(temp, `${JSON.stringify(value, null, 2)}
|
|
3725
|
+
`, "utf8");
|
|
3726
|
+
renameSync2(temp, path);
|
|
3727
|
+
};
|
|
3728
|
+
var writeManifest = (stateDir, run2) => {
|
|
3729
|
+
atomicWrite(join11(stateDir, "manifest.json"), run2);
|
|
3730
|
+
};
|
|
3731
|
+
var appendTransition = (stateDir, transition2) => {
|
|
3732
|
+
appendFileSync(join11(stateDir, "transitions.jsonl"), `${JSON.stringify(transition2)}
|
|
3733
|
+
`, "utf8");
|
|
3734
|
+
};
|
|
3735
|
+
var transition = (run2, to, reason) => {
|
|
3736
|
+
const item = {
|
|
3737
|
+
from: run2.state,
|
|
3738
|
+
to,
|
|
3739
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3740
|
+
...reason ? { reason } : {}
|
|
3741
|
+
};
|
|
3742
|
+
return { ...run2, state: to, transitions: [...run2.transitions, item] };
|
|
3743
|
+
};
|
|
3744
|
+
var runId = () => `${Date.now()}-${process.pid}`;
|
|
3745
|
+
var stageInputHash = (options, stage, input) => sha256NormalizedV1({ stage, input, sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
3746
|
+
var stageArtifactPath = (stateDir, stage, inputHash) => join11(stateDir, "artifacts", `${stage}-${inputHash}.json`);
|
|
3747
|
+
var readArtifact = (path) => JSON.parse(readFileSync11(path, "utf8"));
|
|
3748
|
+
var stepArtifactPath = (stateDir, step) => resolve10(stateDir, step.artifactRefs?.[0] ?? "");
|
|
3749
|
+
var stepOutput = (stateDir, run2, stage) => {
|
|
3750
|
+
const step = run2.steps.find((item) => item.name === stage);
|
|
3751
|
+
if (!step || step.status !== "completed" || !step.artifactRefs?.[0]) return null;
|
|
3752
|
+
return readArtifact(stepArtifactPath(stateDir, step)).value;
|
|
3753
|
+
};
|
|
3754
|
+
var acquireLock = (stateDir) => {
|
|
3755
|
+
const lock = join11(stateDir, ".lock");
|
|
3756
|
+
try {
|
|
3757
|
+
mkdirSync3(lock);
|
|
3758
|
+
} catch {
|
|
3759
|
+
const ownerPath = join11(lock, "owner.json");
|
|
3760
|
+
try {
|
|
3761
|
+
const owner = JSON.parse(readFileSync11(ownerPath, "utf8"));
|
|
3762
|
+
if (typeof owner.pid === "number") process.kill(owner.pid, 0);
|
|
3763
|
+
throw new Error(`Workflow is already running (pid ${owner.pid ?? "unknown"}).`);
|
|
3764
|
+
} catch (error) {
|
|
3765
|
+
if (error instanceof Error && error.message.startsWith("Workflow is already running")) throw error;
|
|
3766
|
+
rmSync(lock, { recursive: true, force: true });
|
|
3767
|
+
mkdirSync3(lock);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
writeFileSync4(join11(lock, "owner.json"), JSON.stringify({ pid: process.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString() }), "utf8");
|
|
3771
|
+
return () => rmSync(lock, { recursive: true, force: true });
|
|
3772
|
+
};
|
|
3773
|
+
var loadManifest = (stateDir) => {
|
|
3774
|
+
const path = join11(stateDir, "manifest.json");
|
|
3775
|
+
if (!existsSync12(path)) return void 0;
|
|
3776
|
+
return WorkflowRunV1Schema.parse(JSON.parse(readFileSync11(path, "utf8")));
|
|
3777
|
+
};
|
|
3778
|
+
var baseRun = (options, stateDir, supersedes) => {
|
|
3779
|
+
const inputHash = sha256NormalizedV1({ sourceRevision: options.sourceRevision, configurationHash: options.configurationHash, toolVersion: options.toolVersion ?? "1.0.0" });
|
|
3780
|
+
return WorkflowRunV1Schema.parse({
|
|
3781
|
+
type: "workflow-run",
|
|
3782
|
+
schemaVersion: 1,
|
|
3783
|
+
contentHash: "0".repeat(64),
|
|
3784
|
+
contentHashAlgo: "sha256-normalized-v1",
|
|
3785
|
+
project: { name: resolve10(options.root).split("/").pop() ?? "project", root: "." },
|
|
3786
|
+
sourceRevision: options.sourceRevision,
|
|
3787
|
+
sourceRevisionKind: "content",
|
|
3788
|
+
configurationHash: options.configurationHash,
|
|
3789
|
+
pipelineVersion: "1.0.0",
|
|
3790
|
+
analyzerVersions: { workflow: options.toolVersion ?? "1.0.0" },
|
|
3791
|
+
runId: options.runId ?? runId(),
|
|
3792
|
+
state: "created",
|
|
3793
|
+
steps: WORKFLOW_STAGES.map((name) => ({ name, status: "pending", inputHash })),
|
|
3794
|
+
transitions: [{ from: null, to: "created", at: (/* @__PURE__ */ new Date()).toISOString() }],
|
|
3795
|
+
artifactRefs: [relative7(resolve10(options.root), stateDir), ...supersedes ? [`supersedes:${supersedes}`] : []]
|
|
3796
|
+
});
|
|
3797
|
+
};
|
|
3798
|
+
var withHash = (run2) => WorkflowRunV1Schema.parse({ ...run2, contentHash: contentHashForArtifactV1(run2) });
|
|
3799
|
+
var sameInputs = (run2, options) => run2.sourceRevision === options.sourceRevision && run2.configurationHash === options.configurationHash && run2.analyzerVersions.workflow === (options.toolVersion ?? "1.0.0");
|
|
3800
|
+
var selectedStages = (stage) => stage && stage !== "all" ? [stage] : WORKFLOW_STAGES;
|
|
3801
|
+
var runWorkflow = (options) => {
|
|
3802
|
+
const root = resolve10(options.root);
|
|
3803
|
+
const stateDir = resolve10(root, options.stateDir ?? defaultStateDir(root));
|
|
3804
|
+
mkdirSync3(join11(stateDir, "artifacts"), { recursive: true });
|
|
3805
|
+
const release = acquireLock(stateDir);
|
|
3806
|
+
try {
|
|
3807
|
+
let run2 = loadManifest(stateDir);
|
|
3808
|
+
let supersedes;
|
|
3809
|
+
if (run2 && !sameInputs(run2, options)) {
|
|
3810
|
+
supersedes = run2.runId;
|
|
3811
|
+
run2 = withHash(transition(run2, "stale", "Source revision, configuration hash, or tool version changed."));
|
|
3812
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
3813
|
+
writeManifest(stateDir, run2);
|
|
3814
|
+
run2 = void 0;
|
|
3815
|
+
}
|
|
3816
|
+
if (!run2) {
|
|
3817
|
+
run2 = withHash(baseRun(options, stateDir, supersedes));
|
|
3818
|
+
appendTransition(stateDir, run2.transitions[0]);
|
|
3819
|
+
writeManifest(stateDir, run2);
|
|
3820
|
+
}
|
|
3821
|
+
if (!run2) throw new Error("Workflow manifest was not initialized.");
|
|
3822
|
+
const firstSelectedStage = selectedStages(options.stage)[0];
|
|
3823
|
+
const previousStageIndex = firstSelectedStage ? WORKFLOW_STAGES.indexOf(firstSelectedStage) - 1 : -1;
|
|
3824
|
+
let previousOutput = previousStageIndex >= 0 ? stepOutput(stateDir, run2, WORKFLOW_STAGES[previousStageIndex]) : null;
|
|
3825
|
+
const reusedStages = [];
|
|
3826
|
+
for (const stage of selectedStages(options.stage)) {
|
|
3827
|
+
const input = options.inputs?.[stage] ?? previousOutput;
|
|
3828
|
+
const inputHash = stageInputHash(options, stage, input);
|
|
3829
|
+
const existing = run2.steps.find((step) => step.name === stage);
|
|
3830
|
+
const artifactPath = existing?.artifactRefs?.[0] ? resolve10(stateDir, existing.artifactRefs[0]) : stageArtifactPath(stateDir, stage, inputHash);
|
|
3831
|
+
if (existing?.status === "completed" && existing.inputHash === inputHash && existing.outputHash && existsSync12(artifactPath)) {
|
|
3832
|
+
previousOutput = readArtifact(artifactPath).value;
|
|
3833
|
+
reusedStages.push(stage);
|
|
3834
|
+
continue;
|
|
3835
|
+
}
|
|
3836
|
+
const handler = options.handlers[stage];
|
|
3837
|
+
if (!handler) throw new Error(`No handler configured for workflow stage "${stage}".`);
|
|
3838
|
+
run2 = withHash(transition(run2, stageState[stage]));
|
|
3839
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
3840
|
+
const runningStep = { name: stage, status: "running", inputHash };
|
|
3841
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? runningStep : step) });
|
|
3842
|
+
writeManifest(stateDir, run2);
|
|
3843
|
+
try {
|
|
3844
|
+
const value = handler({ root, stage, input, previousOutput });
|
|
3845
|
+
const outputHash = sha256NormalizedV1(value);
|
|
3846
|
+
const artifact2 = { type: "workflow-step-artifact", stage, inputHash, outputHash, value };
|
|
3847
|
+
mkdirSync3(join11(stateDir, "artifacts"), { recursive: true });
|
|
3848
|
+
if (existsSync12(artifactPath)) {
|
|
3849
|
+
const existingArtifact = readArtifact(artifactPath);
|
|
3850
|
+
if (existingArtifact.outputHash !== outputHash) throw new Error(`Immutable workflow artifact collision for stage "${stage}".`);
|
|
3851
|
+
} else {
|
|
3852
|
+
atomicWrite(artifactPath, artifact2);
|
|
3853
|
+
}
|
|
3854
|
+
const ref = relative7(stateDir, artifactPath);
|
|
3855
|
+
const completedStep = { name: stage, status: "completed", inputHash, outputHash, artifactRefs: [ref] };
|
|
3856
|
+
run2 = withHash({ ...run2, steps: run2.steps.map((step) => step.name === stage ? completedStep : step) });
|
|
3857
|
+
writeManifest(stateDir, run2);
|
|
3858
|
+
previousOutput = value;
|
|
3859
|
+
} catch (error) {
|
|
3860
|
+
run2 = withHash(transition(run2, "failed", error instanceof Error ? error.message : String(error)));
|
|
3861
|
+
appendTransition(stateDir, run2.transitions[run2.transitions.length - 1]);
|
|
3862
|
+
writeManifest(stateDir, run2);
|
|
3863
|
+
throw error;
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3866
|
+
if (selectedStages(options.stage).every((stage) => run2.steps.find((step) => step.name === stage)?.status === "completed")) {
|
|
3867
|
+
const complete = selectedStages(options.stage).includes("report") && run2.state !== "delivered" ? withHash(transition(run2, "delivered")) : run2;
|
|
3868
|
+
if (complete !== run2) {
|
|
3869
|
+
appendTransition(stateDir, complete.transitions[complete.transitions.length - 1]);
|
|
3870
|
+
run2 = complete;
|
|
3871
|
+
}
|
|
3872
|
+
writeManifest(stateDir, run2);
|
|
3873
|
+
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] });
|
|
3874
|
+
}
|
|
3875
|
+
return { run: run2, stateDir, reusedStages };
|
|
3876
|
+
} finally {
|
|
3877
|
+
release();
|
|
3878
|
+
}
|
|
3879
|
+
};
|
|
3880
|
+
var loadWorkflowManifest = (stateDir) => WorkflowRunV1Schema.parse(JSON.parse(readFileSync11(join11(resolve10(stateDir), "manifest.json"), "utf8")));
|
|
3881
|
+
var loadWorkflowStepOutput = (stateDir, stage) => stepOutput(resolve10(stateDir), loadWorkflowManifest(stateDir), stage);
|
|
3882
|
+
|
|
3883
|
+
// src/rules/engine.ts
|
|
3884
|
+
import { minimatch as minimatch5 } from "minimatch";
|
|
3885
|
+
var diagnosticRules = {
|
|
3886
|
+
DOCUMENTATION_QUALITY: "documentation-quality",
|
|
3887
|
+
RELATION_UNDOCUMENTED: "graph-undocumented-relation",
|
|
3888
|
+
DECLARED_RELATION_STALE: "declared-unobserved-relation",
|
|
3889
|
+
UNRESOLVED_ENTITY_REFERENCE: "unresolved-reference",
|
|
3890
|
+
CONFLICTING_DECLARATIONS: "conflicting-declaration",
|
|
3891
|
+
RELATION_NOT_ANALYZED: "not-analyzed-coverage",
|
|
3892
|
+
STALE_DOCUMENTATION: "stale-documentation",
|
|
3893
|
+
FRESHNESS_FAILURE: "freshness",
|
|
3894
|
+
OWNERSHIP_GAP: "ownership",
|
|
3895
|
+
CENTRALITY_RISK: "centrality-risk",
|
|
3896
|
+
CRITICAL_PATH_RISK: "critical-path-risk"
|
|
3897
|
+
};
|
|
3898
|
+
var defaultSeverity = (mode, ruleId) => {
|
|
3899
|
+
if (mode === "default") return "info";
|
|
3900
|
+
if (mode === "strict") return ruleId === "not-analyzed-coverage" ? "warn" : "error";
|
|
3901
|
+
return ruleId === "not-analyzed-coverage" ? "info" : "warn";
|
|
3902
|
+
};
|
|
3903
|
+
var resolvedOptions = (options) => {
|
|
3904
|
+
const config = RulesConfigSchema.parse(options.config ?? {});
|
|
3905
|
+
const mode = options.preset ?? config.mode ?? "default";
|
|
3906
|
+
const severity = { ...config.severity, ...options.severity };
|
|
3907
|
+
const ignore = /* @__PURE__ */ new Set([...config.ignore ?? [], ...options.ignore ?? []]);
|
|
3908
|
+
return {
|
|
3909
|
+
mode,
|
|
3910
|
+
severity,
|
|
3911
|
+
ignore,
|
|
3912
|
+
criticalEntities: options.criticalEntities ?? config.criticalEntities ?? [],
|
|
3913
|
+
criticalPaths: options.criticalPaths ?? config.criticalPaths ?? [],
|
|
3914
|
+
warningThresholds: { ...config.warningThresholds, ...options.warningThresholds }
|
|
3915
|
+
};
|
|
3916
|
+
};
|
|
3917
|
+
var severityFor = (ruleId, mode, overrides) => overrides[ruleId] ?? defaultSeverity(mode, ruleId);
|
|
3918
|
+
var findingFromDiagnostic = (diagnostic2, ruleId, severity) => ({
|
|
3919
|
+
id: `${diagnostic2.id}:${ruleId}`,
|
|
3920
|
+
ruleId,
|
|
3921
|
+
code: ruleId,
|
|
3922
|
+
status: diagnostic2.status,
|
|
3923
|
+
severity,
|
|
3924
|
+
message: diagnostic2.message,
|
|
3925
|
+
evidence: diagnostic2.evidence,
|
|
3926
|
+
...diagnostic2.entityIds ? { entityIds: diagnostic2.entityIds } : {},
|
|
3927
|
+
...diagnostic2.relationIds ? { relationIds: diagnostic2.relationIds } : {},
|
|
3928
|
+
...diagnostic2.remediation ? { remediation: diagnostic2.remediation } : {},
|
|
3929
|
+
sourceDiagnosticCode: diagnostic2.code
|
|
3930
|
+
});
|
|
3931
|
+
var criticalFinding = (finding, severity, target) => ({
|
|
3932
|
+
...finding,
|
|
3933
|
+
id: `${finding.id}:critical:${target}`,
|
|
3934
|
+
ruleId: "critical-path-risk",
|
|
3935
|
+
code: "critical-path-risk",
|
|
3936
|
+
severity,
|
|
3937
|
+
message: `Critical path or entity is affected: ${target}. ${finding.message}`
|
|
3938
|
+
});
|
|
3939
|
+
var evaluateRules = (report, options = {}) => {
|
|
3940
|
+
const resolved = resolvedOptions(options);
|
|
3941
|
+
const findings = [];
|
|
3942
|
+
for (const diagnostic2 of [...report.diagnostics].sort((a, b) => a.id.localeCompare(b.id))) {
|
|
3943
|
+
const ruleId = diagnosticRules[diagnostic2.code];
|
|
3944
|
+
if (!ruleId || resolved.ignore.has(ruleId)) continue;
|
|
3945
|
+
const severity = severityFor(ruleId, resolved.mode, resolved.severity);
|
|
3946
|
+
if (severity !== "off") findings.push(findingFromDiagnostic(diagnostic2, ruleId, severity));
|
|
3947
|
+
}
|
|
3948
|
+
const criticalSeverity = severityFor("critical-path-risk", resolved.mode, resolved.severity);
|
|
3949
|
+
const criticalEntitySet = new Set(resolved.criticalEntities);
|
|
3950
|
+
for (const finding of [...findings]) {
|
|
3951
|
+
const matchingEntity = (finding.entityIds ?? []).find((id) => criticalEntitySet.has(id));
|
|
3952
|
+
if (matchingEntity && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
3953
|
+
findings.push(criticalFinding(finding, criticalSeverity, matchingEntity));
|
|
3954
|
+
}
|
|
3955
|
+
for (const path of resolved.criticalPaths) {
|
|
3956
|
+
if (finding.evidence.some((item) => minimatch5(item.path, path, { dot: true })) && !resolved.ignore.has("critical-path-risk") && criticalSeverity !== "off") {
|
|
3957
|
+
findings.push(criticalFinding(finding, criticalSeverity, path));
|
|
3958
|
+
}
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
const centralityThreshold = resolved.warningThresholds["centrality-risk"] ?? 3;
|
|
3962
|
+
const centralitySeverity = severityFor("centrality-risk", resolved.mode, resolved.severity);
|
|
3963
|
+
if (!resolved.ignore.has("centrality-risk") && centralitySeverity !== "off") {
|
|
3964
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3965
|
+
for (const finding of findings.filter((item) => item.ruleId === "graph-undocumented-relation")) {
|
|
3966
|
+
for (const entityId2 of finding.entityIds ?? []) counts.set(entityId2, (counts.get(entityId2) ?? 0) + 1);
|
|
3967
|
+
}
|
|
3968
|
+
for (const [entityId2, count2] of [...counts.entries()].sort(([a], [b]) => a.localeCompare(b))) {
|
|
3969
|
+
if (count2 < centralityThreshold || !criticalEntitySet.has(entityId2)) continue;
|
|
3970
|
+
findings.push({
|
|
3971
|
+
id: `centrality-risk:${entityId2}`,
|
|
3972
|
+
ruleId: "centrality-risk",
|
|
3973
|
+
code: "centrality-risk",
|
|
3974
|
+
status: "unresolved",
|
|
3975
|
+
severity: centralitySeverity,
|
|
3976
|
+
message: `Critical entity has ${count2} undocumented relation finding(s); static centrality is a review signal, not a runtime availability claim.`,
|
|
3977
|
+
evidence: findings.filter((item) => item.ruleId === "graph-undocumented-relation" && item.entityIds?.includes(entityId2)).flatMap((item) => item.evidence),
|
|
3978
|
+
entityIds: [entityId2],
|
|
3979
|
+
remediation: "Review ownership, dependency boundaries, and runtime availability before declaring an SPOF."
|
|
3980
|
+
});
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
const sortedFindings = [...findings].sort((a, b) => a.id.localeCompare(b.id));
|
|
3984
|
+
return { mode: resolved.mode, findings: sortedFindings, exitCode: sortedFindings.some((finding) => finding.severity === "error") ? 1 : 0 };
|
|
3985
|
+
};
|
|
3986
|
+
var parseRuleId = (value) => RuleIdSchema.parse(value);
|
|
3987
|
+
var parseRuleSeverity = (value) => RuleSeveritySchema.parse(value);
|
|
3988
|
+
|
|
2222
3989
|
// src/federation/ecosystem-llms.ts
|
|
2223
3990
|
function formatEcosystemLlmsBlock(options) {
|
|
2224
3991
|
const heading = options.heading ?? "AgentsKit ecosystem";
|
|
@@ -2246,95 +4013,96 @@ function formatEcosystemLlmsSection(options) {
|
|
|
2246
4013
|
}
|
|
2247
4014
|
|
|
2248
4015
|
// src/gates/run-gates.ts
|
|
2249
|
-
import { readFileSync as
|
|
4016
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
2250
4017
|
|
|
2251
4018
|
// src/conformance/documentation-standard-v1.ts
|
|
2252
|
-
import { existsSync as
|
|
2253
|
-
import { isAbsolute as
|
|
4019
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, realpathSync as realpathSync8, statSync as statSync5 } from "fs";
|
|
4020
|
+
import { isAbsolute as isAbsolute5, relative as relative8, resolve as resolve11, sep as sep8 } from "path";
|
|
2254
4021
|
|
|
2255
4022
|
// src/conformance/ecosystem-contract.ts
|
|
2256
|
-
import { z as
|
|
2257
|
-
var NonEmptyStringSchema =
|
|
2258
|
-
var HttpsUrlSchema =
|
|
2259
|
-
var RepoSchema =
|
|
2260
|
-
var SlugSchema =
|
|
2261
|
-
var SurfaceSchema =
|
|
4023
|
+
import { z as z7 } from "zod";
|
|
4024
|
+
var NonEmptyStringSchema = z7.string().refine((value) => value.trim().length > 0, "must be non-empty");
|
|
4025
|
+
var HttpsUrlSchema = z7.string().url().refine((value) => value.startsWith("https://"), "must use https");
|
|
4026
|
+
var RepoSchema = z7.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/);
|
|
4027
|
+
var SlugSchema = z7.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
4028
|
+
var SurfaceSchema = z7.object({
|
|
2262
4029
|
home: HttpsUrlSchema.optional(),
|
|
2263
4030
|
docs: HttpsUrlSchema.optional(),
|
|
2264
4031
|
llms: HttpsUrlSchema.optional(),
|
|
2265
4032
|
stats: HttpsUrlSchema.optional(),
|
|
2266
|
-
documentation:
|
|
2267
|
-
chat:
|
|
4033
|
+
documentation: z7.enum(["fumadocs", "repository"]),
|
|
4034
|
+
chat: z7.enum(["agentschat", "custom", "none"])
|
|
2268
4035
|
}).passthrough();
|
|
2269
|
-
var ProductSchema =
|
|
4036
|
+
var ProductSchema = z7.object({
|
|
2270
4037
|
id: SlugSchema,
|
|
2271
4038
|
name: NonEmptyStringSchema,
|
|
2272
4039
|
shortName: NonEmptyStringSchema,
|
|
2273
4040
|
kind: NonEmptyStringSchema,
|
|
2274
4041
|
role: NonEmptyStringSchema,
|
|
2275
4042
|
promise: NonEmptyStringSchema,
|
|
2276
|
-
maturity:
|
|
2277
|
-
repo: RepoSchema,
|
|
2278
|
-
accent:
|
|
4043
|
+
maturity: z7.enum(["planning", "alpha", "beta", "stable", "deprecated"]),
|
|
4044
|
+
repo: RepoSchema.nullable(),
|
|
4045
|
+
accent: z7.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
2279
4046
|
surfaces: SurfaceSchema,
|
|
2280
|
-
navigation:
|
|
2281
|
-
showInBar:
|
|
2282
|
-
order:
|
|
2283
|
-
next:
|
|
4047
|
+
navigation: z7.object({
|
|
4048
|
+
showInBar: z7.boolean(),
|
|
4049
|
+
order: z7.number().int().nonnegative().optional(),
|
|
4050
|
+
next: z7.array(SlugSchema)
|
|
2284
4051
|
}).passthrough()
|
|
2285
4052
|
}).passthrough();
|
|
2286
|
-
var LegacyPropertySchema =
|
|
4053
|
+
var LegacyPropertySchema = z7.object({
|
|
2287
4054
|
id: SlugSchema,
|
|
2288
4055
|
name: NonEmptyStringSchema,
|
|
2289
4056
|
barLabel: NonEmptyStringSchema,
|
|
2290
4057
|
domain: NonEmptyStringSchema,
|
|
2291
4058
|
url: HttpsUrlSchema,
|
|
2292
|
-
repo: RepoSchema,
|
|
4059
|
+
repo: RepoSchema.nullable(),
|
|
2293
4060
|
tagline: NonEmptyStringSchema,
|
|
2294
4061
|
kind: NonEmptyStringSchema,
|
|
2295
|
-
accent:
|
|
4062
|
+
accent: z7.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
2296
4063
|
llms: HttpsUrlSchema.optional(),
|
|
2297
4064
|
stats: HttpsUrlSchema.optional()
|
|
2298
4065
|
}).passthrough();
|
|
2299
|
-
var ManifestSchema =
|
|
2300
|
-
schemaVersion:
|
|
2301
|
-
parentBrand:
|
|
2302
|
-
products:
|
|
4066
|
+
var ManifestSchema = z7.object({
|
|
4067
|
+
schemaVersion: z7.literal(2),
|
|
4068
|
+
parentBrand: z7.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema }).passthrough(),
|
|
4069
|
+
products: z7.array(ProductSchema).min(1),
|
|
2303
4070
|
// Historical four-product shim or full seven-product projection of products[].
|
|
2304
|
-
properties:
|
|
4071
|
+
properties: z7.array(LegacyPropertySchema).refine((value) => value.length === 4 || value.length === 7, {
|
|
2305
4072
|
message: "must project either the legacy four products or the full seven-product catalog"
|
|
2306
4073
|
}),
|
|
2307
|
-
builder:
|
|
4074
|
+
builder: z7.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema, url: HttpsUrlSchema }).passthrough().optional()
|
|
2308
4075
|
}).passthrough();
|
|
2309
|
-
var
|
|
2310
|
-
|
|
2311
|
-
type:
|
|
4076
|
+
var EvidenceSchema2 = z7.discriminatedUnion("type", [
|
|
4077
|
+
z7.object({
|
|
4078
|
+
type: z7.literal("repository-derivation"),
|
|
2312
4079
|
repo: RepoSchema,
|
|
2313
4080
|
path: NonEmptyStringSchema,
|
|
2314
4081
|
summary: NonEmptyStringSchema
|
|
2315
4082
|
}).passthrough(),
|
|
2316
|
-
|
|
4083
|
+
z7.object({ type: z7.literal("endpoint"), url: HttpsUrlSchema, summary: NonEmptyStringSchema }).passthrough()
|
|
2317
4084
|
]);
|
|
2318
|
-
var ClaimSchema =
|
|
4085
|
+
var ClaimSchema = z7.object({
|
|
2319
4086
|
id: NonEmptyStringSchema,
|
|
2320
|
-
value:
|
|
4087
|
+
value: z7.number().finite().nonnegative(),
|
|
2321
4088
|
noun: NonEmptyStringSchema,
|
|
2322
|
-
conservativeFloor:
|
|
2323
|
-
evidence:
|
|
4089
|
+
conservativeFloor: z7.number().int().nonnegative().optional(),
|
|
4090
|
+
evidence: EvidenceSchema2
|
|
2324
4091
|
}).passthrough();
|
|
2325
|
-
var ClaimProductSchema =
|
|
4092
|
+
var ClaimProductSchema = z7.object({
|
|
2326
4093
|
productId: SlugSchema,
|
|
2327
|
-
source:
|
|
2328
|
-
|
|
2329
|
-
|
|
4094
|
+
source: z7.discriminatedUnion("type", [
|
|
4095
|
+
z7.object({ type: z7.literal("endpoint"), url: HttpsUrlSchema }).passthrough(),
|
|
4096
|
+
z7.object({ type: z7.literal("repository"), repo: RepoSchema }).passthrough(),
|
|
4097
|
+
z7.object({ type: z7.literal("declaration"), summary: NonEmptyStringSchema }).passthrough()
|
|
2330
4098
|
]),
|
|
2331
|
-
verification:
|
|
2332
|
-
claims:
|
|
4099
|
+
verification: z7.enum(["verified", "declared"]),
|
|
4100
|
+
claims: z7.array(ClaimSchema)
|
|
2333
4101
|
}).passthrough();
|
|
2334
|
-
var ClaimsSchema =
|
|
2335
|
-
schemaVersion:
|
|
2336
|
-
manifestSchemaVersion:
|
|
2337
|
-
products:
|
|
4102
|
+
var ClaimsSchema = z7.object({
|
|
4103
|
+
schemaVersion: z7.literal(1),
|
|
4104
|
+
manifestSchemaVersion: z7.literal(2),
|
|
4105
|
+
products: z7.array(ClaimProductSchema)
|
|
2338
4106
|
}).passthrough();
|
|
2339
4107
|
var LEGACY_FOUR_PRODUCT_IDS = ["agentskit", "akos", "playbook", "registry"];
|
|
2340
4108
|
var FULL_SEVEN_PRODUCT_IDS = [
|
|
@@ -2403,8 +4171,10 @@ var parseCanonicalEcosystemContract = (manifestInput, claimsInput) => {
|
|
|
2403
4171
|
if (!claimProduct) throw new Error(`Claims are missing product ${productId}.`);
|
|
2404
4172
|
if (claimProduct.source.type === "endpoint") {
|
|
2405
4173
|
if (claimProduct.source.url !== product.surfaces.stats) throw new Error(`Claims source for ${productId} must match stats.`);
|
|
2406
|
-
} else if (claimProduct.source.repo !== product.repo) {
|
|
4174
|
+
} else if (claimProduct.source.type === "repository" && claimProduct.source.repo !== product.repo) {
|
|
2407
4175
|
throw new Error(`Claims source for ${productId} must match repo.`);
|
|
4176
|
+
} else if (claimProduct.source.type === "declaration" && product.repo !== null) {
|
|
4177
|
+
throw new Error(`Declaration source for ${productId} requires a null repo.`);
|
|
2408
4178
|
}
|
|
2409
4179
|
if (claimProduct.verification === "declared" && claimProduct.claims.length > 0) {
|
|
2410
4180
|
throw new Error(`Declared product ${productId} cannot publish claims.`);
|
|
@@ -2429,15 +4199,15 @@ var DOCUMENTATION_STANDARD_V1_ID = "documentation-standard-v1";
|
|
|
2429
4199
|
var DOCUMENTATION_STANDARD_V1_STATUS = "stable";
|
|
2430
4200
|
var MAX_TEXT_EVIDENCE_BYTES = 4 * 1024 * 1024;
|
|
2431
4201
|
var safePath = (root, path) => {
|
|
2432
|
-
const rootAbs =
|
|
2433
|
-
const unresolved =
|
|
2434
|
-
const unresolvedRel =
|
|
2435
|
-
if (
|
|
2436
|
-
if (!
|
|
4202
|
+
const rootAbs = realpathSync8.native(resolve11(root));
|
|
4203
|
+
const unresolved = resolve11(rootAbs, path);
|
|
4204
|
+
const unresolvedRel = relative8(rootAbs, unresolved);
|
|
4205
|
+
if (isAbsolute5(unresolvedRel) || unresolvedRel === ".." || unresolvedRel.startsWith(`..${sep8}`)) return void 0;
|
|
4206
|
+
if (!existsSync13(unresolved)) return unresolved;
|
|
2437
4207
|
try {
|
|
2438
|
-
const abs =
|
|
2439
|
-
const rel =
|
|
2440
|
-
return !
|
|
4208
|
+
const abs = realpathSync8.native(unresolved);
|
|
4209
|
+
const rel = relative8(rootAbs, abs);
|
|
4210
|
+
return !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep8}`) ? abs : void 0;
|
|
2441
4211
|
} catch {
|
|
2442
4212
|
return void 0;
|
|
2443
4213
|
}
|
|
@@ -2451,11 +4221,11 @@ var fileEvidence = (root, path, options) => {
|
|
|
2451
4221
|
evidence: { path, detail: "Path escapes the project root." }
|
|
2452
4222
|
};
|
|
2453
4223
|
}
|
|
2454
|
-
if (!
|
|
4224
|
+
if (!existsSync13(abs)) {
|
|
2455
4225
|
return { exists: false, content: "", evidence: { path, detail: "File does not exist." } };
|
|
2456
4226
|
}
|
|
2457
4227
|
try {
|
|
2458
|
-
const stat =
|
|
4228
|
+
const stat = statSync5(abs);
|
|
2459
4229
|
if (!stat.isFile()) {
|
|
2460
4230
|
return { exists: false, content: "", evidence: { path, detail: "Path is not a regular file." } };
|
|
2461
4231
|
}
|
|
@@ -2466,7 +4236,7 @@ var fileEvidence = (root, path, options) => {
|
|
|
2466
4236
|
return {
|
|
2467
4237
|
exists: true,
|
|
2468
4238
|
content: "",
|
|
2469
|
-
evidence: { path: toPosix(
|
|
4239
|
+
evidence: { path: toPosix(relative8(resolve11(root), abs)) || ".", detail: "File exists and is non-empty." }
|
|
2470
4240
|
};
|
|
2471
4241
|
}
|
|
2472
4242
|
if (stat.size > MAX_TEXT_EVIDENCE_BYTES) {
|
|
@@ -2476,12 +4246,12 @@ var fileEvidence = (root, path, options) => {
|
|
|
2476
4246
|
evidence: { path, detail: `Text evidence exceeds ${MAX_TEXT_EVIDENCE_BYTES} bytes.` }
|
|
2477
4247
|
};
|
|
2478
4248
|
}
|
|
2479
|
-
const content =
|
|
4249
|
+
const content = readFileSync12(abs, "utf8");
|
|
2480
4250
|
return {
|
|
2481
4251
|
exists: content.trim().length > 0,
|
|
2482
4252
|
content,
|
|
2483
4253
|
evidence: {
|
|
2484
|
-
path: toPosix(
|
|
4254
|
+
path: toPosix(relative8(resolve11(root), abs)) || ".",
|
|
2485
4255
|
detail: content.trim().length > 0 ? "File exists and is non-empty." : "File is empty."
|
|
2486
4256
|
}
|
|
2487
4257
|
};
|
|
@@ -2516,7 +4286,7 @@ var humanDocsRule = (root, config) => {
|
|
|
2516
4286
|
passed: docs.length > 0,
|
|
2517
4287
|
message: docs.length > 0 ? `Found ${docs.length} human document(s).` : "No human documentation was discovered.",
|
|
2518
4288
|
evidence: docs.slice(0, 10).map((doc) => ({
|
|
2519
|
-
path: toPosix(
|
|
4289
|
+
path: toPosix(relative8(resolve11(root), doc.path)),
|
|
2520
4290
|
detail: `Human route: ${doc.url}`
|
|
2521
4291
|
})),
|
|
2522
4292
|
remediation: {
|
|
@@ -2527,33 +4297,33 @@ var humanDocsRule = (root, config) => {
|
|
|
2527
4297
|
};
|
|
2528
4298
|
var llmsRule = (root, config, options) => {
|
|
2529
4299
|
const llmsPath = config.index?.llmsTxt?.outFile ?? "llms.txt";
|
|
2530
|
-
const llmsKey = safePath(root, llmsPath) ??
|
|
4300
|
+
const llmsKey = safePath(root, llmsPath) ?? resolve11(root, llmsPath);
|
|
2531
4301
|
const rawSources = /* @__PURE__ */ new Map();
|
|
2532
4302
|
for (const path of options.rawSources ?? []) {
|
|
2533
|
-
const key = safePath(root, path) ??
|
|
4303
|
+
const key = safePath(root, path) ?? resolve11(root, path);
|
|
2534
4304
|
if (key !== llmsKey && !rawSources.has(key)) rawSources.set(key, path);
|
|
2535
4305
|
}
|
|
2536
4306
|
const paths = [llmsPath, ...rawSources.values()];
|
|
2537
|
-
const
|
|
4307
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2538
4308
|
const generated = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
2539
4309
|
const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? "project");
|
|
2540
|
-
const llmsIsFresh =
|
|
2541
|
-
if (
|
|
2542
|
-
|
|
2543
|
-
...
|
|
4310
|
+
const llmsIsFresh = evidence2[0]?.content === expectedLlms;
|
|
4311
|
+
if (evidence2[0]?.exists) {
|
|
4312
|
+
evidence2[0] = {
|
|
4313
|
+
...evidence2[0],
|
|
2544
4314
|
evidence: {
|
|
2545
|
-
...
|
|
4315
|
+
...evidence2[0].evidence,
|
|
2546
4316
|
detail: llmsIsFresh ? "File matches the deterministic ak-docs output." : "File is stale or was not generated by the current ak-docs inputs."
|
|
2547
4317
|
}
|
|
2548
4318
|
};
|
|
2549
4319
|
}
|
|
2550
|
-
const passed = config.index?.llmsTxt?.enabled !== false && paths.length > 1 && llmsIsFresh &&
|
|
4320
|
+
const passed = config.index?.llmsTxt?.enabled !== false && paths.length > 1 && llmsIsFresh && evidence2.every((item) => item.exists);
|
|
2551
4321
|
return {
|
|
2552
4322
|
id: "llms-and-raw-source",
|
|
2553
4323
|
level: "required",
|
|
2554
4324
|
passed,
|
|
2555
4325
|
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.",
|
|
2556
|
-
evidence:
|
|
4326
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2557
4327
|
remediation: {
|
|
2558
4328
|
command: "ak-docs index",
|
|
2559
4329
|
detail: "Generate llms.txt and configure conformance.documentationStandardV1.rawSources."
|
|
@@ -2572,8 +4342,8 @@ var ecosystemContract = (root, options) => {
|
|
|
2572
4342
|
}
|
|
2573
4343
|
const manifestFile = fileEvidence(root, declaration.manifest);
|
|
2574
4344
|
const claimsFile = fileEvidence(root, declaration.claims);
|
|
2575
|
-
const
|
|
2576
|
-
if (!manifestFile.exists || !claimsFile.exists) return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
4345
|
+
const evidence2 = [manifestFile.evidence, claimsFile.evidence];
|
|
4346
|
+
if (!manifestFile.exists || !claimsFile.exists) return { passed: false, urls: /* @__PURE__ */ new Set(), evidence: evidence2 };
|
|
2577
4347
|
try {
|
|
2578
4348
|
const manifest = JSON.parse(manifestFile.content);
|
|
2579
4349
|
const claims = JSON.parse(claimsFile.content);
|
|
@@ -2591,15 +4361,15 @@ var ecosystemContract = (root, options) => {
|
|
|
2591
4361
|
if (typeof value === "string" && /^https:\/\//.test(value)) urls.add(normalizedUrl(value));
|
|
2592
4362
|
}
|
|
2593
4363
|
}
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
return { passed: urls.size > 0, urls, evidence };
|
|
4364
|
+
evidence2[0] = { path: declaration.manifest, detail: `Validated ${productIds.length} canonical product(s), including ${declaration.productId}.` };
|
|
4365
|
+
evidence2[1] = { path: declaration.claims, detail: `Validated claim-ledger identity for ${contract.claims.products.length} product(s).` };
|
|
4366
|
+
return { passed: urls.size > 0, urls, evidence: evidence2 };
|
|
2597
4367
|
} catch (error) {
|
|
2598
|
-
|
|
4368
|
+
evidence2.push({
|
|
2599
4369
|
path: `${declaration.manifest}, ${declaration.claims}`,
|
|
2600
4370
|
detail: error instanceof Error ? error.message : "Canonical ecosystem contract is invalid."
|
|
2601
4371
|
});
|
|
2602
|
-
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
4372
|
+
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence: evidence2 };
|
|
2603
4373
|
}
|
|
2604
4374
|
};
|
|
2605
4375
|
var handoffsRule = (root, config) => {
|
|
@@ -2625,13 +4395,13 @@ var handoffsRule = (root, config) => {
|
|
|
2625
4395
|
};
|
|
2626
4396
|
var contributionRule = (root, options) => {
|
|
2627
4397
|
const paths = options.contributionPaths?.length ? options.contributionPaths : ["CONTRIBUTING.md"];
|
|
2628
|
-
const
|
|
4398
|
+
const evidence2 = paths.map((path) => fileEvidence(root, path));
|
|
2629
4399
|
return {
|
|
2630
4400
|
id: "contribution",
|
|
2631
4401
|
level: "required",
|
|
2632
|
-
passed:
|
|
2633
|
-
message:
|
|
2634
|
-
evidence:
|
|
4402
|
+
passed: evidence2.some((item) => item.exists),
|
|
4403
|
+
message: evidence2.some((item) => item.exists) ? "Contribution guidance is available." : "Contribution guidance is missing.",
|
|
4404
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2635
4405
|
remediation: {
|
|
2636
4406
|
command: "edit CONTRIBUTING.md",
|
|
2637
4407
|
detail: "Document setup, validation commands, and the pull-request workflow."
|
|
@@ -2640,13 +4410,13 @@ var contributionRule = (root, options) => {
|
|
|
2640
4410
|
};
|
|
2641
4411
|
var markersRule = (root, options, kind, level) => {
|
|
2642
4412
|
const declarations = options[kind === "metadata" ? "metadata" : "diagrams"] ?? [];
|
|
2643
|
-
const
|
|
4413
|
+
const evidence2 = [];
|
|
2644
4414
|
let passed = declarations.length > 0;
|
|
2645
4415
|
for (const declaration of declarations) {
|
|
2646
4416
|
const file = fileEvidence(root, declaration.path);
|
|
2647
4417
|
const missing = declaration.contains.filter((marker) => !file.content.includes(marker));
|
|
2648
4418
|
if (!file.exists || missing.length > 0) passed = false;
|
|
2649
|
-
|
|
4419
|
+
evidence2.push({
|
|
2650
4420
|
path: declaration.path,
|
|
2651
4421
|
detail: !file.exists ? file.evidence.detail : missing.length ? `Missing marker(s): ${missing.join(", ")}` : `Found marker(s): ${declaration.contains.join(", ")}`
|
|
2652
4422
|
});
|
|
@@ -2656,7 +4426,7 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2656
4426
|
level,
|
|
2657
4427
|
passed,
|
|
2658
4428
|
message: passed ? `${kind} evidence is complete.` : `${kind} evidence is incomplete.`,
|
|
2659
|
-
evidence,
|
|
4429
|
+
evidence: evidence2,
|
|
2660
4430
|
remediation: {
|
|
2661
4431
|
command: "edit doc-bridge.config.json",
|
|
2662
4432
|
detail: `Declare ${kind} evidence paths and markers that exist in the repository.`
|
|
@@ -2666,14 +4436,14 @@ var markersRule = (root, options, kind, level) => {
|
|
|
2666
4436
|
var linksRule = (root, options) => {
|
|
2667
4437
|
const links = options.links ?? [];
|
|
2668
4438
|
const contract = ecosystemContract(root, options);
|
|
2669
|
-
const
|
|
4439
|
+
const evidence2 = [...contract.evidence];
|
|
2670
4440
|
let passed = links.length > 0 && contract.passed;
|
|
2671
4441
|
for (const link of links) {
|
|
2672
4442
|
const sources = link.paths.map((path) => ({ path, file: fileEvidence(root, path) }));
|
|
2673
4443
|
const matches = sources.filter(({ file }) => file.exists && file.content.includes(link.url));
|
|
2674
4444
|
const canonical = contract.urls.has(normalizedUrl(link.url));
|
|
2675
4445
|
if (matches.length === 0 || !canonical) passed = false;
|
|
2676
|
-
|
|
4446
|
+
evidence2.push({
|
|
2677
4447
|
path: sources.map((source) => source.path).join(", "),
|
|
2678
4448
|
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.`
|
|
2679
4449
|
});
|
|
@@ -2683,7 +4453,7 @@ var linksRule = (root, options) => {
|
|
|
2683
4453
|
level: "required",
|
|
2684
4454
|
passed,
|
|
2685
4455
|
message: passed ? `${links.length} required ecosystem link(s) resolve in source.` : "One or more required ecosystem links are missing from source.",
|
|
2686
|
-
evidence,
|
|
4456
|
+
evidence: evidence2,
|
|
2687
4457
|
remediation: {
|
|
2688
4458
|
command: "edit README.md",
|
|
2689
4459
|
detail: "Sync the canonical ecosystem snapshots and add each configured canonical URL to a declared documentation source."
|
|
@@ -2692,14 +4462,14 @@ var linksRule = (root, options) => {
|
|
|
2692
4462
|
};
|
|
2693
4463
|
var quickstartsRule = (root, options) => {
|
|
2694
4464
|
const quickstarts = options.quickstarts ?? [];
|
|
2695
|
-
const
|
|
4465
|
+
const evidence2 = [];
|
|
2696
4466
|
let passed = quickstarts.length > 0;
|
|
2697
4467
|
for (const quickstart of quickstarts) {
|
|
2698
4468
|
const doc = fileEvidence(root, quickstart.doc);
|
|
2699
4469
|
const test = fileEvidence(root, quickstart.test);
|
|
2700
4470
|
const missingMarkers = quickstart.testContains.filter((marker) => !test.content.includes(marker));
|
|
2701
4471
|
if (!doc.exists || !test.exists || missingMarkers.length > 0 || !quickstart.command.trim()) passed = false;
|
|
2702
|
-
|
|
4472
|
+
evidence2.push(
|
|
2703
4473
|
{ path: quickstart.doc, detail: `${quickstart.id}: ${doc.evidence.detail}` },
|
|
2704
4474
|
{
|
|
2705
4475
|
path: quickstart.test,
|
|
@@ -2712,7 +4482,7 @@ var quickstartsRule = (root, options) => {
|
|
|
2712
4482
|
level: "required",
|
|
2713
4483
|
passed,
|
|
2714
4484
|
message: passed ? `${quickstarts.length} quickstart(s) have executable test evidence.` : "Quickstart test evidence is incomplete.",
|
|
2715
|
-
evidence,
|
|
4485
|
+
evidence: evidence2,
|
|
2716
4486
|
remediation: {
|
|
2717
4487
|
command: "pnpm test",
|
|
2718
4488
|
detail: "Map every quickstart to a documentation path, test file, identifying marker, and CI command."
|
|
@@ -2721,13 +4491,13 @@ var quickstartsRule = (root, options) => {
|
|
|
2721
4491
|
};
|
|
2722
4492
|
var visualsRule = (root, options) => {
|
|
2723
4493
|
const visuals = options.visuals ?? [];
|
|
2724
|
-
const
|
|
4494
|
+
const evidence2 = visuals.map((path) => fileEvidence(root, path, { readContent: false }));
|
|
2725
4495
|
return {
|
|
2726
4496
|
id: "visual-explanations",
|
|
2727
4497
|
level: "recommended",
|
|
2728
|
-
passed: visuals.length > 0 &&
|
|
2729
|
-
message: visuals.length > 0 &&
|
|
2730
|
-
evidence:
|
|
4498
|
+
passed: visuals.length > 0 && evidence2.every((item) => item.exists),
|
|
4499
|
+
message: visuals.length > 0 && evidence2.every((item) => item.exists) ? `${visuals.length} visual asset(s) found.` : "Visual explanation evidence is incomplete.",
|
|
4500
|
+
evidence: evidence2.map((item) => item.evidence),
|
|
2731
4501
|
remediation: {
|
|
2732
4502
|
command: "edit doc-bridge.config.json",
|
|
2733
4503
|
detail: "Declare the images or animations that explain the product workflow."
|
|
@@ -2776,14 +4546,14 @@ var formatDocumentationStandardText = (report) => [
|
|
|
2776
4546
|
"",
|
|
2777
4547
|
...report.results.flatMap((result) => [
|
|
2778
4548
|
`${result.status === "pass" ? "PASS" : result.status === "excepted" ? "EXCEPTED" : "FAIL"} [${result.level}] ${result.id}: ${result.message}`,
|
|
2779
|
-
...result.evidence.map((
|
|
4549
|
+
...result.evidence.map((evidence2) => ` evidence: ${evidence2.path} \u2014 ${evidence2.detail}`),
|
|
2780
4550
|
...result.exception ? [` exception: ${result.exception.reason} \u2014 ${result.exception.approvedBy} (${result.exception.trackingUrl})`] : result.ok ? [] : [` fix: ${result.remediation.command} \u2014 ${result.remediation.detail}`]
|
|
2781
4551
|
])
|
|
2782
4552
|
];
|
|
2783
4553
|
|
|
2784
4554
|
// src/query/load-index.ts
|
|
2785
|
-
import { existsSync as
|
|
2786
|
-
import { join as
|
|
4555
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
4556
|
+
import { join as join12, resolve as resolve12 } from "path";
|
|
2787
4557
|
var IndexNotFoundError = class extends Error {
|
|
2788
4558
|
constructor(path) {
|
|
2789
4559
|
super(`Missing index at ${path}. Run: ak-docs index`);
|
|
@@ -2792,14 +4562,14 @@ var IndexNotFoundError = class extends Error {
|
|
|
2792
4562
|
}
|
|
2793
4563
|
path;
|
|
2794
4564
|
};
|
|
2795
|
-
var indexFilePath = (root, config) =>
|
|
4565
|
+
var indexFilePath = (root, config) => join12(root, config.index?.outFile ?? ".doc-bridge/index.json");
|
|
2796
4566
|
var loadDocBridgeIndex = (root, config) => {
|
|
2797
4567
|
const path = indexFilePath(root, config);
|
|
2798
|
-
if (!
|
|
2799
|
-
const raw = JSON.parse(
|
|
4568
|
+
if (!existsSync14(path)) throw new IndexNotFoundError(path);
|
|
4569
|
+
const raw = JSON.parse(readFileSync13(path, "utf8"));
|
|
2800
4570
|
return parseDocBridgeIndex(raw);
|
|
2801
4571
|
};
|
|
2802
|
-
var resolveRoot = (cwd) =>
|
|
4572
|
+
var resolveRoot = (cwd) => resolve12(cwd ?? process.cwd());
|
|
2803
4573
|
|
|
2804
4574
|
// src/gates/run-gates.ts
|
|
2805
4575
|
var RESERVED_GATE_IDS = /* @__PURE__ */ new Set(["link-rot", "routing-currency", "bootstrap-size"]);
|
|
@@ -2872,7 +4642,7 @@ var runOkfTypeGate = (root, config) => {
|
|
|
2872
4642
|
const required = config.corpus.agent.okf?.requireType ?? config.gates?.preset === "strict";
|
|
2873
4643
|
if (!required) return { id: "okf-type", ok: true, message: "OKF type frontmatter not required" };
|
|
2874
4644
|
const allowed = config.corpus.agent.okf?.allowedTypes;
|
|
2875
|
-
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(
|
|
4645
|
+
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));
|
|
2876
4646
|
if (bad.length) {
|
|
2877
4647
|
return {
|
|
2878
4648
|
id: "okf-type",
|
|
@@ -2899,10 +4669,10 @@ var DOCS_STYLE_RULES = {
|
|
|
2899
4669
|
"playbook-okf-soft": ["title", "no-stale-wording"],
|
|
2900
4670
|
"title-only": ["title"]
|
|
2901
4671
|
};
|
|
2902
|
-
var
|
|
4672
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2903
4673
|
var docsStyleOptions = (config) => {
|
|
2904
4674
|
const raw = config.gates?.options?.["docs-style"];
|
|
2905
|
-
if (!
|
|
4675
|
+
if (!isRecord3(raw)) {
|
|
2906
4676
|
if (config.gates?.preset === "playbook") {
|
|
2907
4677
|
return { profile: "playbook-okf-soft", required: DOCS_STYLE_RULES["playbook-okf-soft"] };
|
|
2908
4678
|
}
|
|
@@ -2946,7 +4716,7 @@ var runDocsStyleGate = (root, config) => {
|
|
|
2946
4716
|
}
|
|
2947
4717
|
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({
|
|
2948
4718
|
path: doc.path,
|
|
2949
|
-
missing: missingStyleRules(
|
|
4719
|
+
missing: missingStyleRules(readFileSync14(doc.absPath, "utf8"), required)
|
|
2950
4720
|
})).filter((doc) => doc.missing.length > 0);
|
|
2951
4721
|
if (bad.length) {
|
|
2952
4722
|
return {
|
|
@@ -2991,9 +4761,9 @@ var resolveGateIds = (config) => {
|
|
|
2991
4761
|
};
|
|
2992
4762
|
|
|
2993
4763
|
// src/mcp/server.ts
|
|
2994
|
-
import { readFileSync as
|
|
2995
|
-
import { relative as
|
|
2996
|
-
import { z as
|
|
4764
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync16, realpathSync as realpathSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
4765
|
+
import { join as join14, relative as relative9, resolve as resolve13 } from "path";
|
|
4766
|
+
import { z as z8, ZodError } from "zod";
|
|
2997
4767
|
|
|
2998
4768
|
// src/query/search.ts
|
|
2999
4769
|
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
@@ -3111,15 +4881,15 @@ var createDocBridgeRetriever = (index, options = {}) => ({
|
|
|
3111
4881
|
});
|
|
3112
4882
|
|
|
3113
4883
|
// src/memory/ingest.ts
|
|
3114
|
-
import { existsSync as
|
|
3115
|
-
import { join as
|
|
4884
|
+
import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
|
|
4885
|
+
import { join as join13 } from "path";
|
|
3116
4886
|
var memoryFact = (raw, id) => firstParagraph(raw) ?? firstHeading(raw) ?? id;
|
|
3117
|
-
var
|
|
4887
|
+
var relativePath2 = (root, abs) => toPosix(abs).replace(`${toPosix(root)}/`, "");
|
|
3118
4888
|
var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
3119
|
-
if (!
|
|
4889
|
+
if (!existsSync15(dir)) return [];
|
|
3120
4890
|
return walkFiles(dir, { extensions: [".md", ".mdc"] }).map((abs) => {
|
|
3121
|
-
const rel =
|
|
3122
|
-
const raw =
|
|
4891
|
+
const rel = relativePath2(root, abs);
|
|
4892
|
+
const raw = readFileSync15(abs, "utf8");
|
|
3123
4893
|
const id = slugFromPath(rel);
|
|
3124
4894
|
return {
|
|
3125
4895
|
schemaVersion: 1,
|
|
@@ -3134,10 +4904,10 @@ var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
|
3134
4904
|
});
|
|
3135
4905
|
};
|
|
3136
4906
|
var ingestCursorRules = (root) => {
|
|
3137
|
-
const dir =
|
|
4907
|
+
const dir = join13(root, ".cursor", "rules");
|
|
3138
4908
|
return ingestMarkdownDir(root, dir, "cursor", 0.6);
|
|
3139
4909
|
};
|
|
3140
|
-
var ingestAgentMemory = (root) => ingestMarkdownDir(root,
|
|
4910
|
+
var ingestAgentMemory = (root) => ingestMarkdownDir(root, join13(root, ".agent-memory"), "agent-memory", 0.7);
|
|
3141
4911
|
var ingestMemoryCandidates = (root) => [
|
|
3142
4912
|
...ingestAgentMemory(root),
|
|
3143
4913
|
...ingestCursorRules(root)
|
|
@@ -3404,25 +5174,70 @@ var MCP_TOOLS = [
|
|
|
3404
5174
|
description: "Return the static Doc Bridge curator and delegate topology.",
|
|
3405
5175
|
annotations: { readOnlyHint: true },
|
|
3406
5176
|
inputSchema: { type: "object", properties: {} }
|
|
5177
|
+
},
|
|
5178
|
+
{
|
|
5179
|
+
name: "docbridge.snapshot",
|
|
5180
|
+
title: "Read the latest discovery snapshot",
|
|
5181
|
+
description: "Read the bounded canonical repository snapshot from the latest workflow run.",
|
|
5182
|
+
annotations: { readOnlyHint: true },
|
|
5183
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5184
|
+
},
|
|
5185
|
+
{
|
|
5186
|
+
name: "docbridge.report",
|
|
5187
|
+
title: "Read the latest reconciliation report",
|
|
5188
|
+
description: "Read the canonical reconciliation report from the latest workflow run.",
|
|
5189
|
+
annotations: { readOnlyHint: true },
|
|
5190
|
+
inputSchema: { type: "object", properties: { runId: { type: "string" } } }
|
|
5191
|
+
},
|
|
5192
|
+
{
|
|
5193
|
+
name: "docbridge.diagnostics",
|
|
5194
|
+
title: "Read reconciliation diagnostics",
|
|
5195
|
+
description: "Read bounded diagnostics from the latest canonical reconciliation report.",
|
|
5196
|
+
annotations: { readOnlyHint: true },
|
|
5197
|
+
inputSchema: { type: "object", properties: { status: { type: "string" }, severity: { type: "string" } } }
|
|
5198
|
+
},
|
|
5199
|
+
{
|
|
5200
|
+
name: "docbridge.relations",
|
|
5201
|
+
title: "Read architecture relations",
|
|
5202
|
+
description: "Read bounded observed and declared relations from the latest canonical snapshot.",
|
|
5203
|
+
annotations: { readOnlyHint: true },
|
|
5204
|
+
inputSchema: { type: "object", properties: { kind: { type: "string" }, limit: { type: "number" } } }
|
|
5205
|
+
},
|
|
5206
|
+
{
|
|
5207
|
+
name: "docbridge.run",
|
|
5208
|
+
title: "Read workflow state",
|
|
5209
|
+
description: "Read the latest resumable workflow state and artifact references.",
|
|
5210
|
+
annotations: { readOnlyHint: true },
|
|
5211
|
+
inputSchema: { type: "object", properties: {} }
|
|
5212
|
+
},
|
|
5213
|
+
{
|
|
5214
|
+
name: "docbridge.proposals",
|
|
5215
|
+
title: "Read or approve proposals",
|
|
5216
|
+
description: "Create, inspect, approve and apply deterministic proposals through the shared human-gated workflow.",
|
|
5217
|
+
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" } } }
|
|
3407
5218
|
}
|
|
3408
5219
|
];
|
|
3409
5220
|
var asRecord = (value) => value && typeof value === "object" ? value : {};
|
|
3410
|
-
var HandoffResolveArgsSchema =
|
|
3411
|
-
id:
|
|
3412
|
-
kind:
|
|
5221
|
+
var HandoffResolveArgsSchema = z8.object({
|
|
5222
|
+
id: z8.string().min(1),
|
|
5223
|
+
kind: z8.enum(["package", "ownership"]).optional()
|
|
3413
5224
|
});
|
|
3414
|
-
var DocSearchArgsSchema =
|
|
3415
|
-
term:
|
|
3416
|
-
limit:
|
|
5225
|
+
var DocSearchArgsSchema = z8.object({
|
|
5226
|
+
term: z8.string().min(1),
|
|
5227
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
3417
5228
|
});
|
|
3418
|
-
var RetrieverQueryArgsSchema =
|
|
3419
|
-
query:
|
|
3420
|
-
limit:
|
|
5229
|
+
var RetrieverQueryArgsSchema = z8.object({
|
|
5230
|
+
query: z8.string().min(1),
|
|
5231
|
+
limit: z8.number().int().positive().max(100).optional()
|
|
3421
5232
|
});
|
|
3422
|
-
var DocGetArgsSchema =
|
|
3423
|
-
id:
|
|
3424
|
-
path:
|
|
5233
|
+
var DocGetArgsSchema = z8.object({
|
|
5234
|
+
id: z8.string().min(1).optional(),
|
|
5235
|
+
path: z8.string().min(1).optional()
|
|
3425
5236
|
}).refine((args) => args.id || args.path, "doc.get requires id or path");
|
|
5237
|
+
var WorkflowRunArgsSchema = z8.object({ runId: z8.string().min(1).optional() });
|
|
5238
|
+
var DiagnosticsArgsSchema = z8.object({ status: z8.string().min(1).optional(), severity: z8.string().min(1).optional() });
|
|
5239
|
+
var RelationsArgsSchema = z8.object({ kind: z8.string().min(1).optional(), limit: z8.number().int().positive().max(500).optional() });
|
|
5240
|
+
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() });
|
|
3426
5241
|
var parseToolArgs = (tool, schema, value) => {
|
|
3427
5242
|
try {
|
|
3428
5243
|
return schema.parse(value);
|
|
@@ -3453,15 +5268,54 @@ var findDocPath = (index, args) => {
|
|
|
3453
5268
|
return doc.path;
|
|
3454
5269
|
};
|
|
3455
5270
|
var resolveDocPath = (root, relPath) => {
|
|
3456
|
-
const rootAbs =
|
|
3457
|
-
const unresolved =
|
|
3458
|
-
const unresolvedRel =
|
|
5271
|
+
const rootAbs = realpathSync9.native(root);
|
|
5272
|
+
const unresolved = resolve13(rootAbs, relPath);
|
|
5273
|
+
const unresolvedRel = relative9(rootAbs, unresolved);
|
|
3459
5274
|
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
3460
|
-
const abs =
|
|
3461
|
-
const rel =
|
|
5275
|
+
const abs = realpathSync9.native(unresolved);
|
|
5276
|
+
const rel = relative9(rootAbs, abs);
|
|
3462
5277
|
if (rel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
3463
5278
|
return abs;
|
|
3464
5279
|
};
|
|
5280
|
+
var workflowStateDir = (ctx) => resolve13(ctx.root, ctx.config.workflow?.stateDir ?? ".doc-bridge/workflow");
|
|
5281
|
+
var workflowRun = (ctx) => loadWorkflowManifest(workflowStateDir(ctx));
|
|
5282
|
+
var ensureLatestRun = (ctx, runId2) => {
|
|
5283
|
+
const run2 = workflowRun(ctx);
|
|
5284
|
+
if (runId2 && run2.runId !== runId2) throw new Error(`Unknown workflow run "${runId2}"`);
|
|
5285
|
+
if (run2.state === "stale" || run2.state === "failed") throw new Error(`Workflow run is ${run2.state}; resume or create a valid run before reading artifacts.`);
|
|
5286
|
+
return run2;
|
|
5287
|
+
};
|
|
5288
|
+
var workflowSnapshot = (ctx, runId2) => {
|
|
5289
|
+
ensureLatestRun(ctx, runId2);
|
|
5290
|
+
return parseDiscoverySnapshot(loadWorkflowStepOutput(workflowStateDir(ctx), "normalize"));
|
|
5291
|
+
};
|
|
5292
|
+
var workflowReport = (ctx, runId2) => {
|
|
5293
|
+
ensureLatestRun(ctx, runId2);
|
|
5294
|
+
return parseReconciliationReport(loadWorkflowStepOutput(workflowStateDir(ctx), "reconcile"));
|
|
5295
|
+
};
|
|
5296
|
+
var proposalPath = (ctx) => join14(ctx.root, ".doc-bridge", "proposal.json");
|
|
5297
|
+
var readSavedProposal = (ctx, input) => {
|
|
5298
|
+
if (input !== void 0) return FixProposalV1Schema.parse(input);
|
|
5299
|
+
try {
|
|
5300
|
+
return FixProposalV1Schema.parse(JSON.parse(readFileSync16(proposalPath(ctx), "utf8")));
|
|
5301
|
+
} catch {
|
|
5302
|
+
throw new Error(`No saved fix proposal at ${proposalPath(ctx)}.`);
|
|
5303
|
+
}
|
|
5304
|
+
};
|
|
5305
|
+
var saveProposal = (ctx, proposal) => {
|
|
5306
|
+
mkdirSync4(join14(ctx.root, ".doc-bridge"), { recursive: true });
|
|
5307
|
+
writeFileSync5(proposalPath(ctx), `${JSON.stringify(proposal, null, 2)}
|
|
5308
|
+
`, "utf8");
|
|
5309
|
+
};
|
|
5310
|
+
var enabledMcpTools = (ctx) => {
|
|
5311
|
+
const configured = ctx.config.surfaces?.mcp?.tools;
|
|
5312
|
+
if (!configured || configured.length === MCP_TOOLS.length && MCP_TOOLS.every((tool) => configured.includes(tool.name))) return MCP_TOOLS;
|
|
5313
|
+
return MCP_TOOLS.filter((tool) => configured.includes(tool.name));
|
|
5314
|
+
};
|
|
5315
|
+
var assertMcpToolEnabled = (ctx, name) => {
|
|
5316
|
+
if (!MCP_TOOLS.some((tool) => tool.name === name)) throw new Error(`Unknown tool "${name}"`);
|
|
5317
|
+
if (!enabledMcpTools(ctx).some((tool) => tool.name === name)) throw new Error(`MCP tool "${name}" is disabled by configuration.`);
|
|
5318
|
+
};
|
|
3465
5319
|
var handleMcpRequest = (ctx, request) => {
|
|
3466
5320
|
if (request.method === "initialize") {
|
|
3467
5321
|
return {
|
|
@@ -3470,11 +5324,13 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
3470
5324
|
serverInfo: { name: "ak-docs", version: PACKAGE_VERSION }
|
|
3471
5325
|
};
|
|
3472
5326
|
}
|
|
3473
|
-
if (request.method === "tools/list") return { tools:
|
|
5327
|
+
if (request.method === "tools/list") return { tools: enabledMcpTools(ctx) };
|
|
3474
5328
|
if (request.method === "tools/call") {
|
|
3475
5329
|
const params = asRecord(request.params);
|
|
3476
5330
|
const name = params.name;
|
|
3477
5331
|
const args = asRecord(params.arguments);
|
|
5332
|
+
if (typeof name !== "string") throw new Error("MCP tools/call requires a tool name.");
|
|
5333
|
+
assertMcpToolEnabled(ctx, name);
|
|
3478
5334
|
const index = () => ctx.loadIndex?.() ?? loadDocBridgeIndex(ctx.root, ctx.config);
|
|
3479
5335
|
if (name === "handoff.resolve") {
|
|
3480
5336
|
const parsed = parseToolArgs("handoff.resolve", HandoffResolveArgsSchema, args);
|
|
@@ -3492,7 +5348,7 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
3492
5348
|
}
|
|
3493
5349
|
if (name === "doc.get") {
|
|
3494
5350
|
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
3495
|
-
return textResult(
|
|
5351
|
+
return textResult(readFileSync16(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
3496
5352
|
}
|
|
3497
5353
|
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
3498
5354
|
if (name === "retriever.query") {
|
|
@@ -3514,6 +5370,81 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
3514
5370
|
mergePolicy: { autoMerge: false, requiresHuman: true }
|
|
3515
5371
|
});
|
|
3516
5372
|
}
|
|
5373
|
+
if (name === "docbridge.snapshot") {
|
|
5374
|
+
const parsed = parseToolArgs("docbridge.snapshot", WorkflowRunArgsSchema, args);
|
|
5375
|
+
return textResult(redactValue(workflowSnapshot(ctx, parsed.runId)));
|
|
5376
|
+
}
|
|
5377
|
+
if (name === "docbridge.report") {
|
|
5378
|
+
const parsed = parseToolArgs("docbridge.report", WorkflowRunArgsSchema, args);
|
|
5379
|
+
return textResult(redactValue(workflowReport(ctx, parsed.runId)));
|
|
5380
|
+
}
|
|
5381
|
+
if (name === "docbridge.diagnostics") {
|
|
5382
|
+
const parsed = parseToolArgs("docbridge.diagnostics", DiagnosticsArgsSchema, args);
|
|
5383
|
+
const diagnostics = workflowReport(ctx).diagnostics.filter(
|
|
5384
|
+
(diagnostic2) => (!parsed.status || diagnostic2.status === parsed.status) && (!parsed.severity || diagnostic2.severity === parsed.severity)
|
|
5385
|
+
);
|
|
5386
|
+
return textResult(redactValue({ reportHash: workflowReport(ctx).contentHash, diagnostics }));
|
|
5387
|
+
}
|
|
5388
|
+
if (name === "docbridge.relations") {
|
|
5389
|
+
const parsed = parseToolArgs("docbridge.relations", RelationsArgsSchema, args);
|
|
5390
|
+
const snapshot = workflowSnapshot(ctx);
|
|
5391
|
+
return textResult({ snapshotHash: snapshot.contentHash, relations: snapshot.relations.filter((relation) => !parsed.kind || relation.kind === parsed.kind).slice(0, parsed.limit ?? 100) });
|
|
5392
|
+
}
|
|
5393
|
+
if (name === "docbridge.run") {
|
|
5394
|
+
parseToolArgs("docbridge.run", z8.object({}), args);
|
|
5395
|
+
return textResult(workflowRun(ctx));
|
|
5396
|
+
}
|
|
5397
|
+
if (name === "docbridge.proposals") {
|
|
5398
|
+
const parsed = parseToolArgs("docbridge.proposals", ProposalsArgsSchema, args);
|
|
5399
|
+
const run2 = (() => {
|
|
5400
|
+
try {
|
|
5401
|
+
return workflowRun(ctx);
|
|
5402
|
+
} catch {
|
|
5403
|
+
return void 0;
|
|
5404
|
+
}
|
|
5405
|
+
})();
|
|
5406
|
+
if (!parsed.action || parsed.action === "list") {
|
|
5407
|
+
let proposal2;
|
|
5408
|
+
try {
|
|
5409
|
+
proposal2 = readSavedProposal(ctx, void 0);
|
|
5410
|
+
} catch {
|
|
5411
|
+
proposal2 = void 0;
|
|
5412
|
+
}
|
|
5413
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposals: proposal2 ? [proposal2] : [] }));
|
|
5414
|
+
}
|
|
5415
|
+
if (parsed.action === "suggest") {
|
|
5416
|
+
return loadRegistryAgentRunner(ctx.root, ctx.config).then(async (runner) => {
|
|
5417
|
+
const snapshot = workflowSnapshot(ctx);
|
|
5418
|
+
const report = workflowReport(ctx);
|
|
5419
|
+
const adapter = createRegistryAgentAdapter(ctx.root, ctx.config, runner);
|
|
5420
|
+
const proposal2 = await adapter.run(snapshot, report);
|
|
5421
|
+
const savedPath = persistRegistryAgentProposal(workflowStateDir(ctx), proposal2);
|
|
5422
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2, proposalPath: savedPath }));
|
|
5423
|
+
});
|
|
5424
|
+
}
|
|
5425
|
+
const discovered = discoverRepository({ root: ctx.root, config: ctx.config });
|
|
5426
|
+
const options = { baseRevision: discovered.sourceRevision, configurationHash: sha256NormalizedV1(ctx.config), ...ctx.config.project?.name ? { projectName: ctx.config.project.name } : {} };
|
|
5427
|
+
if (parsed.action === "propose-links") {
|
|
5428
|
+
const proposal2 = createMarkdownLinkFixProposal(ctx.root, options);
|
|
5429
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
5430
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
5431
|
+
}
|
|
5432
|
+
if (parsed.action === "propose-normalize") {
|
|
5433
|
+
if (!parsed.artifactPath) throw new Error("docbridge.proposals propose-normalize requires artifactPath");
|
|
5434
|
+
const proposal2 = createArtifactNormalizationProposal(ctx.root, parsed.artifactPath, options);
|
|
5435
|
+
if (proposal2) saveProposal(ctx, proposal2);
|
|
5436
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 ?? null }));
|
|
5437
|
+
}
|
|
5438
|
+
if (parsed.action === "approve") {
|
|
5439
|
+
const proposal2 = approveFixProposal(readSavedProposal(ctx, parsed.proposal), parsed.approvedBy ?? "human");
|
|
5440
|
+
if (parsed.proposalHash && proposal2.approval?.proposalHash !== parsed.proposalHash) throw new Error("proposalHash does not match the saved proposal");
|
|
5441
|
+
saveProposal(ctx, proposal2);
|
|
5442
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal: proposal2 }));
|
|
5443
|
+
}
|
|
5444
|
+
const proposal = applyFixProposal(ctx.root, readSavedProposal(ctx, parsed.proposal), { currentRevision: discovered.sourceRevision });
|
|
5445
|
+
saveProposal(ctx, proposal);
|
|
5446
|
+
return textResult(redactValue({ ...run2 ? { runId: run2.runId } : {}, proposal }));
|
|
5447
|
+
}
|
|
3517
5448
|
throw new Error(`Unknown tool "${String(name)}"`);
|
|
3518
5449
|
}
|
|
3519
5450
|
if (request.method?.startsWith("notifications/")) return void 0;
|
|
@@ -3526,16 +5457,16 @@ var writeFrame = (payload, framing) => {
|
|
|
3526
5457
|
\r
|
|
3527
5458
|
${body}`);
|
|
3528
5459
|
};
|
|
3529
|
-
var
|
|
5460
|
+
var respondMcpRequest = async (ctx, request, framing) => {
|
|
3530
5461
|
if (request.id === void 0) {
|
|
3531
5462
|
try {
|
|
3532
|
-
handleMcpRequest(ctx, request);
|
|
5463
|
+
await handleMcpRequest(ctx, request);
|
|
3533
5464
|
} catch {
|
|
3534
5465
|
}
|
|
3535
5466
|
return;
|
|
3536
5467
|
}
|
|
3537
5468
|
try {
|
|
3538
|
-
const result = handleMcpRequest(ctx, request);
|
|
5469
|
+
const result = await handleMcpRequest(ctx, request);
|
|
3539
5470
|
writeFrame({ jsonrpc: "2.0", id: request.id, result: result ?? {} }, framing);
|
|
3540
5471
|
} catch (error) {
|
|
3541
5472
|
writeFrame({
|
|
@@ -3547,6 +5478,10 @@ var respond = (ctx, request, framing) => {
|
|
|
3547
5478
|
};
|
|
3548
5479
|
var startMcpStdioServer = (ctx) => {
|
|
3549
5480
|
let buffer = Buffer.alloc(0);
|
|
5481
|
+
let responseChain = Promise.resolve();
|
|
5482
|
+
const enqueueResponse = (request, framing) => {
|
|
5483
|
+
responseChain = responseChain.then(() => respondMcpRequest(ctx, request, framing));
|
|
5484
|
+
};
|
|
3550
5485
|
process.stdin.on("data", (chunk) => {
|
|
3551
5486
|
buffer = Buffer.concat([buffer, chunk]);
|
|
3552
5487
|
while (true) {
|
|
@@ -3565,7 +5500,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
3565
5500
|
if (buffer.length < bodyEnd) return;
|
|
3566
5501
|
const raw2 = buffer.subarray(bodyStart, bodyEnd).toString("utf8");
|
|
3567
5502
|
buffer = buffer.subarray(bodyEnd);
|
|
3568
|
-
|
|
5503
|
+
enqueueResponse(JSON.parse(raw2), "content-length");
|
|
3569
5504
|
continue;
|
|
3570
5505
|
}
|
|
3571
5506
|
const lineEnd = buffer.indexOf("\n");
|
|
@@ -3574,7 +5509,7 @@ var startMcpStdioServer = (ctx) => {
|
|
|
3574
5509
|
buffer = buffer.subarray(lineEnd + 1);
|
|
3575
5510
|
if (!raw) continue;
|
|
3576
5511
|
try {
|
|
3577
|
-
|
|
5512
|
+
enqueueResponse(JSON.parse(raw), "json-line");
|
|
3578
5513
|
} catch {
|
|
3579
5514
|
writeFrame({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }, "json-line");
|
|
3580
5515
|
}
|
|
@@ -3584,37 +5519,37 @@ var startMcpStdioServer = (ctx) => {
|
|
|
3584
5519
|
};
|
|
3585
5520
|
|
|
3586
5521
|
// src/mcp/install.ts
|
|
3587
|
-
import { existsSync as
|
|
5522
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync17, writeFileSync as writeFileSync6 } from "fs";
|
|
3588
5523
|
import { homedir } from "os";
|
|
3589
|
-
import { dirname as
|
|
5524
|
+
import { dirname as dirname7, join as join15, resolve as resolve14 } from "path";
|
|
3590
5525
|
var SERVER_NAME = "ak-docs";
|
|
3591
5526
|
var mcpServerEntry = (root) => ({
|
|
3592
5527
|
command: "npx",
|
|
3593
5528
|
args: ["ak-docs", "mcp"],
|
|
3594
5529
|
cwd: root
|
|
3595
5530
|
});
|
|
3596
|
-
var
|
|
3597
|
-
if (!
|
|
5531
|
+
var readJson2 = (path) => {
|
|
5532
|
+
if (!existsSync16(path)) return {};
|
|
3598
5533
|
try {
|
|
3599
|
-
const parsed = JSON.parse(
|
|
5534
|
+
const parsed = JSON.parse(readFileSync17(path, "utf8"));
|
|
3600
5535
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
3601
5536
|
} catch {
|
|
3602
5537
|
return {};
|
|
3603
5538
|
}
|
|
3604
5539
|
};
|
|
3605
5540
|
var writeJson = (path, value) => {
|
|
3606
|
-
|
|
3607
|
-
|
|
5541
|
+
mkdirSync5(dirname7(path), { recursive: true });
|
|
5542
|
+
writeFileSync6(path, `${JSON.stringify(value, null, 2)}
|
|
3608
5543
|
`, "utf8");
|
|
3609
5544
|
};
|
|
3610
5545
|
var resolveTargetPath = (target, root) => {
|
|
3611
|
-
if (target === "cursor") return
|
|
3612
|
-
return
|
|
5546
|
+
if (target === "cursor") return resolve14(root, ".cursor", "mcp.json");
|
|
5547
|
+
return join15(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
3613
5548
|
};
|
|
3614
5549
|
var installMcpConfig = (root, target) => {
|
|
3615
5550
|
const configPath = resolveTargetPath(target, root);
|
|
3616
|
-
const created = !
|
|
3617
|
-
const existing =
|
|
5551
|
+
const created = !existsSync16(configPath);
|
|
5552
|
+
const existing = readJson2(configPath);
|
|
3618
5553
|
const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
|
|
3619
5554
|
servers[SERVER_NAME] = mcpServerEntry(root);
|
|
3620
5555
|
writeJson(configPath, { ...existing, mcpServers: servers });
|
|
@@ -3879,23 +5814,23 @@ var formatDoctorText = (report) => {
|
|
|
3879
5814
|
};
|
|
3880
5815
|
|
|
3881
5816
|
// src/index-builder/watch-index.ts
|
|
3882
|
-
import { existsSync as
|
|
3883
|
-
import { dirname as
|
|
5817
|
+
import { existsSync as existsSync17, watch } from "fs";
|
|
5818
|
+
import { dirname as dirname8, resolve as resolve15 } from "path";
|
|
3884
5819
|
var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
|
|
3885
5820
|
var NX_MANIFEST_PATTERN = /(^|[/\\])(project|package)\.json$/i;
|
|
3886
5821
|
var collectWatchRoots = (root, config, configPath) => {
|
|
3887
5822
|
const roots = /* @__PURE__ */ new Set();
|
|
3888
|
-
roots.add(
|
|
5823
|
+
roots.add(resolve15(root, config.corpus.agent.root));
|
|
3889
5824
|
const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
|
|
3890
5825
|
for (const source of humanSources) {
|
|
3891
5826
|
const humanOpts = source.options ?? {};
|
|
3892
5827
|
for (const key of ["contentDir", "docsDir", "root", "srcDir"]) {
|
|
3893
5828
|
const value = humanOpts[key];
|
|
3894
|
-
if (typeof value === "string" && value.length) roots.add(
|
|
5829
|
+
if (typeof value === "string" && value.length) roots.add(resolve15(root, value));
|
|
3895
5830
|
}
|
|
3896
5831
|
}
|
|
3897
|
-
if (configPath) roots.add(
|
|
3898
|
-
return [...roots].filter((dir) =>
|
|
5832
|
+
if (configPath) roots.add(dirname8(resolve15(configPath)));
|
|
5833
|
+
return [...roots].filter((dir) => existsSync17(dir));
|
|
3899
5834
|
};
|
|
3900
5835
|
var watchDocBridgeIndex = (opts) => {
|
|
3901
5836
|
const debounceMs = opts.debounceMs ?? 350;
|
|
@@ -3936,15 +5871,15 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
3936
5871
|
rebuild();
|
|
3937
5872
|
});
|
|
3938
5873
|
}
|
|
3939
|
-
const nxRoot =
|
|
3940
|
-
if (opts.config.routing?.plugin === "nx" &&
|
|
5874
|
+
const nxRoot = resolve15(opts.root);
|
|
5875
|
+
if (opts.config.routing?.plugin === "nx" && existsSync17(nxRoot)) {
|
|
3941
5876
|
watch(nxRoot, { recursive: true }, (_event, filename) => {
|
|
3942
5877
|
if (!filename || !NX_MANIFEST_PATTERN.test(filename)) return;
|
|
3943
5878
|
rebuild();
|
|
3944
5879
|
});
|
|
3945
5880
|
}
|
|
3946
|
-
const configDir =
|
|
3947
|
-
if (
|
|
5881
|
+
const configDir = resolve15(opts.root);
|
|
5882
|
+
if (existsSync17(configDir)) {
|
|
3948
5883
|
watch(configDir, (_event, filename) => {
|
|
3949
5884
|
if (!filename || !/doc-bridge\.config/.test(filename)) return;
|
|
3950
5885
|
rebuild();
|
|
@@ -3962,9 +5897,9 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
3962
5897
|
};
|
|
3963
5898
|
|
|
3964
5899
|
// src/memory/github-pr.ts
|
|
3965
|
-
import { execFileSync, spawnSync } from "child_process";
|
|
3966
|
-
import { existsSync as
|
|
3967
|
-
import { join as
|
|
5900
|
+
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
5901
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
5902
|
+
import { join as join17 } from "path";
|
|
3968
5903
|
var run = (cmd, args, cwd) => {
|
|
3969
5904
|
const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
|
|
3970
5905
|
const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
@@ -3972,11 +5907,11 @@ var run = (cmd, args, cwd) => {
|
|
|
3972
5907
|
};
|
|
3973
5908
|
var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
|
|
3974
5909
|
var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
3975
|
-
var defaultPromotionDraftPath = (root) =>
|
|
5910
|
+
var defaultPromotionDraftPath = (root) => join17(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
|
|
3976
5911
|
var writePromotionDraft = (root, draft, path) => {
|
|
3977
5912
|
const draftPath = path ?? defaultPromotionDraftPath(root);
|
|
3978
|
-
|
|
3979
|
-
|
|
5913
|
+
mkdirSync6(join17(root, ".doc-bridge", "drafts"), { recursive: true });
|
|
5914
|
+
writeFileSync7(draftPath, `${draft.body}
|
|
3980
5915
|
`, "utf8");
|
|
3981
5916
|
return draftPath;
|
|
3982
5917
|
};
|
|
@@ -4011,7 +5946,7 @@ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
|
|
|
4011
5946
|
message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
|
|
4012
5947
|
};
|
|
4013
5948
|
}
|
|
4014
|
-
if (!
|
|
5949
|
+
if (!existsSync18(join17(root, ".git"))) {
|
|
4015
5950
|
return {
|
|
4016
5951
|
ok: false,
|
|
4017
5952
|
dryRun: false,
|
|
@@ -4089,7 +6024,7 @@ ${auth.out}`
|
|
|
4089
6024
|
];
|
|
4090
6025
|
let prUrl = "";
|
|
4091
6026
|
try {
|
|
4092
|
-
prUrl =
|
|
6027
|
+
prUrl = execFileSync2("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
|
|
4093
6028
|
} catch (error) {
|
|
4094
6029
|
const message = error instanceof Error ? error.message : String(error);
|
|
4095
6030
|
return {
|
|
@@ -4114,8 +6049,8 @@ ${auth.out}`
|
|
|
4114
6049
|
};
|
|
4115
6050
|
|
|
4116
6051
|
// src/federation/llms.ts
|
|
4117
|
-
import { existsSync as
|
|
4118
|
-
import { resolve as
|
|
6052
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18 } from "fs";
|
|
6053
|
+
import { resolve as resolve16 } from "path";
|
|
4119
6054
|
var tokenize2 = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 2);
|
|
4120
6055
|
var scoreText = (query, text) => {
|
|
4121
6056
|
const hay = text.toLowerCase();
|
|
@@ -4130,9 +6065,9 @@ var defaultFetchText = async (url) => {
|
|
|
4130
6065
|
var sourceText = async (root, source, fetchText) => {
|
|
4131
6066
|
try {
|
|
4132
6067
|
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
4133
|
-
const path =
|
|
4134
|
-
if (!
|
|
4135
|
-
return
|
|
6068
|
+
const path = resolve16(root, source);
|
|
6069
|
+
if (!existsSync19(path)) return null;
|
|
6070
|
+
return readFileSync18(path, "utf8");
|
|
4136
6071
|
} catch {
|
|
4137
6072
|
return null;
|
|
4138
6073
|
}
|
|
@@ -4217,11 +6152,11 @@ var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
|
4217
6152
|
};
|
|
4218
6153
|
|
|
4219
6154
|
// src/intelligence/rag.ts
|
|
4220
|
-
import { readFileSync as
|
|
4221
|
-
import { join as
|
|
6155
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
6156
|
+
import { join as join19 } from "path";
|
|
4222
6157
|
|
|
4223
6158
|
// src/intelligence/adapter.ts
|
|
4224
|
-
import { join as
|
|
6159
|
+
import { join as join18 } from "path";
|
|
4225
6160
|
|
|
4226
6161
|
// src/intelligence/peers.ts
|
|
4227
6162
|
var PeerMissingError = class extends Error {
|
|
@@ -4348,7 +6283,7 @@ var resolveIntelligenceRuntime = async (config) => {
|
|
|
4348
6283
|
}
|
|
4349
6284
|
return { adapter, embed, provider, ...model ? { model } : {} };
|
|
4350
6285
|
};
|
|
4351
|
-
var defaultVectorStorePath = (root) =>
|
|
6286
|
+
var defaultVectorStorePath = (root) => join18(root, ".doc-bridge", "vectors");
|
|
4352
6287
|
|
|
4353
6288
|
// src/intelligence/rag.ts
|
|
4354
6289
|
var loadDocuments = (root, index, sources) => {
|
|
@@ -4356,10 +6291,10 @@ var loadDocuments = (root, index, sources) => {
|
|
|
4356
6291
|
const docs = [];
|
|
4357
6292
|
if (includeAgent) {
|
|
4358
6293
|
for (const entry of index.knowledge) {
|
|
4359
|
-
const abs =
|
|
6294
|
+
const abs = join19(root, entry.path);
|
|
4360
6295
|
let content = "";
|
|
4361
6296
|
try {
|
|
4362
|
-
content =
|
|
6297
|
+
content = readFileSync19(abs, "utf8");
|
|
4363
6298
|
} catch {
|
|
4364
6299
|
content = [entry.title, entry.description].filter(Boolean).join("\n\n");
|
|
4365
6300
|
}
|
|
@@ -4377,7 +6312,7 @@ var createDocBridgeRag = async (root, config, index) => {
|
|
|
4377
6312
|
const { embed } = await resolveIntelligenceRuntime(config);
|
|
4378
6313
|
const ragMod = await importPeer("@agentskit/rag");
|
|
4379
6314
|
const memoryMod = await importPeer("@agentskit/memory");
|
|
4380
|
-
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ?
|
|
6315
|
+
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join19(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
|
|
4381
6316
|
const store = memoryMod.fileVectorMemory({ path: storePath });
|
|
4382
6317
|
const rag = ragMod.createRAG({
|
|
4383
6318
|
embed,
|
|
@@ -4631,15 +6566,22 @@ var docBridgePatternPayload = () => ({
|
|
|
4631
6566
|
body: docBridgePatternMarkdown()
|
|
4632
6567
|
});
|
|
4633
6568
|
export {
|
|
6569
|
+
AffectedFileSchema,
|
|
4634
6570
|
AgentHandoffLegacySchema,
|
|
4635
6571
|
AgentHandoffV1JsonSchema,
|
|
4636
6572
|
AgentHandoffV1Schema,
|
|
6573
|
+
AgentProposalV1Schema,
|
|
4637
6574
|
AgentSearchV1Schema,
|
|
4638
6575
|
ConfigNotFoundError,
|
|
6576
|
+
CoverageSchema,
|
|
6577
|
+
DEFAULT_REGISTRY_AGENT_ID,
|
|
6578
|
+
DEFAULT_SAFETY_EXCLUDES,
|
|
4639
6579
|
DOCUMENTATION_STANDARD_V1_ID,
|
|
4640
6580
|
DOCUMENTATION_STANDARD_V1_STATUS,
|
|
4641
6581
|
DOC_BRIDGE_PATTERN_ID,
|
|
4642
6582
|
DOC_BRIDGE_PATTERN_META,
|
|
6583
|
+
DiagnosticSeveritySchema,
|
|
6584
|
+
DiscoverySnapshotV1Schema,
|
|
4643
6585
|
DocBridgeConfigV1Schema,
|
|
4644
6586
|
DocBridgeIndexV1JsonSchema,
|
|
4645
6587
|
DocBridgeIndexV1Schema,
|
|
@@ -4647,11 +6589,20 @@ export {
|
|
|
4647
6589
|
DocumentationStandardRuleIdSchema,
|
|
4648
6590
|
DocumentationStandardV1ConfigSchema,
|
|
4649
6591
|
EcosystemContractEvidenceSchema,
|
|
6592
|
+
EntitySchema,
|
|
6593
|
+
EvidenceSchema,
|
|
6594
|
+
EvidenceSourceSchema,
|
|
6595
|
+
FindingStatusSchema,
|
|
6596
|
+
FixChangeSchema,
|
|
6597
|
+
FixProposalStatusSchema,
|
|
6598
|
+
FixProposalV1Schema,
|
|
4650
6599
|
HANDOFF_SCHEMA_VERSION,
|
|
4651
6600
|
HandoffBridgeSchema,
|
|
4652
6601
|
HandoffTargetTypeSchema,
|
|
4653
6602
|
INDEX_SCHEMA_VERSION,
|
|
4654
6603
|
IndexNotFoundError,
|
|
6604
|
+
KNOWLEDGE_CONTENT_HASH_ALGO,
|
|
6605
|
+
KNOWLEDGE_SCHEMA_VERSION,
|
|
4655
6606
|
KnowledgeEntrySchema,
|
|
4656
6607
|
MCP_TOOLS,
|
|
4657
6608
|
MEMORY_CANDIDATE_SCHEMA_VERSION,
|
|
@@ -4659,21 +6610,47 @@ export {
|
|
|
4659
6610
|
MemoryCandidateV1Schema,
|
|
4660
6611
|
PACKAGE_VERSION,
|
|
4661
6612
|
PeerMissingError,
|
|
6613
|
+
ProjectIdentitySchema,
|
|
6614
|
+
ProposalOriginSchema,
|
|
6615
|
+
ProvenanceSchema,
|
|
6616
|
+
ReconciliationReportV1Schema,
|
|
6617
|
+
RelationSchema,
|
|
6618
|
+
RepositorySafetyConfigSchema,
|
|
6619
|
+
RuleIdSchema,
|
|
6620
|
+
RuleSeveritySchema,
|
|
6621
|
+
RulesConfigSchema,
|
|
6622
|
+
WORKFLOW_STAGES,
|
|
6623
|
+
WorkflowConfigSchema,
|
|
6624
|
+
WorkflowRunV1Schema,
|
|
6625
|
+
WorkflowStateSchema,
|
|
6626
|
+
WorkflowStepSchema,
|
|
6627
|
+
WorkflowTransitionSchema,
|
|
4662
6628
|
applyConfigDefaults,
|
|
6629
|
+
applyDocumentationDeclarations,
|
|
6630
|
+
applyFixProposal,
|
|
6631
|
+
approveFixProposal,
|
|
4663
6632
|
buildDocBridgeIndex,
|
|
4664
6633
|
buildLookup,
|
|
6634
|
+
canonicalJsonV1,
|
|
4665
6635
|
chunksFromMarkdown,
|
|
4666
6636
|
classifyMemoryCandidates,
|
|
4667
6637
|
collectPackages,
|
|
6638
|
+
containedPath,
|
|
6639
|
+
contentHashForArtifactV1,
|
|
6640
|
+
createArtifactNormalizationProposal,
|
|
4668
6641
|
createDocBridgeRag,
|
|
4669
6642
|
createDocBridgeRetriever,
|
|
6643
|
+
createMarkdownLinkFixProposal,
|
|
6644
|
+
createRegistryAgentAdapter,
|
|
4670
6645
|
defaultPromotionDraftPath,
|
|
4671
6646
|
defineConfig,
|
|
4672
6647
|
discoverNxProjects,
|
|
6648
|
+
discoverRepository,
|
|
4673
6649
|
docBridgePatternMarkdown,
|
|
4674
6650
|
docBridgePatternPayload,
|
|
4675
6651
|
doctorBadgeMetrics,
|
|
4676
6652
|
draftMemoryPromotion,
|
|
6653
|
+
evaluateRules,
|
|
4677
6654
|
formatDoctorBadgeJson,
|
|
4678
6655
|
formatDoctorBadgeMarkdown,
|
|
4679
6656
|
formatDoctorText,
|
|
@@ -4690,19 +6667,37 @@ export {
|
|
|
4690
6667
|
loadConfig,
|
|
4691
6668
|
loadDocBridgeIndex,
|
|
4692
6669
|
loadFederatedChunks,
|
|
6670
|
+
loadRegistryAgentMetadata,
|
|
6671
|
+
loadRegistryAgentRunner,
|
|
6672
|
+
loadWorkflowManifest,
|
|
6673
|
+
loadWorkflowStepOutput,
|
|
4693
6674
|
mcpSnippet,
|
|
4694
6675
|
normalizeAgentHandoff,
|
|
4695
6676
|
parseAgentHandoff,
|
|
6677
|
+
parseAgentProposal,
|
|
4696
6678
|
parseAgentSearch,
|
|
6679
|
+
parseDiscoverySnapshot,
|
|
4697
6680
|
parseDocBridgeConfig,
|
|
4698
6681
|
parseDocBridgeIndex,
|
|
6682
|
+
parseDocumentationDeclarations,
|
|
6683
|
+
parseFixProposal,
|
|
4699
6684
|
parseLlmsTxtLinks,
|
|
4700
6685
|
parseMemoryCandidate,
|
|
6686
|
+
parseReconciliationReport,
|
|
6687
|
+
parseRuleId,
|
|
6688
|
+
parseRuleSeverity,
|
|
6689
|
+
parseWorkflowRun,
|
|
6690
|
+
persistRegistryAgentProposal,
|
|
4701
6691
|
projectRootFromConfigPath,
|
|
4702
6692
|
promoteMemoryToGithubPr,
|
|
6693
|
+
reconcileKnowledge,
|
|
6694
|
+
redactSecrets,
|
|
6695
|
+
redactValue,
|
|
6696
|
+
renderOfflineReport,
|
|
4703
6697
|
resolveGateIds,
|
|
4704
6698
|
resolveProjectRoot,
|
|
4705
6699
|
resolveRoot,
|
|
6700
|
+
respondMcpRequest,
|
|
4706
6701
|
retrieveDocBridgeChunks,
|
|
4707
6702
|
retrieveHybridChunks,
|
|
4708
6703
|
runChatOnce,
|
|
@@ -4711,7 +6706,9 @@ export {
|
|
|
4711
6706
|
runGate,
|
|
4712
6707
|
runGates,
|
|
4713
6708
|
runQuery,
|
|
6709
|
+
runWorkflow,
|
|
4714
6710
|
safeParseAgentHandoff,
|
|
6711
|
+
safeWalkFiles,
|
|
4715
6712
|
scanHumanDocRecords,
|
|
4716
6713
|
scanHumanDocs,
|
|
4717
6714
|
scanMemorySafety,
|