@abaigram/abaikit 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -2
- package/abai/identity.md +1 -0
- package/abai/learnabai.md +546 -0
- package/abai/use.md +1 -0
- package/abaigram.yaml +208 -0
- package/dist/abaify.d.ts +68 -0
- package/dist/abaify.d.ts.map +1 -0
- package/dist/abaify.js +310 -0
- package/dist/abaify.js.map +1 -0
- package/dist/abaigram.d.ts +18 -0
- package/dist/abaigram.d.ts.map +1 -0
- package/dist/abaigram.js +153 -0
- package/dist/abaigram.js.map +1 -0
- package/dist/app.d.ts +19 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +29 -0
- package/dist/app.js.map +1 -0
- package/dist/bin.d.ts +46 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +274 -0
- package/dist/bin.js.map +1 -0
- package/dist/check.d.ts +119 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +416 -0
- package/dist/check.js.map +1 -0
- package/dist/command.d.ts +41 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +179 -0
- package/dist/command.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +12 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +20 -0
- package/dist/io.js.map +1 -0
- package/dist/learnabai.d.ts +60 -0
- package/dist/learnabai.d.ts.map +1 -0
- package/dist/learnabai.js +199 -0
- package/dist/learnabai.js.map +1 -0
- package/dist/new.d.ts +26 -0
- package/dist/new.d.ts.map +1 -0
- package/dist/new.js +87 -0
- package/dist/new.js.map +1 -0
- package/dist/repo.d.ts +99 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +482 -0
- package/dist/repo.js.map +1 -0
- package/dist/run.d.ts +6 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +47 -0
- package/dist/run.js.map +1 -0
- package/dist/subject.d.ts +15 -0
- package/dist/subject.d.ts.map +1 -0
- package/dist/subject.js +53 -0
- package/dist/subject.js.map +1 -0
- package/dist/template-ts.d.ts +21 -0
- package/dist/template-ts.d.ts.map +1 -0
- package/dist/template-ts.js +322 -0
- package/dist/template-ts.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/examples/hello/abai/identity.md +1 -0
- package/examples/hello/abaigram.yaml +24 -0
- package/examples/hello/hello.ts +18 -0
- package/package.json +40 -5
package/dist/bin.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The abaikit binary: learnabai prints the guide, check verifies a
|
|
3
|
+
// repository's conformance, new creates a fresh abaigram, abaify retrofits
|
|
4
|
+
// an existing repository, and run starts a repository's program with an
|
|
5
|
+
// agent attached. `abaikit abai ...` is abaikit's own standard agent entry,
|
|
6
|
+
// served by the stub, since abaikit is itself an abaigram.
|
|
7
|
+
import { realpathSync } from "node:fs";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
import { runEntry } from "@abaigram/abai-stub";
|
|
10
|
+
import { runAbaify } from "./abaify.js";
|
|
11
|
+
import { createApp } from "./app.js";
|
|
12
|
+
import { runCheck } from "./check.js";
|
|
13
|
+
import { errorMessage, processIo, UsageError } from "./io.js";
|
|
14
|
+
import { runLearnabai } from "./learnabai.js";
|
|
15
|
+
import { runNew } from "./new.js";
|
|
16
|
+
import { runRun } from "./run.js";
|
|
17
|
+
export { processIo, UsageError } from "./io.js";
|
|
18
|
+
const USAGE = [
|
|
19
|
+
"usage: abaikit learnabai|check|new|abaify|run ...",
|
|
20
|
+
" abaikit learnabai [--section <name>] [--json] [--skill] [--skill-dir <dir>]",
|
|
21
|
+
" abaikit check <repo> [--level <1-4>] [--mode <turns|thread|sidecar>] [--entry <cmd>] [--runs <n>] [--timeout <seconds>] [--json]",
|
|
22
|
+
" abaikit new <name> --lang <ts> [--level <1-4>] [--dir <dir>]",
|
|
23
|
+
" abaikit abaify <repo> [--level <1-4>] [--mode <turns|thread|sidecar>] [--rounds <n>] [--dry-run]",
|
|
24
|
+
" abaikit run <repo> [--] ARGS",
|
|
25
|
+
" abaikit abai ... the standard agent entry (session, install, manifest, doctor, config, mcp, run)",
|
|
26
|
+
"",
|
|
27
|
+
"learnabai prints the guide for an agent; check verifies a repository's conformance and needs no model;",
|
|
28
|
+
"new creates a fresh abaigram; abaify retrofits an existing repository, adding and never removing;",
|
|
29
|
+
"run starts the repository's program with everything after -- as its arguments.",
|
|
30
|
+
];
|
|
31
|
+
function positiveInt(value, flag) {
|
|
32
|
+
const n = Number(value);
|
|
33
|
+
if (!Number.isInteger(n) || n <= 0)
|
|
34
|
+
throw new UsageError(`${flag} needs a positive integer`);
|
|
35
|
+
return n;
|
|
36
|
+
}
|
|
37
|
+
/** The verb's flags and positionals; a flag missing its value, an unknown flag or a stray argument is a usage error. */
|
|
38
|
+
function scan(verb, argv, kinds) {
|
|
39
|
+
const positionals = [];
|
|
40
|
+
const values = {};
|
|
41
|
+
for (let i = 0; i < argv.length; i++) {
|
|
42
|
+
const raw = argv[i];
|
|
43
|
+
if (raw === "--")
|
|
44
|
+
throw new UsageError(`${verb} takes no program command`);
|
|
45
|
+
if (!raw.startsWith("-")) {
|
|
46
|
+
positionals.push(raw);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// `--flag value` and `--flag=value`, as the stub's entry accepts them
|
|
50
|
+
const eq = raw.indexOf("=");
|
|
51
|
+
const arg = eq === -1 ? raw : raw.slice(0, eq);
|
|
52
|
+
const kind = kinds[arg];
|
|
53
|
+
if (kind === undefined)
|
|
54
|
+
throw new UsageError(`unknown argument ${arg}`);
|
|
55
|
+
if (kind === "bool") {
|
|
56
|
+
if (eq !== -1)
|
|
57
|
+
throw new UsageError(`${arg} takes no value`);
|
|
58
|
+
values[arg] = true;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
let value;
|
|
62
|
+
if (eq !== -1)
|
|
63
|
+
value = raw.slice(eq + 1);
|
|
64
|
+
else {
|
|
65
|
+
value = argv[i + 1];
|
|
66
|
+
if (value === undefined || value.startsWith("--"))
|
|
67
|
+
throw new UsageError(`${arg} needs a value`);
|
|
68
|
+
i += 1;
|
|
69
|
+
}
|
|
70
|
+
if (value === "")
|
|
71
|
+
throw new UsageError(`${arg} needs a value`);
|
|
72
|
+
switch (kind) {
|
|
73
|
+
case "string":
|
|
74
|
+
values[arg] = value;
|
|
75
|
+
break;
|
|
76
|
+
case "int":
|
|
77
|
+
values[arg] = positiveInt(value, arg);
|
|
78
|
+
break;
|
|
79
|
+
case "level": {
|
|
80
|
+
const n = Number(value);
|
|
81
|
+
if (!Number.isInteger(n) || n < 1 || n > 4)
|
|
82
|
+
throw new UsageError(`${arg} needs a level from 1 to 4`);
|
|
83
|
+
values[arg] = n;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "mode":
|
|
87
|
+
if (value !== "turns" && value !== "thread" && value !== "sidecar")
|
|
88
|
+
throw new UsageError(`${arg} needs turns, thread or sidecar`);
|
|
89
|
+
values[arg] = value;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { positionals, values };
|
|
94
|
+
}
|
|
95
|
+
/** Exactly one positional, named `what` in the error. */
|
|
96
|
+
function one(verb, positionals, what) {
|
|
97
|
+
if (positionals.length === 0)
|
|
98
|
+
throw new UsageError(`${verb} needs ${what}`);
|
|
99
|
+
if (positionals.length > 1)
|
|
100
|
+
throw new UsageError(`unexpected argument ${positionals[1]}`);
|
|
101
|
+
return positionals[0];
|
|
102
|
+
}
|
|
103
|
+
export function parseArgs(argv) {
|
|
104
|
+
const verb = argv[0];
|
|
105
|
+
if (verb === undefined || verb === "help" || verb === "--help" || verb === "-h")
|
|
106
|
+
return { verb: "help" };
|
|
107
|
+
const rest = argv.slice(1);
|
|
108
|
+
switch (verb) {
|
|
109
|
+
case "learnabai": {
|
|
110
|
+
const { positionals, values } = scan(verb, rest, {
|
|
111
|
+
"--section": "string",
|
|
112
|
+
"--json": "bool",
|
|
113
|
+
"--skill": "bool",
|
|
114
|
+
"--skill-dir": "string",
|
|
115
|
+
});
|
|
116
|
+
if (positionals.length > 0)
|
|
117
|
+
throw new UsageError(`unexpected argument ${positionals[0]}`);
|
|
118
|
+
if (values["--skill-dir"] !== undefined && values["--skill"] !== true)
|
|
119
|
+
throw new UsageError("--skill-dir goes with --skill");
|
|
120
|
+
return {
|
|
121
|
+
verb,
|
|
122
|
+
section: values["--section"],
|
|
123
|
+
json: values["--json"] === true,
|
|
124
|
+
skill: values["--skill"] === true,
|
|
125
|
+
skillDir: values["--skill-dir"],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
case "check": {
|
|
129
|
+
const { positionals, values } = scan(verb, rest, {
|
|
130
|
+
"--level": "level",
|
|
131
|
+
"--mode": "mode",
|
|
132
|
+
"--entry": "string",
|
|
133
|
+
"--runs": "int",
|
|
134
|
+
"--timeout": "int",
|
|
135
|
+
"--json": "bool",
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
verb,
|
|
139
|
+
repo: one(verb, positionals, "a repository"),
|
|
140
|
+
level: values["--level"],
|
|
141
|
+
mode: values["--mode"],
|
|
142
|
+
entry: values["--entry"],
|
|
143
|
+
runs: values["--runs"],
|
|
144
|
+
timeoutS: values["--timeout"],
|
|
145
|
+
json: values["--json"] === true,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
case "new": {
|
|
149
|
+
const { positionals, values } = scan(verb, rest, {
|
|
150
|
+
"--lang": "string",
|
|
151
|
+
"--level": "level",
|
|
152
|
+
"--dir": "string",
|
|
153
|
+
"--abai-import": "string",
|
|
154
|
+
});
|
|
155
|
+
const name = one(verb, positionals, "a name");
|
|
156
|
+
const lang = values["--lang"];
|
|
157
|
+
if (lang === undefined)
|
|
158
|
+
throw new UsageError("new needs --lang <ts>");
|
|
159
|
+
if (lang !== "ts")
|
|
160
|
+
throw new UsageError(`unknown language ${lang}; only ts is supported`);
|
|
161
|
+
return {
|
|
162
|
+
verb,
|
|
163
|
+
name,
|
|
164
|
+
lang,
|
|
165
|
+
level: values["--level"],
|
|
166
|
+
dir: values["--dir"],
|
|
167
|
+
abaiImport: values["--abai-import"],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
case "abaify": {
|
|
171
|
+
const { positionals, values } = scan(verb, rest, {
|
|
172
|
+
"--level": "level",
|
|
173
|
+
"--mode": "mode",
|
|
174
|
+
"--rounds": "int",
|
|
175
|
+
"--dry-run": "bool",
|
|
176
|
+
});
|
|
177
|
+
return {
|
|
178
|
+
verb,
|
|
179
|
+
repo: one(verb, positionals, "a repository"),
|
|
180
|
+
level: values["--level"],
|
|
181
|
+
mode: values["--mode"],
|
|
182
|
+
rounds: values["--rounds"],
|
|
183
|
+
dryRun: values["--dry-run"] === true,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
case "run": {
|
|
187
|
+
// the repository, then the program's arguments; `--` before them is allowed, as in the stub's own run
|
|
188
|
+
const repo = rest[0];
|
|
189
|
+
if (repo === undefined || repo.startsWith("-"))
|
|
190
|
+
throw new UsageError("run needs a repository");
|
|
191
|
+
const after = rest.slice(1);
|
|
192
|
+
return { verb, repo, args: after[0] === "--" ? after.slice(1) : after };
|
|
193
|
+
}
|
|
194
|
+
default:
|
|
195
|
+
throw new UsageError(`unknown verb ${verb}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** The binary's entry; resolves to the exit code and never throws. */
|
|
199
|
+
export async function main(argv, io = processIo) {
|
|
200
|
+
// abaikit is an abaigram, built before anything else so its own entry, its null harness under check runs and
|
|
201
|
+
// its capabilities are there whichever verb runs (section 13: main hands control to the harness at once)
|
|
202
|
+
let bundle;
|
|
203
|
+
try {
|
|
204
|
+
bundle = createApp(io);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
io.err(`abaikit: ${errorMessage(error)}`);
|
|
208
|
+
return 1;
|
|
209
|
+
}
|
|
210
|
+
const { app } = bundle;
|
|
211
|
+
try {
|
|
212
|
+
const code = await runEntry(app, argv, io);
|
|
213
|
+
if (code >= 0)
|
|
214
|
+
return code;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
io.err(`abaikit abai: ${errorMessage(error)}`);
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
let parsed;
|
|
221
|
+
try {
|
|
222
|
+
parsed = parseArgs(argv);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
io.err(`abaikit: ${errorMessage(error)}`);
|
|
226
|
+
for (const line of USAGE)
|
|
227
|
+
io.err(line);
|
|
228
|
+
return 2;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
switch (parsed.verb) {
|
|
232
|
+
case "help":
|
|
233
|
+
for (const line of USAGE)
|
|
234
|
+
io.out(`${line}\n`);
|
|
235
|
+
return 0;
|
|
236
|
+
case "learnabai":
|
|
237
|
+
return await runLearnabai(parsed, io);
|
|
238
|
+
case "check":
|
|
239
|
+
return await runCheck(parsed, io, app);
|
|
240
|
+
case "new":
|
|
241
|
+
return await runNew(parsed, io);
|
|
242
|
+
case "abaify":
|
|
243
|
+
return await runAbaify(parsed, io, bundle);
|
|
244
|
+
case "run":
|
|
245
|
+
return await runRun(parsed, io);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error instanceof UsageError) {
|
|
250
|
+
io.err(`abaikit: ${error.message}`);
|
|
251
|
+
return 2;
|
|
252
|
+
}
|
|
253
|
+
io.err(`abaikit ${parsed.verb}: ${errorMessage(error)}`);
|
|
254
|
+
return 1;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/** Whether this module is the process's entry: by its real path, so a relative path or npm's bin symlink both count. */
|
|
258
|
+
function isEntry() {
|
|
259
|
+
const entry = process.argv[1];
|
|
260
|
+
if (entry === undefined)
|
|
261
|
+
return false;
|
|
262
|
+
try {
|
|
263
|
+
return import.meta.url === pathToFileURL(realpathSync(entry)).href;
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (isEntry()) {
|
|
270
|
+
main(process.argv.slice(2)).then((code) => {
|
|
271
|
+
process.exitCode = code;
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=bin.js.map
|
package/dist/bin.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// The abaikit binary: learnabai prints the guide, check verifies a\n// repository's conformance, new creates a fresh abaigram, abaify retrofits\n// an existing repository, and run starts a repository's program with an\n// agent attached. `abaikit abai ...` is abaikit's own standard agent entry,\n// served by the stub, since abaikit is itself an abaigram.\nimport { realpathSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport { type Mode, runEntry } from \"@abaigram/abai-stub\";\nimport { runAbaify } from \"./abaify.ts\";\nimport { type AbaikitApp, createApp } from \"./app.ts\";\nimport { runCheck } from \"./check.ts\";\nimport { type AbaikitIo, errorMessage, processIo, UsageError } from \"./io.ts\";\nimport { runLearnabai } from \"./learnabai.ts\";\nimport { runNew } from \"./new.ts\";\nimport { runRun } from \"./run.ts\";\n\nexport { type AbaikitIo, processIo, UsageError } from \"./io.ts\";\n\nexport type { Mode };\n\nexport type ParsedArgs =\n\t| { verb: \"help\" }\n\t| { verb: \"learnabai\"; section?: string; json: boolean; skill: boolean; skillDir?: string }\n\t| {\n\t\t\tverb: \"check\";\n\t\t\trepo: string;\n\t\t\tlevel?: number;\n\t\t\tmode?: Mode;\n\t\t\tentry?: string;\n\t\t\truns?: number;\n\t\t\ttimeoutS?: number;\n\t\t\tjson: boolean;\n\t }\n\t| { verb: \"new\"; name: string; lang: \"ts\"; level?: number; dir?: string; abaiImport?: string }\n\t| { verb: \"abaify\"; repo: string; level?: number; mode?: Mode; rounds?: number; dryRun: boolean }\n\t| { verb: \"run\"; repo: string; args: string[] };\n\nexport type Verb = ParsedArgs[\"verb\"];\n\nconst USAGE = [\n\t\"usage: abaikit learnabai|check|new|abaify|run ...\",\n\t\" abaikit learnabai [--section <name>] [--json] [--skill] [--skill-dir <dir>]\",\n\t\" abaikit check <repo> [--level <1-4>] [--mode <turns|thread|sidecar>] [--entry <cmd>] [--runs <n>] [--timeout <seconds>] [--json]\",\n\t\" abaikit new <name> --lang <ts> [--level <1-4>] [--dir <dir>]\",\n\t\" abaikit abaify <repo> [--level <1-4>] [--mode <turns|thread|sidecar>] [--rounds <n>] [--dry-run]\",\n\t\" abaikit run <repo> [--] ARGS\",\n\t\" abaikit abai ... the standard agent entry (session, install, manifest, doctor, config, mcp, run)\",\n\t\"\",\n\t\"learnabai prints the guide for an agent; check verifies a repository's conformance and needs no model;\",\n\t\"new creates a fresh abaigram; abaify retrofits an existing repository, adding and never removing;\",\n\t\"run starts the repository's program with everything after -- as its arguments.\",\n];\n\ntype FlagKind = \"bool\" | \"string\" | \"int\" | \"level\" | \"mode\";\n\nfunction positiveInt(value: string, flag: string): number {\n\tconst n = Number(value);\n\tif (!Number.isInteger(n) || n <= 0) throw new UsageError(`${flag} needs a positive integer`);\n\treturn n;\n}\n\n/** The verb's flags and positionals; a flag missing its value, an unknown flag or a stray argument is a usage error. */\nfunction scan(\n\tverb: string,\n\targv: string[],\n\tkinds: Record<string, FlagKind>,\n): { positionals: string[]; values: Record<string, string | number | boolean> } {\n\tconst positionals: string[] = [];\n\tconst values: Record<string, string | number | boolean> = {};\n\tfor (let i = 0; i < argv.length; i++) {\n\t\tconst raw = argv[i];\n\t\tif (raw === \"--\") throw new UsageError(`${verb} takes no program command`);\n\t\tif (!raw.startsWith(\"-\")) {\n\t\t\tpositionals.push(raw);\n\t\t\tcontinue;\n\t\t}\n\t\t// `--flag value` and `--flag=value`, as the stub's entry accepts them\n\t\tconst eq = raw.indexOf(\"=\");\n\t\tconst arg = eq === -1 ? raw : raw.slice(0, eq);\n\t\tconst kind = kinds[arg];\n\t\tif (kind === undefined) throw new UsageError(`unknown argument ${arg}`);\n\t\tif (kind === \"bool\") {\n\t\t\tif (eq !== -1) throw new UsageError(`${arg} takes no value`);\n\t\t\tvalues[arg] = true;\n\t\t\tcontinue;\n\t\t}\n\t\tlet value: string | undefined;\n\t\tif (eq !== -1) value = raw.slice(eq + 1);\n\t\telse {\n\t\t\tvalue = argv[i + 1];\n\t\t\tif (value === undefined || value.startsWith(\"--\")) throw new UsageError(`${arg} needs a value`);\n\t\t\ti += 1;\n\t\t}\n\t\tif (value === \"\") throw new UsageError(`${arg} needs a value`);\n\t\tswitch (kind) {\n\t\t\tcase \"string\":\n\t\t\t\tvalues[arg] = value;\n\t\t\t\tbreak;\n\t\t\tcase \"int\":\n\t\t\t\tvalues[arg] = positiveInt(value, arg);\n\t\t\t\tbreak;\n\t\t\tcase \"level\": {\n\t\t\t\tconst n = Number(value);\n\t\t\t\tif (!Number.isInteger(n) || n < 1 || n > 4) throw new UsageError(`${arg} needs a level from 1 to 4`);\n\t\t\t\tvalues[arg] = n;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"mode\":\n\t\t\t\tif (value !== \"turns\" && value !== \"thread\" && value !== \"sidecar\")\n\t\t\t\t\tthrow new UsageError(`${arg} needs turns, thread or sidecar`);\n\t\t\t\tvalues[arg] = value;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\treturn { positionals, values };\n}\n\n/** Exactly one positional, named `what` in the error. */\nfunction one(verb: string, positionals: string[], what: string): string {\n\tif (positionals.length === 0) throw new UsageError(`${verb} needs ${what}`);\n\tif (positionals.length > 1) throw new UsageError(`unexpected argument ${positionals[1]}`);\n\treturn positionals[0];\n}\n\nexport function parseArgs(argv: string[]): ParsedArgs {\n\tconst verb = argv[0];\n\tif (verb === undefined || verb === \"help\" || verb === \"--help\" || verb === \"-h\") return { verb: \"help\" };\n\tconst rest = argv.slice(1);\n\tswitch (verb) {\n\t\tcase \"learnabai\": {\n\t\t\tconst { positionals, values } = scan(verb, rest, {\n\t\t\t\t\"--section\": \"string\",\n\t\t\t\t\"--json\": \"bool\",\n\t\t\t\t\"--skill\": \"bool\",\n\t\t\t\t\"--skill-dir\": \"string\",\n\t\t\t});\n\t\t\tif (positionals.length > 0) throw new UsageError(`unexpected argument ${positionals[0]}`);\n\t\t\tif (values[\"--skill-dir\"] !== undefined && values[\"--skill\"] !== true)\n\t\t\t\tthrow new UsageError(\"--skill-dir goes with --skill\");\n\t\t\treturn {\n\t\t\t\tverb,\n\t\t\t\tsection: values[\"--section\"] as string | undefined,\n\t\t\t\tjson: values[\"--json\"] === true,\n\t\t\t\tskill: values[\"--skill\"] === true,\n\t\t\t\tskillDir: values[\"--skill-dir\"] as string | undefined,\n\t\t\t};\n\t\t}\n\t\tcase \"check\": {\n\t\t\tconst { positionals, values } = scan(verb, rest, {\n\t\t\t\t\"--level\": \"level\",\n\t\t\t\t\"--mode\": \"mode\",\n\t\t\t\t\"--entry\": \"string\",\n\t\t\t\t\"--runs\": \"int\",\n\t\t\t\t\"--timeout\": \"int\",\n\t\t\t\t\"--json\": \"bool\",\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tverb,\n\t\t\t\trepo: one(verb, positionals, \"a repository\"),\n\t\t\t\tlevel: values[\"--level\"] as number | undefined,\n\t\t\t\tmode: values[\"--mode\"] as Mode | undefined,\n\t\t\t\tentry: values[\"--entry\"] as string | undefined,\n\t\t\t\truns: values[\"--runs\"] as number | undefined,\n\t\t\t\ttimeoutS: values[\"--timeout\"] as number | undefined,\n\t\t\t\tjson: values[\"--json\"] === true,\n\t\t\t};\n\t\t}\n\t\tcase \"new\": {\n\t\t\tconst { positionals, values } = scan(verb, rest, {\n\t\t\t\t\"--lang\": \"string\",\n\t\t\t\t\"--level\": \"level\",\n\t\t\t\t\"--dir\": \"string\",\n\t\t\t\t\"--abai-import\": \"string\",\n\t\t\t});\n\t\t\tconst name = one(verb, positionals, \"a name\");\n\t\t\tconst lang = values[\"--lang\"];\n\t\t\tif (lang === undefined) throw new UsageError(\"new needs --lang <ts>\");\n\t\t\tif (lang !== \"ts\") throw new UsageError(`unknown language ${lang}; only ts is supported`);\n\t\t\treturn {\n\t\t\t\tverb,\n\t\t\t\tname,\n\t\t\t\tlang,\n\t\t\t\tlevel: values[\"--level\"] as number | undefined,\n\t\t\t\tdir: values[\"--dir\"] as string | undefined,\n\t\t\t\tabaiImport: values[\"--abai-import\"] as string | undefined,\n\t\t\t};\n\t\t}\n\t\tcase \"abaify\": {\n\t\t\tconst { positionals, values } = scan(verb, rest, {\n\t\t\t\t\"--level\": \"level\",\n\t\t\t\t\"--mode\": \"mode\",\n\t\t\t\t\"--rounds\": \"int\",\n\t\t\t\t\"--dry-run\": \"bool\",\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tverb,\n\t\t\t\trepo: one(verb, positionals, \"a repository\"),\n\t\t\t\tlevel: values[\"--level\"] as number | undefined,\n\t\t\t\tmode: values[\"--mode\"] as Mode | undefined,\n\t\t\t\trounds: values[\"--rounds\"] as number | undefined,\n\t\t\t\tdryRun: values[\"--dry-run\"] === true,\n\t\t\t};\n\t\t}\n\t\tcase \"run\": {\n\t\t\t// the repository, then the program's arguments; `--` before them is allowed, as in the stub's own run\n\t\t\tconst repo = rest[0];\n\t\t\tif (repo === undefined || repo.startsWith(\"-\")) throw new UsageError(\"run needs a repository\");\n\t\t\tconst after = rest.slice(1);\n\t\t\treturn { verb, repo, args: after[0] === \"--\" ? after.slice(1) : after };\n\t\t}\n\t\tdefault:\n\t\t\tthrow new UsageError(`unknown verb ${verb}`);\n\t}\n}\n\n/** The binary's entry; resolves to the exit code and never throws. */\nexport async function main(argv: string[], io: AbaikitIo = processIo): Promise<number> {\n\t// abaikit is an abaigram, built before anything else so its own entry, its null harness under check runs and\n\t// its capabilities are there whichever verb runs (section 13: main hands control to the harness at once)\n\tlet bundle: AbaikitApp;\n\ttry {\n\t\tbundle = createApp(io);\n\t} catch (error) {\n\t\tio.err(`abaikit: ${errorMessage(error)}`);\n\t\treturn 1;\n\t}\n\tconst { app } = bundle;\n\ttry {\n\t\tconst code = await runEntry(app, argv, io);\n\t\tif (code >= 0) return code;\n\t} catch (error) {\n\t\tio.err(`abaikit abai: ${errorMessage(error)}`);\n\t\treturn 1;\n\t}\n\tlet parsed: ParsedArgs;\n\ttry {\n\t\tparsed = parseArgs(argv);\n\t} catch (error) {\n\t\tio.err(`abaikit: ${errorMessage(error)}`);\n\t\tfor (const line of USAGE) io.err(line);\n\t\treturn 2;\n\t}\n\ttry {\n\t\tswitch (parsed.verb) {\n\t\t\tcase \"help\":\n\t\t\t\tfor (const line of USAGE) io.out(`${line}\\n`);\n\t\t\t\treturn 0;\n\t\t\tcase \"learnabai\":\n\t\t\t\treturn await runLearnabai(parsed, io);\n\t\t\tcase \"check\":\n\t\t\t\treturn await runCheck(parsed, io, app);\n\t\t\tcase \"new\":\n\t\t\t\treturn await runNew(parsed, io);\n\t\t\tcase \"abaify\":\n\t\t\t\treturn await runAbaify(parsed, io, bundle);\n\t\t\tcase \"run\":\n\t\t\t\treturn await runRun(parsed, io);\n\t\t}\n\t} catch (error) {\n\t\tif (error instanceof UsageError) {\n\t\t\tio.err(`abaikit: ${error.message}`);\n\t\t\treturn 2;\n\t\t}\n\t\tio.err(`abaikit ${parsed.verb}: ${errorMessage(error)}`);\n\t\treturn 1;\n\t}\n}\n\n/** Whether this module is the process's entry: by its real path, so a relative path or npm's bin symlink both count. */\nfunction isEntry(): boolean {\n\tconst entry = process.argv[1];\n\tif (entry === undefined) return false;\n\ttry {\n\t\treturn import.meta.url === pathToFileURL(realpathSync(entry)).href;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nif (isEntry()) {\n\tmain(process.argv.slice(2)).then((code) => {\n\t\tprocess.exitCode = code;\n\t});\n}\n"]}
|
package/dist/check.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Abaigram, DoctorCheck, Mode } from "@abaigram/abai-stub";
|
|
2
|
+
import type { ParsedArgs } from "./bin.ts";
|
|
3
|
+
import { type CommandRun } from "./command.ts";
|
|
4
|
+
import { type AbaikitIo } from "./io.ts";
|
|
5
|
+
export declare const DEFAULT_RUNS = 3;
|
|
6
|
+
export declare const DEFAULT_TIMEOUT_MS = 120000;
|
|
7
|
+
/** How long an entry verb (manifest, doctor) may take. */
|
|
8
|
+
export declare const ENTRY_TIMEOUT_MS = 60000;
|
|
9
|
+
/** Below this median for the off run, startup noise is most of the number. */
|
|
10
|
+
export declare const NOISE_FLOOR_MS = 500;
|
|
11
|
+
export declare const LEVEL_NAMES: readonly ["none", "reachable", "observable", "steerable", "deliberative"];
|
|
12
|
+
export interface CheckOptions {
|
|
13
|
+
repo: string;
|
|
14
|
+
cwd: string;
|
|
15
|
+
env: NodeJS.ProcessEnv;
|
|
16
|
+
level?: number;
|
|
17
|
+
mode?: Mode;
|
|
18
|
+
entry?: string;
|
|
19
|
+
runs?: number;
|
|
20
|
+
timeoutMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/** What the stub's exercise harness reports at exit (abai's NullHarnessReport, as JSON on stderr). */
|
|
23
|
+
export interface ExerciseReport {
|
|
24
|
+
form: string;
|
|
25
|
+
events: number;
|
|
26
|
+
dropped: number;
|
|
27
|
+
decisions: number;
|
|
28
|
+
turns: number;
|
|
29
|
+
touchpoints: number;
|
|
30
|
+
cellsWritten: string[];
|
|
31
|
+
/** The program's own events, by name; absent in a report from an older stub. */
|
|
32
|
+
eventsEmitted?: string[];
|
|
33
|
+
knobsWritten: string[];
|
|
34
|
+
capabilitiesCalled: string[];
|
|
35
|
+
unexercised: Array<{
|
|
36
|
+
kind: string;
|
|
37
|
+
name: string;
|
|
38
|
+
why: string;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
export interface Measurement {
|
|
42
|
+
runs: number;
|
|
43
|
+
off: number[];
|
|
44
|
+
null: number[];
|
|
45
|
+
offMedianMs: number;
|
|
46
|
+
nullMedianMs: number;
|
|
47
|
+
/** null over off, minus one; NaN when either side has no run. */
|
|
48
|
+
slowdown: number;
|
|
49
|
+
limit?: number;
|
|
50
|
+
/** The runs that did not exit 0 or timed out, by harness. */
|
|
51
|
+
failed: string[];
|
|
52
|
+
/** The numbers are within startup noise or the two ranges overlap. */
|
|
53
|
+
withinNoise: boolean;
|
|
54
|
+
}
|
|
55
|
+
export interface ModeResult {
|
|
56
|
+
mode: Mode;
|
|
57
|
+
/** The highest level this mode demonstrated, 1 to 4. */
|
|
58
|
+
level: number;
|
|
59
|
+
exercise?: {
|
|
60
|
+
run: CommandRun;
|
|
61
|
+
report?: ExerciseReport;
|
|
62
|
+
};
|
|
63
|
+
evidence: {
|
|
64
|
+
l2: string;
|
|
65
|
+
l3: string;
|
|
66
|
+
l4: string;
|
|
67
|
+
};
|
|
68
|
+
measurement?: Measurement;
|
|
69
|
+
}
|
|
70
|
+
export interface Violation {
|
|
71
|
+
where: string;
|
|
72
|
+
what: string;
|
|
73
|
+
}
|
|
74
|
+
export interface CheckReport {
|
|
75
|
+
root: string;
|
|
76
|
+
name: string;
|
|
77
|
+
abaigrammar: string;
|
|
78
|
+
entry: string;
|
|
79
|
+
declaredModes: Mode[];
|
|
80
|
+
checkedModes: Mode[];
|
|
81
|
+
checkRun?: string;
|
|
82
|
+
overheadLimit?: string;
|
|
83
|
+
/** This instantiation offers no build with the stub compiled out, and builds nothing: the command runs as is. */
|
|
84
|
+
compiledOut: "not offered";
|
|
85
|
+
built: "no";
|
|
86
|
+
l1: {
|
|
87
|
+
ok: boolean;
|
|
88
|
+
manifestAgrees: boolean;
|
|
89
|
+
doctor: DoctorCheck[];
|
|
90
|
+
ran: string[];
|
|
91
|
+
detail: string;
|
|
92
|
+
};
|
|
93
|
+
perMode: ModeResult[];
|
|
94
|
+
level: number;
|
|
95
|
+
requested?: number;
|
|
96
|
+
passed: boolean;
|
|
97
|
+
unexercised: Array<{
|
|
98
|
+
mode: Mode;
|
|
99
|
+
kind: string;
|
|
100
|
+
name: string;
|
|
101
|
+
why: string;
|
|
102
|
+
}>;
|
|
103
|
+
violations: Violation[];
|
|
104
|
+
notes: string[];
|
|
105
|
+
}
|
|
106
|
+
export declare function checkRepository(options: CheckOptions): Promise<CheckReport>;
|
|
107
|
+
export declare function renderReport(report: CheckReport): string;
|
|
108
|
+
/** abaikit's own channels see every check: two cells and one event, for the agent fused into abaikit. */
|
|
109
|
+
export declare function recordCheck(app: Abaigram | undefined, report: CheckReport): void;
|
|
110
|
+
/** The `check` capability: the same check, its report as text, for the agent on any front end. */
|
|
111
|
+
export declare function checkHandler(app: Abaigram, io: Pick<AbaikitIo, "cwd" | "env">): (params: Record<string, unknown>) => Promise<{
|
|
112
|
+
level: number;
|
|
113
|
+
passed: boolean;
|
|
114
|
+
report: string;
|
|
115
|
+
}>;
|
|
116
|
+
export declare function runCheck(parsed: Extract<ParsedArgs, {
|
|
117
|
+
verb: "check";
|
|
118
|
+
}>, io: AbaikitIo, app?: Abaigram): Promise<number>;
|
|
119
|
+
//# sourceMappingURL=check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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","sourcesContent":["// abaikit check: conformance as section 12 defines it, with no model.\n// Level 1 is reached through the subject's entry alone (what `manifest`\n// prints, byte for byte the checked-in file, and `doctor --offline --json`); levels 2 to 4 through its `check.run`\n// under the exercise null harness, once per declared mode, read from the\n// report the stub prints at exit; the overhead of 11.6 by the same command\n// under `ABAI_HARNESS=off` and `ABAI_HARNESS=null`, interleaved, side by\n// side. The subject's environment is constructed, never inherited: each\n// run gets exactly the `ABAI_HARNESS` and `ABAI_MODE` it means, the entry\n// runs get neither. A level is the one reached under every checked mode,\n// and passing means that level with no violation.\nimport { readFileSync } from \"node:fs\";\nimport type { Abaigram, DoctorCheck, Manifest, Mode } from \"@abaigram/abai-stub\";\nimport { parseManifestYaml, validateManifest } from \"@abaigram/abai-stub\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport {\n\ttype CommandRun,\n\tjsonDocument,\n\tlastJsonLine,\n\tlastPlainLine,\n\tmedian,\n\trunCommand,\n\tsubjectEnv,\n\ttailOf,\n} from \"./command.ts\";\nimport { type AbaikitIo, errorMessage, UsageError } from \"./io.ts\";\nimport { entryCommand, loadSubject, type Subject } from \"./subject.ts\";\n\nexport const DEFAULT_RUNS = 3;\nexport const DEFAULT_TIMEOUT_MS = 120_000;\n/** How long an entry verb (manifest, doctor) may take. */\nexport const ENTRY_TIMEOUT_MS = 60_000;\n/** Below this median for the off run, startup noise is most of the number. */\nexport const NOISE_FLOOR_MS = 500;\n/** Characters of a run's output the JSON report carries. */\nconst REPORT_TAIL_CHARS = 4096;\nexport const LEVEL_NAMES = [\"none\", \"reachable\", \"observable\", \"steerable\", \"deliberative\"] as const;\n\nexport interface CheckOptions {\n\trepo: string;\n\tcwd: string;\n\tenv: NodeJS.ProcessEnv;\n\tlevel?: number;\n\tmode?: Mode;\n\tentry?: string;\n\truns?: number;\n\ttimeoutMs?: number;\n}\n\n/** What the stub's exercise harness reports at exit (abai's NullHarnessReport, as JSON on stderr). */\nexport interface ExerciseReport {\n\tform: string;\n\tevents: number;\n\tdropped: number;\n\tdecisions: number;\n\tturns: number;\n\ttouchpoints: number;\n\tcellsWritten: string[];\n\t/** The program's own events, by name; absent in a report from an older stub. */\n\teventsEmitted?: string[];\n\tknobsWritten: string[];\n\tcapabilitiesCalled: string[];\n\tunexercised: Array<{ kind: string; name: string; why: string }>;\n}\n\nexport interface Measurement {\n\truns: number;\n\toff: number[];\n\tnull: number[];\n\toffMedianMs: number;\n\tnullMedianMs: number;\n\t/** null over off, minus one; NaN when either side has no run. */\n\tslowdown: number;\n\tlimit?: number;\n\t/** The runs that did not exit 0 or timed out, by harness. */\n\tfailed: string[];\n\t/** The numbers are within startup noise or the two ranges overlap. */\n\twithinNoise: boolean;\n}\n\nexport interface ModeResult {\n\tmode: Mode;\n\t/** The highest level this mode demonstrated, 1 to 4. */\n\tlevel: number;\n\texercise?: { run: CommandRun; report?: ExerciseReport };\n\tevidence: { l2: string; l3: string; l4: string };\n\tmeasurement?: Measurement;\n}\n\nexport interface Violation {\n\twhere: string;\n\twhat: string;\n}\n\nexport interface CheckReport {\n\troot: string;\n\tname: string;\n\tabaigrammar: string;\n\tentry: string;\n\tdeclaredModes: Mode[];\n\tcheckedModes: Mode[];\n\tcheckRun?: string;\n\toverheadLimit?: string;\n\t/** This instantiation offers no build with the stub compiled out, and builds nothing: the command runs as is. */\n\tcompiledOut: \"not offered\";\n\tbuilt: \"no\";\n\tl1: { ok: boolean; manifestAgrees: boolean; doctor: DoctorCheck[]; ran: string[]; detail: string };\n\tperMode: ModeResult[];\n\tlevel: number;\n\trequested?: number;\n\tpassed: boolean;\n\tunexercised: Array<{ mode: Mode; kind: string; name: string; why: string }>;\n\tviolations: Violation[];\n\tnotes: string[];\n}\n\nfunction parseOverhead(limit: string | undefined): number | undefined {\n\tif (limit === undefined) return undefined;\n\tconst match = /^(\\d+(?:\\.\\d+)?)\\s*%$/.exec(limit.trim());\n\treturn match ? Number(match[1]) / 100 : undefined;\n}\n\nfunction firstDifferingLine(a: string, b: string): number {\n\tconst al = a.split(\"\\n\");\n\tconst bl = b.split(\"\\n\");\n\tfor (let i = 0; i < Math.max(al.length, bl.length); i++) if (al[i] !== bl[i]) return i + 1;\n\treturn 0;\n}\n\nfunction isExerciseReport(\n\tvalue: Record<string, unknown> | undefined,\n): value is ExerciseReport & Record<string, unknown> {\n\treturn value !== undefined && value.form === \"exercise\" && Array.isArray(value.unexercised);\n}\n\nfunction describeRun(run: CommandRun): string {\n\tif (run.timedOut) return \"did not finish within the timeout and was ended\";\n\tif (run.code !== 0) return `exited ${run.code ?? run.signal ?? \"?\"}`;\n\treturn \"exited 0\";\n}\n\nfunction lastWords(run: CommandRun): string {\n\treturn lastPlainLine(run.stderr) || lastPlainLine(run.stdout) || \"no output\";\n}\n\n/** A run as the JSON report carries it: the newest part of each stream, not a whole log. */\nfunction trimmed(run: CommandRun): CommandRun {\n\treturn { ...run, stdout: tailOf(run.stdout, REPORT_TAIL_CHARS), stderr: tailOf(run.stderr, REPORT_TAIL_CHARS) };\n}\n\n/** Level 1 through the entry: the effective manifest, the checked-in file, the offline doctor. */\nasync function checkLevelOne(\n\tsubject: Subject,\n\tentry: string,\n\tenv: NodeJS.ProcessEnv,\n\tviolations: Violation[],\n): Promise<CheckReport[\"l1\"]> {\n\tconst manifest = subject.manifest;\n\tconst fileText = readFileSync(subject.manifestPath, \"utf8\");\n\tlet manifestAgrees = false;\n\tconst details: string[] = [];\n\tconst ran: string[] = [];\n\t// what the entry prints is the manifest: byte for byte the checked-in file (section 12), and valid\n\tconst manifestCommand = entryCommand(manifest, \"manifest\");\n\tran.push(manifestCommand);\n\tconst shown = await runCommand(manifestCommand, { cwd: subject.root, env, timeoutMs: ENTRY_TIMEOUT_MS });\n\tif (shown.code !== 0) {\n\t\tviolations.push({ where: manifestCommand, what: `${describeRun(shown)}: ${lastWords(shown)}` });\n\t} else {\n\t\tlet effective: unknown;\n\t\ttry {\n\t\t\teffective = parseManifestYaml(shown.stdout);\n\t\t} catch (error) {\n\t\t\tviolations.push({ where: manifestCommand, what: `did not print a manifest: ${errorMessage(error)}` });\n\t\t}\n\t\tif (effective !== undefined) {\n\t\t\tconst problems = validateManifest(effective);\n\t\t\tif (problems.length > 0) {\n\t\t\t\tviolations.push({\n\t\t\t\t\twhere: manifestCommand,\n\t\t\t\t\twhat: `the printed manifest does not validate: ${problems.join(\"; \")}`,\n\t\t\t\t});\n\t\t\t} else if ((effective as Manifest).name !== manifest.name) {\n\t\t\t\tviolations.push({\n\t\t\t\t\twhere: subject.manifestPath,\n\t\t\t\t\twhat: `names ${manifest.name}, but the entry's program is ${(effective as Manifest).name}`,\n\t\t\t\t});\n\t\t\t} else if (shown.stdout === fileText) {\n\t\t\t\tmanifestAgrees = true;\n\t\t\t\tdetails.push(\"abaigram.yaml is what the entry prints\");\n\t\t\t} else {\n\t\t\t\tviolations.push({\n\t\t\t\t\twhere: subject.manifestPath,\n\t\t\t\t\twhat: `is stale: first difference at line ${firstDifferingLine(fileText, shown.stdout)}; run: ${entry} manifest > abaigram.yaml`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\tlet doctor: DoctorCheck[] = [];\n\tconst doctorCommand = entryCommand(manifest, \"doctor\", \"--offline\", \"--json\");\n\tran.push(doctorCommand);\n\tconst examined = await runCommand(doctorCommand, { cwd: subject.root, env, timeoutMs: ENTRY_TIMEOUT_MS });\n\tconst parsed = jsonDocument(examined.stdout);\n\tif (parsed && Array.isArray(parsed.checks)) {\n\t\tdoctor = parsed.checks as DoctorCheck[];\n\t\tfor (const check of doctor) {\n\t\t\t// the manifest file is judged above, with the hint for this entry; the doctor's word on it would repeat it\n\t\t\tif (check.status === \"fail\" && check.name !== \"abaigram.yaml\") {\n\t\t\t\tviolations.push({ where: `doctor: ${check.name}`, what: check.detail });\n\t\t\t}\n\t\t}\n\t\tconst failed = doctor.filter((c) => c.status === \"fail\").length;\n\t\tdetails.push(`doctor: ${doctor.filter((c) => c.status === \"ok\").length} ok, ${failed} failed`);\n\t} else {\n\t\tviolations.push({\n\t\t\twhere: doctorCommand,\n\t\t\twhat: `${describeRun(examined)} and printed no report: ${lastWords(examined)}`,\n\t\t});\n\t}\n\tconst ok = manifestAgrees && doctor.length > 0 && doctor.every((c) => c.status !== \"fail\");\n\treturn { ok, manifestAgrees, doctor, ran, detail: details.join(\"; \") };\n}\n\nfunction spread(values: number[]): { min: number; max: number } {\n\treturn { min: Math.min(...values), max: Math.max(...values) };\n}\n\n/** Levels 2 to 4 and the measurement, under one mode. */\nasync function checkMode(\n\tsubject: Subject,\n\tmode: Mode,\n\toptions: { runs: number; timeoutMs: number; env: NodeJS.ProcessEnv },\n\tviolations: Violation[],\n\tunexercised: CheckReport[\"unexercised\"],\n\tnotes: string[],\n): Promise<ModeResult> {\n\tconst manifest = subject.manifest;\n\tconst check = manifest.check as { run: string; overhead?: string };\n\tconst result: ModeResult = { mode, level: 1, evidence: { l2: \"\", l3: \"\", l4: \"\" } };\n\tconst where = (harness: string) => `check.run under ${mode} with ABAI_HARNESS=${harness}`;\n\tconst run = await runCommand(check.run, {\n\t\tcwd: subject.root,\n\t\tenv: subjectEnv(options.env, { ABAI_HARNESS: \"exercise\", ABAI_MODE: mode }),\n\t\ttimeoutMs: options.timeoutMs,\n\t});\n\tresult.exercise = { run: trimmed(run) };\n\t// the report is the stub's exit hook: it is there even when the program failed, and says what was reached\n\tconst report = lastJsonLine(run.stderr, (v) => v.form === \"exercise\");\n\tif (isExerciseReport(report)) {\n\t\tresult.exercise.report = report;\n\t\tfor (const u of report.unexercised) unexercised.push({ mode, ...u });\n\t\tfor (const cell of manifest.cells) {\n\t\t\tif (cell.access === \"read\" && !report.cellsWritten.includes(cell.name)) {\n\t\t\t\tunexercised.push({ mode, kind: \"cell\", name: cell.name, why: \"not written during the exercise run\" });\n\t\t\t}\n\t\t}\n\t\tif (report.eventsEmitted) {\n\t\t\tfor (const event of manifest.events) {\n\t\t\t\tif (!report.eventsEmitted.includes(event.name)) {\n\t\t\t\t\tunexercised.push({ mode, kind: \"event\", name: event.name, why: \"not emitted during the exercise run\" });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif (run.timedOut || run.code !== 0) {\n\t\tviolations.push({ where: where(\"exercise\"), what: `${describeRun(run)}: ${lastWords(run)}` });\n\t\treturn result;\n\t}\n\tif (!isExerciseReport(report)) {\n\t\tviolations.push({\n\t\t\twhere: where(\"exercise\"),\n\t\t\twhat: `printed no exercise report at exit${run.dropped.stderr > 0 ? ` in the ${run.stderr.length} newest bytes of stderr kept` : \"\"}: the stub did not attach its null harness (is the entry hook in main, and the program built?)`,\n\t\t});\n\t\treturn result;\n\t}\n\t// the program's own events, by name; a report that names none (an older stub) shows none: the library's do not count\n\tconst emitted = report.eventsEmitted ?? [];\n\tconst l2 = report.cellsWritten.length > 0 || emitted.length > 0;\n\tresult.evidence.l2 = l2\n\t\t? `cells written: ${report.cellsWritten.join(\", \") || \"none\"}; events: ${emitted.join(\", \") || \"none\"}`\n\t\t: \"no cell written and no event emitted during the exercise run\";\n\tconst l3 = report.capabilitiesCalled.length > 0 || report.knobsWritten.length > 0;\n\tresult.evidence.l3 = l3\n\t\t? `capabilities called: ${report.capabilitiesCalled.join(\", \") || \"none\"}; knobs landed: ${report.knobsWritten.join(\", \") || \"none\"}`\n\t\t: \"no capability called and no knob landed\";\n\tconst l4 = report.decisions > 0;\n\tresult.evidence.l4 = l4 ? `decisions reached: ${report.decisions}` : \"no decision reached\";\n\tresult.level = l2 ? (l3 ? (l4 ? 4 : 3) : 2) : 1;\n\n\t// the measurement (11.6): off and null, interleaved so drift lands on both, K runs each, the medians side by side\n\tconst measurement: Measurement = {\n\t\truns: options.runs,\n\t\toff: [],\n\t\tnull: [],\n\t\toffMedianMs: Number.NaN,\n\t\tnullMedianMs: Number.NaN,\n\t\tslowdown: Number.NaN,\n\t\tfailed: [],\n\t\twithinNoise: false,\n\t};\n\tfor (let i = 0; i < options.runs; i++) {\n\t\tfor (const harness of [\"off\", \"null\"] as const) {\n\t\t\tconst timed = await runCommand(check.run, {\n\t\t\t\tcwd: subject.root,\n\t\t\t\tenv: subjectEnv(options.env, { ABAI_HARNESS: harness, ABAI_MODE: mode }),\n\t\t\t\ttimeoutMs: options.timeoutMs,\n\t\t\t});\n\t\t\tif (timed.timedOut || timed.code !== 0) measurement.failed.push(`${harness}: ${describeRun(timed)}`);\n\t\t\telse measurement[harness].push(timed.ms);\n\t\t}\n\t}\n\tif (measurement.failed.length > 0) {\n\t\tviolations.push({\n\t\t\twhere: `check.run under ${mode} with ABAI_HARNESS=off and null`,\n\t\t\twhat: `measurement runs failed: ${measurement.failed.join(\"; \")}`,\n\t\t});\n\t}\n\tif (measurement.off.length > 0 && measurement.null.length > 0) {\n\t\tmeasurement.offMedianMs = median(measurement.off);\n\t\tmeasurement.nullMedianMs = median(measurement.null);\n\t\tmeasurement.slowdown =\n\t\t\tmeasurement.offMedianMs > 0 ? measurement.nullMedianMs / measurement.offMedianMs - 1 : Number.NaN;\n\t\tconst off = spread(measurement.off);\n\t\tconst nul = spread(measurement.null);\n\t\tmeasurement.withinNoise = measurement.offMedianMs < NOISE_FLOOR_MS || (nul.min <= off.max && off.min <= nul.max);\n\t\tif (measurement.withinNoise) {\n\t\t\tnotes.push(\n\t\t\t\t`under ${mode} the overhead numbers are within noise (off ${off.min.toFixed(0)}..${off.max.toFixed(0)} ms, null ${nul.min.toFixed(0)}..${nul.max.toFixed(0)} ms, ${options.runs} runs each): raise --runs or give check.run more work before reading the slowdown`,\n\t\t\t);\n\t\t}\n\t\tconst limit = parseOverhead(check.overhead);\n\t\tif (check.overhead !== undefined && limit === undefined) {\n\t\t\tviolations.push({\n\t\t\t\twhere: \"check.overhead\",\n\t\t\t\twhat: `${JSON.stringify(check.overhead)} is not a percentage such as \"5%\"`,\n\t\t\t});\n\t\t} else if (limit !== undefined) {\n\t\t\tmeasurement.limit = limit;\n\t\t\tif (measurement.slowdown > limit) {\n\t\t\t\tviolations.push({\n\t\t\t\t\twhere: where(\"null\"),\n\t\t\t\t\twhat: `the null-harness run is ${(measurement.slowdown * 100).toFixed(1)}% slower than the off run (medians of ${options.runs}${measurement.withinNoise ? \", within noise\" : \"\"}); check.overhead allows ${check.overhead}`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\tresult.measurement = measurement;\n\treturn result;\n}\n\nexport async function checkRepository(options: CheckOptions): Promise<CheckReport> {\n\tconst violations: Violation[] = [];\n\tconst unexercised: CheckReport[\"unexercised\"] = [];\n\tconst notes: string[] = [];\n\tconst subject = loadSubject(options.repo, options.cwd);\n\tconst manifest = subject.manifest;\n\tconst entry = options.entry ?? manifest.entry;\n\tconst subjectForEntry: Subject = options.entry\n\t\t? { ...subject, manifest: { ...manifest, entry: options.entry } }\n\t\t: subject;\n\tconst runs = options.runs ?? DEFAULT_RUNS;\n\tconst timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n\n\tconst l1 = await checkLevelOne(subjectForEntry, entry, subjectEnv(options.env), violations);\n\n\tlet checkedModes: Mode[] = manifest.modes;\n\tif (options.mode !== undefined) {\n\t\tif (!manifest.modes.includes(options.mode)) {\n\t\t\tviolations.push({\n\t\t\t\twhere: \"--mode\",\n\t\t\t\twhat: `${manifest.name} declares ${manifest.modes.join(\", \")}, not ${options.mode}`,\n\t\t\t});\n\t\t\tcheckedModes = [];\n\t\t} else checkedModes = [options.mode];\n\t}\n\tif (!/\\babai$/.test(entry.trim())) {\n\t\tnotes.push(\n\t\t\t`the entry \"${entry}\" does not end in the word abai: the stub's runEntry answers only \"abai ...\", so the verbs reach the program only if it translates them itself`,\n\t\t);\n\t}\n\tif (checkedModes.includes(\"sidecar\") && manifest.check?.run) {\n\t\tnotes.push(\n\t\t\t\"under sidecar this instantiation keeps the null harness in the program's process and opens no socket for it, so the measured overhead leaves out the serialization sidecar observation costs (a deviation every manifest it writes declares)\",\n\t\t);\n\t}\n\n\tconst perMode: ModeResult[] = [];\n\tif (l1.ok && manifest.check?.run) {\n\t\tfor (const mode of checkedModes) {\n\t\t\tperMode.push(\n\t\t\t\tawait checkMode(subject, mode, { runs, timeoutMs, env: options.env }, violations, unexercised, notes),\n\t\t\t);\n\t\t}\n\t} else if (l1.ok) {\n\t\tnotes.push(\n\t\t\t\"check.run is not declared, so nothing above level 1 can be shown and nothing is measured (section 12)\",\n\t\t);\n\t}\n\n\tlet level = 0;\n\tif (l1.ok) level = perMode.length > 0 ? Math.min(...perMode.map((m) => m.level)) : 1;\n\tconst requested = options.level;\n\tconst passed = level >= (requested ?? 1) && violations.length === 0;\n\treturn {\n\t\troot: subject.root,\n\t\tname: manifest.name,\n\t\tabaigrammar: manifest.abaigrammar,\n\t\tentry,\n\t\tdeclaredModes: manifest.modes,\n\t\tcheckedModes,\n\t\tcheckRun: manifest.check?.run,\n\t\toverheadLimit: manifest.check?.overhead,\n\t\tcompiledOut: \"not offered\",\n\t\tbuilt: \"no\",\n\t\tl1,\n\t\tperMode,\n\t\tlevel,\n\t\trequested,\n\t\tpassed,\n\t\tunexercised,\n\t\tviolations,\n\t\tnotes,\n\t};\n}\n\nfunction pct(value: number): string {\n\treturn Number.isNaN(value) ? \"n/a\" : `${value >= 0 ? \"+\" : \"\"}${(value * 100).toFixed(1)}%`;\n}\n\nfunction range(values: number[]): string {\n\tif (values.length === 0) return \"failed\";\n\tconst { min, max } = spread(values);\n\treturn `${median(values).toFixed(0)} (${min.toFixed(0)}..${max.toFixed(0)})`;\n}\n\nexport function renderReport(report: CheckReport): string {\n\tconst lines: string[] = [];\n\tlines.push(\n\t\t`abaikit check: ${report.name} at ${report.root} (abaigrammar ${report.abaigrammar}, entry: ${report.entry})`,\n\t);\n\tlet verdict: string;\n\tif (report.requested === undefined) verdict = report.violations.length === 0 ? \"no violations\" : \"with violations\";\n\telse if (report.level < report.requested) verdict = `requested ${report.requested}: not reached`;\n\telse\n\t\tverdict =\n\t\t\treport.violations.length === 0\n\t\t\t\t? `requested ${report.requested}: passed`\n\t\t\t\t: `requested ${report.requested}: reached, with violations`;\n\tlines.push(\n\t\treport.level === 0\n\t\t\t? `level: 0 (L1 not reached; ${verdict})`\n\t\t\t: `level: ${report.level} ${LEVEL_NAMES[report.level]} (${verdict})`,\n\t);\n\tlines.push(\n\t\t`modes: ${report.declaredModes.join(\", \")}${report.checkedModes.length === report.declaredModes.length ? \"\" : `; checked: ${report.checkedModes.join(\", \") || \"none\"}`}`,\n\t);\n\tlines.push(\n\t\t`L1 reachable: ${report.l1.ok ? \"ok\" : \"not reached\"}${report.l1.detail ? ` (${report.l1.detail})` : \"\"}; ran: ${report.l1.ran.join(\"; \")}`,\n\t);\n\tfor (const n of [2, 3, 4] as const) {\n\t\tconst key = `l${n}` as const;\n\t\tconst parts = report.perMode.map((m) => {\n\t\t\tif (m.exercise && !m.exercise.report) return `${m.mode}: exercise run failed`;\n\t\t\tif (!m.evidence[key]) return `${m.mode}: not run`;\n\t\t\treturn `${m.mode}: ${m.level >= n ? \"ok\" : \"no\"}, ${m.evidence[key]}`;\n\t\t});\n\t\tlines.push(`L${n} ${LEVEL_NAMES[n]}: ${parts.length > 0 ? parts.join(\"; \") : \"not shown\"}`);\n\t}\n\tif (report.checkRun !== undefined && report.perMode.some((m) => m.measurement)) {\n\t\tlines.push(\n\t\t\t`overhead (check.run: ${report.checkRun}; built: ${report.built}, the command runs as is; compiled out: ${report.compiledOut}; median ms (min..max) of each run set):`,\n\t\t);\n\t\tlines.push(\" mode runs off null slowdown limit\");\n\t\tfor (const m of report.perMode) {\n\t\t\tconst x = m.measurement;\n\t\t\tif (!x) continue;\n\t\t\tconst limit = x.limit === undefined ? \"none\" : `${(x.limit * 100).toFixed(1)}%`;\n\t\t\tlines.push(\n\t\t\t\t` ${m.mode.padEnd(9)} ${String(x.runs).padEnd(5)} ${range(x.off).padEnd(18)} ${range(x.null).padEnd(18)} ${pct(x.slowdown).padEnd(10)} ${limit}${x.withinNoise ? \" (within noise)\" : \"\"}`,\n\t\t\t);\n\t\t}\n\t}\n\tlines.push(\n\t\treport.unexercised.length === 0\n\t\t\t? \"unexercised: none\"\n\t\t\t: `unexercised:\\n${report.unexercised.map((u) => ` ${u.mode}: ${u.kind} ${u.name}: ${u.why}`).join(\"\\n\")}`,\n\t);\n\tlines.push(\n\t\treport.violations.length === 0\n\t\t\t? \"violations: none\"\n\t\t\t: `violations:\\n${report.violations.map((v) => ` ${v.where}: ${v.what}`).join(\"\\n\")}`,\n\t);\n\tfor (const note of report.notes) lines.push(`note: ${note}`);\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\n/** abaikit's own channels see every check: two cells and one event, for the agent fused into abaikit. */\nexport function recordCheck(app: Abaigram | undefined, report: CheckReport): void {\n\tif (!app) return;\n\tconst runs = app.cell<number>(\"check.runs\");\n\truns.set((runs.get() ?? 0) + 1);\n\tapp.cell<number>(\"check.last_level\").set(report.level);\n\tapp.event(\"check.completed\").emit({ repo: report.root, level: report.level });\n}\n\n/** The `check` capability: the same check, its report as text, for the agent on any front end. */\nexport function checkHandler(app: Abaigram, io: Pick<AbaikitIo, \"cwd\" | \"env\">) {\n\treturn async (params: Record<string, unknown>): Promise<{ level: number; passed: boolean; report: string }> => {\n\t\tif (typeof params.repo !== \"string\" || params.repo === \"\") throw new UsageError(\"check needs a repo\");\n\t\tconst level = typeof params.level === \"number\" ? params.level : undefined;\n\t\tif (level !== undefined && (!Number.isInteger(level) || level < 1 || level > 4)) {\n\t\t\tthrow new UsageError(\"level must be 1 to 4\");\n\t\t}\n\t\tconst mode = typeof params.mode === \"string\" && params.mode !== \"\" ? params.mode : undefined;\n\t\tif (mode !== undefined && mode !== \"turns\" && mode !== \"thread\" && mode !== \"sidecar\")\n\t\t\tthrow new UsageError(\"mode must be turns, thread or sidecar\");\n\t\tconst entry = typeof params.entry === \"string\" && params.entry !== \"\" ? params.entry : undefined;\n\t\tconst runs =\n\t\t\ttypeof params.runs === \"number\" && Number.isInteger(params.runs) && params.runs > 0 ? params.runs : undefined;\n\t\tconst report = await checkRepository({ repo: params.repo, cwd: io.cwd, env: io.env, level, mode, entry, runs });\n\t\trecordCheck(app, report);\n\t\treturn { level: report.level, passed: report.passed, report: renderReport(report) };\n\t};\n}\n\nexport async function runCheck(\n\tparsed: Extract<ParsedArgs, { verb: \"check\" }>,\n\tio: AbaikitIo,\n\tapp?: Abaigram,\n): Promise<number> {\n\tconst report = await checkRepository({\n\t\trepo: parsed.repo,\n\t\tcwd: io.cwd,\n\t\tenv: io.env,\n\t\tlevel: parsed.level,\n\t\tmode: parsed.mode,\n\t\tentry: parsed.entry,\n\t\truns: parsed.runs,\n\t\ttimeoutMs: parsed.timeoutS === undefined ? undefined : parsed.timeoutS * 1000,\n\t});\n\trecordCheck(app, report);\n\tio.out(parsed.json ? `${JSON.stringify(report, null, 2)}\\n` : renderReport(report));\n\treturn report.passed ? 0 : 1;\n}\n"]}
|