@yansirplus/cli 0.5.17
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/PUBLIC_API.md +22 -0
- package/README.md +34 -0
- package/dist/build/agent-authoring/config.d.ts +177 -0
- package/dist/build/agent-authoring/config.js +607 -0
- package/dist/build/agent-authoring/manifest-compiler.d.ts +159 -0
- package/dist/build/agent-authoring/manifest-compiler.js +737 -0
- package/dist/build/agent-authoring/shared.d.ts +10 -0
- package/dist/build/agent-authoring/shared.js +57 -0
- package/dist/build/agent-authoring/static-target.d.ts +59 -0
- package/dist/build/agent-authoring/static-target.js +1857 -0
- package/dist/build/agent-authoring.d.ts +9 -0
- package/dist/build/agent-authoring.js +5 -0
- package/dist/build/build-cli.d.ts +2 -0
- package/dist/build/build-cli.js +264 -0
- package/dist/check/algorithmic/architecture-checks.mjs +971 -0
- package/dist/check/algorithmic/client-boundary-checks.mjs +337 -0
- package/dist/check/algorithmic/convergence-smoke-checks.mjs +608 -0
- package/dist/check/algorithmic/distribution-checks.mjs +919 -0
- package/dist/check/algorithmic/owner-checks.mjs +647 -0
- package/dist/check/algorithmic/package-boundary-checks.mjs +985 -0
- package/dist/check/algorithmic/projection-boundary-checks.mjs +302 -0
- package/dist/check/algorithmic/repo-surface-checks.mjs +267 -0
- package/dist/check/algorithmic/runtime-structural-checks.mjs +264 -0
- package/dist/check/algorithmic/source-alias-checks.mjs +106 -0
- package/dist/check/algorithmic/static-target-checks.mjs +447 -0
- package/dist/check/algorithmic-checks.mjs +482 -0
- package/dist/check/check-coverage.mjs +231 -0
- package/dist/check/command-runner.mjs +22 -0
- package/dist/check/default-gate.mjs +51 -0
- package/dist/check/gate-selector.mjs +305 -0
- package/dist/check/manifest-rules.mjs +223 -0
- package/dist/check/package-graph.mjs +464 -0
- package/dist/generate/generate-agent-docs.mjs +435 -0
- package/dist/generate/generate-carrier-reference.mjs +514 -0
- package/dist/generate/generate-docs.mjs +345 -0
- package/dist/generate/generate-effect-skill-manifests.mjs +193 -0
- package/dist/generate/project-docs-site.mjs +190 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +25 -0
- package/dist/lib/agent-docs-model.mjs +888 -0
- package/dist/lib/boundary-rules.mjs +63 -0
- package/dist/lib/capability-routes.mjs +354 -0
- package/dist/lib/projection-sink.mjs +113 -0
- package/dist/lib/public-api-model.mjs +306 -0
- package/dist/main.mjs +233 -0
- package/dist/runner.mjs +127 -0
- package/package.json +32 -0
|
@@ -0,0 +1,888 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { sourceTsdocRecordsForPackage } from "./public-api-model.mjs";
|
|
5
|
+
|
|
6
|
+
const repoRoot = process.cwd();
|
|
7
|
+
const sourceRoots = ["packages"];
|
|
8
|
+
const writerNames = new Set(["append", "insertEvent", "logLedgerEvent", "commit", "commitEvents"]);
|
|
9
|
+
const runtimeProtocolOwnerId = "@agent-os/runtime-protocol";
|
|
10
|
+
const backendProtocolOwnerId = "@agent-os/backend-protocol";
|
|
11
|
+
|
|
12
|
+
const toRepoPath = (file) =>
|
|
13
|
+
(path.isAbsolute(file) ? path.relative(repoRoot, file) : file).split(path.sep).join("/");
|
|
14
|
+
|
|
15
|
+
const sourceFiles = (root) => {
|
|
16
|
+
const files = [];
|
|
17
|
+
const visit = (dir) => {
|
|
18
|
+
if (!fs.existsSync(dir)) return;
|
|
19
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
20
|
+
const file = path.join(dir, entry.name);
|
|
21
|
+
if (entry.isDirectory()) {
|
|
22
|
+
if (
|
|
23
|
+
entry.name !== "node_modules" &&
|
|
24
|
+
entry.name !== "dist" &&
|
|
25
|
+
entry.name !== ".wrangler" &&
|
|
26
|
+
entry.name !== ".turbo"
|
|
27
|
+
) {
|
|
28
|
+
visit(file);
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (
|
|
33
|
+
/\.(?:ts|tsx|mts|cts)$/.test(entry.name) &&
|
|
34
|
+
!entry.name.endsWith(".d.ts") &&
|
|
35
|
+
file.split(path.sep).includes("src")
|
|
36
|
+
) {
|
|
37
|
+
files.push(file);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
for (const sourceRoot of sourceRoots) visit(path.join(root, sourceRoot));
|
|
42
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const localConstants = (sourceFile) => {
|
|
46
|
+
const constants = new Map();
|
|
47
|
+
const record = (name, initializer) => {
|
|
48
|
+
if (initializer === undefined) return;
|
|
49
|
+
if (ts.isStringLiteralLike(initializer)) {
|
|
50
|
+
constants.set(name, initializer.text);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (ts.isNoSubstitutionTemplateLiteral(initializer)) {
|
|
54
|
+
constants.set(name, initializer.text);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
for (const statement of sourceFile.statements) {
|
|
59
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
60
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
61
|
+
if (ts.isIdentifier(declaration.name)) record(declaration.name.text, declaration.initializer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return constants;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const unwrapExpression = (node) => {
|
|
68
|
+
let current = node;
|
|
69
|
+
for (;;) {
|
|
70
|
+
if (
|
|
71
|
+
ts.isAsExpression(current) ||
|
|
72
|
+
ts.isSatisfiesExpression(current) ||
|
|
73
|
+
ts.isParenthesizedExpression(current)
|
|
74
|
+
) {
|
|
75
|
+
current = current.expression;
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
return current;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const sourcePackageRoot = (file) => {
|
|
83
|
+
const relative = toRepoPath(file);
|
|
84
|
+
const parts = relative.split("/");
|
|
85
|
+
if (parts[0] !== "packages" || parts.length < 3) return path.dirname(file);
|
|
86
|
+
if (parts[1].startsWith("@")) return path.join(repoRoot, parts[0], parts[1], parts[2]);
|
|
87
|
+
return path.join(repoRoot, parts[0], parts[1], parts[2]);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const literalValue = (node, constants) => {
|
|
91
|
+
node = unwrapExpression(node);
|
|
92
|
+
if (ts.isStringLiteralLike(node) || ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
|
|
93
|
+
if (ts.isIdentifier(node)) return constants.get(node.text);
|
|
94
|
+
if (ts.isTemplateExpression(node)) {
|
|
95
|
+
let out = node.head.text;
|
|
96
|
+
for (const span of node.templateSpans) {
|
|
97
|
+
const value = literalValue(span.expression, constants);
|
|
98
|
+
if (value === undefined) return undefined;
|
|
99
|
+
out += value + span.literal.text;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
return undefined;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const propertyNameText = (name, constants) => {
|
|
107
|
+
if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) {
|
|
108
|
+
return name.text;
|
|
109
|
+
}
|
|
110
|
+
if (ts.isComputedPropertyName(name)) return literalValue(name.expression, constants);
|
|
111
|
+
return undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const objectProperty = (object, name, constants) => {
|
|
115
|
+
for (const property of object.properties) {
|
|
116
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
117
|
+
if (propertyNameText(property.name, constants) === name) return property.initializer;
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const objectLiteral = (node) => {
|
|
123
|
+
if (node === undefined) return undefined;
|
|
124
|
+
const unwrapped = unwrapExpression(node);
|
|
125
|
+
return ts.isObjectLiteralExpression(unwrapped) ? unwrapped : undefined;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const collectObjectStringValues = (node, constants, out) => {
|
|
129
|
+
if (node === undefined) return;
|
|
130
|
+
node = unwrapExpression(node);
|
|
131
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
132
|
+
for (const property of node.properties) {
|
|
133
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
134
|
+
collectObjectStringValues(property.initializer, constants, out);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
139
|
+
for (const element of node.elements) collectObjectStringValues(element, constants, out);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const value = literalValue(node, constants);
|
|
143
|
+
if (value !== undefined) out.push(value);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const packageVocabulary = (parsed) => {
|
|
147
|
+
const byPackage = new Map();
|
|
148
|
+
for (const { file, sourceFile } of parsed) {
|
|
149
|
+
const packageRoot = sourcePackageRoot(file);
|
|
150
|
+
const values = byPackage.get(packageRoot) ?? [];
|
|
151
|
+
const constants = localConstants(sourceFile);
|
|
152
|
+
for (const statement of sourceFile.statements) {
|
|
153
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
154
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
155
|
+
collectObjectStringValues(declaration.initializer, constants, values);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
byPackage.set(packageRoot, values);
|
|
159
|
+
}
|
|
160
|
+
return byPackage;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const arrayLiteralStrings = (node, constants) => {
|
|
164
|
+
if (!node || !ts.isArrayLiteralExpression(node)) return [];
|
|
165
|
+
return node.elements.flatMap((element) => {
|
|
166
|
+
const value = literalValue(element, constants);
|
|
167
|
+
return value === undefined ? [] : [value];
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const variableArrayStrings = (sourceFile, name) => {
|
|
172
|
+
const constants = localConstants(sourceFile);
|
|
173
|
+
for (const statement of sourceFile.statements) {
|
|
174
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
175
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
176
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name) continue;
|
|
177
|
+
return arrayLiteralStrings(unwrapExpression(declaration.initializer), constants);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return [];
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const variableNamespaceDeclarations = (sourceFile, name) => {
|
|
184
|
+
const constants = localConstants(sourceFile);
|
|
185
|
+
for (const statement of sourceFile.statements) {
|
|
186
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
187
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
188
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== name) continue;
|
|
189
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
190
|
+
if (!initializer || !ts.isArrayLiteralExpression(initializer)) return [];
|
|
191
|
+
return initializer.elements.flatMap((element) => {
|
|
192
|
+
const namespace = objectLiteral(element);
|
|
193
|
+
if (namespace === undefined) return [];
|
|
194
|
+
const ownerId = literalValue(objectProperty(namespace, "ownerId", constants), constants);
|
|
195
|
+
const prefixesNode = objectProperty(namespace, "kindPrefixes", constants);
|
|
196
|
+
const prefixes = arrayLiteralStrings(
|
|
197
|
+
prefixesNode === undefined ? undefined : unwrapExpression(prefixesNode),
|
|
198
|
+
constants,
|
|
199
|
+
);
|
|
200
|
+
return ownerId === undefined || prefixes.length === 0 ? [] : [{ owner: ownerId, prefixes }];
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return [];
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const ownedProtocolEvents = (sourceFile, prefixes) => {
|
|
208
|
+
const values = [];
|
|
209
|
+
const constants = localConstants(sourceFile);
|
|
210
|
+
for (const statement of sourceFile.statements) {
|
|
211
|
+
if (!ts.isVariableStatement(statement)) continue;
|
|
212
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
213
|
+
collectObjectStringValues(declaration.initializer, constants, values);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return values.filter((value) => prefixes.some((prefix) => value.startsWith(prefix)));
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const carrierEventKinds = (eventsNode, prefix, constants) => {
|
|
220
|
+
const events = objectLiteral(eventsNode);
|
|
221
|
+
if (!events) return [];
|
|
222
|
+
const kinds = [];
|
|
223
|
+
for (const property of events.properties) {
|
|
224
|
+
if (!ts.isPropertyAssignment(property)) continue;
|
|
225
|
+
const call = property.initializer;
|
|
226
|
+
if (!ts.isCallExpression(call)) continue;
|
|
227
|
+
const spec = objectLiteral(call.arguments[0]);
|
|
228
|
+
if (!spec) continue;
|
|
229
|
+
const kindNode = objectProperty(spec, "kind", constants);
|
|
230
|
+
const suffix = kindNode === undefined ? undefined : literalValue(kindNode, constants);
|
|
231
|
+
if (suffix !== undefined) kinds.push(`${prefix}${suffix}`);
|
|
232
|
+
}
|
|
233
|
+
return kinds;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const boundaryEventKinds = (eventsNode, constants) => {
|
|
237
|
+
const events = objectLiteral(eventsNode);
|
|
238
|
+
if (!events) return [];
|
|
239
|
+
return events.properties.flatMap((property) => {
|
|
240
|
+
if (!ts.isPropertyAssignment(property)) return [];
|
|
241
|
+
const key = propertyNameText(property.name, constants);
|
|
242
|
+
return key === undefined ? [] : [key];
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const collectDeclarations = (sourceFile, filePath, vocabularyByPackage) => {
|
|
247
|
+
const constants = localConstants(sourceFile);
|
|
248
|
+
const declarations = [];
|
|
249
|
+
|
|
250
|
+
const visit = (node) => {
|
|
251
|
+
if (ts.isReturnStatement(node)) {
|
|
252
|
+
const spec = objectLiteral(node.expression);
|
|
253
|
+
if (spec) {
|
|
254
|
+
const ownerIdNode = objectProperty(spec, "ownerId", constants);
|
|
255
|
+
const prefixesNode = objectProperty(spec, "kindPrefixes", constants);
|
|
256
|
+
const versionNode = objectProperty(spec, "version", constants);
|
|
257
|
+
const ownerId =
|
|
258
|
+
ownerIdNode === undefined ? undefined : literalValue(ownerIdNode, constants);
|
|
259
|
+
const prefixes = arrayLiteralStrings(prefixesNode, constants);
|
|
260
|
+
const version =
|
|
261
|
+
versionNode === undefined ? undefined : literalValue(versionNode, constants);
|
|
262
|
+
if (ownerId !== undefined && version !== undefined && prefixes.length > 0) {
|
|
263
|
+
const packageValues = vocabularyByPackage.get(sourcePackageRoot(filePath)) ?? [];
|
|
264
|
+
declarations.push({
|
|
265
|
+
owner: ownerId,
|
|
266
|
+
filePath,
|
|
267
|
+
prefixes,
|
|
268
|
+
events: packageValues.filter((value) =>
|
|
269
|
+
prefixes.some((prefix) => value.startsWith(prefix)),
|
|
270
|
+
),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) {
|
|
276
|
+
const callName = node.expression.text;
|
|
277
|
+
if (callName === "defineCarrier") {
|
|
278
|
+
const spec = objectLiteral(node.arguments[0]);
|
|
279
|
+
if (spec) {
|
|
280
|
+
const ownerIdNode = objectProperty(spec, "ownerId", constants);
|
|
281
|
+
const prefixNode = objectProperty(spec, "prefix", constants);
|
|
282
|
+
const eventsNode = objectProperty(spec, "events", constants);
|
|
283
|
+
const ownerId =
|
|
284
|
+
ownerIdNode === undefined ? "unknown carrier" : literalValue(ownerIdNode, constants);
|
|
285
|
+
const prefix = prefixNode === undefined ? undefined : literalValue(prefixNode, constants);
|
|
286
|
+
if (prefix !== undefined) {
|
|
287
|
+
declarations.push({
|
|
288
|
+
owner: ownerId ?? "unknown carrier",
|
|
289
|
+
filePath,
|
|
290
|
+
prefixes: [prefix],
|
|
291
|
+
events: carrierEventKinds(eventsNode, prefix, constants),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (callName === "defineBoundaryContract") {
|
|
297
|
+
const spec = objectLiteral(node.arguments[0]);
|
|
298
|
+
if (spec) {
|
|
299
|
+
const ownerIdNode = objectProperty(spec, "ownerId", constants);
|
|
300
|
+
const prefixesNode = objectProperty(spec, "kindPrefixes", constants);
|
|
301
|
+
const eventsNode = objectProperty(spec, "events", constants);
|
|
302
|
+
const ownerId =
|
|
303
|
+
ownerIdNode === undefined ? "unknown boundary" : literalValue(ownerIdNode, constants);
|
|
304
|
+
const prefixes = arrayLiteralStrings(prefixesNode, constants);
|
|
305
|
+
if (prefixes.length > 0) {
|
|
306
|
+
declarations.push({
|
|
307
|
+
owner: ownerId ?? "unknown boundary",
|
|
308
|
+
filePath,
|
|
309
|
+
prefixes,
|
|
310
|
+
events: boundaryEventKinds(eventsNode, constants),
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (callName === "eventNamespace") {
|
|
316
|
+
const spec = objectLiteral(node.arguments[0]);
|
|
317
|
+
if (spec) {
|
|
318
|
+
const ownerIdNode = objectProperty(spec, "ownerId", constants);
|
|
319
|
+
const prefixesNode = objectProperty(spec, "kindPrefixes", constants);
|
|
320
|
+
const ownerId =
|
|
321
|
+
ownerIdNode === undefined ? "unknown namespace" : literalValue(ownerIdNode, constants);
|
|
322
|
+
const prefixes = arrayLiteralStrings(prefixesNode, constants);
|
|
323
|
+
if (prefixes.length > 0) {
|
|
324
|
+
const packageValues = vocabularyByPackage.get(sourcePackageRoot(filePath)) ?? [];
|
|
325
|
+
declarations.push({
|
|
326
|
+
owner: ownerId ?? "unknown namespace",
|
|
327
|
+
filePath,
|
|
328
|
+
prefixes,
|
|
329
|
+
events: packageValues.filter((value) =>
|
|
330
|
+
prefixes.some((prefix) => value.startsWith(prefix)),
|
|
331
|
+
),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
ts.forEachChild(node, visit);
|
|
338
|
+
};
|
|
339
|
+
visit(sourceFile);
|
|
340
|
+
return declarations;
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const collectReservedDeclarations = (parsed) => {
|
|
344
|
+
const kernel = parsed.find(({ file }) =>
|
|
345
|
+
file.endsWith(path.join("packages", "core", "src", "errors.ts")),
|
|
346
|
+
);
|
|
347
|
+
const backendProtocol = parsed.find(({ file }) =>
|
|
348
|
+
file.endsWith(path.join("packages", "core", "src", "backend-protocol", "index.ts")),
|
|
349
|
+
);
|
|
350
|
+
const namespaceDeclarations =
|
|
351
|
+
kernel === undefined
|
|
352
|
+
? []
|
|
353
|
+
: variableNamespaceDeclarations(kernel.sourceFile, "CORE_CLAIMED_EVENT_NAMESPACES");
|
|
354
|
+
const backendPrefixes =
|
|
355
|
+
namespaceDeclarations.find((namespace) => namespace.owner === backendProtocolOwnerId)
|
|
356
|
+
?.prefixes ??
|
|
357
|
+
(backendProtocol === undefined
|
|
358
|
+
? []
|
|
359
|
+
: variableArrayStrings(backendProtocol.sourceFile, "BACKEND_PROTOCOL_EVENT_PREFIXES"));
|
|
360
|
+
const coreNamespaces =
|
|
361
|
+
namespaceDeclarations.length === 0
|
|
362
|
+
? [
|
|
363
|
+
{
|
|
364
|
+
owner: runtimeProtocolOwnerId,
|
|
365
|
+
prefixes:
|
|
366
|
+
kernel === undefined
|
|
367
|
+
? []
|
|
368
|
+
: variableArrayStrings(kernel.sourceFile, "CORE_CLAIMED_PREFIXES").filter(
|
|
369
|
+
(prefix) => !new Set(backendPrefixes).has(prefix),
|
|
370
|
+
),
|
|
371
|
+
},
|
|
372
|
+
]
|
|
373
|
+
: namespaceDeclarations.filter((namespace) => namespace.owner !== backendProtocolOwnerId);
|
|
374
|
+
return [
|
|
375
|
+
...coreNamespaces.flatMap((namespace) => {
|
|
376
|
+
const events = parsed.flatMap(({ sourceFile }) =>
|
|
377
|
+
ownedProtocolEvents(sourceFile, namespace.prefixes),
|
|
378
|
+
);
|
|
379
|
+
return kernel === undefined || namespace.prefixes.length === 0
|
|
380
|
+
? []
|
|
381
|
+
: [
|
|
382
|
+
{
|
|
383
|
+
owner: namespace.owner,
|
|
384
|
+
filePath: kernel.file,
|
|
385
|
+
prefixes: namespace.prefixes,
|
|
386
|
+
events,
|
|
387
|
+
},
|
|
388
|
+
];
|
|
389
|
+
}),
|
|
390
|
+
...(backendProtocol === undefined
|
|
391
|
+
? []
|
|
392
|
+
: [
|
|
393
|
+
{
|
|
394
|
+
owner: backendProtocolOwnerId,
|
|
395
|
+
filePath: backendProtocol.file,
|
|
396
|
+
prefixes: backendPrefixes,
|
|
397
|
+
events: ownedProtocolEvents(backendProtocol.sourceFile, backendPrefixes),
|
|
398
|
+
},
|
|
399
|
+
]),
|
|
400
|
+
];
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const isWriterCall = (call) => {
|
|
404
|
+
const expression = call.expression;
|
|
405
|
+
if (ts.isPropertyAccessExpression(expression)) return writerNames.has(expression.name.text);
|
|
406
|
+
if (ts.isIdentifier(expression)) return writerNames.has(expression.text);
|
|
407
|
+
return false;
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
const collectKindObjects = (node, out) => {
|
|
411
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
412
|
+
const kindProperty = node.properties.find(
|
|
413
|
+
(property) =>
|
|
414
|
+
ts.isPropertyAssignment(property) &&
|
|
415
|
+
(ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) &&
|
|
416
|
+
property.name.text === "kind",
|
|
417
|
+
);
|
|
418
|
+
if (kindProperty !== undefined && ts.isPropertyAssignment(kindProperty)) {
|
|
419
|
+
out.push(kindProperty.initializer);
|
|
420
|
+
}
|
|
421
|
+
const eventProperty = node.properties.find(
|
|
422
|
+
(property) =>
|
|
423
|
+
ts.isPropertyAssignment(property) &&
|
|
424
|
+
(ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) &&
|
|
425
|
+
property.name.text === "event",
|
|
426
|
+
);
|
|
427
|
+
if (eventProperty !== undefined && ts.isPropertyAssignment(eventProperty)) {
|
|
428
|
+
out.push(eventProperty.initializer);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
432
|
+
for (const element of node.elements) collectKindObjects(element, out);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const lineAndColumn = (sourceFile, node) => {
|
|
437
|
+
const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
438
|
+
return { line: pos.line + 1, column: pos.character + 1 };
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
const collectWriterKinds = (sourceFile, filePath) => {
|
|
442
|
+
const constants = localConstants(sourceFile);
|
|
443
|
+
const writes = [];
|
|
444
|
+
const visit = (node) => {
|
|
445
|
+
if (ts.isCallExpression(node) && isWriterCall(node)) {
|
|
446
|
+
const kindNodes = [];
|
|
447
|
+
for (const argument of node.arguments) collectKindObjects(argument, kindNodes);
|
|
448
|
+
for (const kindNode of kindNodes) {
|
|
449
|
+
const kind = literalValue(kindNode, constants);
|
|
450
|
+
if (kind === undefined) continue;
|
|
451
|
+
const position = lineAndColumn(sourceFile, kindNode);
|
|
452
|
+
writes.push({ filePath, kind, line: position.line, column: position.column });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
ts.forEachChild(node, visit);
|
|
456
|
+
};
|
|
457
|
+
visit(sourceFile);
|
|
458
|
+
return writes;
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
const collectNamespaceModel = (root) => {
|
|
462
|
+
const files = sourceFiles(root);
|
|
463
|
+
const parsed = files.map((file) => ({
|
|
464
|
+
file,
|
|
465
|
+
sourceFile: ts.createSourceFile(
|
|
466
|
+
file,
|
|
467
|
+
fs.readFileSync(file, "utf8"),
|
|
468
|
+
ts.ScriptTarget.Latest,
|
|
469
|
+
true,
|
|
470
|
+
),
|
|
471
|
+
}));
|
|
472
|
+
const vocabularyByPackage = packageVocabulary(parsed);
|
|
473
|
+
const declarations = [
|
|
474
|
+
...collectReservedDeclarations(parsed),
|
|
475
|
+
...parsed.flatMap(({ file, sourceFile }) =>
|
|
476
|
+
collectDeclarations(sourceFile, file, vocabularyByPackage),
|
|
477
|
+
),
|
|
478
|
+
].filter((declaration) => declaration.prefixes.length > 0);
|
|
479
|
+
const owners = declarations.flatMap((declaration) =>
|
|
480
|
+
declaration.prefixes.map((prefix) => ({
|
|
481
|
+
prefix,
|
|
482
|
+
owner: declaration.owner,
|
|
483
|
+
filePath: toRepoPath(declaration.filePath),
|
|
484
|
+
declared: new Set(declaration.events),
|
|
485
|
+
})),
|
|
486
|
+
);
|
|
487
|
+
const writes = parsed.flatMap(({ file, sourceFile }) => collectWriterKinds(sourceFile, file));
|
|
488
|
+
const failures = [];
|
|
489
|
+
|
|
490
|
+
for (let leftIndex = 0; leftIndex < owners.length; leftIndex++) {
|
|
491
|
+
const left = owners[leftIndex];
|
|
492
|
+
for (let rightIndex = leftIndex + 1; rightIndex < owners.length; rightIndex++) {
|
|
493
|
+
const right = owners[rightIndex];
|
|
494
|
+
if (!left.prefix.startsWith(right.prefix) && !right.prefix.startsWith(left.prefix)) continue;
|
|
495
|
+
if (left.owner === right.owner && left.prefix === right.prefix) {
|
|
496
|
+
failures.push(
|
|
497
|
+
`${toRepoPath(left.filePath)} and ${toRepoPath(right.filePath)} duplicate owner ${
|
|
498
|
+
left.owner
|
|
499
|
+
} prefix ${JSON.stringify(left.prefix)}`,
|
|
500
|
+
);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (left.owner !== right.owner) {
|
|
504
|
+
failures.push(
|
|
505
|
+
`${toRepoPath(left.filePath)}:${left.owner} prefix ${JSON.stringify(
|
|
506
|
+
left.prefix,
|
|
507
|
+
)} overlaps ${toRepoPath(right.filePath)}:${right.owner} prefix ${JSON.stringify(
|
|
508
|
+
right.prefix,
|
|
509
|
+
)}`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
for (const write of writes) {
|
|
516
|
+
const owner = owners.find((candidate) => write.kind.startsWith(candidate.prefix));
|
|
517
|
+
if (owner === undefined) continue;
|
|
518
|
+
if (owner.declared.has(write.kind)) continue;
|
|
519
|
+
failures.push(
|
|
520
|
+
`${toRepoPath(write.filePath)}:${write.line}:${write.column}: ${JSON.stringify(
|
|
521
|
+
write.kind,
|
|
522
|
+
)} writes under ${owner.owner} owned prefix ${JSON.stringify(
|
|
523
|
+
owner.prefix,
|
|
524
|
+
)} but is not declared by ${toRepoPath(owner.filePath)}`,
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
return { owners, writes, failures };
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const posix = (file) => file.split(path.sep).join("/");
|
|
531
|
+
const unique = (values) => [...new Set(values)].sort((left, right) => left.localeCompare(right));
|
|
532
|
+
|
|
533
|
+
const tagValues = (record, name) =>
|
|
534
|
+
record.tags
|
|
535
|
+
.filter((tag) => tag.name === name)
|
|
536
|
+
.map((tag) => tag.text)
|
|
537
|
+
.filter(Boolean);
|
|
538
|
+
|
|
539
|
+
const defaultReadFile = (root, file) => fs.readFileSync(path.join(root, file), "utf8");
|
|
540
|
+
|
|
541
|
+
const readJson = (root, file) => JSON.parse(defaultReadFile(root, file));
|
|
542
|
+
|
|
543
|
+
const exists = (root, file) => fs.existsSync(path.join(root, file));
|
|
544
|
+
|
|
545
|
+
const rel = (root, file) => posix(path.relative(root, file));
|
|
546
|
+
|
|
547
|
+
const walk = (root, dir) => {
|
|
548
|
+
const start = path.join(root, dir);
|
|
549
|
+
if (!fs.existsSync(start)) return [];
|
|
550
|
+
const out = [];
|
|
551
|
+
for (const entry of fs.readdirSync(start, { withFileTypes: true })) {
|
|
552
|
+
const full = path.join(start, entry.name);
|
|
553
|
+
if (entry.isDirectory()) {
|
|
554
|
+
out.push(...walk(root, posix(path.relative(root, full))));
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
if (entry.isFile()) out.push(posix(path.relative(root, full)));
|
|
558
|
+
}
|
|
559
|
+
return out.sort((left, right) => left.localeCompare(right));
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
const ensureUnique = (failures, items, key, label) => {
|
|
563
|
+
const seen = new Set();
|
|
564
|
+
for (const item of items) {
|
|
565
|
+
const value = key(item);
|
|
566
|
+
if (seen.has(value)) failures.push(`duplicate ${label}: ${value}`);
|
|
567
|
+
seen.add(value);
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
const ensurePath = (root, failures, file, owner) => {
|
|
572
|
+
if (!exists(root, file)) failures.push(`${owner} references missing path ${file}`);
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const classifyCapabilityKind = (primitive) => {
|
|
576
|
+
const id = primitive.id.toLowerCase();
|
|
577
|
+
const symbol = primitive.symbol.toLowerCase();
|
|
578
|
+
const pkg = primitive.packagePath.toLowerCase();
|
|
579
|
+
const identity = `${id} ${symbol}`;
|
|
580
|
+
|
|
581
|
+
if (
|
|
582
|
+
identity.includes("agent-authoring") ||
|
|
583
|
+
identity.includes("compileagenttree") ||
|
|
584
|
+
identity.includes("linkworkspacestatictarget")
|
|
585
|
+
) {
|
|
586
|
+
return "composer";
|
|
587
|
+
}
|
|
588
|
+
if (identity.includes("workspacejobprofile") || identity.includes("profile")) return "profile";
|
|
589
|
+
if (pkg.includes("/composers/")) return "composer";
|
|
590
|
+
if (identity.includes("facade") || identity.includes("response")) return "facade";
|
|
591
|
+
if (symbol.startsWith("project") || identity.includes("projection")) return "projection";
|
|
592
|
+
if (pkg.includes("/wire-adapters/")) return "adapter";
|
|
593
|
+
if (pkg.includes("/backends/")) return "backend";
|
|
594
|
+
if (pkg.includes("/providers/")) return "provider";
|
|
595
|
+
if (pkg.includes("/carriers/")) return "carrier";
|
|
596
|
+
if (pkg.includes("/runtime") || id.includes(".runtime.")) return "runtime";
|
|
597
|
+
if (pkg.includes("/kernel") || id.includes(".kernel.")) return "kernel";
|
|
598
|
+
return "package";
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
const collectPrimitiveAnnotations = ({ root, surface, invariantIds, failures }) => {
|
|
602
|
+
const primitivesById = new Map();
|
|
603
|
+
for (const pkg of surface.packages) {
|
|
604
|
+
if (!exists(root, `${pkg.path}/package.json`)) continue;
|
|
605
|
+
for (const record of sourceTsdocRecordsForPackage(root, pkg)) {
|
|
606
|
+
const primitiveIds = tagValues(record, "agentosPrimitive");
|
|
607
|
+
if (primitiveIds.length === 0) continue;
|
|
608
|
+
if (primitiveIds.length > 1) {
|
|
609
|
+
failures.push(`${pkg.name}:${record.key} has multiple @agentosPrimitive tags`);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const docs = tagValues(record, "agentosDocs");
|
|
614
|
+
const invariants = tagValues(record, "agentosInvariant");
|
|
615
|
+
if (docs.length !== 1) {
|
|
616
|
+
failures.push(`${pkg.name}:${record.key} must have exactly one @agentosDocs tag`);
|
|
617
|
+
}
|
|
618
|
+
if (invariants.length === 0) {
|
|
619
|
+
failures.push(`${pkg.name}:${record.key} must have at least one @agentosInvariant tag`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
for (const invariant of invariants) {
|
|
623
|
+
if (!invariantIds.has(invariant)) {
|
|
624
|
+
failures.push(`${pkg.name}:${record.key} references unknown invariant ${invariant}`);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
for (const doc of docs) ensurePath(root, failures, doc, `${pkg.name}:${record.key}`);
|
|
628
|
+
|
|
629
|
+
const primitive = {
|
|
630
|
+
id: primitiveIds[0],
|
|
631
|
+
package: pkg.name,
|
|
632
|
+
packagePath: pkg.path,
|
|
633
|
+
entrypoints: [record.entrypoint],
|
|
634
|
+
symbol: record.name,
|
|
635
|
+
exportKey: record.key,
|
|
636
|
+
sourceFile: rel(root, record.file),
|
|
637
|
+
summary: record.summary,
|
|
638
|
+
aliases: tagValues(record, "agentosAlias"),
|
|
639
|
+
invariants,
|
|
640
|
+
docs: docs[0] ?? "",
|
|
641
|
+
};
|
|
642
|
+
const noRouteReasons = tagValues(record, "agentosNoRouteReason");
|
|
643
|
+
if (noRouteReasons.length > 1) {
|
|
644
|
+
failures.push(`${pkg.name}:${record.key} has multiple @agentosNoRouteReason tags`);
|
|
645
|
+
}
|
|
646
|
+
if (noRouteReasons.length === 1) primitive.noRouteReason = noRouteReasons[0];
|
|
647
|
+
primitive.capabilityKind = classifyCapabilityKind(primitive);
|
|
648
|
+
|
|
649
|
+
const existing = primitivesById.get(primitive.id);
|
|
650
|
+
if (existing === undefined) {
|
|
651
|
+
primitivesById.set(primitive.id, primitive);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (
|
|
655
|
+
existing.package !== primitive.package ||
|
|
656
|
+
existing.symbol !== primitive.symbol ||
|
|
657
|
+
existing.sourceFile !== primitive.sourceFile
|
|
658
|
+
) {
|
|
659
|
+
failures.push(
|
|
660
|
+
`primitive id ${primitive.id} is attached to multiple exported symbols: ${existing.package}:${existing.symbol} and ${primitive.package}:${primitive.symbol}`,
|
|
661
|
+
);
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
existing.entrypoints = unique([...existing.entrypoints, ...primitive.entrypoints]);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return [...primitivesById.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
const discoverErrorTags = ({ root, readFile }) => {
|
|
671
|
+
const agentOsTagPattern = /agent_os\.[a-z0-9]+(?:_[a-z0-9]+)*(?![a-z0-9_])/gu;
|
|
672
|
+
const sources = new Map();
|
|
673
|
+
const codeFiles = walk(root, "packages").filter((file) => file.endsWith(".ts"));
|
|
674
|
+
for (const file of codeFiles) {
|
|
675
|
+
const text = readFile(file);
|
|
676
|
+
for (const match of text.matchAll(agentOsTagPattern)) {
|
|
677
|
+
const tag = match[0];
|
|
678
|
+
const list = sources.get(tag) ?? [];
|
|
679
|
+
if (!list.includes(file)) list.push(file);
|
|
680
|
+
sources.set(tag, list);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return [...sources.entries()]
|
|
684
|
+
.map(([tag, sourceFiles]) => ({ tag, sourceFiles }))
|
|
685
|
+
.sort((left, right) => left.tag.localeCompare(right.tag));
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
const attachPrimitiveEvidence = ({ root, failures, primitiveEvidenceSource, primitives }) => {
|
|
689
|
+
const primitiveIds = new Set(primitives.map((primitive) => primitive.id));
|
|
690
|
+
const primitiveEvidenceById = new Map();
|
|
691
|
+
|
|
692
|
+
for (const entry of primitiveEvidenceSource.evidence) {
|
|
693
|
+
if (!primitiveIds.has(entry.primitive)) {
|
|
694
|
+
failures.push(`primitive evidence references unknown primitive ${entry.primitive}`);
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
const hasTests = Array.isArray(entry.tests) && entry.tests.length > 0;
|
|
698
|
+
const hasNoTestReason =
|
|
699
|
+
typeof entry.noTestReason === "string" && entry.noTestReason.trim().length > 0;
|
|
700
|
+
if (hasTests === hasNoTestReason) {
|
|
701
|
+
failures.push(
|
|
702
|
+
`${entry.primitive} must have exactly one of tests[] or non-empty noTestReason`,
|
|
703
|
+
);
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (hasTests) {
|
|
707
|
+
for (const test of entry.tests) ensurePath(root, failures, test, entry.primitive);
|
|
708
|
+
primitiveEvidenceById.set(entry.primitive, {
|
|
709
|
+
tests: [...entry.tests].sort((left, right) => left.localeCompare(right)),
|
|
710
|
+
});
|
|
711
|
+
} else {
|
|
712
|
+
primitiveEvidenceById.set(entry.primitive, { noTestReason: entry.noTestReason.trim() });
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
for (const primitive of primitives) {
|
|
717
|
+
const evidence = primitiveEvidenceById.get(primitive.id);
|
|
718
|
+
if (evidence === undefined) {
|
|
719
|
+
failures.push(`${primitive.id} is missing primitive test evidence`);
|
|
720
|
+
primitive.testEvidence = { noTestReason: "missing evidence source" };
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
primitive.testEvidence = evidence;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
const buildErrors = ({ root, failures, errorsSource, invariantIds, readFile }) => {
|
|
728
|
+
const errorMetadataByTag = new Map(errorsSource.errors.map((error) => [error.tag, error]));
|
|
729
|
+
const discoveredErrors = discoverErrorTags({ root, readFile });
|
|
730
|
+
for (const discovered of discoveredErrors) {
|
|
731
|
+
const metadata = errorMetadataByTag.get(discovered.tag);
|
|
732
|
+
if (metadata === undefined) {
|
|
733
|
+
failures.push(`missing docs/agent/error-metadata.source.json entry for ${discovered.tag}`);
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
for (const invariant of metadata.invariants) {
|
|
737
|
+
if (!invariantIds.has(invariant)) {
|
|
738
|
+
failures.push(`${discovered.tag} references unknown invariant ${invariant}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
ensurePath(root, failures, metadata.docs, discovered.tag);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
return discoveredErrors
|
|
745
|
+
.map((discovered) => {
|
|
746
|
+
const metadata = errorMetadataByTag.get(discovered.tag);
|
|
747
|
+
if (metadata === undefined) return null;
|
|
748
|
+
return {
|
|
749
|
+
tag: discovered.tag,
|
|
750
|
+
invariants: metadata.invariants,
|
|
751
|
+
docs: metadata.docs,
|
|
752
|
+
fix: metadata.fix,
|
|
753
|
+
sourceFiles: discovered.sourceFiles,
|
|
754
|
+
};
|
|
755
|
+
})
|
|
756
|
+
.filter(Boolean);
|
|
757
|
+
};
|
|
758
|
+
|
|
759
|
+
const buildInvariantMatrix = ({ root, failures, invariantsSource, primitives, errors }) =>
|
|
760
|
+
invariantsSource.invariants.map((invariant) => {
|
|
761
|
+
const invariantPrimitives = primitives
|
|
762
|
+
.filter((primitive) => primitive.invariants.includes(invariant.id))
|
|
763
|
+
.map((primitive) => primitive.id);
|
|
764
|
+
const invariantErrors = errors
|
|
765
|
+
.filter((error) => error.invariants.includes(invariant.id))
|
|
766
|
+
.map((error) => error.tag);
|
|
767
|
+
const docs = unique([
|
|
768
|
+
invariant.docs,
|
|
769
|
+
...primitives
|
|
770
|
+
.filter((primitive) => primitive.invariants.includes(invariant.id))
|
|
771
|
+
.map((primitive) => primitive.docs),
|
|
772
|
+
...errors
|
|
773
|
+
.filter((error) => error.invariants.includes(invariant.id))
|
|
774
|
+
.map((error) => error.docs),
|
|
775
|
+
]);
|
|
776
|
+
const row = {
|
|
777
|
+
invariant: invariant.id,
|
|
778
|
+
statement: invariant.statement,
|
|
779
|
+
primitives: invariantPrimitives,
|
|
780
|
+
errors: invariantErrors,
|
|
781
|
+
docs,
|
|
782
|
+
tests: invariant.tests,
|
|
783
|
+
};
|
|
784
|
+
if (row.docs.length === 0) failures.push(`${row.invariant} has no docs mapping`);
|
|
785
|
+
for (const test of row.tests) ensurePath(root, failures, test, row.invariant);
|
|
786
|
+
return row;
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
export const collectAgentDocsModel = (root) => {
|
|
790
|
+
const failures = [];
|
|
791
|
+
const readFile = (file) => defaultReadFile(root, file);
|
|
792
|
+
const surface = readJson(root, "docs/surface.json");
|
|
793
|
+
const rootPackage = readJson(root, "package.json");
|
|
794
|
+
const recipesSource = readJson(root, "docs/agent/recipes.source.json");
|
|
795
|
+
const capabilityRulesSource = readJson(root, "docs/agent/capability-rules.source.json");
|
|
796
|
+
const invariantsSource = readJson(root, "docs/agent/invariants.source.json");
|
|
797
|
+
const primitiveEvidenceSource = readJson(root, "docs/agent/primitive-evidence.source.json");
|
|
798
|
+
const errorsSource = readJson(root, "docs/agent/error-metadata.source.json");
|
|
799
|
+
const externalVocabularySource = readJson(root, "docs/agent/external-vocabulary.source.json");
|
|
800
|
+
|
|
801
|
+
ensureUnique(failures, recipesSource.recipes, (recipe) => recipe.id, "recipe id");
|
|
802
|
+
ensureUnique(
|
|
803
|
+
failures,
|
|
804
|
+
capabilityRulesSource.rules,
|
|
805
|
+
(rule) => rule.primitive,
|
|
806
|
+
"capability rule primitive",
|
|
807
|
+
);
|
|
808
|
+
ensureUnique(failures, invariantsSource.invariants, (invariant) => invariant.id, "invariant id");
|
|
809
|
+
ensureUnique(
|
|
810
|
+
failures,
|
|
811
|
+
primitiveEvidenceSource.evidence,
|
|
812
|
+
(entry) => entry.primitive,
|
|
813
|
+
"primitive evidence id",
|
|
814
|
+
);
|
|
815
|
+
ensureUnique(failures, errorsSource.errors, (error) => error.tag, "error tag metadata");
|
|
816
|
+
ensureUnique(
|
|
817
|
+
failures,
|
|
818
|
+
externalVocabularySource.vocabulary,
|
|
819
|
+
(entry) => entry.id,
|
|
820
|
+
"external vocabulary id",
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
const invariantIds = new Set(invariantsSource.invariants.map((invariant) => invariant.id));
|
|
824
|
+
for (const invariant of invariantsSource.invariants) {
|
|
825
|
+
ensurePath(root, failures, invariant.docs, invariant.id);
|
|
826
|
+
for (const test of invariant.tests) ensurePath(root, failures, test, invariant.id);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
const primitives = collectPrimitiveAnnotations({ root, surface, invariantIds, failures });
|
|
830
|
+
ensureUnique(failures, primitives, (primitive) => primitive.id, "primitive id");
|
|
831
|
+
const primitiveIds = new Set(primitives.map((primitive) => primitive.id));
|
|
832
|
+
attachPrimitiveEvidence({ root, failures, primitiveEvidenceSource, primitives });
|
|
833
|
+
|
|
834
|
+
for (const recipe of recipesSource.recipes) {
|
|
835
|
+
ensurePath(root, failures, recipe.tutorial, recipe.id);
|
|
836
|
+
for (const primitive of recipe.primitives) {
|
|
837
|
+
if (!primitiveIds.has(primitive)) {
|
|
838
|
+
failures.push(`${recipe.id} references unknown primitive ${primitive}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
for (const evidence of recipe.evidence) ensurePath(root, failures, evidence, recipe.id);
|
|
842
|
+
if (
|
|
843
|
+
recipe.noRouteReason !== undefined &&
|
|
844
|
+
(typeof recipe.noRouteReason !== "string" || recipe.noRouteReason.trim().length === 0)
|
|
845
|
+
) {
|
|
846
|
+
failures.push(`${recipe.id} noRouteReason must be a non-empty string when present`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
for (const entry of externalVocabularySource.vocabulary) {
|
|
851
|
+
ensurePath(root, failures, entry.docs, entry.id);
|
|
852
|
+
for (const primitive of entry.mapsTo) {
|
|
853
|
+
if (!primitiveIds.has(primitive)) {
|
|
854
|
+
failures.push(`${entry.id} references unknown primitive ${primitive}`);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const errors = buildErrors({ root, failures, errorsSource, invariantIds, readFile });
|
|
860
|
+
const invariantMatrix = buildInvariantMatrix({
|
|
861
|
+
root,
|
|
862
|
+
failures,
|
|
863
|
+
invariantsSource,
|
|
864
|
+
primitives,
|
|
865
|
+
errors,
|
|
866
|
+
});
|
|
867
|
+
const namespaceModel = collectNamespaceModel(root);
|
|
868
|
+
failures.push(...namespaceModel.failures);
|
|
869
|
+
|
|
870
|
+
return {
|
|
871
|
+
root,
|
|
872
|
+
failures,
|
|
873
|
+
surface,
|
|
874
|
+
rootPackage,
|
|
875
|
+
rootScripts: rootPackage.scripts ?? {},
|
|
876
|
+
recipesSource,
|
|
877
|
+
capabilityRulesSource,
|
|
878
|
+
invariantsSource,
|
|
879
|
+
primitiveEvidenceSource,
|
|
880
|
+
errorsSource,
|
|
881
|
+
externalVocabularySource,
|
|
882
|
+
primitives,
|
|
883
|
+
primitiveIds,
|
|
884
|
+
errors,
|
|
885
|
+
invariantMatrix,
|
|
886
|
+
namespaceModel,
|
|
887
|
+
};
|
|
888
|
+
};
|