@yansirplus/cli 0.5.17 → 0.5.19
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/README.md +12 -6
- package/agent-catalog/agentOS/SKILL.md +22 -0
- package/agent-catalog/agentOS/references/agent/decision-graph.json +530 -0
- package/agent-catalog/agentOS/references/agent/errors.json +497 -0
- package/agent-catalog/agentOS/references/agent/invariant-matrix.json +337 -0
- package/agent-catalog/agentOS/references/agent/primitives.json +989 -0
- package/agent-catalog/agentOS/references/agent/recipes.json +109 -0
- package/agent-catalog/agentOS/references/agent/start-here.md +25 -0
- package/agent-catalog/agentOS/references/package-map.md +73 -0
- package/agent-catalog/agentOS/references/provenance.json +251 -0
- package/agent-catalog/agentOS/references/public-api/cli.md +20 -0
- package/agent-catalog/agentOS/references/public-api/client.md +90 -0
- package/agent-catalog/agentOS/references/public-api/core.md +1907 -0
- package/agent-catalog/agentOS/references/public-api/runtime.md +843 -0
- package/dist/build/agent-authoring/config.d.ts +20 -5
- package/dist/build/agent-authoring/config.js +132 -32
- package/dist/build/agent-authoring/manifest-compiler.d.ts +131 -2
- package/dist/build/agent-authoring/manifest-compiler.js +630 -8
- package/dist/build/agent-authoring/shared.d.ts +2 -0
- package/dist/build/agent-authoring/shared.js +2 -0
- package/dist/build/agent-authoring/static-target.d.ts +6 -3
- package/dist/build/agent-authoring/static-target.js +1900 -281
- package/dist/build/agent-authoring.d.ts +3 -3
- package/dist/build/agent-authoring.js +1 -1
- package/dist/build/build-cli.d.ts +1 -1
- package/dist/build/build-cli.js +1629 -26
- package/dist/check/algorithmic/client-boundary-checks.mjs +3 -34
- package/dist/check/algorithmic/convergence-smoke-checks.mjs +652 -6
- package/dist/check/algorithmic/distribution-checks.mjs +8 -7
- package/dist/check/algorithmic/package-boundary-checks.mjs +3 -2
- package/dist/check/algorithmic/repo-surface-checks.mjs +55 -1
- package/dist/check/algorithmic/static-target-checks.mjs +83 -5
- package/dist/check/algorithmic-checks.mjs +10 -17
- package/dist/check/default-gate.mjs +3 -3
- package/dist/check/effect-scan-gate.mjs +121 -0
- package/dist/check/package-graph.mjs +2 -32
- package/dist/consumer-overlay.mjs +1281 -0
- package/dist/lib/public-api-model.mjs +19 -0
- package/dist/lib/repo-source-files.mjs +26 -0
- package/dist/lib/ts-module-loader.mjs +44 -0
- package/dist/lib/workspace-manifest.mjs +77 -0
- package/dist/main.mjs +171 -21
- package/dist/release-status.mjs +515 -0
- package/package.json +8 -4
- package/dist/check/check-coverage.mjs +0 -231
- package/dist/generate/generate-agent-docs.mjs +0 -435
- package/dist/generate/generate-carrier-reference.mjs +0 -514
- package/dist/generate/generate-docs.mjs +0 -345
- package/dist/generate/generate-effect-skill-manifests.mjs +0 -193
- package/dist/generate/project-docs-site.mjs +0 -190
- package/dist/lib/boundary-rules.mjs +0 -63
- package/dist/lib/capability-routes.mjs +0 -354
- package/dist/lib/projection-sink.mjs +0 -113
|
@@ -1,345 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
-
checkProjectionSink,
|
|
6
|
-
defineProjectionSpec,
|
|
7
|
-
runProjectionSink,
|
|
8
|
-
} from "../lib/projection-sink.mjs";
|
|
9
|
-
import {
|
|
10
|
-
apiSourceMode,
|
|
11
|
-
sourceTsdocApiMarkdown,
|
|
12
|
-
sourceTsdocModes,
|
|
13
|
-
sourceTsdocRecordsForPackage,
|
|
14
|
-
validateSourceTsdocRecords,
|
|
15
|
-
} from "../lib/public-api-model.mjs";
|
|
16
|
-
|
|
17
|
-
const root = process.cwd();
|
|
18
|
-
const check = process.argv.includes("--check");
|
|
19
|
-
const surfacePath = path.join(root, "docs/surface.json");
|
|
20
|
-
const rootPackage = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
21
|
-
const rawSurfaceText = fs.readFileSync(surfacePath, "utf8");
|
|
22
|
-
const rawSurface = JSON.parse(rawSurfaceText);
|
|
23
|
-
const releaseVersion = rootPackage.agentOsRelease?.version;
|
|
24
|
-
const releaseLine =
|
|
25
|
-
typeof releaseVersion === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u.test(releaseVersion)
|
|
26
|
-
? `${releaseVersion.split(".").slice(0, 2).join(".")}.x`
|
|
27
|
-
: undefined;
|
|
28
|
-
const hardCodedReleaseLinePattern = /\b\d+\.\d+\.x\b/u;
|
|
29
|
-
|
|
30
|
-
const expandReleaseTokens = (value) => {
|
|
31
|
-
if (typeof value === "string") {
|
|
32
|
-
return releaseLine === undefined ? value : value.replaceAll("{releaseLine}", releaseLine);
|
|
33
|
-
}
|
|
34
|
-
if (Array.isArray(value)) return value.map(expandReleaseTokens);
|
|
35
|
-
if (value !== null && typeof value === "object") {
|
|
36
|
-
return Object.fromEntries(
|
|
37
|
-
Object.entries(value).map(([key, entry]) => [key, expandReleaseTokens(entry)]),
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
return value;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const surface = expandReleaseTokens(rawSurface);
|
|
44
|
-
|
|
45
|
-
const generatedNotice = (source) =>
|
|
46
|
-
`<!-- generated by packages/cli/src/generate/generate-docs.mjs; edit ${source} -->`;
|
|
47
|
-
|
|
48
|
-
const read = (file) => fs.readFileSync(path.join(root, file), "utf8");
|
|
49
|
-
|
|
50
|
-
const docsSourceFacts = {
|
|
51
|
-
kind: "source-set",
|
|
52
|
-
ref: "docs.generate-docs.source-facts",
|
|
53
|
-
sources: [
|
|
54
|
-
{ kind: "file", ref: "package.json" },
|
|
55
|
-
{ kind: "file", ref: "docs/surface.json" },
|
|
56
|
-
{ kind: "directory", ref: "docs/packages" },
|
|
57
|
-
{ kind: "directory", ref: "docs/api" },
|
|
58
|
-
{ kind: "tsdoc", ref: "exported public API TSDoc" },
|
|
59
|
-
],
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
const fileProjection = (file) =>
|
|
63
|
-
defineProjectionSpec({
|
|
64
|
-
id: `docs.generate-docs:${file}`,
|
|
65
|
-
version: 1,
|
|
66
|
-
source: docsSourceFacts,
|
|
67
|
-
project: (text, ctx) => ctx.ok(`${text.replace(/\s+$/u, "")}\n`),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
const fileSink = (file) => {
|
|
71
|
-
const target = path.join(root, file);
|
|
72
|
-
return {
|
|
73
|
-
id: file,
|
|
74
|
-
read: () =>
|
|
75
|
-
fs.existsSync(target)
|
|
76
|
-
? { _tag: "found", output: fs.readFileSync(target, "utf8") }
|
|
77
|
-
: { _tag: "missing" },
|
|
78
|
-
write: (output) => {
|
|
79
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
80
|
-
fs.writeFileSync(target, output);
|
|
81
|
-
},
|
|
82
|
-
equals: (actual, expected) => actual === expected,
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const write = async (file, text) => {
|
|
87
|
-
const spec = fileProjection(file);
|
|
88
|
-
const sink = fileSink(file);
|
|
89
|
-
if (check) {
|
|
90
|
-
const result = await checkProjectionSink(spec, text, sink);
|
|
91
|
-
if (result._tag === "projection_failed") {
|
|
92
|
-
failures.push(`${file} projection failed: ${result.result.reason}`);
|
|
93
|
-
} else if (result._tag === "stale") {
|
|
94
|
-
failures.push(`${file} is stale`);
|
|
95
|
-
}
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const result = await runProjectionSink(spec, text, sink);
|
|
100
|
-
if (result._tag === "projection_failed") {
|
|
101
|
-
failures.push(`${file} projection failed: ${result.result.reason}`);
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const sectionBody = (text, heading) => {
|
|
106
|
-
const marker = `## ${heading}`;
|
|
107
|
-
const start = text.indexOf(marker);
|
|
108
|
-
if (start === -1) throw new Error(`missing section ${heading}`);
|
|
109
|
-
const after = text.slice(start + marker.length);
|
|
110
|
-
const next = after.search(/^## /m);
|
|
111
|
-
return (next === -1 ? after : after.slice(0, next)).trim();
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
const optionalSectionBody = (text, heading, fallback) => {
|
|
115
|
-
const marker = `## ${heading}`;
|
|
116
|
-
return text.includes(marker) ? sectionBody(text, heading) : fallback;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const pad = (value, width) => `${value}${" ".repeat(Math.max(0, width - value.length))}`;
|
|
120
|
-
|
|
121
|
-
const table = (headers, rows) => {
|
|
122
|
-
const widths = headers.map((header, index) =>
|
|
123
|
-
Math.max(header.length, ...rows.map((row) => row[index].length)),
|
|
124
|
-
);
|
|
125
|
-
const line = (row) => `| ${row.map((cell, index) => pad(cell, widths[index])).join(" | ")} |`;
|
|
126
|
-
const divider = `| ${widths.map((width) => "-".repeat(width)).join(" | ")} |`;
|
|
127
|
-
return [line(headers), divider, ...rows.map(line)].join("\n");
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
const githubDocsBase = "https://github.com/yansircc/agentOS/blob/main/docs";
|
|
131
|
-
|
|
132
|
-
const rewriteDocsLinksForGithub = (text, sourceFile) =>
|
|
133
|
-
text.replace(/\[([^\]\n]+)\]\(([^)\s]+)(\s+"[^"]*")?\)/gu, (full, label, href, title = "") => {
|
|
134
|
-
if (!href.endsWith(".md") && !href.includes(".md#")) return full;
|
|
135
|
-
const [rawPath, rawAnchor] = href.split("#");
|
|
136
|
-
if (!rawPath.endsWith(".md")) return full;
|
|
137
|
-
const sourceDir = path.posix.dirname(sourceFile);
|
|
138
|
-
const target = path.posix.normalize(path.posix.join(sourceDir, rawPath));
|
|
139
|
-
if (!target.startsWith("docs/")) return full;
|
|
140
|
-
const anchor = rawAnchor === undefined ? "" : `#${rawAnchor}`;
|
|
141
|
-
return `[${label}](${githubDocsBase}/${target.slice("docs/".length)}${anchor}${title})`;
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const replaceBlock = async (file, id, content) => {
|
|
145
|
-
const source = read(file);
|
|
146
|
-
const startMarker = `<!-- agentos:generated ${id}:start -->`;
|
|
147
|
-
const endMarker = `<!-- agentos:generated ${id}:end -->`;
|
|
148
|
-
const start = source.indexOf(startMarker);
|
|
149
|
-
const end = source.indexOf(endMarker);
|
|
150
|
-
if (start === -1 || end === -1 || end < start) {
|
|
151
|
-
throw new Error(`${file} missing generated block ${id}`);
|
|
152
|
-
}
|
|
153
|
-
const next = end + endMarker.length;
|
|
154
|
-
await write(
|
|
155
|
-
file,
|
|
156
|
-
`${source.slice(0, start + startMarker.length)}\n\n${content}\n\n${source.slice(end, next)}${source.slice(next)}`,
|
|
157
|
-
);
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const packages = surface.packages;
|
|
161
|
-
const packagesByPath = new Map(packages.map((pkg) => [pkg.path, pkg]));
|
|
162
|
-
const failures = [];
|
|
163
|
-
|
|
164
|
-
if (releaseLine === undefined) {
|
|
165
|
-
failures.push("package.json agentOsRelease.version must be a semver release source");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (hardCodedReleaseLinePattern.test(rawSurfaceText)) {
|
|
169
|
-
failures.push("docs/surface.json must use {releaseLine}; hard-coded release lines are forbidden");
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const workspacePackagePaths = () => {
|
|
173
|
-
const rootPackage = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
174
|
-
const workspaces = Array.isArray(rootPackage.workspaces)
|
|
175
|
-
? rootPackage.workspaces
|
|
176
|
-
: Array.isArray(rootPackage.workspaces?.packages)
|
|
177
|
-
? rootPackage.workspaces.packages
|
|
178
|
-
: [];
|
|
179
|
-
const paths = new Set();
|
|
180
|
-
|
|
181
|
-
for (const workspace of workspaces) {
|
|
182
|
-
if (typeof workspace !== "string") continue;
|
|
183
|
-
if (workspace.endsWith("/*")) {
|
|
184
|
-
const base = workspace.slice(0, -2);
|
|
185
|
-
const baseDir = path.join(root, base);
|
|
186
|
-
if (!fs.existsSync(baseDir)) continue;
|
|
187
|
-
for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
|
|
188
|
-
if (!entry.isDirectory()) continue;
|
|
189
|
-
const packagePath = `${base}/${entry.name}`;
|
|
190
|
-
if (fs.existsSync(path.join(root, packagePath, "package.json"))) {
|
|
191
|
-
paths.add(packagePath);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (fs.existsSync(path.join(root, workspace, "package.json"))) {
|
|
198
|
-
paths.add(workspace);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return [...paths].sort((left, right) => left.localeCompare(right));
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
for (const pkg of packages) {
|
|
206
|
-
const packageJsonPath = path.join(root, pkg.path, "package.json");
|
|
207
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
208
|
-
failures.push(`${pkg.path}/package.json missing`);
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
212
|
-
if (packageJson.name !== pkg.name) {
|
|
213
|
-
failures.push(`${pkg.path}: package.json name ${packageJson.name} does not match ${pkg.name}`);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
for (const packagePath of workspacePackagePaths()) {
|
|
218
|
-
const pkgPath = String(packagePath);
|
|
219
|
-
if (!packagesByPath.has(pkgPath)) {
|
|
220
|
-
failures.push(`${pkgPath} is missing from docs/surface.json`);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
for (const pkg of packages) {
|
|
225
|
-
const packageDocPath = `docs/packages/${pkg.slug}.md`;
|
|
226
|
-
const packageDoc = read(packageDocPath);
|
|
227
|
-
const packageSection = (heading) =>
|
|
228
|
-
rewriteDocsLinksForGithub(sectionBody(packageDoc, heading), packageDocPath);
|
|
229
|
-
const readme = [
|
|
230
|
-
generatedNotice(`docs/surface.json and ${packageDocPath}`),
|
|
231
|
-
"",
|
|
232
|
-
`# ${pkg.name}`,
|
|
233
|
-
"",
|
|
234
|
-
"## Purpose",
|
|
235
|
-
"",
|
|
236
|
-
packageSection("Purpose"),
|
|
237
|
-
"",
|
|
238
|
-
"## Public API Status",
|
|
239
|
-
"",
|
|
240
|
-
pkg.readmeStatus,
|
|
241
|
-
"",
|
|
242
|
-
"## Invariant",
|
|
243
|
-
"",
|
|
244
|
-
packageSection("Invariant"),
|
|
245
|
-
"",
|
|
246
|
-
"## Minimal Usage",
|
|
247
|
-
"",
|
|
248
|
-
packageSection("Minimal Usage"),
|
|
249
|
-
"",
|
|
250
|
-
"## Verification",
|
|
251
|
-
"",
|
|
252
|
-
packageSection("Verification"),
|
|
253
|
-
"",
|
|
254
|
-
].join("\n");
|
|
255
|
-
await write(`${pkg.path}/README.md`, readme);
|
|
256
|
-
|
|
257
|
-
if (pkg.apiSource !== undefined) {
|
|
258
|
-
const mode = apiSourceMode(pkg);
|
|
259
|
-
if (sourceTsdocModes.has(mode)) {
|
|
260
|
-
const records = sourceTsdocRecordsForPackage(root, pkg);
|
|
261
|
-
failures.push(...validateSourceTsdocRecords(pkg, records));
|
|
262
|
-
await write(pkg.apiSource, sourceTsdocApiMarkdown(pkg, records));
|
|
263
|
-
} else if (mode !== "manual") {
|
|
264
|
-
failures.push(`${pkg.name}: unsupported apiSourceMode ${mode}`);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const apiDoc = read(pkg.apiSource);
|
|
268
|
-
const publicApiSource = sourceTsdocModes.has(mode)
|
|
269
|
-
? `docs/surface.json and exported TSDoc in ${pkg.path}/src`
|
|
270
|
-
: `docs/surface.json and ${pkg.apiSource}`;
|
|
271
|
-
const publicApi = [
|
|
272
|
-
generatedNotice(publicApiSource),
|
|
273
|
-
"",
|
|
274
|
-
`# ${pkg.name} Public API`,
|
|
275
|
-
"",
|
|
276
|
-
`Status: ${pkg.apiStatus}`,
|
|
277
|
-
"",
|
|
278
|
-
"## Public exports",
|
|
279
|
-
"",
|
|
280
|
-
sectionBody(apiDoc, "Public exports"),
|
|
281
|
-
"",
|
|
282
|
-
"## Experimental exports",
|
|
283
|
-
"",
|
|
284
|
-
sectionBody(apiDoc, "Experimental exports"),
|
|
285
|
-
"",
|
|
286
|
-
"## Deprecated exports",
|
|
287
|
-
"",
|
|
288
|
-
optionalSectionBody(apiDoc, "Deprecated exports", "None."),
|
|
289
|
-
"",
|
|
290
|
-
"## Internal-only exports",
|
|
291
|
-
"",
|
|
292
|
-
sectionBody(apiDoc, "Internal-only exports"),
|
|
293
|
-
"",
|
|
294
|
-
].join("\n");
|
|
295
|
-
await write(`${pkg.path}/PUBLIC_API.md`, publicApi);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const primaryPackages = packages.filter((pkg) => pkg.primary === true);
|
|
300
|
-
const releasePosture = `Current release posture: ${surface.release.line} ${surface.release.posture}. ${surface.release.publicApiMeaning}`;
|
|
301
|
-
|
|
302
|
-
await replaceBlock("README.md", "release-posture", releasePosture);
|
|
303
|
-
|
|
304
|
-
const rootPackageMap = table(
|
|
305
|
-
["Package", "Role"],
|
|
306
|
-
primaryPackages.map((pkg) => [`\`${pkg.name}\``, pkg.role]),
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
await replaceBlock("README.md", "package-map", rootPackageMap);
|
|
310
|
-
|
|
311
|
-
const runtimePackageMap = table(
|
|
312
|
-
["Package", "Published", "Status", "Boundary"],
|
|
313
|
-
packages.map((pkg) => [
|
|
314
|
-
`\`${pkg.name}\``,
|
|
315
|
-
pkg.published === true ? "yes" : "no",
|
|
316
|
-
pkg.status,
|
|
317
|
-
pkg.boundary,
|
|
318
|
-
]),
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
await replaceBlock("docs/runtime-packages.md", "runtime-package-map", runtimePackageMap);
|
|
322
|
-
|
|
323
|
-
await replaceBlock(
|
|
324
|
-
"docs/runtime-packages.md",
|
|
325
|
-
"holds",
|
|
326
|
-
surface.holds.map((hold) => hold.text).join("\n\n"),
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
const skillPackageMap = table(
|
|
330
|
-
["Need", "Package"],
|
|
331
|
-
primaryPackages.map((pkg) => [pkg.need, `\`${pkg.name}\``]),
|
|
332
|
-
);
|
|
333
|
-
|
|
334
|
-
await replaceBlock("skills/agentos/references/package-map.md", "package-map", skillPackageMap);
|
|
335
|
-
|
|
336
|
-
if (failures.length > 0) {
|
|
337
|
-
console.error(failures.join("\n"));
|
|
338
|
-
process.exit(1);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if (check) {
|
|
342
|
-
console.log("generated docs are current");
|
|
343
|
-
} else {
|
|
344
|
-
console.log("generated docs updated");
|
|
345
|
-
}
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
const root = process.cwd();
|
|
6
|
-
const check = process.argv.includes("--check");
|
|
7
|
-
const sourcePath = path.join(root, "docs/effect-skill.json");
|
|
8
|
-
const source = JSON.parse(fs.readFileSync(sourcePath, "utf8"));
|
|
9
|
-
const failures = [];
|
|
10
|
-
|
|
11
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
-
|
|
13
|
-
const stableJson = (value) =>
|
|
14
|
-
`${JSON.stringify(value, null, 2).replace(
|
|
15
|
-
/\[\n((?:\s+"(?:\\.|[^"\\])*",?\n)+)\s+\]/gu,
|
|
16
|
-
(match, body) => {
|
|
17
|
-
const items = body
|
|
18
|
-
.trim()
|
|
19
|
-
.split("\n")
|
|
20
|
-
.map((line) => line.trim().replace(/,$/u, ""));
|
|
21
|
-
return items.every((item) => item.startsWith('"') && item.endsWith('"'))
|
|
22
|
-
? `[${items.join(", ")}]`
|
|
23
|
-
: match;
|
|
24
|
-
},
|
|
25
|
-
)}\n`;
|
|
26
|
-
|
|
27
|
-
const writeJson = (file, value) => {
|
|
28
|
-
const target = path.join(root, file);
|
|
29
|
-
const expected = stableJson(value);
|
|
30
|
-
if (check) {
|
|
31
|
-
const actual = fs.existsSync(target) ? fs.readFileSync(target, "utf8") : "";
|
|
32
|
-
if (actual !== expected) failures.push(`${file} is stale`);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
36
|
-
fs.writeFileSync(target, expected);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const validateAllowedAdapters = ({ label, allowedAdapters, resolvePath }) => {
|
|
40
|
-
if (allowedAdapters === undefined) return;
|
|
41
|
-
if (!Array.isArray(allowedAdapters)) {
|
|
42
|
-
failures.push(`${label}.allowedAdapters must be an array`);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
for (const [index, adapter] of allowedAdapters.entries()) {
|
|
46
|
-
const adapterLabel = `${label}.allowedAdapters[${index}]`;
|
|
47
|
-
if (!isRecord(adapter) || typeof adapter.path !== "string" || adapter.path.length === 0) {
|
|
48
|
-
failures.push(`${adapterLabel}.path must be a non-empty string`);
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
const target = resolvePath(adapter.path);
|
|
52
|
-
if (!fs.existsSync(path.join(root, target))) {
|
|
53
|
-
failures.push(`${adapterLabel}.path references missing file ${target}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const workspacePackagePaths = () => {
|
|
59
|
-
const rootPackage = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
60
|
-
const workspaces = Array.isArray(rootPackage.workspaces)
|
|
61
|
-
? rootPackage.workspaces
|
|
62
|
-
: Array.isArray(rootPackage.workspaces?.packages)
|
|
63
|
-
? rootPackage.workspaces.packages
|
|
64
|
-
: [];
|
|
65
|
-
const paths = new Set();
|
|
66
|
-
|
|
67
|
-
for (const workspace of workspaces) {
|
|
68
|
-
if (typeof workspace !== "string") continue;
|
|
69
|
-
if (workspace.endsWith("/*")) {
|
|
70
|
-
const base = workspace.slice(0, -2);
|
|
71
|
-
const baseDir = path.join(root, base);
|
|
72
|
-
if (!fs.existsSync(baseDir)) continue;
|
|
73
|
-
for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
|
|
74
|
-
if (!entry.isDirectory()) continue;
|
|
75
|
-
const packagePath = `${base}/${entry.name}`;
|
|
76
|
-
if (fs.existsSync(path.join(root, packagePath, "package.json"))) {
|
|
77
|
-
paths.add(packagePath);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (fs.existsSync(path.join(root, workspace, "package.json"))) {
|
|
84
|
-
paths.add(workspace);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return [...paths].sort((left, right) => left.localeCompare(right));
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const scannerPackagesFromWorkspaces = (rootSource) => {
|
|
92
|
-
if (Object.hasOwn(rootSource, "packages")) {
|
|
93
|
-
failures.push("docs/effect-skill.json root.packages duplicates package ownership");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const packageDefaults = isRecord(rootSource.packageDefaults) ? rootSource.packageDefaults : {};
|
|
97
|
-
const packageOverrides = isRecord(rootSource.packageOverrides) ? rootSource.packageOverrides : {};
|
|
98
|
-
const paths = workspacePackagePaths();
|
|
99
|
-
const pathSet = new Set(paths);
|
|
100
|
-
|
|
101
|
-
for (const packagePath of Object.keys(packageOverrides)) {
|
|
102
|
-
if (!pathSet.has(packagePath)) {
|
|
103
|
-
failures.push(`${packagePath} has an effect scanner override but is not a workspace package`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return paths
|
|
108
|
-
.map((packagePath) => {
|
|
109
|
-
const packagePathText = String(packagePath);
|
|
110
|
-
const override = isRecord(packageOverrides[packagePath]) ? packageOverrides[packagePath] : {};
|
|
111
|
-
if (override.scan === false) {
|
|
112
|
-
if (
|
|
113
|
-
typeof override.scanExclusionReason !== "string" ||
|
|
114
|
-
override.scanExclusionReason.trim().length === 0
|
|
115
|
-
) {
|
|
116
|
-
failures.push(`${packagePathText} scan:false requires scanExclusionReason`);
|
|
117
|
-
}
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
const {
|
|
121
|
-
scan: _scan,
|
|
122
|
-
scanExclusionReason: _scanExclusionReason,
|
|
123
|
-
...scannerOverride
|
|
124
|
-
} = override;
|
|
125
|
-
return {
|
|
126
|
-
path: packagePathText,
|
|
127
|
-
...packageDefaults,
|
|
128
|
-
...scannerOverride,
|
|
129
|
-
};
|
|
130
|
-
})
|
|
131
|
-
.filter(Boolean);
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
if (!isRecord(source.root)) {
|
|
135
|
-
failures.push("docs/effect-skill.json missing root manifest");
|
|
136
|
-
} else {
|
|
137
|
-
validateAllowedAdapters({
|
|
138
|
-
label: "docs/effect-skill.json.root",
|
|
139
|
-
allowedAdapters: source.root.allowedAdapters,
|
|
140
|
-
resolvePath: (adapterPath) => adapterPath,
|
|
141
|
-
});
|
|
142
|
-
const {
|
|
143
|
-
packageDefaults: _packageDefaults,
|
|
144
|
-
packageOverrides: _packageOverrides,
|
|
145
|
-
...rootSource
|
|
146
|
-
} = source.root;
|
|
147
|
-
writeJson(".effect-skill.json", {
|
|
148
|
-
packages: scannerPackagesFromWorkspaces(source.root),
|
|
149
|
-
...rootSource,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const packageManifests =
|
|
154
|
-
typeof source.packageManifests === "object" && source.packageManifests !== null
|
|
155
|
-
? source.packageManifests
|
|
156
|
-
: null;
|
|
157
|
-
|
|
158
|
-
if (packageManifests === null) {
|
|
159
|
-
failures.push("docs/effect-skill.json missing packageManifests object");
|
|
160
|
-
} else {
|
|
161
|
-
const expectedPackageFiles = new Set(
|
|
162
|
-
Object.keys(packageManifests).map((packagePath) => `${packagePath}/.effect-skill.json`),
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
for (const [packagePath, manifest] of Object.entries(packageManifests)) {
|
|
166
|
-
const packageJson = path.join(root, packagePath, "package.json");
|
|
167
|
-
if (!fs.existsSync(packageJson)) {
|
|
168
|
-
failures.push(`${packagePath} has an effect manifest but no package.json`);
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
validateAllowedAdapters({
|
|
172
|
-
label: `docs/effect-skill.json.packageManifests.${packagePath}`,
|
|
173
|
-
allowedAdapters: isRecord(manifest) ? manifest.allowedAdapters : undefined,
|
|
174
|
-
resolvePath: (adapterPath) => `${packagePath}/${adapterPath}`,
|
|
175
|
-
});
|
|
176
|
-
writeJson(`${packagePath}/.effect-skill.json`, manifest);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
for (const workspacePath of workspacePackagePaths()) {
|
|
180
|
-
const packagePath = String(workspacePath);
|
|
181
|
-
const manifestFile = `${packagePath}/.effect-skill.json`;
|
|
182
|
-
if (fs.existsSync(path.join(root, manifestFile)) && !expectedPackageFiles.has(manifestFile)) {
|
|
183
|
-
failures.push(`${manifestFile} exists but is not declared in docs/effect-skill.json`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (failures.length > 0) {
|
|
189
|
-
console.error(failures.join("\n"));
|
|
190
|
-
process.exit(1);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
console.log(check ? "effect skill manifests are current" : "effect skill manifests updated");
|