@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 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 mode) or a local socket between
22
- two processes (sidecar mode). It means the same thing either way.
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) and `attention` (what wakes the agent, and
36
- a budget on it).
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. `sidecar`: the harness is a separate process, attached through
41
- a socket; the reference mode, the one with the whole lifecycle channel.
42
- `thread` is in the contract but not in this instantiation yet. A manifest
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", // the program's name; also the manifest's
89
- entry: "node dist/main.js abai", // how the entry is reached: a runnable command line
90
- modes: ["turns", "sidecar"], // supported; the first is the default
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(); // optional: ask for the agent now
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, and in turns mode they run only inside the
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"]`, `content` and
323
- `root` to begin with. When the program's real entry is a plain launcher
324
- that runs without the build (a `bin/x.mjs` that imports `dist/`), put the
325
- declaration beside it as a `.mjs` module the launcher can import, so the
326
- entry answers `manifest` and `doctor` before anything is built. Verify: build as the repository does, then
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 mode in-process; sidecar attaches to or starts the
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 set provider anthropic|freetoken
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 set key <provider> # read without echo
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 `sidecar`,
429
- overriding the manifest's first mode), `ABAI_HARNESS` (`off`, `null`,
430
- `exercise`: check runs only; never set it for real use), `ABAI_HOME` (the
431
- configuration directory, default `~/.abai`), `ABAI_RUN_DIR` (descriptors
432
- of running programs, default `~/.abai/run`), `ABAI_HARNESS_BIN` (the
433
- sidecar harness binary to spawn), `ABAI_HARNESS_MODULE` (the in-process
434
- harness module to import), `ABAI_CHANNEL` (`terminal` or `none`),
435
- `ABAI_ATTACH_WAIT_MS` (how long a touchpoint waits for a harness).
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 last when
444
- `sidecar` is declared, so an agent reading the manifest sees them too:
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
 
@@ -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`).",
@@ -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":"AAAA,wEAAwE;AACxE,0EAA0E;AAC1E,yEAAyE;AACzE,yEAAyE;AACzE,2EAA2E;AAC3E,2EAA2E;AAC3E,wEAAwE;AACxE,oEAAoE;AACpE,2EAA2E;AAC3E,0EAA0E;AAC1E,oCAAoC;AACpC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAK1C,OAAO,EAAoB,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1F,OAAO,EAAkB,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAe,QAAQ,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAEjE,MAAM,CAAC,MAAM,WAAW,GAAG,uBAAuB,CAAC;AACnD,4GAA4G;AAC5G,MAAM,UAAU,GAAG,EAAE,CAAC;AACtB,gHAAgH;AAChH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC;AACxC,8DAA8D;AAC9D,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;AA4CjG,SAAS,SAAS,CAAC,IAAY,EAAa;IAC3C,IAAI,WAAgD,CAAC;IACrD,IAAI,CAAC;QACJ,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAC;IACvG,CAAC;IAAC,MAAM,CAAC;QACR,WAAW,GAAG,SAAS,CAAC;IACzB,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,MAAM,CAClD,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CACzE,CAAC;IACF,MAAM,MAAM,GAAc,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;IACvE,MAAM,GAAG,GAAG,WAAW,EAAE,GAAG,CAAC;IAC7B,MAAM,SAAS,GACd,OAAO,GAAG,KAAK,QAAQ;QACtB,CAAC,CAAC,GAAG;QACL,CAAC,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAC/B,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAA6B,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,CAAC;YAChE,CAAC,CAAC,SAAS,CAAC;IACf,MAAM,IAAI,GAAG,OAAO,WAAW,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAClF,KAAK,MAAM,SAAS,IAAI;QACvB,SAAS;QACT,IAAI;QACJ,aAAa;QACb,aAAa;QACb,cAAc;QACd,cAAc;QACd,UAAU;KACV,EAAE,CAAC;QACH,IAAI,SAAS,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;YACpD,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,WAAW,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;YAC3E,MAAM,CAAC,SAAS,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;YAC3E,MAAM;QACP,CAAC;IACF,CAAC;IACD,KAAK,MAAM,SAAS,IAAI,CAAC,WAAW,EAAE,iBAAiB,EAAE,WAAW,CAAC,EAAE,CAAC;QACvE,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC;YACvC,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YAClD,MAAM,CAAC,UAAU,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YACpD,MAAM;QACP,CAAC;IACF,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,SAAS,aAAa,CAAC,OAA+B,EAA0B;IAC/E,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,aAAa;QAAE,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChG,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,IAAI,EAAE;YAAE,MAAM;QAC5C,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,SAAS;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACtD,CAAC;IACD,OAAO,MAAM,CAAC;AAAA,CACd;AAED,2HAA2H;AAC3H,MAAM,UAAU,KAAK,CAAC,IAAY,EAAE,KAAa,EAAE,IAAsB,EAAE,GAAc,EAAE,MAAe,EAAU;IACnH,MAAM,KAAK,GAAG,SAAS,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,CAAC;IAC5B,MAAM,MAAM,GAAG,GAAG;QACjB,CAAC,CAAC,IAAI,CAAC,SAAS,CACd;YACC,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,GAAG,EAAE,GAAG,CAAC,GAAG;YACZ,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,aAAa,CAAE,GAAG,CAAC,OAAkC,IAAI,EAAE,CAAC;SACrE,EACD,IAAI,EACJ,CAAC,CACD;QACF,CAAC,CAAC,mBAAmB,CAAC;IACvB,MAAM,KAAK,GAAG;QACb,8BAA8B,IAAI,8BAA8B,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,mBAAmB,IAAI,kDAAkD,CAAC,CAAC,CAAC,EAAE,GAAG;QAC3K,EAAE;QACF,+GAA+G;QAC/G,uMAAuM;QACvM,geAAge;QAChe,qXAAqX;YACpX,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;gBAC1B,CAAC,CAAC,gCAAgC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,6JAA6J;gBAC7O,CAAC,CAAC,yJAAyJ,CAAC;QAC9J,qWAAqW;QACrW,gEAAgE;QAChE,MAAM;YACL,CAAC,CAAC,4LAA4L;YAC9L,CAAC,CAAC,EAAE;QACL,EAAE;QACF,qBAAqB,GAAG,CAAC,KAAK,kJAAkJ;QAChL,KAAK;QACL,GAAG,CAAC,OAAO;QACX,KAAK;QACL,yCAAyC;QACzC,SAAS;QACT,MAAM;QACN,KAAK;KACL,CAAC;IACF,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CACT,kBAAkB,GAAG,CAAC,SAAS,CAAC,IAAI,gBAAgB,GAAG,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,CAAC,SAAS,CAAC,KAAK,GAAG,EACjG,KAAK,EACL,GAAG,CAAC,SAAS,CAAC,IAAI,EAClB,KAAK,CACL,CAAC;IACH,CAAC;IACD,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,oCAAoC,GAAG,CAAC,UAAU,CAAC,IAAI,SAAS,EAAE,KAAK,EAAE,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACjH,CAAC;IACD,KAAK,CAAC,IAAI,CACT,EAAE,EACF,yEAAyE,EACzE,WAAW,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,IAAI,EACnC,EAAE,EACF,sBAAsB,EACtB,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC,IAAI,EAClC,EAAE,EACF,mBAAmB,EACnB,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,EAChC,EAAE,EACF,0BAA0B,KAAK,uDAAuD,KAAK,oJAAoJ,CAC/O,CAAC;IACF,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACvD;AAED,SAAS,QAAQ,CAAC,KAAY,EAAE,KAAa,EAAU;IACtD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,wBAAwB,KAAK,CAAC,UAAU,EAAE,CAAC;IACpG,OAAO,kDAAkD,KAAK,uCAAuC,MAAM,+GAA+G,KAAK,2BAA2B,CAAC;AAAA,CAC3P;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,KAAa,EAAE,MAAe,EAAU;IAC1E,OAAO,MAAM;QACZ,CAAC,CAAC,sBAAsB,IAAI,4CAA4C,KAAK,0EAA0E;QACvJ,CAAC,CAAC,WAAW,IAAI,aAAa,KAAK,iOAAiO,CAAC;AAAA,CACtQ;AAED,gGAAgG;AAChG,MAAM,UAAU,kBAAkB,CAAC,MAAoB,EAAE,GAAW,EAAU;IAC7E,MAAM,KAAK,GAAG;QACb,oBAAoB,MAAM,CAAC,IAAI,SAAS,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,GAAG;QAC3E,EAAE;QACF,MAAM,CAAC,MAAM;YACZ,CAAC,CAAC,oBAAoB,MAAM,CAAC,SAAS,KAAK,MAAM,CAAC,OAAO,CAAC,MAAM,UAAU,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,2CAA2C;YAC3J,CAAC,CAAC,mBAAmB,MAAM,CAAC,SAAS,mBAAmB,MAAM,CAAC,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,UAAU,MAAM,CAAC,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG;QAChM,WAAW,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,WAAW,CAAC;QAC3F,EAAE;KACF,CAAC;IACF,IAAI,MAAM,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,wBAAwB,MAAM,CAAC,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;IAC7E,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;IACvE,KAAK,CAAC,IAAI,CAAC,8FAA8F,EAAE,EAAE,CAAC,CAAC;IAC/G,KAAK,CAAC,IAAI,CACT,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAC3G,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,4CAA4C,EAAE,EAAE,CAAC,CAAC;IACjE,KAAK,CAAC,IAAI,CACT,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QACzB,CAAC,CAAC,CAAC,QAAQ,CAAC;QACZ,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,MAAM,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAC5E,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,WAAW,EAAE,EAAE,CAAC,CAAC;QACnG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CACT,CAAC,CAAC,KAAK;gBACN,CAAC,CAAC,gCAAgC,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,EAAE;gBAC9F,CAAC,CAAC,wBAAwB,CAAC,CAAC,UAAU,IAAI,SAAS,EAAE,EACtD,EAAE,CACF,CAAC;QACH,CAAC;IACF,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;IACzC,IAAI,IAAI;QAAE,KAAK,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,EAAE,KAAK,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IACrG,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAAA,CAC/B;AAED,gGAAgG;AAChG,SAAS,WAAW,CAAC,IAAY,EAAE,MAAoB,EAAU;IAChE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;IACrC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IACrE,MAAM,CAAC,MAAM,GAAG,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChD,aAAa,CAAC,IAAI,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IACjG,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAsB,EAAE,EAAa,EAAE,MAAkB,EAAyB;IAC9G,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;IAC7C,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC;QAC7C,MAAM,IAAI,UAAU,CACnB,GAAG,OAAO,CAAC,IAAI,0FAA0F,CACzG,CAAC;IACH,CAAC;IACD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QACvB,MAAM,IAAI,UAAU,CAAC,4DAA4D,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAS,mBAAmB,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,mBAAmB,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/F,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAClC,IAAI,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;YACzD,MAAM,IAAI,UAAU,CAAC,oBAAoB,KAAK,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IACD,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3C,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC9C,GAAG,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAC5B,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC;IACjB,GAAG,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACzF,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;IACzC,MAAM,MAAM,GAAiB;QAC5B,IAAI;QACJ,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,KAAK;QACb,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,EAAE;QACV,OAAO,EAAE,EAAE;QACX,MAAM,EAAE,EAAE;KACV,CAAC;IAEF,uHAAuH;IACvH,MAAM,OAAO,GAAgB,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC,EAAE,CAAC;IAC9G,SAAS,CAAC,OAAO,GAAG;QACnB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,OAAO;QACP,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;QACvB,gBAAgB,EAAE,gBAAgB;QAClC,QAAQ,EAAE,wBAAwB,IAAI,wGAAwG;KAC9I,CAAC;IACF,IAAI,IAA6B,CAAC;IAClC,IAAI,CAAC;QACJ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACjD,MAAM,IAAI,KAAK,CACd,+BAA+B,GAAG,CAAC,WAAW,IAAI,SAAS,oGAAoG,CAC/J,CAAC;QACH,CAAC;QACD,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;QAC9B,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC;QAC1C,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC5B,IAAI,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACnE,sFAAsF;QACtF,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;QAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,GAAG,CAAC,iBAAiB,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;YACpC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACzC,MAAM,KAAK,GAAU,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;YAC7D,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,uGAAuG;YACvG,+FAA+F;YAC/F,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;YAC1F,MAAM,IAAI,GAAG,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;YAC7E,IAAI,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;gBAC1D,MAAM,CAAC,OAAO,GAAG,mCAAmC,IAAI,CAAC,YAAY,IAAI,sBAAsB,EAAE,CAAC;gBAClG,MAAM;YACP,CAAC;YACD,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC5C,MAAM,GAAG,GACR,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,IAAI,cAAc,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;gBACxG,MAAM,CAAC,OAAO,GAAG,+CAA+C,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,KAAK,GAAG,qCAAqC,CAAC;gBAC3J,MAAM;YACP,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;gBACrB,GAAG,CAAC,6BAA6B,CAAC,EAAE,CAAC,CAAC;gBACtC,IAAI,CAAC;oBACJ,KAAK,CAAC,KAAK,GAAG,MAAM,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;oBACzG,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;oBAC9B,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;oBACjC,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC;gBACpC,CAAC;gBAAC,OAAO,KAAK,EAAE,CAAC;oBAChB,KAAK,CAAC,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;gBACxC,CAAC;YACF,CAAC;YACD,IAAI,MAAM,CAAC,MAAM;gBAAE,MAAM;YACzB,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC;IACF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,KAAK,CAAC;IACb,CAAC;YAAS,CAAC;QACV,IAAI,EAAE,QAAQ,CAAC,cAAc,CAAC,CAAC;QAC/B,GAAG,CAAC,MAAM,GAAG,KAAK,CAAC;QACnB,GAAG,CAAC,QAAQ,GAAG,SAAS,CAAC;QACzB,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAClD,sGAAsG;QACtG,IAAI,OAAO,CAAC,MAAM;YAAE,MAAM,CAAC,MAAM,GAAG,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;aAC7D,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,UAAU,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC/G,CAAC;IACD,IAAI,OAAO,CAAC,MAAM;QAAE,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,MAAM,CAAC;AAAA,CACd;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC9B,MAA+C,EAC/C,EAAa,EACb,MAAkB,EAClB,IAAI,GAA2B,EAAE,EACf;IAClB,MAAM,MAAM,GAAG,MAAM,MAAM,CAC1B;QACC,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,KAAK,EAAE,IAAI,CAAC,KAAK;KACjB,EACD,EAAE,EACF,MAAM,CACN,CAAC;IACF,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtB,IAAI,MAAM,CAAC,UAAU;QAAE,EAAE,CAAC,GAAG,CAAC,WAAW,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;IAChE,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,EAAE,CAAC,GAAG,CACL,6BAA6B,MAAM,CAAC,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,MAAM,CAAC,OAAO,IAAI,CACtH,CAAC;QACF,OAAO,CAAC,CAAC;IACV,CAAC;IACD,EAAE,CAAC,GAAG,CACL,MAAM,CAAC,MAAM;QACZ,CAAC,CAAC,YAAY,MAAM,CAAC,OAAO,CAAC,MAAM,UAAU,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,8BAA8B;QACjH,CAAC,CAAC,MAAM,CAAC,MAAM;YACd,CAAC,CAAC,iBAAiB,MAAM,CAAC,KAAK,eAAe,MAAM,CAAC,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI;YACpH,CAAC,CAAC,iBAAiB,MAAM,CAAC,SAAS,sBAAsB,MAAM,CAAC,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,aAAa,MAAM,CAAC,KAAK,mCAAmC,CACzL,CAAC;IACF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAAA,CAC7B","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 = "turns" | "sidecar";
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"]}