@yansirplus/cli 0.5.17 → 0.5.18

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.
Files changed (52) hide show
  1. package/README.md +12 -6
  2. package/agent-catalog/agentOS/SKILL.md +22 -0
  3. package/agent-catalog/agentOS/references/agent/decision-graph.json +530 -0
  4. package/agent-catalog/agentOS/references/agent/errors.json +497 -0
  5. package/agent-catalog/agentOS/references/agent/invariant-matrix.json +337 -0
  6. package/agent-catalog/agentOS/references/agent/primitives.json +989 -0
  7. package/agent-catalog/agentOS/references/agent/recipes.json +109 -0
  8. package/agent-catalog/agentOS/references/agent/start-here.md +25 -0
  9. package/agent-catalog/agentOS/references/package-map.md +72 -0
  10. package/agent-catalog/agentOS/references/provenance.json +251 -0
  11. package/agent-catalog/agentOS/references/public-api/cli.md +20 -0
  12. package/agent-catalog/agentOS/references/public-api/client.md +88 -0
  13. package/agent-catalog/agentOS/references/public-api/core.md +1817 -0
  14. package/agent-catalog/agentOS/references/public-api/runtime.md +794 -0
  15. package/dist/build/agent-authoring/config.d.ts +20 -5
  16. package/dist/build/agent-authoring/config.js +132 -32
  17. package/dist/build/agent-authoring/manifest-compiler.d.ts +131 -2
  18. package/dist/build/agent-authoring/manifest-compiler.js +630 -8
  19. package/dist/build/agent-authoring/shared.d.ts +2 -0
  20. package/dist/build/agent-authoring/shared.js +2 -0
  21. package/dist/build/agent-authoring/static-target.d.ts +6 -3
  22. package/dist/build/agent-authoring/static-target.js +1807 -286
  23. package/dist/build/agent-authoring.d.ts +3 -3
  24. package/dist/build/agent-authoring.js +1 -1
  25. package/dist/build/build-cli.d.ts +1 -1
  26. package/dist/build/build-cli.js +1614 -26
  27. package/dist/check/algorithmic/client-boundary-checks.mjs +3 -34
  28. package/dist/check/algorithmic/convergence-smoke-checks.mjs +652 -6
  29. package/dist/check/algorithmic/distribution-checks.mjs +8 -7
  30. package/dist/check/algorithmic/package-boundary-checks.mjs +3 -2
  31. package/dist/check/algorithmic/repo-surface-checks.mjs +55 -1
  32. package/dist/check/algorithmic/static-target-checks.mjs +83 -5
  33. package/dist/check/algorithmic-checks.mjs +10 -17
  34. package/dist/check/default-gate.mjs +3 -3
  35. package/dist/check/effect-scan-gate.mjs +121 -0
  36. package/dist/check/package-graph.mjs +2 -32
  37. package/dist/consumer-overlay.mjs +802 -0
  38. package/dist/lib/public-api-model.mjs +19 -0
  39. package/dist/lib/repo-source-files.mjs +26 -0
  40. package/dist/lib/ts-module-loader.mjs +44 -0
  41. package/dist/lib/workspace-manifest.mjs +77 -0
  42. package/dist/main.mjs +151 -21
  43. package/package.json +8 -4
  44. package/dist/check/check-coverage.mjs +0 -231
  45. package/dist/generate/generate-agent-docs.mjs +0 -435
  46. package/dist/generate/generate-carrier-reference.mjs +0 -514
  47. package/dist/generate/generate-docs.mjs +0 -345
  48. package/dist/generate/generate-effect-skill-manifests.mjs +0 -193
  49. package/dist/generate/project-docs-site.mjs +0 -190
  50. package/dist/lib/boundary-rules.mjs +0 -63
  51. package/dist/lib/capability-routes.mjs +0 -354
  52. package/dist/lib/projection-sink.mjs +0 -113
@@ -167,6 +167,25 @@ const sourceExportRecordsFromAst = (file, entrypoint, seen) => {
167
167
  records.push(
168
168
  ...sourceExportRecordsFromAst(resolveRelativeModule(abs, specifier), entrypoint, seen),
169
169
  );
170
+ } else if (
171
+ statement.exportClause !== undefined &&
172
+ ts.isNamedExports(statement.exportClause) &&
173
+ specifier !== null &&
174
+ specifier.startsWith(".")
175
+ ) {
176
+ const target = resolveRelativeModule(abs, specifier);
177
+ const targetRecords = sourceExportRecordsFromAst(target, entrypoint, new Set(seen));
178
+ for (const element of statement.exportClause.elements) {
179
+ const importedName = element.propertyName?.text ?? element.name.text;
180
+ const exportedName = element.name.text;
181
+ const targetRecord = targetRecords.find((record) => record.name === importedName);
182
+ if (targetRecord === undefined) continue;
183
+ records.push({
184
+ ...targetRecord,
185
+ name: exportedName,
186
+ key: `${entrypoint}:${exportedName}`,
187
+ });
188
+ }
170
189
  }
171
190
  continue;
172
191
  }
@@ -0,0 +1,26 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export const repoSourceIgnoredDirectoryNames = Object.freeze(
5
+ new Set(["node_modules", "dist", ".wrangler", ".turbo", ".parallel", ".cst", ".git", ".codex"]),
6
+ );
7
+
8
+ const compare = (left, right) => left.localeCompare(right);
9
+
10
+ const toRepoPath = (file) => file.split(path.sep).join("/");
11
+
12
+ export const walkRepoSourceFiles = (repoRoot, relativePath = ".", options = {}) => {
13
+ const absolutePath = path.join(repoRoot, relativePath);
14
+ if (!fs.existsSync(absolutePath)) return [];
15
+ const stat = fs.statSync(absolutePath);
16
+ if (stat.isFile()) return [toRepoPath(relativePath)];
17
+ const ignored = options.ignored ?? repoSourceIgnoredDirectoryNames;
18
+ const files = [];
19
+ for (const entry of fs.readdirSync(absolutePath, { withFileTypes: true })) {
20
+ if (entry.isDirectory() && ignored.has(entry.name)) continue;
21
+ const child = path.join(relativePath, entry.name);
22
+ if (entry.isDirectory()) files.push(...walkRepoSourceFiles(repoRoot, child, options));
23
+ if (entry.isFile()) files.push(toRepoPath(child));
24
+ }
25
+ return files.sort(compare);
26
+ };
@@ -0,0 +1,44 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdir, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ import { build } from "esbuild";
8
+
9
+ const compare = (left, right) => left.localeCompare(right);
10
+
11
+ export const bundleModuleForNode = async (
12
+ entryPoint,
13
+ { define = {}, external = [], prefix = "agentos-ts-module-", tempRoot = os.tmpdir() } = {},
14
+ ) => {
15
+ const outDir = path.join(tempRoot, `${prefix}${randomUUID()}`);
16
+ const outfile = path.join(outDir, "entry.mjs");
17
+ await mkdir(outDir, { recursive: true });
18
+ await build({
19
+ entryPoints: [entryPoint],
20
+ outfile,
21
+ bundle: true,
22
+ format: "esm",
23
+ platform: "node",
24
+ target: "node22",
25
+ define,
26
+ external: [...new Set(["esbuild", "cloudflare:*", ...external])].sort(compare),
27
+ logLevel: "silent",
28
+ });
29
+ return {
30
+ outfile,
31
+ cleanup: async () => {
32
+ await rm(outDir, { recursive: true, force: true });
33
+ },
34
+ };
35
+ };
36
+
37
+ export const importBundledModule = async (entryPoint, options = {}) => {
38
+ const bundled = await bundleModuleForNode(entryPoint, options);
39
+ try {
40
+ return await import(`${pathToFileURL(bundled.outfile).href}?agentos=${Date.now()}`);
41
+ } finally {
42
+ await bundled.cleanup();
43
+ }
44
+ };
@@ -0,0 +1,77 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import { parse as parseYaml } from "yaml";
5
+
6
+ const compare = (left, right) => left.localeCompare(right);
7
+ const readJsonFile = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
8
+
9
+ const workspaceManifestPath = (repoRoot) => path.join(repoRoot, "pnpm-workspace.yaml");
10
+
11
+ const requireRecord = (value, label) => {
12
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
13
+ throw new Error(`${label} must be an object`);
14
+ }
15
+ return value;
16
+ };
17
+
18
+ export const readWorkspaceManifest = (repoRoot) => {
19
+ const file = workspaceManifestPath(repoRoot);
20
+ if (!fs.existsSync(file)) {
21
+ throw new Error("pnpm-workspace.yaml is the workspace manifest SSOT and must exist");
22
+ }
23
+ const manifest = requireRecord(parseYaml(fs.readFileSync(file, "utf8")), "pnpm-workspace.yaml");
24
+ return manifest;
25
+ };
26
+
27
+ export const workspacePackagePatterns = (repoRoot) => {
28
+ const packages = readWorkspaceManifest(repoRoot).packages;
29
+ if (!Array.isArray(packages)) {
30
+ throw new Error("pnpm-workspace.yaml packages must be an array");
31
+ }
32
+ return packages.filter((entry) => typeof entry === "string").sort(compare);
33
+ };
34
+
35
+ export const workspaceCatalog = (repoRoot) => {
36
+ const catalog = readWorkspaceManifest(repoRoot).catalog;
37
+ if (catalog === undefined) return {};
38
+ return requireRecord(catalog, "pnpm-workspace.yaml catalog");
39
+ };
40
+
41
+ export const workspaceOverrides = (repoRoot) => {
42
+ const overrides = readWorkspaceManifest(repoRoot).overrides;
43
+ if (overrides === undefined) return {};
44
+ return requireRecord(overrides, "pnpm-workspace.yaml overrides");
45
+ };
46
+
47
+ export const workspacePackagePaths = (repoRoot) => {
48
+ const paths = new Set();
49
+ for (const workspace of workspacePackagePatterns(repoRoot)) {
50
+ if (workspace.endsWith("/*")) {
51
+ const base = workspace.slice(0, -2);
52
+ const baseDir = path.join(repoRoot, base);
53
+ if (!fs.existsSync(baseDir)) continue;
54
+ for (const entry of fs.readdirSync(baseDir, { withFileTypes: true })) {
55
+ if (!entry.isDirectory()) continue;
56
+ const packagePath = `${base}/${entry.name}`;
57
+ if (fs.existsSync(path.join(repoRoot, packagePath, "package.json"))) {
58
+ paths.add(packagePath);
59
+ }
60
+ }
61
+ continue;
62
+ }
63
+
64
+ if (fs.existsSync(path.join(repoRoot, workspace, "package.json"))) {
65
+ paths.add(workspace);
66
+ }
67
+ }
68
+ return [...paths].sort(compare);
69
+ };
70
+
71
+ export const workspacePackageRecords = (repoRoot) =>
72
+ workspacePackagePaths(repoRoot)
73
+ .map((packagePath) => ({
74
+ name: readJsonFile(path.join(repoRoot, packagePath, "package.json")).name,
75
+ path: packagePath,
76
+ }))
77
+ .sort((left, right) => left.path.localeCompare(right.path));
package/dist/main.mjs CHANGED
@@ -1,7 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from "node:child_process";
3
- import { fileURLToPath } from "node:url";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import path from "node:path";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
6
 
7
+ import { bundleModuleForNode } from "./lib/ts-module-loader.mjs";
8
+ import {
9
+ consumerCheck,
10
+ consumerStatus,
11
+ installConsumer,
12
+ restoreConsumer,
13
+ } from "./consumer-overlay.mjs";
5
14
  import {
6
15
  algorithmicCheckerAcceptsArgs,
7
16
  hasAlgorithmicChecker,
@@ -13,9 +22,27 @@ import {
13
22
  runAffectedGates,
14
23
  } from "./check/gate-selector.mjs";
15
24
  import { runDefaultGate } from "./check/default-gate.mjs";
25
+ import { runEffectScanGate } from "./check/effect-scan-gate.mjs";
16
26
  import { listGuards, runGroup, runGuard } from "./runner.mjs";
17
27
 
18
- const version = "0.5.16";
28
+ const packageRootFromMain = () => path.dirname(path.dirname(fileURLToPath(import.meta.url)));
29
+
30
+ const repoRootFromMain = () => path.dirname(path.dirname(packageRootFromMain()));
31
+
32
+ const readReleaseVersion = () => {
33
+ const rootPackagePath = path.join(repoRootFromMain(), "package.json");
34
+ const packagePath = existsSync(rootPackagePath)
35
+ ? rootPackagePath
36
+ : path.join(packageRootFromMain(), "package.json");
37
+ const packageJson = JSON.parse(readFileSync(packagePath, "utf8"));
38
+ const version = packageJson.agentOsRelease?.version ?? packageJson.version;
39
+ if (typeof version !== "string" || version.length === 0) {
40
+ throw new Error("package.json version must be a non-empty string");
41
+ }
42
+ return version;
43
+ };
44
+
45
+ const version = readReleaseVersion();
19
46
 
20
47
  const helpText = `agentOS repository CLI ${version}
21
48
 
@@ -23,11 +50,21 @@ Usage:
23
50
  agentos --help
24
51
  agentos --version
25
52
  agentos build [--cwd <path>] [--config <path>] [--package-scope <scope>]
53
+ agentos info [--cwd <path>] [--config <path>] [--json]
54
+ agentos serve [--cwd <path>] [--config <path>] [--package-scope <scope>] [--host <host>] [--port <port>] [--llm config|test] [--llm-response <text>] [--json]
55
+ agentos dev [--cwd <path>] [--config <path>] [--package-scope <scope>] [--host <host>] [--port <port>] [--llm config|test] [--llm-response <text>] [--json]
56
+ agentos eval [--cwd <path>] [--config <path>] [--package-scope <scope>] [--target local|remote] [--base-url <url>] [--header <name=value>] [--llm config|test] [--llm-response <text>] [--json]
57
+ agentos preflight llm [--cwd <path>] [--config <path>] [--route <binding-ref>] [--json]
58
+ agentos consumer install /path/to/consumer [--from-manifest <path>] [--no-install] [--skip-pack] [--json]
59
+ agentos consumer status /path/to/consumer [--json] [--check-npm] [--registry <url>]
60
+ agentos consumer check /path/to/consumer [--json] [--check-npm] [--registry <url>]
61
+ agentos consumer restore /path/to/consumer [--no-install] [--json]
26
62
  agentos check all
27
63
  agentos check default
28
64
  agentos check structural
29
65
  agentos check affected [--base <ref>] [--head <ref>] [--json] [--explain] [--run]
30
66
  agentos check docs
67
+ agentos check effect-scan [--repo <path>] [--evidence <path>] [--scanner <command>]
31
68
  agentos check effect-manifests
32
69
  agentos check release
33
70
  agentos check site
@@ -50,6 +87,12 @@ const fail = (message) => {
50
87
  if (
51
88
  message.startsWith("agentos:") ||
52
89
  message.startsWith("agentos build:") ||
90
+ message.startsWith("agentos info:") ||
91
+ message.startsWith("agentos serve:") ||
92
+ message.startsWith("agentos dev:") ||
93
+ message.startsWith("agentos eval:") ||
94
+ message.startsWith("agentos preflight:") ||
95
+ message.startsWith("agentos consumer:") ||
53
96
  message.startsWith("agentos check:") ||
54
97
  message.startsWith("agentos generate:")
55
98
  ) {
@@ -64,25 +107,89 @@ const expectNoExtraArgs = (args, command) => {
64
107
  }
65
108
  };
66
109
 
67
- const runBuild = async (args) => {
110
+ const runBuildRunner = async (command, args) => {
68
111
  const runner = fileURLToPath(new URL("./build/build-cli.js", import.meta.url));
69
- await new Promise((resolve, reject) => {
70
- const child = spawn("bun", [runner, "build", ...args], { stdio: "inherit" });
71
- child.on("error", (error) => {
72
- reject(new Error(`agentos build: failed to start bun: ${error.message}`));
73
- });
74
- child.on("exit", (code, signal) => {
75
- if (signal !== null) {
76
- reject(new Error(`agentos build: build runner terminated by ${signal}`));
77
- return;
78
- }
79
- if (code !== 0) {
80
- reject(new Error(`agentos build: build runner failed with exit code ${code ?? 1}`));
81
- return;
82
- }
83
- resolve();
84
- });
112
+ const bundled = await bundleModuleForNode(runner, {
113
+ prefix: "agentos-build-runner-",
114
+ tempRoot: path.join(packageRootFromMain(), "node_modules", ".cache", "agentos-build"),
85
115
  });
116
+ try {
117
+ await new Promise((resolve, reject) => {
118
+ const child = spawn(process.execPath, [bundled.outfile, command, ...args], {
119
+ stdio: "inherit",
120
+ });
121
+ child.on("error", (error) => {
122
+ reject(
123
+ new Error(`agentos ${command}: failed to start node build runner: ${error.message}`),
124
+ );
125
+ });
126
+ child.on("exit", (code, signal) => {
127
+ if (signal !== null) {
128
+ reject(new Error(`agentos ${command}: build runner terminated by ${signal}`));
129
+ return;
130
+ }
131
+ if (code !== 0) {
132
+ process.exitCode = code ?? 1;
133
+ }
134
+ resolve();
135
+ });
136
+ });
137
+ } finally {
138
+ await bundled.cleanup();
139
+ }
140
+ };
141
+
142
+ const runBuild = async (args) => runBuildRunner("build", args);
143
+
144
+ const runInfo = async (args) => runBuildRunner("info", args);
145
+
146
+ const runServe = async (args) => runBuildRunner("serve", args);
147
+
148
+ const runDev = async (args) => runBuildRunner("dev", args);
149
+
150
+ const runEval = async (args) => runBuildRunner("eval", args);
151
+
152
+ const runPreflight = async (args) => runBuildRunner("preflight", args);
153
+
154
+ const sourceConsumerProducer = () => {
155
+ const modulePath = path.join(repoRootFromMain(), "tooling/distribution/pack-check.mjs");
156
+ const supportPath = path.join(repoRootFromMain(), "tooling/distribution/support.mjs");
157
+ if (!existsSync(modulePath) || !existsSync(supportPath)) return undefined;
158
+ return {
159
+ sourceRoot: repoRootFromMain(),
160
+ defaultInstallManifestPath: path.join(
161
+ repoRootFromMain(),
162
+ "dist/internal-npm/install-manifest.json",
163
+ ),
164
+ produceInstallManifest: async () => {
165
+ const producer = await import(pathToFileURL(modulePath).href);
166
+ producer.packInternal();
167
+ return path.join(repoRootFromMain(), "dist/internal-npm/install-manifest.json");
168
+ },
169
+ };
170
+ };
171
+
172
+ const runConsumer = async (args) => {
173
+ const [command, ...rest] = args;
174
+ const commandArgs = rest[0] === "--" ? rest.slice(1) : rest;
175
+ const sourceContext = sourceConsumerProducer() ?? {};
176
+ const context = { packageRoot: packageRootFromMain(), ...sourceContext };
177
+ switch (command) {
178
+ case "install":
179
+ await installConsumer(commandArgs, context);
180
+ return;
181
+ case "status":
182
+ consumerStatus(commandArgs, context);
183
+ return;
184
+ case "check":
185
+ consumerCheck(commandArgs, context);
186
+ return;
187
+ case "restore":
188
+ restoreConsumer(commandArgs, context);
189
+ return;
190
+ default:
191
+ throw new Error("agentos consumer: choose one of install, status, check, restore");
192
+ }
86
193
  };
87
194
 
88
195
  const runCheck = async (args) => {
@@ -134,6 +241,9 @@ const runCheck = async (args) => {
134
241
  expectNoExtraArgs(rest, "agentos check docs");
135
242
  await runGroup("check-docs");
136
243
  return;
244
+ case "effect-scan":
245
+ runEffectScanGate(rest, { defaultRepoRoot: repoRootFromMain() });
246
+ return;
137
247
  case "effect-manifests":
138
248
  expectNoExtraArgs(rest, "agentos check effect-manifests");
139
249
  await runGroup("check-effect-manifests");
@@ -170,7 +280,7 @@ const runCheck = async (args) => {
170
280
  return;
171
281
  }
172
282
  throw new Error(
173
- "agentos check: choose one of all, default, structural, affected, docs, effect-manifests, release, site, guard-coverage, guard, guards, or an algorithmic checker id",
283
+ "agentos check: choose one of all, default, structural, affected, docs, effect-scan, effect-manifests, release, site, guard-coverage, guard, guards, or an algorithmic checker id",
174
284
  );
175
285
  }
176
286
  };
@@ -215,6 +325,24 @@ const main = async () => {
215
325
  case "build":
216
326
  await runBuild(rest);
217
327
  return;
328
+ case "info":
329
+ await runInfo(rest);
330
+ return;
331
+ case "serve":
332
+ await runServe(rest);
333
+ return;
334
+ case "dev":
335
+ await runDev(rest);
336
+ return;
337
+ case "eval":
338
+ await runEval(rest);
339
+ return;
340
+ case "preflight":
341
+ await runPreflight(rest);
342
+ return;
343
+ case "consumer":
344
+ await runConsumer(rest);
345
+ return;
218
346
  case "check":
219
347
  await runCheck(rest);
220
348
  return;
@@ -222,7 +350,9 @@ const main = async () => {
222
350
  await runGenerate(rest);
223
351
  return;
224
352
  default:
225
- throw new Error("agentos: choose one of build, check, generate");
353
+ throw new Error(
354
+ "agentos: choose one of build, info, serve, dev, eval, preflight, consumer, check, generate",
355
+ );
226
356
  }
227
357
  };
228
358
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yansirplus/cli",
3
- "version": "0.5.17",
3
+ "version": "0.5.18",
4
4
  "type": "module",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": {
@@ -19,14 +19,18 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
+ "agent-catalog",
22
23
  "README.md",
23
24
  "PUBLIC_API.md"
24
25
  ],
25
26
  "dependencies": {
26
- "@yansirplus/core": "0.5.17",
27
- "@yansirplus/runtime": "0.5.17"
27
+ "@yansirplus/core": "0.5.18",
28
+ "@yansirplus/evals": "0.5.18",
29
+ "@yansirplus/runtime": "0.5.18",
30
+ "esbuild": "0.27.3",
31
+ "yaml": "2.9.0"
28
32
  },
29
33
  "peerDependencies": {
30
- "effect": "^4.0.0-beta.84"
34
+ "effect": "4.0.0-beta.84"
31
35
  }
32
36
  }
@@ -1,231 +0,0 @@
1
- #!/usr/bin/env node
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { listAlgorithmicCheckers } from "./algorithmic-checks.mjs";
6
-
7
- const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../..");
8
-
9
- const readJson = (relativePath) =>
10
- JSON.parse(fs.readFileSync(path.join(repoRoot, relativePath), "utf8"));
11
-
12
- const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
13
- const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
14
-
15
- const failures = [];
16
- const fail = (message) => failures.push(message);
17
-
18
- const coverage = readJson("packages/cli/src/check/check-coverage.source.json");
19
- const rulesSource = readJson("docs/agent/boundary-rules.source.json");
20
- const cliPackage = readJson("packages/cli/package.json");
21
-
22
- if (coverage.schemaVersion !== 1) fail("check coverage schemaVersion must be 1");
23
- if (!Array.isArray(coverage.entries) || coverage.entries.length === 0) {
24
- fail("check coverage entries must be non-empty");
25
- }
26
-
27
- const rules = new Map((rulesSource.rules ?? []).map((rule) => [rule.id, rule]));
28
- const algorithmicCheckers = new Set(listAlgorithmicCheckers());
29
- const coveredRuleIds = new Set();
30
- const genericPackageNegativeWitness =
31
- "package-owned test fails when the owned contract is violated";
32
-
33
- const assertProofWitnesses = (label, assertionIndex, assertion) => {
34
- const witnesses = assertion.packageWitnesses;
35
- if (witnesses === undefined) return;
36
- if (!Array.isArray(witnesses) || witnesses.length === 0)
37
- fail(`${label}: assertions[${assertionIndex}] packageWitnesses must be non-empty when present`);
38
-
39
- for (const [witnessIndex, witness] of witnesses.entries()) {
40
- const witnessLabel = `${label}: assertions[${assertionIndex}].packageWitnesses[${witnessIndex}]`;
41
- if (!isRecord(witness)) {
42
- fail(`${witnessLabel} must be an object`);
43
- continue;
44
- }
45
- if (typeof witness.file !== "string" || !witness.file.startsWith("packages/")) {
46
- fail(`${witnessLabel}.file must be a package test path`);
47
- continue;
48
- }
49
- if (typeof witness.name !== "string" || witness.name.length === 0) {
50
- fail(`${witnessLabel}.name must be non-empty`);
51
- continue;
52
- }
53
-
54
- const absolutePath = path.join(repoRoot, witness.file);
55
- if (!fs.existsSync(absolutePath)) {
56
- fail(`${witnessLabel}.file does not exist`);
57
- continue;
58
- }
59
- const source = fs.readFileSync(absolutePath, "utf8");
60
- const testNamePattern = new RegExp(
61
- `\\b(?:describe|it|test)(?:\\.effect)?\\(\\s*(["'\`])${escapeRegExp(witness.name)}\\1`,
62
- "u",
63
- );
64
- if (!testNamePattern.test(source)) {
65
- fail(`${witnessLabel}.name was not found as a test or describe name`);
66
- }
67
- }
68
- };
69
-
70
- for (const [index, entry] of (coverage.entries ?? []).entries()) {
71
- const label =
72
- isRecord(entry) && typeof entry.ruleId === "string" ? entry.ruleId : `entry[${index}]`;
73
- if (!isRecord(entry)) {
74
- fail(`${label}: coverage entry must be an object`);
75
- continue;
76
- }
77
- if (!isRecord(entry.source)) fail(`${label}: missing source`);
78
- if (!isRecord(entry.target)) fail(`${label}: missing target`);
79
- if (!Array.isArray(entry.assertions) || entry.assertions.length === 0) {
80
- fail(`${label}: assertions must be non-empty`);
81
- } else {
82
- for (const [assertionIndex, assertion] of entry.assertions.entries()) {
83
- if (!isRecord(assertion)) {
84
- fail(`${label}: assertions[${assertionIndex}] must be an object`);
85
- continue;
86
- }
87
- if (
88
- typeof assertion.failureCondition !== "string" ||
89
- assertion.failureCondition.length === 0
90
- ) {
91
- fail(`${label}: assertions[${assertionIndex}] missing failureCondition`);
92
- }
93
- if (typeof assertion.negativeWitness !== "string" || assertion.negativeWitness.length === 0) {
94
- fail(`${label}: assertions[${assertionIndex}] missing negativeWitness`);
95
- }
96
- if (assertion.negativeWitness === genericPackageNegativeWitness) {
97
- fail(`${label}: assertions[${assertionIndex}] has generic package negativeWitness`);
98
- }
99
- }
100
- }
101
-
102
- if (typeof entry.ruleId === "string" && entry.ruleId.includes(",")) {
103
- for (const ruleId of entry.ruleId.split(",")) coveredRuleIds.add(ruleId);
104
- } else if (typeof entry.ruleId === "string") {
105
- coveredRuleIds.add(entry.ruleId);
106
- }
107
-
108
- if (entry.target?.kind === "algorithmic") {
109
- const rule = rules.get(entry.target.ruleId);
110
- if (!isRecord(rule)) {
111
- fail(`${label}: algorithmic target references unknown rule ${entry.target.ruleId}`);
112
- } else if (rule.acceptance?.engine !== "algorithmic") {
113
- fail(`${label}: algorithmic target rule must use algorithmic acceptance`);
114
- } else if (rule.acceptance.checker !== entry.target.checker) {
115
- fail(`${label}: coverage checker ${entry.target.checker} does not match rule acceptance`);
116
- }
117
- if (!algorithmicCheckers.has(entry.target.checker)) {
118
- fail(`${label}: missing algorithmic checker ${entry.target.checker}`);
119
- }
120
- }
121
-
122
- if (entry.target?.kind === "proofClass") {
123
- const rule = rules.get(entry.target.ruleId);
124
- if (!isRecord(rule)) {
125
- fail(`${label}: proofClass target references unknown rule ${entry.target.ruleId}`);
126
- } else if (rule.acceptance?.engine !== "proofClass") {
127
- fail(`${label}: proofClass target rule must use proofClass acceptance`);
128
- } else {
129
- for (const [assertionIndex, assertion] of (entry.assertions ?? []).entries()) {
130
- if (isRecord(assertion)) {
131
- assertProofWitnesses(label, assertionIndex, assertion);
132
- }
133
- }
134
- }
135
- }
136
-
137
- if (entry.target?.kind === "manifestRule") {
138
- const rule = rules.get(entry.target.ruleId);
139
- if (!isRecord(rule)) {
140
- fail(`${label}: manifestRule target references unknown rule ${entry.target.ruleId}`);
141
- } else if (rule.acceptance?.engine !== entry.target.engine) {
142
- fail(`${label}: manifestRule target engine must match rule acceptance`);
143
- }
144
- }
145
-
146
- if (typeof entry.ruleId === "string" && !entry.ruleId.includes(",")) {
147
- const rule = rules.get(entry.ruleId);
148
- if (isRecord(rule) && entry.owner !== rule.owner) {
149
- fail(
150
- `${label}: coverage owner ${entry.owner} does not match boundary rule owner ${rule.owner}`,
151
- );
152
- }
153
- }
154
-
155
- if (entry.target?.kind === "algorithmic-helper") {
156
- for (const checker of entry.target.checkers ?? []) {
157
- if (!algorithmicCheckers.has(checker))
158
- fail(`${label}: helper references unknown checker ${checker}`);
159
- }
160
- }
161
- }
162
-
163
- for (const rule of rulesSource.rules ?? []) {
164
- if (!coveredRuleIds.has(rule.id)) fail(`${rule.id}: rule lacks check coverage entry`);
165
- }
166
-
167
- const sourceText = fs.readFileSync(
168
- path.join(repoRoot, "docs/agent/boundary-rules.source.json"),
169
- "utf8",
170
- );
171
- if (sourceText.includes("positiveAcceptance")) {
172
- fail("docs/agent/boundary-rules.source.json: positiveAcceptance is no longer allowed");
173
- }
174
- if (/\s--fix(?:\s|")/u.test(sourceText)) {
175
- fail("docs/agent/boundary-rules.source.json: check commands must not include --fix");
176
- }
177
-
178
- const walk = (relativePath) => {
179
- const absolutePath = path.join(repoRoot, relativePath);
180
- const files = [];
181
- for (const entry of fs.readdirSync(absolutePath, { withFileTypes: true })) {
182
- const child = path.join(relativePath, entry.name);
183
- if (entry.isDirectory()) files.push(...walk(child));
184
- if (entry.isFile()) files.push(child.split(path.sep).join("/"));
185
- }
186
- return files;
187
- };
188
-
189
- const cliMjsFiles = walk("packages/cli/src").filter((file) => file.endsWith(".mjs"));
190
- const checkMjsFiles = cliMjsFiles.filter((file) => file.startsWith("packages/cli/src/check/"));
191
- if (cliMjsFiles.length > 19) {
192
- fail(`packages/cli/src: expected at most 19 .mjs files; observed ${cliMjsFiles.length}`);
193
- }
194
- if (checkMjsFiles.length > 12) {
195
- fail(`packages/cli/src/check: expected at most 12 .mjs files; observed ${checkMjsFiles.length}`);
196
- }
197
- for (const file of checkMjsFiles) {
198
- const content = fs.readFileSync(path.join(repoRoot, file), "utf8");
199
- const selfTestFlag = "--" + "self-test";
200
- if (content.includes(selfTestFlag)) fail(`${file}: per-checker self-test flag is not allowed`);
201
- const harnessPattern = new RegExp(
202
- ["mkd" + "te" + "mp", "tm" + "pdir", "Tem" + "por" + "ary", "te" + "mp"].join("|"),
203
- "i",
204
- );
205
- if (file !== "packages/cli/src/check/algorithmic-checks.mjs" && harnessPattern.test(content)) {
206
- fail(`${file}: per-checker ad hoc harness is not allowed`);
207
- }
208
- }
209
-
210
- const cliDependencies = Object.keys(cliPackage.dependencies ?? {});
211
- for (const dependency of cliDependencies) {
212
- if (
213
- dependency === "@effect/cli" ||
214
- dependency === "@effect/platform-node" ||
215
- dependency === "@effect/printer" ||
216
- dependency === "@effect/printer-ansi" ||
217
- dependency === "@effect/typeclass"
218
- ) {
219
- fail(`packages/cli/package.json: removed Effect CLI dependency returned: ${dependency}`);
220
- }
221
- }
222
- if (cliPackage.dependencies?.effect === "3.21.2") {
223
- fail("packages/cli/package.json: Effect v3 CLI dependency returned");
224
- }
225
-
226
- if (failures.length > 0) {
227
- console.error(failures.join("\n"));
228
- process.exit(1);
229
- }
230
-
231
- console.log("guard coverage passed");