@abaigram/abaikit 0.1.0 → 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/abai/learnabai.md +104 -32
- package/dist/abaify.d.ts.map +1 -1
- package/dist/abaify.js +1 -1
- package/dist/abaify.js.map +1 -1
- package/dist/bin.d.ts +2 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +4 -4
- package/dist/bin.js.map +1 -1
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +2 -2
- package/dist/check.js.map +1 -1
- package/dist/learnabai.d.ts.map +1 -1
- package/dist/learnabai.js +4 -2
- package/dist/learnabai.js.map +1 -1
- package/dist/template-ts.d.ts.map +1 -1
- package/dist/template-ts.js +1 -1
- package/dist/template-ts.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +3 -3
package/abai/learnabai.md
CHANGED
|
@@ -18,8 +18,9 @@ only through channels the program declares. The contract is
|
|
|
18
18
|
|
|
19
19
|
Two halves, one boundary. The stub lives in the program. The harness is
|
|
20
20
|
the agent's runtime: a model, a tool set, a user channel. The boundary is
|
|
21
|
-
a function call inside the process (turns
|
|
22
|
-
two processes (sidecar mode). It means the same thing
|
|
21
|
+
a function call inside the process (turns and thread modes) or a local
|
|
22
|
+
socket between two processes (sidecar mode). It means the same thing
|
|
23
|
+
either way.
|
|
23
24
|
|
|
24
25
|
Five channels, declared in a manifest the program builds in code:
|
|
25
26
|
|
|
@@ -32,14 +33,24 @@ Five channels, declared in a manifest the program builds in code:
|
|
|
32
33
|
- `turns`: the program hands the agent a bounded moment to act.
|
|
33
34
|
|
|
34
35
|
Plus `lifecycle` (pause, resume, stop, restart, status: what a supervising
|
|
35
|
-
harness may do to the process)
|
|
36
|
-
|
|
36
|
+
harness may do to the process), `attention` (what wakes the agent, and a
|
|
37
|
+
budget on it), and `network`, optional: the agent may meet the agents of
|
|
38
|
+
other programs on an agent network, and its `doc` sentence tells the agent
|
|
39
|
+
how the members it sees relate to this program's own names. abai carries
|
|
40
|
+
only the basic acts here (an identity on a network, who is on it, who is
|
|
41
|
+
reachable, a message sent to a member, a message received as a wake);
|
|
42
|
+
how agents find each other and what they say is each program's own, in
|
|
43
|
+
its declaration and its content.
|
|
37
44
|
|
|
38
45
|
Three modes. `turns`: one process, one thread, the agent runs only inside
|
|
39
46
|
a touchpoint; the simplest, the natural first rung for a command-line
|
|
40
|
-
program. `
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
program. `thread`: one process, the agent live beside the program once
|
|
48
|
+
it is attached (`await app.attach()` at startup, or any touchpoint), with
|
|
49
|
+
no touchpoint needed after that; in Node the harness runs on the
|
|
50
|
+
program's event loop between its callbacks, which the manifest states as
|
|
51
|
+
a deviation, so a long synchronous step on either side delays the other.
|
|
52
|
+
`sidecar`: the harness is a separate process, attached through a socket;
|
|
53
|
+
the reference mode, the one with the whole lifecycle channel. A manifest
|
|
43
54
|
declares the modes it supports; the first is the default; `ABAI_MODE`
|
|
44
55
|
overrides at startup.
|
|
45
56
|
|
|
@@ -85,9 +96,9 @@ import { createAbaigram } from "@abaigram/abai-stub";
|
|
|
85
96
|
|
|
86
97
|
export const app = createAbaigram(
|
|
87
98
|
{
|
|
88
|
-
name: "myprog",
|
|
89
|
-
entry: "node dist/main.js abai",
|
|
90
|
-
modes: ["turns", "sidecar"],
|
|
99
|
+
name: "myprog", // the program's name; also the manifest's
|
|
100
|
+
entry: "node dist/main.js abai", // how the entry is reached: a runnable command line
|
|
101
|
+
modes: ["turns", "thread", "sidecar"], // supported; the first is the default
|
|
91
102
|
content: { identity: "abai/identity.md" }, // the prompts; identity is required
|
|
92
103
|
check: { run: "node dist/main.js --demo" }, // a short command that exercises the channels
|
|
93
104
|
cells: {
|
|
@@ -136,6 +147,10 @@ export const app = createAbaigram(
|
|
|
136
147
|
pause: "cooperative", // "cooperative" | "os" | "none"
|
|
137
148
|
stop: { graceful: "10s", handler: () => shutdown() },
|
|
138
149
|
},
|
|
150
|
+
network: { // optional: the agent may be a member of an agent network
|
|
151
|
+
doc: "Members are other job queues of this fleet; a member's name is its host name.",
|
|
152
|
+
required: false, // true: a run with no membership attaches no agent
|
|
153
|
+
},
|
|
139
154
|
},
|
|
140
155
|
// the directory holding abaigram.yaml and abai/: one level up from src/abai.ts and from dist/abai.js;
|
|
141
156
|
// count the levels from the built file if the build nests deeper (dist/src/abai.js is two)
|
|
@@ -162,7 +177,7 @@ failed.emit({ job_id, attempt }); // nothing happens when n
|
|
|
162
177
|
const choice = await app.decide<string>("on_backlog", { depth: queue.length, workers: n }); // "wait" when nobody answers
|
|
163
178
|
await app.turn("end_of_batch"); // returns at once when nobody is there
|
|
164
179
|
app.safepoint(); // where "safepoint" calls and a pause take effect
|
|
165
|
-
await app.attach(); //
|
|
180
|
+
await app.attach(); // ask for the agent now: optional in turns mode, a thread program's first act
|
|
166
181
|
```
|
|
167
182
|
|
|
168
183
|
abai is ESM-only. In a CommonJS repository load it inside the async main:
|
|
@@ -186,8 +201,10 @@ runnable. Every act is written to the audit log with the agent's reason.
|
|
|
186
201
|
|
|
187
202
|
Cost: detached, every operation above is a load or a store. Keep your own
|
|
188
203
|
handlers cheap too; the agent's model calls never run on the program's
|
|
189
|
-
thread in sidecar mode,
|
|
190
|
-
touchpoints you placed
|
|
204
|
+
thread in sidecar mode, in turns mode they run only inside the
|
|
205
|
+
touchpoints you placed, and in thread mode the agent's own work runs on
|
|
206
|
+
the program's event loop between its callbacks, so a slow synchronous
|
|
207
|
+
step on either side delays the other.
|
|
191
208
|
|
|
192
209
|
## 3. The standard entry (entry)
|
|
193
210
|
|
|
@@ -319,11 +336,14 @@ prints `{{stub}}`.
|
|
|
319
336
|
|
|
320
337
|
Step 2. Declare the abaigram in a new module (`src/abai.ts`, or
|
|
321
338
|
`src/abai.js` in a JavaScript repository) as in section 2, with only
|
|
322
|
-
`name`, a runnable `entry`, `modes: ["turns", "sidecar"]
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
339
|
+
`name`, a runnable `entry`, `modes: ["turns", "sidecar"]` (add `"thread"`
|
|
340
|
+
when the program keeps running between touchpoints and wants the agent
|
|
341
|
+
live in its process; it then calls `await app.attach()` at startup),
|
|
342
|
+
`content` and `root` to begin with. When the program's real entry is a
|
|
343
|
+
plain launcher that runs without the build (a `bin/x.mjs` that imports
|
|
344
|
+
`dist/`), put the declaration beside it as a `.mjs` module the launcher
|
|
345
|
+
can import, so the entry answers `manifest` and `doctor` before anything
|
|
346
|
+
is built. Verify: build as the repository does, then
|
|
327
347
|
`node -e "import('./dist/abai.js')"` (the built path) runs without error.
|
|
328
348
|
|
|
329
349
|
Step 3. Hook the entry. In the file where `main()` starts, insert the
|
|
@@ -383,8 +403,8 @@ they name the file or the declaration and what the contract wanted.
|
|
|
383
403
|
## 6. Running an abaigram (use)
|
|
384
404
|
|
|
385
405
|
Session on the terminal, the person talking to the agent while the
|
|
386
|
-
program runs (turns
|
|
387
|
-
harness binary):
|
|
406
|
+
program runs (turns and thread modes in-process; sidecar attaches to or
|
|
407
|
+
starts the harness binary):
|
|
388
408
|
|
|
389
409
|
```
|
|
390
410
|
<entry>
|
|
@@ -418,21 +438,72 @@ The machine-level model configuration, shared by every abaigram on the
|
|
|
418
438
|
machine, lives under `~/.abai/`:
|
|
419
439
|
|
|
420
440
|
```
|
|
421
|
-
<entry> config
|
|
441
|
+
<entry> config providers # every provider and the login it offers
|
|
442
|
+
<entry> config set provider <id>
|
|
422
443
|
<entry> config set model <id>
|
|
423
|
-
<entry> config
|
|
444
|
+
<entry> config login <provider> # sign in with an account; --api-key for a key, --browser opens the URL
|
|
445
|
+
<entry> config set key <provider> # a key, read without echo
|
|
424
446
|
<entry> config set url <provider> <base url>
|
|
425
447
|
<entry> doctor # offline checks, then the model
|
|
426
448
|
```
|
|
427
449
|
|
|
428
|
-
Environment variables, all optional: `ABAI_MODE` (`turns` or
|
|
429
|
-
overriding the manifest's first mode), `ABAI_HARNESS` (`off`,
|
|
430
|
-
`exercise`: check runs only; never set it for real use),
|
|
431
|
-
configuration directory, default `~/.abai`),
|
|
432
|
-
of running programs, default `~/.abai/run`),
|
|
433
|
-
sidecar harness binary to spawn),
|
|
434
|
-
harness module to import),
|
|
435
|
-
`
|
|
450
|
+
Environment variables, all optional: `ABAI_MODE` (`turns`, `thread` or
|
|
451
|
+
`sidecar`, overriding the manifest's first mode), `ABAI_HARNESS` (`off`,
|
|
452
|
+
`null`, `exercise`: check runs only; never set it for real use),
|
|
453
|
+
`ABAI_HOME` (the configuration directory, default `~/.abai`),
|
|
454
|
+
`ABAI_RUN_DIR` (descriptors of running programs, default `~/.abai/run`),
|
|
455
|
+
`ABAI_HARNESS_BIN` (the sidecar harness binary to spawn),
|
|
456
|
+
`ABAI_HARNESS_MODULE` (the in-process harness module to import),
|
|
457
|
+
`ABAI_CHANNEL` (`terminal` or `none`), `ABAI_ATTACH_WAIT_MS` (how long a
|
|
458
|
+
touchpoint waits for a harness), and for the network below `ABAI_NET_DIR`,
|
|
459
|
+
`ABAI_NET_TOKEN` and `ABAI_AIIP_BIN`.
|
|
460
|
+
|
|
461
|
+
A program that declares `network` puts its agent on an aiip network
|
|
462
|
+
(`~/projects/aiip`: an overlay network for agents, one Go binary, no
|
|
463
|
+
root). The harness talks to aiip's daemon over its socket; the `aiip`
|
|
464
|
+
binary must be on the machine (on the PATH, or named by `ABAI_AIIP_BIN`
|
|
465
|
+
or `<entry> config set aiip-bin <path>`), and `doctor` says when it is
|
|
466
|
+
not. A run's identity lives in a state directory: `ABAI_NET_DIR`, else
|
|
467
|
+
`~/.abai/net/<name>/`, so two runs of one program on one machine are one
|
|
468
|
+
member unless the run is given a directory of its own. At attach the
|
|
469
|
+
harness finds or starts the daemon for that directory, joins with the
|
|
470
|
+
invite in `ABAI_NET_TOKEN` when the run is not yet a member, and tells
|
|
471
|
+
the agent where it is; `abai_peers` lists who else is there and who is
|
|
472
|
+
reachable now. A message from a member wakes the agent as the event
|
|
473
|
+
`abai.net.message`, with the sender's issued name and what it said;
|
|
474
|
+
`abai_send` answers a member by name, with a reason, and `abai_messages`
|
|
475
|
+
lists the run's messages. Every message sent or received is one line in
|
|
476
|
+
the audit log (`act: message`), written by the stub. The envelope on the
|
|
477
|
+
wire is aiap's (`~/projects/agents-cowork`), so an abaigram and an aiap
|
|
478
|
+
agent on one network read each other. A declared network the run cannot
|
|
479
|
+
have is said in the prompt, unless `required` is true, in which case no
|
|
480
|
+
agent attaches.
|
|
481
|
+
|
|
482
|
+
```
|
|
483
|
+
<entry> config net # the state dir, the daemon, the memberships
|
|
484
|
+
<entry> config net create <network> --as <member> # a new network, this run as its authority
|
|
485
|
+
<entry> config net invite <network> --for <member> # an invite token for a new member (authority only)
|
|
486
|
+
<entry> config net join --token - # join with an invite read from stdin
|
|
487
|
+
<entry> config net leave <network>
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
An invite token carries the member's private key: hand it to that
|
|
491
|
+
member's run as `ABAI_NET_TOKEN` or on stdin, never in a command line, a
|
|
492
|
+
file or a log, and never over a link that is not encrypted end to end to
|
|
493
|
+
that run. A network has 253 slots besides its authority and no removal:
|
|
494
|
+
every name ever invited takes one, whether or not its holder enrolls, and
|
|
495
|
+
a name whose holder has enrolled is that member's for good. Keep a
|
|
496
|
+
member's state directory, since a lost one cannot rejoin under its name,
|
|
497
|
+
and let the member run now and then: a certificate lasts 30 days and the
|
|
498
|
+
daemon renews it only while it runs, so a member that has not run for 30
|
|
499
|
+
days is locked out just the same. An invite for a name whose holder never
|
|
500
|
+
enrolled reissues that slot with fresh credentials and voids the earlier
|
|
501
|
+
token, so mint a second invite for a name only when the authority's
|
|
502
|
+
roster (`abai_peers`, or the harness client's `members`) shows its holder
|
|
503
|
+
not enrolled and nobody is about to enroll with the first. Who creates
|
|
504
|
+
the network, who mints the invites, how members find each other and what
|
|
505
|
+
they say to one another is the program's to decide, in its code and its
|
|
506
|
+
content, not abai's.
|
|
436
507
|
|
|
437
508
|
The audit log (`audit.path` in the manifest; default
|
|
438
509
|
`~/.abai/audit/<name>.jsonl`) records every act of control with its
|
|
@@ -440,8 +511,9 @@ reason, consent and outcome. Read it when you wonder what the agent did.
|
|
|
440
511
|
|
|
441
512
|
## 7. Where this instantiation departs from the contract (deviations)
|
|
442
513
|
|
|
443
|
-
The first four are in every manifest this stub writes, the
|
|
444
|
-
`
|
|
514
|
+
The first four are in every manifest this stub writes, the fifth when
|
|
515
|
+
`thread` is declared and the last when `sidecar` is, so an agent reading
|
|
516
|
+
the manifest sees them too:
|
|
445
517
|
|
|
446
518
|
{{deviations}}
|
|
447
519
|
|
package/dist/abaify.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abaify.d.ts","sourceRoot":"","sources":["../src/abaify.ts"],"names":[],"mappings":"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","sourcesContent":["// abaikit abaify: the agent-driven retrofit (section 13). abaikit's own\n// agent, with the configured model, works through the repo.* capabilities\n// and nothing else: no source line the repository had is ever removed or\n// reordered, and the three files structured tools rewrite (package.json,\n// abaigram.yaml, the lock file) are named as such. The person's invocation\n// is the consent for its writes, said at the start and in the report. Each\n// round the agent works until it stops, then abaikit runs check itself;\n// the level reached ends the loop, anything short of it is the next\n// prompt, up to the abaify.max_rounds knob. A model that cannot be reached\n// ends the run at once, and the report of what changed and why is left in\n// the repository whatever happened.\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { AgentModel, SessionForm, UserChannel } from \"@abaigram/abai-embedded-harness\";\nimport type { Mode } from \"@abaigram/abai-stub\";\nimport type { AbaikitApp } from \"./app.ts\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type CheckReport, checkRepository, recordCheck, renderReport } from \"./check.ts\";\nimport { type AbaikitIo, errorMessage, UsageError } from \"./io.ts\";\nimport { findSection, loadGuide } from \"./learnabai.ts\";\nimport { type Change, readRepo, summarizeRepo } from \"./repo.ts\";\n\nexport const REPORT_FILE = \"abai/abaify-report.md\";\n/** Lines of the entry file the brief quotes: enough to see where a launcher checks or imports its build. */\nconst HEAD_LINES = 80;\n/** The brief is thousands of tokens: the model's first answer may take minutes, which is not unreachability. */\nexport const FIRST_CONTACT_MS = 180_000;\n/** Scripts a brief names first, when the package has them. */\nconst KNOWN_SCRIPTS = [\"start\", \"build\", \"compile\", \"dev\", \"test\", \"check\", \"lint\", \"typecheck\"];\n\nexport interface AbaifyOptions {\n\trepo: string;\n\tlevel?: number;\n\tmode?: Mode;\n\trounds?: number;\n\tdryRun: boolean;\n\t/** A model for the agent; the machine's configured one otherwise. */\n\tmodel?: AgentModel;\n}\n\nexport interface Round {\n\tnumber: number;\n\tanswer: string;\n\tcheck: CheckReport | undefined;\n\t/** When check could not run: why (no abaigram.yaml yet, for one). */\n\tcheckError?: string;\n}\n\nexport interface AbaifyResult {\n\troot: string;\n\trequested: number;\n\tlevel: number;\n\tpassed: boolean;\n\tdryRun: boolean;\n\trounds: Round[];\n\tchanges: Change[];\n\t/** Why the run ended short, when it did: the model gone, an error. */\n\tfailure?: string;\n\treportPath?: string;\n\treport: string;\n}\n\ninterface Inventory {\n\tpackageJson: Record<string, unknown> | undefined;\n\tentryFile?: { path: string; head: string; to: number; total: number };\n\tguidelines?: { path: string; head: string };\n\tsummary: string;\n\ttotal: number;\n\t/** Build output present at the root (`dist/`, `build/`, `out/`), so the agent knows whether built paths run. */\n\tbuildOutput: string[];\n}\n\nfunction inventory(root: string): Inventory {\n\tlet packageJson: Record<string, unknown> | undefined;\n\ttry {\n\t\tpackageJson = JSON.parse(readFileSync(join(root, \"package.json\"), \"utf8\")) as Record<string, unknown>;\n\t} catch {\n\t\tpackageJson = undefined;\n\t}\n\tconst { total, summary } = summarizeRepo(root);\n\tconst buildOutput = [\"dist\", \"build\", \"out\"].filter(\n\t\t(d) => existsSync(join(root, d)) && statSync(join(root, d)).isDirectory(),\n\t);\n\tconst result: Inventory = { packageJson, summary, total, buildOutput };\n\tconst bin = packageJson?.bin;\n\tconst binTarget =\n\t\ttypeof bin === \"string\"\n\t\t\t? bin\n\t\t\t: bin && typeof bin === \"object\"\n\t\t\t\t? (Object.values(bin as Record<string, string>)[0] ?? undefined)\n\t\t\t\t: undefined;\n\tconst main = typeof packageJson?.main === \"string\" ? packageJson.main : undefined;\n\tfor (const candidate of [\n\t\tbinTarget,\n\t\tmain,\n\t\t\"src/main.ts\",\n\t\t\"src/main.js\",\n\t\t\"src/index.ts\",\n\t\t\"src/index.js\",\n\t\t\"index.js\",\n\t]) {\n\t\tif (candidate && existsSync(join(root, candidate))) {\n\t\t\tconst { text, to, total_lines } = readRepo(root, candidate, 1, HEAD_LINES);\n\t\t\tresult.entryFile = { path: candidate, head: text, to, total: total_lines };\n\t\t\tbreak;\n\t\t}\n\t}\n\tfor (const candidate of [\"AGENTS.md\", \"CONTRIBUTING.md\", \"CLAUDE.md\"]) {\n\t\tif (existsSync(join(root, candidate))) {\n\t\t\tconst { text } = readRepo(root, candidate, 1, 40);\n\t\t\tresult.guidelines = { path: candidate, head: text };\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction pickedScripts(scripts: Record<string, string>): Record<string, string> {\n\tconst picked: Record<string, string> = {};\n\tfor (const name of KNOWN_SCRIPTS) if (scripts[name] !== undefined) picked[name] = scripts[name];\n\tfor (const [name, value] of Object.entries(scripts)) {\n\t\tif (Object.keys(picked).length >= 15) break;\n\t\tif (picked[name] === undefined) picked[name] = value;\n\t}\n\treturn picked;\n}\n\n/** The first prompt: the task, the repository as it is, the procedure mapped onto the capabilities, the API, the rules. */\nexport function brief(root: string, level: number, mode: Mode | undefined, inv: Inventory, dryRun: boolean): string {\n\tconst guide = loadGuide();\n\tconst pkg = inv.packageJson;\n\tconst picked = pkg\n\t\t? JSON.stringify(\n\t\t\t\t{\n\t\t\t\t\tname: pkg.name,\n\t\t\t\t\ttype: pkg.type,\n\t\t\t\t\tbin: pkg.bin,\n\t\t\t\t\tmain: pkg.main,\n\t\t\t\t\tscripts: pickedScripts((pkg.scripts as Record<string, string>) ?? {}),\n\t\t\t\t},\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)\n\t\t: \"(no package.json)\";\n\tconst lines = [\n\t\t`Retrofit the repository at ${root} into an abaigram at level ${level}${mode ? ` in ${mode} mode` : \"\"}.`,\n\t\t\"\",\n\t\t\"You work only through abaikit's capabilities (the abai_call tool), with `repo` set to exactly the path above:\",\n\t\t\"- repo.list (glob narrows: `*.json` by name anywhere, `src/**` or `scripts/*.mjs` by path; the listing is bounded and says so first), repo.read (a window of lines; the result says `from` and `to`).\",\n\t\t\"- repo.write makes a new file only. repo.insert puts text before or after one unique anchor line of an existing file; no line the repository had is ever removed or reordered. repo.add_dependency adds to package.json (a structured edit that re-serializes the file). repo.manifest runs the entry's `manifest` and writes abaigram.yaml: never write that file yourself. repo.install runs npm install (the lock file and node_modules are rewritten). check gives the conformance report.\",\n\t\t\"- You cannot run shell commands or build the repository. Where the procedure below says `npm install`, call repo.add_dependency for both `@abaigram/abai-stub` and `@abaigram/abai-embedded-harness`, then repo.install once. Where it says `<entry> manifest > abaigram.yaml`, call repo.manifest with the entry. Where it says `abaikit check`, call check. Where it says build: \" +\n\t\t\t(inv.buildOutput.length > 0\n\t\t\t\t? `the build output is present (${inv.buildOutput.map((d) => `${d}/`).join(\", \")}), so the built program runs; prefer a cell or event at a real boundary of the program and a check.run that is a real command of it, over a synthetic mode.`\n\t\t\t\t: \"there is no build output, so choose a path that needs no build: a plain `.mjs` module beside a launcher, a cell or event set in code that runs unbuilt.\"),\n\t\t\"- The `entry` you declare must end with the word `abai` (such as `node scripts/x.mjs abai` or `node dist/main.js abai`): the tools append `manifest`, `doctor` and `run` to it, and the stub's runEntry answers only when `abai` comes first. A file you wrote in this run you may write again with repo.write; a file the repository had you may only insert into.\",\n\t\t\"- You have the person's consent for this run; do not ask them.\",\n\t\tdryRun\n\t\t\t? \"- This is a dry run: call the capabilities as you would; nothing is written, repo.manifest and check cannot succeed on files that are not on disk, so propose the whole retrofit and stop.\"\n\t\t\t: \"\",\n\t\t\"\",\n\t\t`Repository shape (${inv.total} files after skipping dependencies, build output and ignored trees; directories with their counts, use repo.list with a glob to see inside one):`,\n\t\t\"```\",\n\t\tinv.summary,\n\t\t\"```\",\n\t\t\"package.json (the known scripts first):\",\n\t\t\"```json\",\n\t\tpicked,\n\t\t\"```\",\n\t];\n\tif (inv.entryFile) {\n\t\tlines.push(\n\t\t\t`The entry file ${inv.entryFile.path}, lines 1 to ${inv.entryFile.to} of ${inv.entryFile.total}:`,\n\t\t\t\"```\",\n\t\t\tinv.entryFile.head,\n\t\t\t\"```\",\n\t\t);\n\t}\n\tif (inv.guidelines) {\n\t\tlines.push(`The repository's own guidelines, ${inv.guidelines.path}, head:`, \"```\", inv.guidelines.head, \"```\");\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"The procedure, from abaikit learnabai (read it with the mapping above):\",\n\t\tfindSection(guide, \"retrofit\").text,\n\t\t\"\",\n\t\t\"The declaration API:\",\n\t\tfindSection(guide, \"declare\").text,\n\t\t\"\",\n\t\t\"What never to do:\",\n\t\tfindSection(guide, \"never\").text,\n\t\t\"\",\n\t\t`When you believe level ${level} is reached, call check once; when it reports level ${level} with no violation, answer with the word DONE on its own line followed by a short summary of what you added and why. If you cannot go on, say why.`,\n\t);\n\treturn lines.filter((l) => l !== undefined).join(\"\\n\");\n}\n\nfunction followUp(round: Round, level: number): string {\n\tconst status = round.check ? renderReport(round.check) : `check could not run: ${round.checkError}`;\n\treturn `abaikit ran check after your last round. Level ${level} is not reached yet. The report:\\n\\n${status}\\nFix what it names, through the capabilities, then call check again. Answer DONE with a summary when level ${level} holds with no violation.`;\n}\n\nfunction consentLine(root: string, level: number, dryRun: boolean): string {\n\treturn dryRun\n\t\t? `abaify: dry run on ${root}: the agent proposes a retrofit to level ${level}; nothing is written, though the repository's entry may be run read-only`\n\t\t: `abaify: ${root} to level ${level}, with your consent for this run: the agent may add files, insert lines, add dependencies, run npm install (network, the repository's own install scripts), run the repository's entry and its check.run under the null harness`;\n}\n\n/** The report left in the repository: every change with its why, the rounds, the last check. */\nexport function renderAbaifyReport(result: AbaifyResult, run: number): string {\n\tconst lines = [\n\t\t`# abaify report: ${result.root} (run ${run}, ${new Date().toISOString()})`,\n\t\t\"\",\n\t\tresult.dryRun\n\t\t\t? `Dry run to level ${result.requested}: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written, no check run.`\n\t\t\t: `Requested level ${result.requested}; reached level ${result.level}; ${result.passed ? \"passed\" : \"not passed\"} after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}.`,\n\t\tconsentLine(result.root, result.requested, result.dryRun).replace(/^abaify: /, \"Consent: \"),\n\t\t\"\",\n\t];\n\tif (result.failure) lines.push(`The run ended short: ${result.failure}`, \"\");\n\tconst added = result.changes.filter((c) => c.kind === \"added\");\n\tconst rewritten = result.changes.filter((c) => c.kind === \"rewritten\");\n\tlines.push(\"## Added (new files and inserted lines; nothing the repository had was removed or reordered)\", \"\");\n\tlines.push(\n\t\t...(added.length === 0 ? [\"(none)\"] : added.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rewritten in place by a structured tool\", \"\");\n\tlines.push(\n\t\t...(rewritten.length === 0\n\t\t\t? [\"(none)\"]\n\t\t\t: rewritten.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rounds\", \"\");\n\tfor (const r of result.rounds) {\n\t\tlines.push(`### Round ${r.number}`, \"\", \"The agent said:\", \"\", r.answer.trim() || \"(nothing)\", \"\");\n\t\tif (!result.dryRun) {\n\t\t\tlines.push(\n\t\t\t\tr.check\n\t\t\t\t\t? `check after the round: level ${r.check.level}, ${r.check.passed ? \"passed\" : \"not passed\"}`\n\t\t\t\t\t: `check could not run: ${r.checkError ?? \"unknown\"}`,\n\t\t\t\t\"\",\n\t\t\t);\n\t\t}\n\t}\n\tconst last = result.rounds.at(-1)?.check;\n\tif (last) lines.push(\"## The last check report\", \"\", \"```\", renderReport(last).trimEnd(), \"```\", \"\");\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\n/** The report appended to the repository's; the run number counts the reports already there. */\nfunction writeReport(root: string, result: AbaifyResult): string {\n\tconst path = join(root, REPORT_FILE);\n\tmkdirSync(join(root, \"abai\"), { recursive: true });\n\tconst previous = existsSync(path) ? readFileSync(path, \"utf8\") : \"\";\n\tconst run = (previous.match(/^# abaify report:/gm) ?? []).length + 1;\n\tresult.report = renderAbaifyReport(result, run);\n\twriteFileSync(path, (previous === \"\" ? \"\" : `${previous.trimEnd()}\\n\\n---\\n\\n`) + result.report);\n\treturn path;\n}\n\nexport async function abaify(options: AbaifyOptions, io: AbaikitIo, bundle: AbaikitApp): Promise<AbaifyResult> {\n\tconst { app, embedding, repo: ctx } = bundle;\n\tconst root = resolve(io.cwd, options.repo);\n\tif (!existsSync(join(root, \"package.json\"))) {\n\t\tthrow new UsageError(\n\t\t\t`${options.repo}: no package.json; abaify retrofits Node repositories (TypeScript or JavaScript) for now`,\n\t\t);\n\t}\n\tif (app.mode !== \"turns\")\n\t\tthrow new UsageError(`abaify runs its agent in-process; unset ABAI_MODE (it is ${app.mode})`);\n\tconst level = options.level ?? 1;\n\tconst rounds = app.knob<number>(\"abaify.max_rounds\");\n\tconst range = app.manifest.cells.find((c) => c.name === \"abaify.max_rounds\")?.range ?? [1, 20];\n\tif (options.rounds !== undefined) {\n\t\tif (options.rounds < range[0] || options.rounds > range[1])\n\t\t\tthrow new UsageError(`--rounds must be ${range[0]} to ${range[1]}`);\n\t\trounds.set(options.rounds);\n\t}\n\tconst say = (text: string) => io.err(text);\n\tsay(consentLine(root, level, options.dryRun));\n\tctx.dryRun = options.dryRun;\n\tctx.cwd = io.cwd;\n\tctx.onChange = (change) => say(`abaify: ${change.how} ${change.path}: ${change.detail}`);\n\tconst changesBefore = ctx.changes.length;\n\tconst result: AbaifyResult = {\n\t\troot,\n\t\trequested: level,\n\t\tlevel: 0,\n\t\tpassed: false,\n\t\tdryRun: options.dryRun,\n\t\trounds: [],\n\t\tchanges: [],\n\t\treport: \"\",\n\t};\n\n\t// the agent: abaikit's own embedded harness, auto consent (the invocation is the person's), a channel that only speaks\n\tconst channel: UserChannel = { say, ask: async () => undefined, confirm: async () => false, close: () => {} };\n\tembedding.options = {\n\t\tmodel: options.model,\n\t\tchannel,\n\t\tharness: { auto: true },\n\t\tconnectTimeoutMs: FIRST_CONTACT_MS,\n\t\taddendum: `You are retrofitting ${root} for a person who asked abaikit to do it; every write is theirs to approve and they approved this run.`,\n\t};\n\tlet form: SessionForm | undefined;\n\ttry {\n\t\tif (!(await app.attach()) || !embedding.current) {\n\t\t\tthrow new Error(\n\t\t\t\t`no agent could be attached: ${app.attachError ?? \"unknown\"} (configure one with \\`abaikit abai config\\`, or follow \\`abaikit learnabai\\` with your own agent)`,\n\t\t\t);\n\t\t}\n\t\tform = embedding.current.form;\n\t\tconst harness = embedding.current.harness;\n\t\tconst inv = inventory(root);\n\t\tlet prompt = brief(root, level, options.mode, inv, options.dryRun);\n\t\t// read once: an agent setting its own knob mid-run changes the next run, not this one\n\t\tconst max = options.dryRun ? 1 : rounds.get();\n\t\tfor (let n = 1; n <= max; n++) {\n\t\t\tsay(`abaify: round ${n} of ${max}`);\n\t\t\tconst answer = await form.prompt(prompt);\n\t\t\tconst round: Round = { number: n, answer, check: undefined };\n\t\t\tresult.rounds.push(round);\n\t\t\t// a run the model could not finish ends the loop: before first contact the form detaches; after it the\n\t\t\t// agent's last message carries the error, and another round would only spend the person's time\n\t\t\tconst last = [...form.agent.state.messages].reverse().find((m) => m.role === \"assistant\");\n\t\t\tconst stop = last && last.role === \"assistant\" ? last.stopReason : undefined;\n\t\t\tif (form.modelFailure !== undefined || !harness.attached) {\n\t\t\t\tresult.failure = `the model could not be reached: ${form.modelFailure ?? \"the harness detached\"}`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (stop === \"error\" || stop === \"aborted\") {\n\t\t\t\tconst why =\n\t\t\t\t\tlast && last.role === \"assistant\" && \"errorMessage\" in last ? String(last.errorMessage ?? stop) : stop;\n\t\t\t\tresult.failure = `the model could not be reached: the request ${stop === \"aborted\" ? \"was aborted\" : \"failed\"} (${why}); run abaify again when it is back`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!options.dryRun) {\n\t\t\t\tsay(`abaify: check after round ${n}`);\n\t\t\t\ttry {\n\t\t\t\t\tround.check = await checkRepository({ repo: root, cwd: io.cwd, env: io.env, level, mode: options.mode });\n\t\t\t\t\trecordCheck(app, round.check);\n\t\t\t\t\tresult.level = round.check.level;\n\t\t\t\t\tresult.passed = round.check.passed;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tround.checkError = errorMessage(error);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.passed) break;\n\t\t\tprompt = followUp(round, level);\n\t\t}\n\t} catch (error) {\n\t\tresult.failure = result.failure ?? errorMessage(error);\n\t\tthrow error;\n\t} finally {\n\t\tform?.shutdown(\"abaify ended\");\n\t\tctx.dryRun = false;\n\t\tctx.onChange = undefined;\n\t\tresult.changes = ctx.changes.slice(changesBefore);\n\t\t// the report is written whatever happened once the agent was at work, so nothing done goes unrecorded\n\t\tif (options.dryRun) result.report = renderAbaifyReport(result, 1);\n\t\telse if (result.rounds.length > 0 || result.changes.length > 0) result.reportPath = writeReport(root, result);\n\t}\n\tif (options.dryRun) result.passed = true;\n\treturn result;\n}\n\nexport async function runAbaify(\n\tparsed: Extract<ParsedArgs, { verb: \"abaify\" }>,\n\tio: AbaikitIo,\n\tbundle: AbaikitApp,\n\tdeps: { model?: AgentModel } = {},\n): Promise<number> {\n\tconst result = await abaify(\n\t\t{\n\t\t\trepo: parsed.repo,\n\t\t\tlevel: parsed.level,\n\t\t\tmode: parsed.mode,\n\t\t\trounds: parsed.rounds,\n\t\t\tdryRun: parsed.dryRun,\n\t\t\tmodel: deps.model,\n\t\t},\n\t\tio,\n\t\tbundle,\n\t);\n\tio.out(result.report);\n\tif (result.reportPath) io.out(`report: ${result.reportPath}\\n`);\n\tif (result.failure) {\n\t\tio.out(\n\t\t\t`abaify: ended short after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}: ${result.failure}\\n`,\n\t\t);\n\t\treturn 1;\n\t}\n\tio.out(\n\t\tresult.dryRun\n\t\t\t? `dry run: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written\\n`\n\t\t\t: result.passed\n\t\t\t\t? `abaify: level ${result.level} reached in ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}\\n`\n\t\t\t\t: `abaify: level ${result.requested} not reached after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"} (reached ${result.level}); the report says what remains\\n`,\n\t);\n\treturn result.passed ? 0 : 1;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"abaify.d.ts","sourceRoot":"","sources":["../src/abaify.ts"],"names":[],"mappings":"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","sourcesContent":["// abaikit abaify: the agent-driven retrofit (section 13). abaikit's own\n// agent, with the configured model, works through the repo.* capabilities\n// and nothing else: no source line the repository had is ever removed or\n// reordered, and the three files structured tools rewrite (package.json,\n// abaigram.yaml, the lock file) are named as such. The person's invocation\n// is the consent for its writes, said at the start and in the report. Each\n// round the agent works until it stops, then abaikit runs check itself;\n// the level reached ends the loop, anything short of it is the next\n// prompt, up to the abaify.max_rounds knob. A model that cannot be reached\n// ends the run at once, and the report of what changed and why is left in\n// the repository whatever happened.\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { AgentModel, SessionForm, UserChannel } from \"@abaigram/abai-embedded-harness\";\nimport type { Mode } from \"@abaigram/abai-stub\";\nimport type { AbaikitApp } from \"./app.ts\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type CheckReport, checkRepository, recordCheck, renderReport } from \"./check.ts\";\nimport { type AbaikitIo, errorMessage, UsageError } from \"./io.ts\";\nimport { findSection, loadGuide } from \"./learnabai.ts\";\nimport { type Change, readRepo, summarizeRepo } from \"./repo.ts\";\n\nexport const REPORT_FILE = \"abai/abaify-report.md\";\n/** Lines of the entry file the brief quotes: enough to see where a launcher checks or imports its build. */\nconst HEAD_LINES = 80;\n/** The brief is thousands of tokens: the model's first answer may take minutes, which is not unreachability. */\nexport const FIRST_CONTACT_MS = 180_000;\n/** Scripts a brief names first, when the package has them. */\nconst KNOWN_SCRIPTS = [\"start\", \"build\", \"compile\", \"dev\", \"test\", \"check\", \"lint\", \"typecheck\"];\n\nexport interface AbaifyOptions {\n\trepo: string;\n\tlevel?: number;\n\tmode?: Mode;\n\trounds?: number;\n\tdryRun: boolean;\n\t/** A model for the agent; the machine's configured one otherwise. */\n\tmodel?: AgentModel;\n}\n\nexport interface Round {\n\tnumber: number;\n\tanswer: string;\n\tcheck: CheckReport | undefined;\n\t/** When check could not run: why (no abaigram.yaml yet, for one). */\n\tcheckError?: string;\n}\n\nexport interface AbaifyResult {\n\troot: string;\n\trequested: number;\n\tlevel: number;\n\tpassed: boolean;\n\tdryRun: boolean;\n\trounds: Round[];\n\tchanges: Change[];\n\t/** Why the run ended short, when it did: the model gone, an error. */\n\tfailure?: string;\n\treportPath?: string;\n\treport: string;\n}\n\ninterface Inventory {\n\tpackageJson: Record<string, unknown> | undefined;\n\tentryFile?: { path: string; head: string; to: number; total: number };\n\tguidelines?: { path: string; head: string };\n\tsummary: string;\n\ttotal: number;\n\t/** Build output present at the root (`dist/`, `build/`, `out/`), so the agent knows whether built paths run. */\n\tbuildOutput: string[];\n}\n\nfunction inventory(root: string): Inventory {\n\tlet packageJson: Record<string, unknown> | undefined;\n\ttry {\n\t\tpackageJson = JSON.parse(readFileSync(join(root, \"package.json\"), \"utf8\")) as Record<string, unknown>;\n\t} catch {\n\t\tpackageJson = undefined;\n\t}\n\tconst { total, summary } = summarizeRepo(root);\n\tconst buildOutput = [\"dist\", \"build\", \"out\"].filter(\n\t\t(d) => existsSync(join(root, d)) && statSync(join(root, d)).isDirectory(),\n\t);\n\tconst result: Inventory = { packageJson, summary, total, buildOutput };\n\tconst bin = packageJson?.bin;\n\tconst binTarget =\n\t\ttypeof bin === \"string\"\n\t\t\t? bin\n\t\t\t: bin && typeof bin === \"object\"\n\t\t\t\t? (Object.values(bin as Record<string, string>)[0] ?? undefined)\n\t\t\t\t: undefined;\n\tconst main = typeof packageJson?.main === \"string\" ? packageJson.main : undefined;\n\tfor (const candidate of [\n\t\tbinTarget,\n\t\tmain,\n\t\t\"src/main.ts\",\n\t\t\"src/main.js\",\n\t\t\"src/index.ts\",\n\t\t\"src/index.js\",\n\t\t\"index.js\",\n\t]) {\n\t\tif (candidate && existsSync(join(root, candidate))) {\n\t\t\tconst { text, to, total_lines } = readRepo(root, candidate, 1, HEAD_LINES);\n\t\t\tresult.entryFile = { path: candidate, head: text, to, total: total_lines };\n\t\t\tbreak;\n\t\t}\n\t}\n\tfor (const candidate of [\"AGENTS.md\", \"CONTRIBUTING.md\", \"CLAUDE.md\"]) {\n\t\tif (existsSync(join(root, candidate))) {\n\t\t\tconst { text } = readRepo(root, candidate, 1, 40);\n\t\t\tresult.guidelines = { path: candidate, head: text };\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction pickedScripts(scripts: Record<string, string>): Record<string, string> {\n\tconst picked: Record<string, string> = {};\n\tfor (const name of KNOWN_SCRIPTS) if (scripts[name] !== undefined) picked[name] = scripts[name];\n\tfor (const [name, value] of Object.entries(scripts)) {\n\t\tif (Object.keys(picked).length >= 15) break;\n\t\tif (picked[name] === undefined) picked[name] = value;\n\t}\n\treturn picked;\n}\n\n/** The first prompt: the task, the repository as it is, the procedure mapped onto the capabilities, the API, the rules. */\nexport function brief(root: string, level: number, mode: Mode | undefined, inv: Inventory, dryRun: boolean): string {\n\tconst guide = loadGuide();\n\tconst pkg = inv.packageJson;\n\tconst picked = pkg\n\t\t? JSON.stringify(\n\t\t\t\t{\n\t\t\t\t\tname: pkg.name,\n\t\t\t\t\ttype: pkg.type,\n\t\t\t\t\tbin: pkg.bin,\n\t\t\t\t\tmain: pkg.main,\n\t\t\t\t\tscripts: pickedScripts((pkg.scripts as Record<string, string>) ?? {}),\n\t\t\t\t},\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)\n\t\t: \"(no package.json)\";\n\tconst lines = [\n\t\t`Retrofit the repository at ${root} into an abaigram at level ${level}${mode ? ` in ${mode} mode: declare \"${mode}\" among the modes, since the check runs under it` : \"\"}.`,\n\t\t\"\",\n\t\t\"You work only through abaikit's capabilities (the abai_call tool), with `repo` set to exactly the path above:\",\n\t\t\"- repo.list (glob narrows: `*.json` by name anywhere, `src/**` or `scripts/*.mjs` by path; the listing is bounded and says so first), repo.read (a window of lines; the result says `from` and `to`).\",\n\t\t\"- repo.write makes a new file only. repo.insert puts text before or after one unique anchor line of an existing file; no line the repository had is ever removed or reordered. repo.add_dependency adds to package.json (a structured edit that re-serializes the file). repo.manifest runs the entry's `manifest` and writes abaigram.yaml: never write that file yourself. repo.install runs npm install (the lock file and node_modules are rewritten). check gives the conformance report.\",\n\t\t\"- You cannot run shell commands or build the repository. Where the procedure below says `npm install`, call repo.add_dependency for both `@abaigram/abai-stub` and `@abaigram/abai-embedded-harness`, then repo.install once. Where it says `<entry> manifest > abaigram.yaml`, call repo.manifest with the entry. Where it says `abaikit check`, call check. Where it says build: \" +\n\t\t\t(inv.buildOutput.length > 0\n\t\t\t\t? `the build output is present (${inv.buildOutput.map((d) => `${d}/`).join(\", \")}), so the built program runs; prefer a cell or event at a real boundary of the program and a check.run that is a real command of it, over a synthetic mode.`\n\t\t\t\t: \"there is no build output, so choose a path that needs no build: a plain `.mjs` module beside a launcher, a cell or event set in code that runs unbuilt.\"),\n\t\t\"- The `entry` you declare must end with the word `abai` (such as `node scripts/x.mjs abai` or `node dist/main.js abai`): the tools append `manifest`, `doctor` and `run` to it, and the stub's runEntry answers only when `abai` comes first. A file you wrote in this run you may write again with repo.write; a file the repository had you may only insert into.\",\n\t\t\"- You have the person's consent for this run; do not ask them.\",\n\t\tdryRun\n\t\t\t? \"- This is a dry run: call the capabilities as you would; nothing is written, repo.manifest and check cannot succeed on files that are not on disk, so propose the whole retrofit and stop.\"\n\t\t\t: \"\",\n\t\t\"\",\n\t\t`Repository shape (${inv.total} files after skipping dependencies, build output and ignored trees; directories with their counts, use repo.list with a glob to see inside one):`,\n\t\t\"```\",\n\t\tinv.summary,\n\t\t\"```\",\n\t\t\"package.json (the known scripts first):\",\n\t\t\"```json\",\n\t\tpicked,\n\t\t\"```\",\n\t];\n\tif (inv.entryFile) {\n\t\tlines.push(\n\t\t\t`The entry file ${inv.entryFile.path}, lines 1 to ${inv.entryFile.to} of ${inv.entryFile.total}:`,\n\t\t\t\"```\",\n\t\t\tinv.entryFile.head,\n\t\t\t\"```\",\n\t\t);\n\t}\n\tif (inv.guidelines) {\n\t\tlines.push(`The repository's own guidelines, ${inv.guidelines.path}, head:`, \"```\", inv.guidelines.head, \"```\");\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"The procedure, from abaikit learnabai (read it with the mapping above):\",\n\t\tfindSection(guide, \"retrofit\").text,\n\t\t\"\",\n\t\t\"The declaration API:\",\n\t\tfindSection(guide, \"declare\").text,\n\t\t\"\",\n\t\t\"What never to do:\",\n\t\tfindSection(guide, \"never\").text,\n\t\t\"\",\n\t\t`When you believe level ${level} is reached, call check once; when it reports level ${level} with no violation, answer with the word DONE on its own line followed by a short summary of what you added and why. If you cannot go on, say why.`,\n\t);\n\treturn lines.filter((l) => l !== undefined).join(\"\\n\");\n}\n\nfunction followUp(round: Round, level: number): string {\n\tconst status = round.check ? renderReport(round.check) : `check could not run: ${round.checkError}`;\n\treturn `abaikit ran check after your last round. Level ${level} is not reached yet. The report:\\n\\n${status}\\nFix what it names, through the capabilities, then call check again. Answer DONE with a summary when level ${level} holds with no violation.`;\n}\n\nfunction consentLine(root: string, level: number, dryRun: boolean): string {\n\treturn dryRun\n\t\t? `abaify: dry run on ${root}: the agent proposes a retrofit to level ${level}; nothing is written, though the repository's entry may be run read-only`\n\t\t: `abaify: ${root} to level ${level}, with your consent for this run: the agent may add files, insert lines, add dependencies, run npm install (network, the repository's own install scripts), run the repository's entry and its check.run under the null harness`;\n}\n\n/** The report left in the repository: every change with its why, the rounds, the last check. */\nexport function renderAbaifyReport(result: AbaifyResult, run: number): string {\n\tconst lines = [\n\t\t`# abaify report: ${result.root} (run ${run}, ${new Date().toISOString()})`,\n\t\t\"\",\n\t\tresult.dryRun\n\t\t\t? `Dry run to level ${result.requested}: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written, no check run.`\n\t\t\t: `Requested level ${result.requested}; reached level ${result.level}; ${result.passed ? \"passed\" : \"not passed\"} after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}.`,\n\t\tconsentLine(result.root, result.requested, result.dryRun).replace(/^abaify: /, \"Consent: \"),\n\t\t\"\",\n\t];\n\tif (result.failure) lines.push(`The run ended short: ${result.failure}`, \"\");\n\tconst added = result.changes.filter((c) => c.kind === \"added\");\n\tconst rewritten = result.changes.filter((c) => c.kind === \"rewritten\");\n\tlines.push(\"## Added (new files and inserted lines; nothing the repository had was removed or reordered)\", \"\");\n\tlines.push(\n\t\t...(added.length === 0 ? [\"(none)\"] : added.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rewritten in place by a structured tool\", \"\");\n\tlines.push(\n\t\t...(rewritten.length === 0\n\t\t\t? [\"(none)\"]\n\t\t\t: rewritten.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rounds\", \"\");\n\tfor (const r of result.rounds) {\n\t\tlines.push(`### Round ${r.number}`, \"\", \"The agent said:\", \"\", r.answer.trim() || \"(nothing)\", \"\");\n\t\tif (!result.dryRun) {\n\t\t\tlines.push(\n\t\t\t\tr.check\n\t\t\t\t\t? `check after the round: level ${r.check.level}, ${r.check.passed ? \"passed\" : \"not passed\"}`\n\t\t\t\t\t: `check could not run: ${r.checkError ?? \"unknown\"}`,\n\t\t\t\t\"\",\n\t\t\t);\n\t\t}\n\t}\n\tconst last = result.rounds.at(-1)?.check;\n\tif (last) lines.push(\"## The last check report\", \"\", \"```\", renderReport(last).trimEnd(), \"```\", \"\");\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\n/** The report appended to the repository's; the run number counts the reports already there. */\nfunction writeReport(root: string, result: AbaifyResult): string {\n\tconst path = join(root, REPORT_FILE);\n\tmkdirSync(join(root, \"abai\"), { recursive: true });\n\tconst previous = existsSync(path) ? readFileSync(path, \"utf8\") : \"\";\n\tconst run = (previous.match(/^# abaify report:/gm) ?? []).length + 1;\n\tresult.report = renderAbaifyReport(result, run);\n\twriteFileSync(path, (previous === \"\" ? \"\" : `${previous.trimEnd()}\\n\\n---\\n\\n`) + result.report);\n\treturn path;\n}\n\nexport async function abaify(options: AbaifyOptions, io: AbaikitIo, bundle: AbaikitApp): Promise<AbaifyResult> {\n\tconst { app, embedding, repo: ctx } = bundle;\n\tconst root = resolve(io.cwd, options.repo);\n\tif (!existsSync(join(root, \"package.json\"))) {\n\t\tthrow new UsageError(\n\t\t\t`${options.repo}: no package.json; abaify retrofits Node repositories (TypeScript or JavaScript) for now`,\n\t\t);\n\t}\n\tif (app.mode !== \"turns\")\n\t\tthrow new UsageError(`abaify runs its agent in-process; unset ABAI_MODE (it is ${app.mode})`);\n\tconst level = options.level ?? 1;\n\tconst rounds = app.knob<number>(\"abaify.max_rounds\");\n\tconst range = app.manifest.cells.find((c) => c.name === \"abaify.max_rounds\")?.range ?? [1, 20];\n\tif (options.rounds !== undefined) {\n\t\tif (options.rounds < range[0] || options.rounds > range[1])\n\t\t\tthrow new UsageError(`--rounds must be ${range[0]} to ${range[1]}`);\n\t\trounds.set(options.rounds);\n\t}\n\tconst say = (text: string) => io.err(text);\n\tsay(consentLine(root, level, options.dryRun));\n\tctx.dryRun = options.dryRun;\n\tctx.cwd = io.cwd;\n\tctx.onChange = (change) => say(`abaify: ${change.how} ${change.path}: ${change.detail}`);\n\tconst changesBefore = ctx.changes.length;\n\tconst result: AbaifyResult = {\n\t\troot,\n\t\trequested: level,\n\t\tlevel: 0,\n\t\tpassed: false,\n\t\tdryRun: options.dryRun,\n\t\trounds: [],\n\t\tchanges: [],\n\t\treport: \"\",\n\t};\n\n\t// the agent: abaikit's own embedded harness, auto consent (the invocation is the person's), a channel that only speaks\n\tconst channel: UserChannel = { say, ask: async () => undefined, confirm: async () => false, close: () => {} };\n\tembedding.options = {\n\t\tmodel: options.model,\n\t\tchannel,\n\t\tharness: { auto: true },\n\t\tconnectTimeoutMs: FIRST_CONTACT_MS,\n\t\taddendum: `You are retrofitting ${root} for a person who asked abaikit to do it; every write is theirs to approve and they approved this run.`,\n\t};\n\tlet form: SessionForm | undefined;\n\ttry {\n\t\tif (!(await app.attach()) || !embedding.current) {\n\t\t\tthrow new Error(\n\t\t\t\t`no agent could be attached: ${app.attachError ?? \"unknown\"} (configure one with \\`abaikit abai config\\`, or follow \\`abaikit learnabai\\` with your own agent)`,\n\t\t\t);\n\t\t}\n\t\tform = embedding.current.form;\n\t\tconst harness = embedding.current.harness;\n\t\tconst inv = inventory(root);\n\t\tlet prompt = brief(root, level, options.mode, inv, options.dryRun);\n\t\t// read once: an agent setting its own knob mid-run changes the next run, not this one\n\t\tconst max = options.dryRun ? 1 : rounds.get();\n\t\tfor (let n = 1; n <= max; n++) {\n\t\t\tsay(`abaify: round ${n} of ${max}`);\n\t\t\tconst answer = await form.prompt(prompt);\n\t\t\tconst round: Round = { number: n, answer, check: undefined };\n\t\t\tresult.rounds.push(round);\n\t\t\t// a run the model could not finish ends the loop: before first contact the form detaches; after it the\n\t\t\t// agent's last message carries the error, and another round would only spend the person's time\n\t\t\tconst last = [...form.agent.state.messages].reverse().find((m) => m.role === \"assistant\");\n\t\t\tconst stop = last && last.role === \"assistant\" ? last.stopReason : undefined;\n\t\t\tif (form.modelFailure !== undefined || !harness.attached) {\n\t\t\t\tresult.failure = `the model could not be reached: ${form.modelFailure ?? \"the harness detached\"}`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (stop === \"error\" || stop === \"aborted\") {\n\t\t\t\tconst why =\n\t\t\t\t\tlast && last.role === \"assistant\" && \"errorMessage\" in last ? String(last.errorMessage ?? stop) : stop;\n\t\t\t\tresult.failure = `the model could not be reached: the request ${stop === \"aborted\" ? \"was aborted\" : \"failed\"} (${why}); run abaify again when it is back`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!options.dryRun) {\n\t\t\t\tsay(`abaify: check after round ${n}`);\n\t\t\t\ttry {\n\t\t\t\t\tround.check = await checkRepository({ repo: root, cwd: io.cwd, env: io.env, level, mode: options.mode });\n\t\t\t\t\trecordCheck(app, round.check);\n\t\t\t\t\tresult.level = round.check.level;\n\t\t\t\t\tresult.passed = round.check.passed;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tround.checkError = errorMessage(error);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.passed) break;\n\t\t\tprompt = followUp(round, level);\n\t\t}\n\t} catch (error) {\n\t\tresult.failure = result.failure ?? errorMessage(error);\n\t\tthrow error;\n\t} finally {\n\t\tform?.shutdown(\"abaify ended\");\n\t\tctx.dryRun = false;\n\t\tctx.onChange = undefined;\n\t\tresult.changes = ctx.changes.slice(changesBefore);\n\t\t// the report is written whatever happened once the agent was at work, so nothing done goes unrecorded\n\t\tif (options.dryRun) result.report = renderAbaifyReport(result, 1);\n\t\telse if (result.rounds.length > 0 || result.changes.length > 0) result.reportPath = writeReport(root, result);\n\t}\n\tif (options.dryRun) result.passed = true;\n\treturn result;\n}\n\nexport async function runAbaify(\n\tparsed: Extract<ParsedArgs, { verb: \"abaify\" }>,\n\tio: AbaikitIo,\n\tbundle: AbaikitApp,\n\tdeps: { model?: AgentModel } = {},\n): Promise<number> {\n\tconst result = await abaify(\n\t\t{\n\t\t\trepo: parsed.repo,\n\t\t\tlevel: parsed.level,\n\t\t\tmode: parsed.mode,\n\t\t\trounds: parsed.rounds,\n\t\t\tdryRun: parsed.dryRun,\n\t\t\tmodel: deps.model,\n\t\t},\n\t\tio,\n\t\tbundle,\n\t);\n\tio.out(result.report);\n\tif (result.reportPath) io.out(`report: ${result.reportPath}\\n`);\n\tif (result.failure) {\n\t\tio.out(\n\t\t\t`abaify: ended short after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}: ${result.failure}\\n`,\n\t\t);\n\t\treturn 1;\n\t}\n\tio.out(\n\t\tresult.dryRun\n\t\t\t? `dry run: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written\\n`\n\t\t\t: result.passed\n\t\t\t\t? `abaify: level ${result.level} reached in ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}\\n`\n\t\t\t\t: `abaify: level ${result.requested} not reached after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"} (reached ${result.level}); the report says what remains\\n`,\n\t);\n\treturn result.passed ? 0 : 1;\n}\n"]}
|
package/dist/abaify.js
CHANGED
|
@@ -91,7 +91,7 @@ export function brief(root, level, mode, inv, dryRun) {
|
|
|
91
91
|
}, null, 2)
|
|
92
92
|
: "(no package.json)";
|
|
93
93
|
const lines = [
|
|
94
|
-
`Retrofit the repository at ${root} into an abaigram at level ${level}${mode ? ` in ${mode} mode` : ""}.`,
|
|
94
|
+
`Retrofit the repository at ${root} into an abaigram at level ${level}${mode ? ` in ${mode} mode: declare "${mode}" among the modes, since the check runs under it` : ""}.`,
|
|
95
95
|
"",
|
|
96
96
|
"You work only through abaikit's capabilities (the abai_call tool), with `repo` set to exactly the path above:",
|
|
97
97
|
"- repo.list (glob narrows: `*.json` by name anywhere, `src/**` or `scripts/*.mjs` by path; the listing is bounded and says so first), repo.read (a window of lines; the result says `from` and `to`).",
|
package/dist/abaify.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abaify.js","sourceRoot":"","sources":["../src/abaify.ts"],"names":[],"mappings":"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","sourcesContent":["// abaikit abaify: the agent-driven retrofit (section 13). abaikit's own\n// agent, with the configured model, works through the repo.* capabilities\n// and nothing else: no source line the repository had is ever removed or\n// reordered, and the three files structured tools rewrite (package.json,\n// abaigram.yaml, the lock file) are named as such. The person's invocation\n// is the consent for its writes, said at the start and in the report. Each\n// round the agent works until it stops, then abaikit runs check itself;\n// the level reached ends the loop, anything short of it is the next\n// prompt, up to the abaify.max_rounds knob. A model that cannot be reached\n// ends the run at once, and the report of what changed and why is left in\n// the repository whatever happened.\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { AgentModel, SessionForm, UserChannel } from \"@abaigram/abai-embedded-harness\";\nimport type { Mode } from \"@abaigram/abai-stub\";\nimport type { AbaikitApp } from \"./app.ts\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type CheckReport, checkRepository, recordCheck, renderReport } from \"./check.ts\";\nimport { type AbaikitIo, errorMessage, UsageError } from \"./io.ts\";\nimport { findSection, loadGuide } from \"./learnabai.ts\";\nimport { type Change, readRepo, summarizeRepo } from \"./repo.ts\";\n\nexport const REPORT_FILE = \"abai/abaify-report.md\";\n/** Lines of the entry file the brief quotes: enough to see where a launcher checks or imports its build. */\nconst HEAD_LINES = 80;\n/** The brief is thousands of tokens: the model's first answer may take minutes, which is not unreachability. */\nexport const FIRST_CONTACT_MS = 180_000;\n/** Scripts a brief names first, when the package has them. */\nconst KNOWN_SCRIPTS = [\"start\", \"build\", \"compile\", \"dev\", \"test\", \"check\", \"lint\", \"typecheck\"];\n\nexport interface AbaifyOptions {\n\trepo: string;\n\tlevel?: number;\n\tmode?: Mode;\n\trounds?: number;\n\tdryRun: boolean;\n\t/** A model for the agent; the machine's configured one otherwise. */\n\tmodel?: AgentModel;\n}\n\nexport interface Round {\n\tnumber: number;\n\tanswer: string;\n\tcheck: CheckReport | undefined;\n\t/** When check could not run: why (no abaigram.yaml yet, for one). */\n\tcheckError?: string;\n}\n\nexport interface AbaifyResult {\n\troot: string;\n\trequested: number;\n\tlevel: number;\n\tpassed: boolean;\n\tdryRun: boolean;\n\trounds: Round[];\n\tchanges: Change[];\n\t/** Why the run ended short, when it did: the model gone, an error. */\n\tfailure?: string;\n\treportPath?: string;\n\treport: string;\n}\n\ninterface Inventory {\n\tpackageJson: Record<string, unknown> | undefined;\n\tentryFile?: { path: string; head: string; to: number; total: number };\n\tguidelines?: { path: string; head: string };\n\tsummary: string;\n\ttotal: number;\n\t/** Build output present at the root (`dist/`, `build/`, `out/`), so the agent knows whether built paths run. */\n\tbuildOutput: string[];\n}\n\nfunction inventory(root: string): Inventory {\n\tlet packageJson: Record<string, unknown> | undefined;\n\ttry {\n\t\tpackageJson = JSON.parse(readFileSync(join(root, \"package.json\"), \"utf8\")) as Record<string, unknown>;\n\t} catch {\n\t\tpackageJson = undefined;\n\t}\n\tconst { total, summary } = summarizeRepo(root);\n\tconst buildOutput = [\"dist\", \"build\", \"out\"].filter(\n\t\t(d) => existsSync(join(root, d)) && statSync(join(root, d)).isDirectory(),\n\t);\n\tconst result: Inventory = { packageJson, summary, total, buildOutput };\n\tconst bin = packageJson?.bin;\n\tconst binTarget =\n\t\ttypeof bin === \"string\"\n\t\t\t? bin\n\t\t\t: bin && typeof bin === \"object\"\n\t\t\t\t? (Object.values(bin as Record<string, string>)[0] ?? undefined)\n\t\t\t\t: undefined;\n\tconst main = typeof packageJson?.main === \"string\" ? packageJson.main : undefined;\n\tfor (const candidate of [\n\t\tbinTarget,\n\t\tmain,\n\t\t\"src/main.ts\",\n\t\t\"src/main.js\",\n\t\t\"src/index.ts\",\n\t\t\"src/index.js\",\n\t\t\"index.js\",\n\t]) {\n\t\tif (candidate && existsSync(join(root, candidate))) {\n\t\t\tconst { text, to, total_lines } = readRepo(root, candidate, 1, HEAD_LINES);\n\t\t\tresult.entryFile = { path: candidate, head: text, to, total: total_lines };\n\t\t\tbreak;\n\t\t}\n\t}\n\tfor (const candidate of [\"AGENTS.md\", \"CONTRIBUTING.md\", \"CLAUDE.md\"]) {\n\t\tif (existsSync(join(root, candidate))) {\n\t\t\tconst { text } = readRepo(root, candidate, 1, 40);\n\t\t\tresult.guidelines = { path: candidate, head: text };\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction pickedScripts(scripts: Record<string, string>): Record<string, string> {\n\tconst picked: Record<string, string> = {};\n\tfor (const name of KNOWN_SCRIPTS) if (scripts[name] !== undefined) picked[name] = scripts[name];\n\tfor (const [name, value] of Object.entries(scripts)) {\n\t\tif (Object.keys(picked).length >= 15) break;\n\t\tif (picked[name] === undefined) picked[name] = value;\n\t}\n\treturn picked;\n}\n\n/** The first prompt: the task, the repository as it is, the procedure mapped onto the capabilities, the API, the rules. */\nexport function brief(root: string, level: number, mode: Mode | undefined, inv: Inventory, dryRun: boolean): string {\n\tconst guide = loadGuide();\n\tconst pkg = inv.packageJson;\n\tconst picked = pkg\n\t\t? JSON.stringify(\n\t\t\t\t{\n\t\t\t\t\tname: pkg.name,\n\t\t\t\t\ttype: pkg.type,\n\t\t\t\t\tbin: pkg.bin,\n\t\t\t\t\tmain: pkg.main,\n\t\t\t\t\tscripts: pickedScripts((pkg.scripts as Record<string, string>) ?? {}),\n\t\t\t\t},\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)\n\t\t: \"(no package.json)\";\n\tconst lines = [\n\t\t`Retrofit the repository at ${root} into an abaigram at level ${level}${mode ? ` in ${mode} mode` : \"\"}.`,\n\t\t\"\",\n\t\t\"You work only through abaikit's capabilities (the abai_call tool), with `repo` set to exactly the path above:\",\n\t\t\"- repo.list (glob narrows: `*.json` by name anywhere, `src/**` or `scripts/*.mjs` by path; the listing is bounded and says so first), repo.read (a window of lines; the result says `from` and `to`).\",\n\t\t\"- repo.write makes a new file only. repo.insert puts text before or after one unique anchor line of an existing file; no line the repository had is ever removed or reordered. repo.add_dependency adds to package.json (a structured edit that re-serializes the file). repo.manifest runs the entry's `manifest` and writes abaigram.yaml: never write that file yourself. repo.install runs npm install (the lock file and node_modules are rewritten). check gives the conformance report.\",\n\t\t\"- You cannot run shell commands or build the repository. Where the procedure below says `npm install`, call repo.add_dependency for both `@abaigram/abai-stub` and `@abaigram/abai-embedded-harness`, then repo.install once. Where it says `<entry> manifest > abaigram.yaml`, call repo.manifest with the entry. Where it says `abaikit check`, call check. Where it says build: \" +\n\t\t\t(inv.buildOutput.length > 0\n\t\t\t\t? `the build output is present (${inv.buildOutput.map((d) => `${d}/`).join(\", \")}), so the built program runs; prefer a cell or event at a real boundary of the program and a check.run that is a real command of it, over a synthetic mode.`\n\t\t\t\t: \"there is no build output, so choose a path that needs no build: a plain `.mjs` module beside a launcher, a cell or event set in code that runs unbuilt.\"),\n\t\t\"- The `entry` you declare must end with the word `abai` (such as `node scripts/x.mjs abai` or `node dist/main.js abai`): the tools append `manifest`, `doctor` and `run` to it, and the stub's runEntry answers only when `abai` comes first. A file you wrote in this run you may write again with repo.write; a file the repository had you may only insert into.\",\n\t\t\"- You have the person's consent for this run; do not ask them.\",\n\t\tdryRun\n\t\t\t? \"- This is a dry run: call the capabilities as you would; nothing is written, repo.manifest and check cannot succeed on files that are not on disk, so propose the whole retrofit and stop.\"\n\t\t\t: \"\",\n\t\t\"\",\n\t\t`Repository shape (${inv.total} files after skipping dependencies, build output and ignored trees; directories with their counts, use repo.list with a glob to see inside one):`,\n\t\t\"```\",\n\t\tinv.summary,\n\t\t\"```\",\n\t\t\"package.json (the known scripts first):\",\n\t\t\"```json\",\n\t\tpicked,\n\t\t\"```\",\n\t];\n\tif (inv.entryFile) {\n\t\tlines.push(\n\t\t\t`The entry file ${inv.entryFile.path}, lines 1 to ${inv.entryFile.to} of ${inv.entryFile.total}:`,\n\t\t\t\"```\",\n\t\t\tinv.entryFile.head,\n\t\t\t\"```\",\n\t\t);\n\t}\n\tif (inv.guidelines) {\n\t\tlines.push(`The repository's own guidelines, ${inv.guidelines.path}, head:`, \"```\", inv.guidelines.head, \"```\");\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"The procedure, from abaikit learnabai (read it with the mapping above):\",\n\t\tfindSection(guide, \"retrofit\").text,\n\t\t\"\",\n\t\t\"The declaration API:\",\n\t\tfindSection(guide, \"declare\").text,\n\t\t\"\",\n\t\t\"What never to do:\",\n\t\tfindSection(guide, \"never\").text,\n\t\t\"\",\n\t\t`When you believe level ${level} is reached, call check once; when it reports level ${level} with no violation, answer with the word DONE on its own line followed by a short summary of what you added and why. If you cannot go on, say why.`,\n\t);\n\treturn lines.filter((l) => l !== undefined).join(\"\\n\");\n}\n\nfunction followUp(round: Round, level: number): string {\n\tconst status = round.check ? renderReport(round.check) : `check could not run: ${round.checkError}`;\n\treturn `abaikit ran check after your last round. Level ${level} is not reached yet. The report:\\n\\n${status}\\nFix what it names, through the capabilities, then call check again. Answer DONE with a summary when level ${level} holds with no violation.`;\n}\n\nfunction consentLine(root: string, level: number, dryRun: boolean): string {\n\treturn dryRun\n\t\t? `abaify: dry run on ${root}: the agent proposes a retrofit to level ${level}; nothing is written, though the repository's entry may be run read-only`\n\t\t: `abaify: ${root} to level ${level}, with your consent for this run: the agent may add files, insert lines, add dependencies, run npm install (network, the repository's own install scripts), run the repository's entry and its check.run under the null harness`;\n}\n\n/** The report left in the repository: every change with its why, the rounds, the last check. */\nexport function renderAbaifyReport(result: AbaifyResult, run: number): string {\n\tconst lines = [\n\t\t`# abaify report: ${result.root} (run ${run}, ${new Date().toISOString()})`,\n\t\t\"\",\n\t\tresult.dryRun\n\t\t\t? `Dry run to level ${result.requested}: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written, no check run.`\n\t\t\t: `Requested level ${result.requested}; reached level ${result.level}; ${result.passed ? \"passed\" : \"not passed\"} after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}.`,\n\t\tconsentLine(result.root, result.requested, result.dryRun).replace(/^abaify: /, \"Consent: \"),\n\t\t\"\",\n\t];\n\tif (result.failure) lines.push(`The run ended short: ${result.failure}`, \"\");\n\tconst added = result.changes.filter((c) => c.kind === \"added\");\n\tconst rewritten = result.changes.filter((c) => c.kind === \"rewritten\");\n\tlines.push(\"## Added (new files and inserted lines; nothing the repository had was removed or reordered)\", \"\");\n\tlines.push(\n\t\t...(added.length === 0 ? [\"(none)\"] : added.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rewritten in place by a structured tool\", \"\");\n\tlines.push(\n\t\t...(rewritten.length === 0\n\t\t\t? [\"(none)\"]\n\t\t\t: rewritten.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rounds\", \"\");\n\tfor (const r of result.rounds) {\n\t\tlines.push(`### Round ${r.number}`, \"\", \"The agent said:\", \"\", r.answer.trim() || \"(nothing)\", \"\");\n\t\tif (!result.dryRun) {\n\t\t\tlines.push(\n\t\t\t\tr.check\n\t\t\t\t\t? `check after the round: level ${r.check.level}, ${r.check.passed ? \"passed\" : \"not passed\"}`\n\t\t\t\t\t: `check could not run: ${r.checkError ?? \"unknown\"}`,\n\t\t\t\t\"\",\n\t\t\t);\n\t\t}\n\t}\n\tconst last = result.rounds.at(-1)?.check;\n\tif (last) lines.push(\"## The last check report\", \"\", \"```\", renderReport(last).trimEnd(), \"```\", \"\");\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\n/** The report appended to the repository's; the run number counts the reports already there. */\nfunction writeReport(root: string, result: AbaifyResult): string {\n\tconst path = join(root, REPORT_FILE);\n\tmkdirSync(join(root, \"abai\"), { recursive: true });\n\tconst previous = existsSync(path) ? readFileSync(path, \"utf8\") : \"\";\n\tconst run = (previous.match(/^# abaify report:/gm) ?? []).length + 1;\n\tresult.report = renderAbaifyReport(result, run);\n\twriteFileSync(path, (previous === \"\" ? \"\" : `${previous.trimEnd()}\\n\\n---\\n\\n`) + result.report);\n\treturn path;\n}\n\nexport async function abaify(options: AbaifyOptions, io: AbaikitIo, bundle: AbaikitApp): Promise<AbaifyResult> {\n\tconst { app, embedding, repo: ctx } = bundle;\n\tconst root = resolve(io.cwd, options.repo);\n\tif (!existsSync(join(root, \"package.json\"))) {\n\t\tthrow new UsageError(\n\t\t\t`${options.repo}: no package.json; abaify retrofits Node repositories (TypeScript or JavaScript) for now`,\n\t\t);\n\t}\n\tif (app.mode !== \"turns\")\n\t\tthrow new UsageError(`abaify runs its agent in-process; unset ABAI_MODE (it is ${app.mode})`);\n\tconst level = options.level ?? 1;\n\tconst rounds = app.knob<number>(\"abaify.max_rounds\");\n\tconst range = app.manifest.cells.find((c) => c.name === \"abaify.max_rounds\")?.range ?? [1, 20];\n\tif (options.rounds !== undefined) {\n\t\tif (options.rounds < range[0] || options.rounds > range[1])\n\t\t\tthrow new UsageError(`--rounds must be ${range[0]} to ${range[1]}`);\n\t\trounds.set(options.rounds);\n\t}\n\tconst say = (text: string) => io.err(text);\n\tsay(consentLine(root, level, options.dryRun));\n\tctx.dryRun = options.dryRun;\n\tctx.cwd = io.cwd;\n\tctx.onChange = (change) => say(`abaify: ${change.how} ${change.path}: ${change.detail}`);\n\tconst changesBefore = ctx.changes.length;\n\tconst result: AbaifyResult = {\n\t\troot,\n\t\trequested: level,\n\t\tlevel: 0,\n\t\tpassed: false,\n\t\tdryRun: options.dryRun,\n\t\trounds: [],\n\t\tchanges: [],\n\t\treport: \"\",\n\t};\n\n\t// the agent: abaikit's own embedded harness, auto consent (the invocation is the person's), a channel that only speaks\n\tconst channel: UserChannel = { say, ask: async () => undefined, confirm: async () => false, close: () => {} };\n\tembedding.options = {\n\t\tmodel: options.model,\n\t\tchannel,\n\t\tharness: { auto: true },\n\t\tconnectTimeoutMs: FIRST_CONTACT_MS,\n\t\taddendum: `You are retrofitting ${root} for a person who asked abaikit to do it; every write is theirs to approve and they approved this run.`,\n\t};\n\tlet form: SessionForm | undefined;\n\ttry {\n\t\tif (!(await app.attach()) || !embedding.current) {\n\t\t\tthrow new Error(\n\t\t\t\t`no agent could be attached: ${app.attachError ?? \"unknown\"} (configure one with \\`abaikit abai config\\`, or follow \\`abaikit learnabai\\` with your own agent)`,\n\t\t\t);\n\t\t}\n\t\tform = embedding.current.form;\n\t\tconst harness = embedding.current.harness;\n\t\tconst inv = inventory(root);\n\t\tlet prompt = brief(root, level, options.mode, inv, options.dryRun);\n\t\t// read once: an agent setting its own knob mid-run changes the next run, not this one\n\t\tconst max = options.dryRun ? 1 : rounds.get();\n\t\tfor (let n = 1; n <= max; n++) {\n\t\t\tsay(`abaify: round ${n} of ${max}`);\n\t\t\tconst answer = await form.prompt(prompt);\n\t\t\tconst round: Round = { number: n, answer, check: undefined };\n\t\t\tresult.rounds.push(round);\n\t\t\t// a run the model could not finish ends the loop: before first contact the form detaches; after it the\n\t\t\t// agent's last message carries the error, and another round would only spend the person's time\n\t\t\tconst last = [...form.agent.state.messages].reverse().find((m) => m.role === \"assistant\");\n\t\t\tconst stop = last && last.role === \"assistant\" ? last.stopReason : undefined;\n\t\t\tif (form.modelFailure !== undefined || !harness.attached) {\n\t\t\t\tresult.failure = `the model could not be reached: ${form.modelFailure ?? \"the harness detached\"}`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (stop === \"error\" || stop === \"aborted\") {\n\t\t\t\tconst why =\n\t\t\t\t\tlast && last.role === \"assistant\" && \"errorMessage\" in last ? String(last.errorMessage ?? stop) : stop;\n\t\t\t\tresult.failure = `the model could not be reached: the request ${stop === \"aborted\" ? \"was aborted\" : \"failed\"} (${why}); run abaify again when it is back`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!options.dryRun) {\n\t\t\t\tsay(`abaify: check after round ${n}`);\n\t\t\t\ttry {\n\t\t\t\t\tround.check = await checkRepository({ repo: root, cwd: io.cwd, env: io.env, level, mode: options.mode });\n\t\t\t\t\trecordCheck(app, round.check);\n\t\t\t\t\tresult.level = round.check.level;\n\t\t\t\t\tresult.passed = round.check.passed;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tround.checkError = errorMessage(error);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.passed) break;\n\t\t\tprompt = followUp(round, level);\n\t\t}\n\t} catch (error) {\n\t\tresult.failure = result.failure ?? errorMessage(error);\n\t\tthrow error;\n\t} finally {\n\t\tform?.shutdown(\"abaify ended\");\n\t\tctx.dryRun = false;\n\t\tctx.onChange = undefined;\n\t\tresult.changes = ctx.changes.slice(changesBefore);\n\t\t// the report is written whatever happened once the agent was at work, so nothing done goes unrecorded\n\t\tif (options.dryRun) result.report = renderAbaifyReport(result, 1);\n\t\telse if (result.rounds.length > 0 || result.changes.length > 0) result.reportPath = writeReport(root, result);\n\t}\n\tif (options.dryRun) result.passed = true;\n\treturn result;\n}\n\nexport async function runAbaify(\n\tparsed: Extract<ParsedArgs, { verb: \"abaify\" }>,\n\tio: AbaikitIo,\n\tbundle: AbaikitApp,\n\tdeps: { model?: AgentModel } = {},\n): Promise<number> {\n\tconst result = await abaify(\n\t\t{\n\t\t\trepo: parsed.repo,\n\t\t\tlevel: parsed.level,\n\t\t\tmode: parsed.mode,\n\t\t\trounds: parsed.rounds,\n\t\t\tdryRun: parsed.dryRun,\n\t\t\tmodel: deps.model,\n\t\t},\n\t\tio,\n\t\tbundle,\n\t);\n\tio.out(result.report);\n\tif (result.reportPath) io.out(`report: ${result.reportPath}\\n`);\n\tif (result.failure) {\n\t\tio.out(\n\t\t\t`abaify: ended short after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}: ${result.failure}\\n`,\n\t\t);\n\t\treturn 1;\n\t}\n\tio.out(\n\t\tresult.dryRun\n\t\t\t? `dry run: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written\\n`\n\t\t\t: result.passed\n\t\t\t\t? `abaify: level ${result.level} reached in ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}\\n`\n\t\t\t\t: `abaify: level ${result.requested} not reached after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"} (reached ${result.level}); the report says what remains\\n`,\n\t);\n\treturn result.passed ? 0 : 1;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"abaify.js","sourceRoot":"","sources":["../src/abaify.ts"],"names":[],"mappings":"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","sourcesContent":["// abaikit abaify: the agent-driven retrofit (section 13). abaikit's own\n// agent, with the configured model, works through the repo.* capabilities\n// and nothing else: no source line the repository had is ever removed or\n// reordered, and the three files structured tools rewrite (package.json,\n// abaigram.yaml, the lock file) are named as such. The person's invocation\n// is the consent for its writes, said at the start and in the report. Each\n// round the agent works until it stops, then abaikit runs check itself;\n// the level reached ends the loop, anything short of it is the next\n// prompt, up to the abaify.max_rounds knob. A model that cannot be reached\n// ends the run at once, and the report of what changed and why is left in\n// the repository whatever happened.\nimport { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\nimport type { AgentModel, SessionForm, UserChannel } from \"@abaigram/abai-embedded-harness\";\nimport type { Mode } from \"@abaigram/abai-stub\";\nimport type { AbaikitApp } from \"./app.ts\";\nimport type { ParsedArgs } from \"./bin.ts\";\nimport { type CheckReport, checkRepository, recordCheck, renderReport } from \"./check.ts\";\nimport { type AbaikitIo, errorMessage, UsageError } from \"./io.ts\";\nimport { findSection, loadGuide } from \"./learnabai.ts\";\nimport { type Change, readRepo, summarizeRepo } from \"./repo.ts\";\n\nexport const REPORT_FILE = \"abai/abaify-report.md\";\n/** Lines of the entry file the brief quotes: enough to see where a launcher checks or imports its build. */\nconst HEAD_LINES = 80;\n/** The brief is thousands of tokens: the model's first answer may take minutes, which is not unreachability. */\nexport const FIRST_CONTACT_MS = 180_000;\n/** Scripts a brief names first, when the package has them. */\nconst KNOWN_SCRIPTS = [\"start\", \"build\", \"compile\", \"dev\", \"test\", \"check\", \"lint\", \"typecheck\"];\n\nexport interface AbaifyOptions {\n\trepo: string;\n\tlevel?: number;\n\tmode?: Mode;\n\trounds?: number;\n\tdryRun: boolean;\n\t/** A model for the agent; the machine's configured one otherwise. */\n\tmodel?: AgentModel;\n}\n\nexport interface Round {\n\tnumber: number;\n\tanswer: string;\n\tcheck: CheckReport | undefined;\n\t/** When check could not run: why (no abaigram.yaml yet, for one). */\n\tcheckError?: string;\n}\n\nexport interface AbaifyResult {\n\troot: string;\n\trequested: number;\n\tlevel: number;\n\tpassed: boolean;\n\tdryRun: boolean;\n\trounds: Round[];\n\tchanges: Change[];\n\t/** Why the run ended short, when it did: the model gone, an error. */\n\tfailure?: string;\n\treportPath?: string;\n\treport: string;\n}\n\ninterface Inventory {\n\tpackageJson: Record<string, unknown> | undefined;\n\tentryFile?: { path: string; head: string; to: number; total: number };\n\tguidelines?: { path: string; head: string };\n\tsummary: string;\n\ttotal: number;\n\t/** Build output present at the root (`dist/`, `build/`, `out/`), so the agent knows whether built paths run. */\n\tbuildOutput: string[];\n}\n\nfunction inventory(root: string): Inventory {\n\tlet packageJson: Record<string, unknown> | undefined;\n\ttry {\n\t\tpackageJson = JSON.parse(readFileSync(join(root, \"package.json\"), \"utf8\")) as Record<string, unknown>;\n\t} catch {\n\t\tpackageJson = undefined;\n\t}\n\tconst { total, summary } = summarizeRepo(root);\n\tconst buildOutput = [\"dist\", \"build\", \"out\"].filter(\n\t\t(d) => existsSync(join(root, d)) && statSync(join(root, d)).isDirectory(),\n\t);\n\tconst result: Inventory = { packageJson, summary, total, buildOutput };\n\tconst bin = packageJson?.bin;\n\tconst binTarget =\n\t\ttypeof bin === \"string\"\n\t\t\t? bin\n\t\t\t: bin && typeof bin === \"object\"\n\t\t\t\t? (Object.values(bin as Record<string, string>)[0] ?? undefined)\n\t\t\t\t: undefined;\n\tconst main = typeof packageJson?.main === \"string\" ? packageJson.main : undefined;\n\tfor (const candidate of [\n\t\tbinTarget,\n\t\tmain,\n\t\t\"src/main.ts\",\n\t\t\"src/main.js\",\n\t\t\"src/index.ts\",\n\t\t\"src/index.js\",\n\t\t\"index.js\",\n\t]) {\n\t\tif (candidate && existsSync(join(root, candidate))) {\n\t\t\tconst { text, to, total_lines } = readRepo(root, candidate, 1, HEAD_LINES);\n\t\t\tresult.entryFile = { path: candidate, head: text, to, total: total_lines };\n\t\t\tbreak;\n\t\t}\n\t}\n\tfor (const candidate of [\"AGENTS.md\", \"CONTRIBUTING.md\", \"CLAUDE.md\"]) {\n\t\tif (existsSync(join(root, candidate))) {\n\t\t\tconst { text } = readRepo(root, candidate, 1, 40);\n\t\t\tresult.guidelines = { path: candidate, head: text };\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction pickedScripts(scripts: Record<string, string>): Record<string, string> {\n\tconst picked: Record<string, string> = {};\n\tfor (const name of KNOWN_SCRIPTS) if (scripts[name] !== undefined) picked[name] = scripts[name];\n\tfor (const [name, value] of Object.entries(scripts)) {\n\t\tif (Object.keys(picked).length >= 15) break;\n\t\tif (picked[name] === undefined) picked[name] = value;\n\t}\n\treturn picked;\n}\n\n/** The first prompt: the task, the repository as it is, the procedure mapped onto the capabilities, the API, the rules. */\nexport function brief(root: string, level: number, mode: Mode | undefined, inv: Inventory, dryRun: boolean): string {\n\tconst guide = loadGuide();\n\tconst pkg = inv.packageJson;\n\tconst picked = pkg\n\t\t? JSON.stringify(\n\t\t\t\t{\n\t\t\t\t\tname: pkg.name,\n\t\t\t\t\ttype: pkg.type,\n\t\t\t\t\tbin: pkg.bin,\n\t\t\t\t\tmain: pkg.main,\n\t\t\t\t\tscripts: pickedScripts((pkg.scripts as Record<string, string>) ?? {}),\n\t\t\t\t},\n\t\t\t\tnull,\n\t\t\t\t2,\n\t\t\t)\n\t\t: \"(no package.json)\";\n\tconst lines = [\n\t\t`Retrofit the repository at ${root} into an abaigram at level ${level}${mode ? ` in ${mode} mode: declare \"${mode}\" among the modes, since the check runs under it` : \"\"}.`,\n\t\t\"\",\n\t\t\"You work only through abaikit's capabilities (the abai_call tool), with `repo` set to exactly the path above:\",\n\t\t\"- repo.list (glob narrows: `*.json` by name anywhere, `src/**` or `scripts/*.mjs` by path; the listing is bounded and says so first), repo.read (a window of lines; the result says `from` and `to`).\",\n\t\t\"- repo.write makes a new file only. repo.insert puts text before or after one unique anchor line of an existing file; no line the repository had is ever removed or reordered. repo.add_dependency adds to package.json (a structured edit that re-serializes the file). repo.manifest runs the entry's `manifest` and writes abaigram.yaml: never write that file yourself. repo.install runs npm install (the lock file and node_modules are rewritten). check gives the conformance report.\",\n\t\t\"- You cannot run shell commands or build the repository. Where the procedure below says `npm install`, call repo.add_dependency for both `@abaigram/abai-stub` and `@abaigram/abai-embedded-harness`, then repo.install once. Where it says `<entry> manifest > abaigram.yaml`, call repo.manifest with the entry. Where it says `abaikit check`, call check. Where it says build: \" +\n\t\t\t(inv.buildOutput.length > 0\n\t\t\t\t? `the build output is present (${inv.buildOutput.map((d) => `${d}/`).join(\", \")}), so the built program runs; prefer a cell or event at a real boundary of the program and a check.run that is a real command of it, over a synthetic mode.`\n\t\t\t\t: \"there is no build output, so choose a path that needs no build: a plain `.mjs` module beside a launcher, a cell or event set in code that runs unbuilt.\"),\n\t\t\"- The `entry` you declare must end with the word `abai` (such as `node scripts/x.mjs abai` or `node dist/main.js abai`): the tools append `manifest`, `doctor` and `run` to it, and the stub's runEntry answers only when `abai` comes first. A file you wrote in this run you may write again with repo.write; a file the repository had you may only insert into.\",\n\t\t\"- You have the person's consent for this run; do not ask them.\",\n\t\tdryRun\n\t\t\t? \"- This is a dry run: call the capabilities as you would; nothing is written, repo.manifest and check cannot succeed on files that are not on disk, so propose the whole retrofit and stop.\"\n\t\t\t: \"\",\n\t\t\"\",\n\t\t`Repository shape (${inv.total} files after skipping dependencies, build output and ignored trees; directories with their counts, use repo.list with a glob to see inside one):`,\n\t\t\"```\",\n\t\tinv.summary,\n\t\t\"```\",\n\t\t\"package.json (the known scripts first):\",\n\t\t\"```json\",\n\t\tpicked,\n\t\t\"```\",\n\t];\n\tif (inv.entryFile) {\n\t\tlines.push(\n\t\t\t`The entry file ${inv.entryFile.path}, lines 1 to ${inv.entryFile.to} of ${inv.entryFile.total}:`,\n\t\t\t\"```\",\n\t\t\tinv.entryFile.head,\n\t\t\t\"```\",\n\t\t);\n\t}\n\tif (inv.guidelines) {\n\t\tlines.push(`The repository's own guidelines, ${inv.guidelines.path}, head:`, \"```\", inv.guidelines.head, \"```\");\n\t}\n\tlines.push(\n\t\t\"\",\n\t\t\"The procedure, from abaikit learnabai (read it with the mapping above):\",\n\t\tfindSection(guide, \"retrofit\").text,\n\t\t\"\",\n\t\t\"The declaration API:\",\n\t\tfindSection(guide, \"declare\").text,\n\t\t\"\",\n\t\t\"What never to do:\",\n\t\tfindSection(guide, \"never\").text,\n\t\t\"\",\n\t\t`When you believe level ${level} is reached, call check once; when it reports level ${level} with no violation, answer with the word DONE on its own line followed by a short summary of what you added and why. If you cannot go on, say why.`,\n\t);\n\treturn lines.filter((l) => l !== undefined).join(\"\\n\");\n}\n\nfunction followUp(round: Round, level: number): string {\n\tconst status = round.check ? renderReport(round.check) : `check could not run: ${round.checkError}`;\n\treturn `abaikit ran check after your last round. Level ${level} is not reached yet. The report:\\n\\n${status}\\nFix what it names, through the capabilities, then call check again. Answer DONE with a summary when level ${level} holds with no violation.`;\n}\n\nfunction consentLine(root: string, level: number, dryRun: boolean): string {\n\treturn dryRun\n\t\t? `abaify: dry run on ${root}: the agent proposes a retrofit to level ${level}; nothing is written, though the repository's entry may be run read-only`\n\t\t: `abaify: ${root} to level ${level}, with your consent for this run: the agent may add files, insert lines, add dependencies, run npm install (network, the repository's own install scripts), run the repository's entry and its check.run under the null harness`;\n}\n\n/** The report left in the repository: every change with its why, the rounds, the last check. */\nexport function renderAbaifyReport(result: AbaifyResult, run: number): string {\n\tconst lines = [\n\t\t`# abaify report: ${result.root} (run ${run}, ${new Date().toISOString()})`,\n\t\t\"\",\n\t\tresult.dryRun\n\t\t\t? `Dry run to level ${result.requested}: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written, no check run.`\n\t\t\t: `Requested level ${result.requested}; reached level ${result.level}; ${result.passed ? \"passed\" : \"not passed\"} after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}.`,\n\t\tconsentLine(result.root, result.requested, result.dryRun).replace(/^abaify: /, \"Consent: \"),\n\t\t\"\",\n\t];\n\tif (result.failure) lines.push(`The run ended short: ${result.failure}`, \"\");\n\tconst added = result.changes.filter((c) => c.kind === \"added\");\n\tconst rewritten = result.changes.filter((c) => c.kind === \"rewritten\");\n\tlines.push(\"## Added (new files and inserted lines; nothing the repository had was removed or reordered)\", \"\");\n\tlines.push(\n\t\t...(added.length === 0 ? [\"(none)\"] : added.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rewritten in place by a structured tool\", \"\");\n\tlines.push(\n\t\t...(rewritten.length === 0\n\t\t\t? [\"(none)\"]\n\t\t\t: rewritten.map((c) => `- ${c.how} ${c.path}: ${c.detail}. Why: ${c.why}`)),\n\t);\n\tlines.push(\"\", \"## Rounds\", \"\");\n\tfor (const r of result.rounds) {\n\t\tlines.push(`### Round ${r.number}`, \"\", \"The agent said:\", \"\", r.answer.trim() || \"(nothing)\", \"\");\n\t\tif (!result.dryRun) {\n\t\t\tlines.push(\n\t\t\t\tr.check\n\t\t\t\t\t? `check after the round: level ${r.check.level}, ${r.check.passed ? \"passed\" : \"not passed\"}`\n\t\t\t\t\t: `check could not run: ${r.checkError ?? \"unknown\"}`,\n\t\t\t\t\"\",\n\t\t\t);\n\t\t}\n\t}\n\tconst last = result.rounds.at(-1)?.check;\n\tif (last) lines.push(\"## The last check report\", \"\", \"```\", renderReport(last).trimEnd(), \"```\", \"\");\n\treturn `${lines.join(\"\\n\")}\\n`;\n}\n\n/** The report appended to the repository's; the run number counts the reports already there. */\nfunction writeReport(root: string, result: AbaifyResult): string {\n\tconst path = join(root, REPORT_FILE);\n\tmkdirSync(join(root, \"abai\"), { recursive: true });\n\tconst previous = existsSync(path) ? readFileSync(path, \"utf8\") : \"\";\n\tconst run = (previous.match(/^# abaify report:/gm) ?? []).length + 1;\n\tresult.report = renderAbaifyReport(result, run);\n\twriteFileSync(path, (previous === \"\" ? \"\" : `${previous.trimEnd()}\\n\\n---\\n\\n`) + result.report);\n\treturn path;\n}\n\nexport async function abaify(options: AbaifyOptions, io: AbaikitIo, bundle: AbaikitApp): Promise<AbaifyResult> {\n\tconst { app, embedding, repo: ctx } = bundle;\n\tconst root = resolve(io.cwd, options.repo);\n\tif (!existsSync(join(root, \"package.json\"))) {\n\t\tthrow new UsageError(\n\t\t\t`${options.repo}: no package.json; abaify retrofits Node repositories (TypeScript or JavaScript) for now`,\n\t\t);\n\t}\n\tif (app.mode !== \"turns\")\n\t\tthrow new UsageError(`abaify runs its agent in-process; unset ABAI_MODE (it is ${app.mode})`);\n\tconst level = options.level ?? 1;\n\tconst rounds = app.knob<number>(\"abaify.max_rounds\");\n\tconst range = app.manifest.cells.find((c) => c.name === \"abaify.max_rounds\")?.range ?? [1, 20];\n\tif (options.rounds !== undefined) {\n\t\tif (options.rounds < range[0] || options.rounds > range[1])\n\t\t\tthrow new UsageError(`--rounds must be ${range[0]} to ${range[1]}`);\n\t\trounds.set(options.rounds);\n\t}\n\tconst say = (text: string) => io.err(text);\n\tsay(consentLine(root, level, options.dryRun));\n\tctx.dryRun = options.dryRun;\n\tctx.cwd = io.cwd;\n\tctx.onChange = (change) => say(`abaify: ${change.how} ${change.path}: ${change.detail}`);\n\tconst changesBefore = ctx.changes.length;\n\tconst result: AbaifyResult = {\n\t\troot,\n\t\trequested: level,\n\t\tlevel: 0,\n\t\tpassed: false,\n\t\tdryRun: options.dryRun,\n\t\trounds: [],\n\t\tchanges: [],\n\t\treport: \"\",\n\t};\n\n\t// the agent: abaikit's own embedded harness, auto consent (the invocation is the person's), a channel that only speaks\n\tconst channel: UserChannel = { say, ask: async () => undefined, confirm: async () => false, close: () => {} };\n\tembedding.options = {\n\t\tmodel: options.model,\n\t\tchannel,\n\t\tharness: { auto: true },\n\t\tconnectTimeoutMs: FIRST_CONTACT_MS,\n\t\taddendum: `You are retrofitting ${root} for a person who asked abaikit to do it; every write is theirs to approve and they approved this run.`,\n\t};\n\tlet form: SessionForm | undefined;\n\ttry {\n\t\tif (!(await app.attach()) || !embedding.current) {\n\t\t\tthrow new Error(\n\t\t\t\t`no agent could be attached: ${app.attachError ?? \"unknown\"} (configure one with \\`abaikit abai config\\`, or follow \\`abaikit learnabai\\` with your own agent)`,\n\t\t\t);\n\t\t}\n\t\tform = embedding.current.form;\n\t\tconst harness = embedding.current.harness;\n\t\tconst inv = inventory(root);\n\t\tlet prompt = brief(root, level, options.mode, inv, options.dryRun);\n\t\t// read once: an agent setting its own knob mid-run changes the next run, not this one\n\t\tconst max = options.dryRun ? 1 : rounds.get();\n\t\tfor (let n = 1; n <= max; n++) {\n\t\t\tsay(`abaify: round ${n} of ${max}`);\n\t\t\tconst answer = await form.prompt(prompt);\n\t\t\tconst round: Round = { number: n, answer, check: undefined };\n\t\t\tresult.rounds.push(round);\n\t\t\t// a run the model could not finish ends the loop: before first contact the form detaches; after it the\n\t\t\t// agent's last message carries the error, and another round would only spend the person's time\n\t\t\tconst last = [...form.agent.state.messages].reverse().find((m) => m.role === \"assistant\");\n\t\t\tconst stop = last && last.role === \"assistant\" ? last.stopReason : undefined;\n\t\t\tif (form.modelFailure !== undefined || !harness.attached) {\n\t\t\t\tresult.failure = `the model could not be reached: ${form.modelFailure ?? \"the harness detached\"}`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (stop === \"error\" || stop === \"aborted\") {\n\t\t\t\tconst why =\n\t\t\t\t\tlast && last.role === \"assistant\" && \"errorMessage\" in last ? String(last.errorMessage ?? stop) : stop;\n\t\t\t\tresult.failure = `the model could not be reached: the request ${stop === \"aborted\" ? \"was aborted\" : \"failed\"} (${why}); run abaify again when it is back`;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif (!options.dryRun) {\n\t\t\t\tsay(`abaify: check after round ${n}`);\n\t\t\t\ttry {\n\t\t\t\t\tround.check = await checkRepository({ repo: root, cwd: io.cwd, env: io.env, level, mode: options.mode });\n\t\t\t\t\trecordCheck(app, round.check);\n\t\t\t\t\tresult.level = round.check.level;\n\t\t\t\t\tresult.passed = round.check.passed;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tround.checkError = errorMessage(error);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (result.passed) break;\n\t\t\tprompt = followUp(round, level);\n\t\t}\n\t} catch (error) {\n\t\tresult.failure = result.failure ?? errorMessage(error);\n\t\tthrow error;\n\t} finally {\n\t\tform?.shutdown(\"abaify ended\");\n\t\tctx.dryRun = false;\n\t\tctx.onChange = undefined;\n\t\tresult.changes = ctx.changes.slice(changesBefore);\n\t\t// the report is written whatever happened once the agent was at work, so nothing done goes unrecorded\n\t\tif (options.dryRun) result.report = renderAbaifyReport(result, 1);\n\t\telse if (result.rounds.length > 0 || result.changes.length > 0) result.reportPath = writeReport(root, result);\n\t}\n\tif (options.dryRun) result.passed = true;\n\treturn result;\n}\n\nexport async function runAbaify(\n\tparsed: Extract<ParsedArgs, { verb: \"abaify\" }>,\n\tio: AbaikitIo,\n\tbundle: AbaikitApp,\n\tdeps: { model?: AgentModel } = {},\n): Promise<number> {\n\tconst result = await abaify(\n\t\t{\n\t\t\trepo: parsed.repo,\n\t\t\tlevel: parsed.level,\n\t\t\tmode: parsed.mode,\n\t\t\trounds: parsed.rounds,\n\t\t\tdryRun: parsed.dryRun,\n\t\t\tmodel: deps.model,\n\t\t},\n\t\tio,\n\t\tbundle,\n\t);\n\tio.out(result.report);\n\tif (result.reportPath) io.out(`report: ${result.reportPath}\\n`);\n\tif (result.failure) {\n\t\tio.out(\n\t\t\t`abaify: ended short after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}: ${result.failure}\\n`,\n\t\t);\n\t\treturn 1;\n\t}\n\tio.out(\n\t\tresult.dryRun\n\t\t\t? `dry run: ${result.changes.length} change${result.changes.length === 1 ? \"\" : \"s\"} proposed, nothing written\\n`\n\t\t\t: result.passed\n\t\t\t\t? `abaify: level ${result.level} reached in ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"}\\n`\n\t\t\t\t: `abaify: level ${result.requested} not reached after ${result.rounds.length} round${result.rounds.length === 1 ? \"\" : \"s\"} (reached ${result.level}); the report says what remains\\n`,\n\t);\n\treturn result.passed ? 0 : 1;\n}\n"]}
|
package/dist/bin.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { type Mode } from "@abaigram/abai-stub";
|
|
2
3
|
import { type AbaikitIo } from "./io.ts";
|
|
3
4
|
export { type AbaikitIo, processIo, UsageError } from "./io.ts";
|
|
4
|
-
export type Mode
|
|
5
|
+
export type { Mode };
|
|
5
6
|
export type ParsedArgs = {
|
|
6
7
|
verb: "help";
|
|
7
8
|
} | {
|
package/dist/bin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAYA,OAAO,EAAE,KAAK,SAAS,EAAuC,MAAM,SAAS,CAAC;AAK9E,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEhE,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvC,MAAM,MAAM,UAAU,GACnB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACzF;IACA,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;CACb,GACD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5F;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC/F;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;AAsFtC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAyFpD;AAED,sEAAsE;AACtE,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,SAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDrF","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 { 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 = \"turns\" | \"sidecar\";\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|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|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 !== \"sidecar\") throw new UsageError(`${arg} needs turns 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"]}
|
|
1
|
+
{"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AAQA,OAAO,EAAE,KAAK,IAAI,EAAY,MAAM,qBAAqB,CAAC;AAI1D,OAAO,EAAE,KAAK,SAAS,EAAuC,MAAM,SAAS,CAAC;AAK9E,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAEhE,YAAY,EAAE,IAAI,EAAE,CAAC;AAErB,MAAM,MAAM,UAAU,GACnB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAChB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACzF;IACA,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;CACb,GACD;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5F;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC/F;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEjD,MAAM,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;AAuFtC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,UAAU,CAyFpD;AAED,sEAAsE;AACtE,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,GAAE,SAAqB,GAAG,OAAO,CAAC,MAAM,CAAC,CAkDrF","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"]}
|