@aid-framework/cli 0.1.39 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,46 +1,226 @@
1
- # CLI
1
+ # `@aid-framework/cli`
2
2
 
3
- The command-line interface that drives the whole loop: scaffold, generate, run, ingest, evaluate, trace, and
4
- eject.
3
+ The `aid` command: scaffold, validate, compile, index and evaluate an AI application from a single declarative
4
+ spec file.
5
5
 
6
- **Responsibility**
6
+ You write `app/app.spec.yaml`. `aid` checks it, compiles it into a runnable service, and gates its behaviour.
7
+ Nothing in the generated tree is hand-written, and nothing you write is ever overwritten.
7
8
 
8
- - Implement the commands in [`docs/design.md` §11](../../docs/design.md): `init`, `gen`, `dev`, `ingest`,
9
- `eval`, `trace`, `cost`, `guard`, `migrate`, `eject`, `doctor`.
10
- - Orchestrate `spec → IR → generator plugins`, and surface validation and capability errors with actionable
11
- file/line context.
12
- - Provide `--dry-run` (via the generator SDK's pure `plan()` phase) and `--force`, plus `--json` on every
13
- command and CI-friendly exit codes.
9
+ ## Install
14
10
 
15
- The CLI owns **no** AI behavior of its own; it is a thin driver over [`../spec/`](../spec/),
16
- [`../ir/`](../ir/), and [`../generators/`](../generators/).
11
+ ```sh
12
+ npm install --save-dev @aid-framework/cli
13
+ ```
17
14
 
18
- > **Naming:** the command is **`aid`** — `aid init`, `aid gen`, `aid eval`, and so on. The command surface is
19
- > specified in [`docs/design.md` §11](../../docs/design.md); the framework name and architecture are in
20
- > [`§5`](../../docs/design.md).
15
+ Requires Node.js 24 or newer. Or run it without installing:
21
16
 
22
- ## Status: the Phase 0 commands and `ingest` are implemented
17
+ ```sh
18
+ npx @aid-framework/cli --help
19
+ ```
23
20
 
24
- | Command | Behaviour |
21
+ ## Quick start
22
+
23
+ ```sh
24
+ npx aid init my-app
25
+ cd my-app
26
+ npx aid validate
27
+ npx aid gen
28
+ ```
29
+
30
+ Three commands, and the generated app exists:
31
+
32
+ ```text
33
+ my-app/
34
+ README.md
35
+ app/
36
+ app.spec.yaml
37
+ business/ ← yours: prompts, tool handlers, domain logic
38
+ generated/ ← the compiler's: never hand-edit
39
+ aid.manifest.json ← provenance and per-file hashes
40
+ ```
41
+
42
+ ### Serving it
43
+
44
+ The generator owns the routes; the application decides what to mount and how to run it. That entry point is
45
+ yours, and it is a few lines — `app/main.py`:
46
+
47
+ ```python
48
+ from fastapi import FastAPI
49
+
50
+ from app.generated.routes.answer_ticket import answer_ticket_router
51
+
52
+ app = FastAPI(title="my-app", version="0.1.0")
53
+ app.include_router(answer_ticket_router)
54
+ ```
55
+
56
+ Install the runtime and the server, then run it:
57
+
58
+ ```sh
59
+ pip install aid-runtime-python "fastapi>=0.115,<1" "uvicorn>=0.30,<1"
60
+ uvicorn app.main:app --reload
61
+ ```
62
+
63
+ `aid gen` never writes `main.py` or a `pyproject.toml`: mounting and packaging are application decisions, and a
64
+ generator that guessed them would overwrite your choice on the next run.
65
+
66
+ The starter spec declares no pipelines, so a fresh `init` generates the structure but no routes. Add a pipeline
67
+ to `app/app.spec.yaml` and regenerate, and one router module appears per HTTP-served pipeline.
68
+
69
+ ## Commands
70
+
71
+ | Command | What it does |
72
+ | --- | --- |
73
+ | `aid init [dir]` | Scaffold a project: a starter spec and a README |
74
+ | `aid validate [spec]` | Parse the spec, build the IR, run every build-time gate |
75
+ | `aid gen [spec]` | Compile the spec and reconcile the tree with it |
76
+ | `aid ingest [spec]` | Index a retriever corpus into the store the spec declares |
77
+ | `aid eval [spec]` | Run the eval gate, or record the baseline it measures against |
78
+ | `aid doctor [spec]` | Diagnose the tree against the spec: drift, provenance and version mismatches, missing and outdated generated files. Reports; writes nothing |
79
+
80
+ Every command accepts `--help` for its own options and `--json` for machine-readable output.
81
+
82
+ ## Exit codes
83
+
84
+ The exit code is the contract, because it is what CI reads:
85
+
86
+ | Code | Meaning |
25
87
  | --- | --- |
26
- | `aid init [dir] [--from <spec>] [--target <name>] [--force] [--gen]` | Scaffolds `app.spec.yaml` — a starter, or `--from <spec>` imported once it lowers to an IR and passes the gates — plus a `README.md`. **Does not generate**: `aid gen` does, and `--gen` is only the convenience for the file-set a person types next. An existing spec is never replaced without `--force`; an existing `README.md` is never replaced at all. |
27
- | `aid validate [spec]` | Parses, schema-validates, builds the IR, and runs **every** build-time gate. Diagnostics carry `file:line:column`. |
28
- | `aid gen [spec] [--target <name>] [--dry-run] [--force]` | Runs `plan()` always, and writes unless `--dry-run`. Reports `created`/`updated`/`unchanged`/`kept`/`conflicts`. A generator-owned file that has been hand-edited is refused: the regenerated bytes land beside it as `<file>.aid-rej` and the command exits non-zero. `--force` takes those bytes instead. |
29
- | `aid ingest [spec] [--target <name>] [--retriever <id> \| --all] [--dry-run]` | Runs the generated ingest module for a declared retriever through the Python toolchain, and propagates its exit code. `--retriever` names one; `--all` runs every retriever the spec declares; passing both, or neither, is a usage error. `--dry-run` is forwarded to that module, which applies the chunker and reports the counts a run would write without opening a store, so the CLI does not keep a second answer to the same question. A retriever id the spec does not declare is a usage error naming the ids that do exist. `--all` over a project with no retrievers succeeds with nothing to do. A declared retriever whose module has not been generated exits non-zero and says to run `aid gen`. Ingest is idempotent because a chunk id is a content hash and the store upserts on it, so a second run over unchanged documents replaces rather than appends. |
30
- | `aid eval [spec] [--target <name>] [--suite <id>] [--baseline]` | Runs the project's generated eval module through the Python toolchain, streams its report, and propagates its exit code. `--baseline` records a fresh baseline from an N-run sample instead of gating against one. |
31
-
32
- Every command answers `--json` with one JSON document on stdout and `-h`/`--help` with its usage. Exit codes
33
- are `0` (the project is as asked), `1` (the spec, a gate, the target, or the working tree said no), and `2`
34
- (the invocation was wrong or the CLI failed internally).
35
-
36
- `dev`, `trace`, `cost`, `guard`, `migrate`, `eject`, and `doctor` are specified in
37
- [`docs/design.md` §11](../../docs/design.md) but are **not** implemented; they are deferred past Phase 0.
38
-
39
- **The CLI is glue, and only glue.** Spec parsing and validation come from [`../spec/`](../spec/), the IR
40
- builder and every build-time gate from [`../ir/`](../ir/), and the generation plan, the manifest, and the
41
- drift algorithm from [`../generator-sdk/`](../generator-sdk/) and the target plugin. A gate reimplemented
42
- here would be a second source of truth for the same rule, so there are none. Determinism is the generator
43
- SDK's contract and the CLI adds no liberty to it: `aid gen` on an unmodified project is a no-op that writes
44
- no bytes, and a file under the target's business directory is created once and then never written again.
45
-
46
- Reference: [`docs/design.md` §11](../../docs/design.md).
88
+ | `0` | The project is as asked |
89
+ | `1` | The spec, a gate, the target, or the working tree said no |
90
+ | `2` | The invocation was wrong, or the CLI failed internally |
91
+
92
+ `1` and `2` are deliberately distinct. A red build caused by a spec problem is fixed in the spec; a red build
93
+ caused by `2` is fixed in the invocation or is a bug. Collapsing them sends people to the wrong file.
94
+
95
+ ## `--json`
96
+
97
+ Every command answers `--json` with **exactly one** JSON document on stdout. Human-readable text and progress go
98
+ to stderr, so `aid validate --json | jq` works without filtering.
99
+
100
+ ```sh
101
+ npx aid validate --json | jq -r '.diagnostics[] | select(.severity=="error") | .code'
102
+ ```
103
+
104
+ ## The spec path
105
+
106
+ Given no argument, a command reads `app/app.spec.yaml` — the path `aid init` creates and the path the generators
107
+ compile into. Pass a path to override it:
108
+
109
+ ```sh
110
+ npx aid validate path/to/other.spec.yaml
111
+ ```
112
+
113
+ ## Targets
114
+
115
+ A spec declares the targets it is for, and `aid gen` emits one target at a time:
116
+
117
+ ```yaml
118
+ project:
119
+ name: my-app
120
+ targets:
121
+ - py-fastapi
122
+ - ts-vercel
123
+ ```
124
+
125
+ ```sh
126
+ npx aid gen # the first declared target
127
+ npx aid gen --target ts-vercel # any other declared target
128
+ ```
129
+
130
+ `--target` selects among the targets the spec declares; it cannot invent one. Naming a target the spec does not
131
+ declare fails with `target-unsupported` and lists what is declared. That is stricter on purpose — a spec is the
132
+ statement of intent, and silently generating a stack it did not ask for would make the spec a lie.
133
+
134
+ | Target | Emits | Runtime |
135
+ | --- | --- | --- |
136
+ | `py-fastapi` | FastAPI service, Pydantic models | [`aid-runtime-python`](https://pypi.org/project/aid-runtime-python/) |
137
+ | `ts-vercel` | Next.js / Vercel functions, zod models | [`@aid-framework/runtime`](https://www.npmjs.com/package/@aid-framework/runtime) |
138
+
139
+ ## `aid gen`: planning, then reconciling
140
+
141
+ `gen` never writes blindly. It plans the full file set from the IR, compares that plan against the tree, and only
142
+ then reconciles.
143
+
144
+ | Tier | Owner | On regeneration |
145
+ | --- | --- | --- |
146
+ | `app/generated/` | the compiler | overwritten — never hand-edit |
147
+ | `app/business/` | you | created once if missing, then never touched |
148
+
149
+ Preview without writing anything:
150
+
151
+ ```sh
152
+ npx aid gen --dry-run
153
+ ```
154
+
155
+ ```text
156
+ py-fastapi: dry run, 16 created, 0 updated, 0 unchanged, 0 kept
157
+ created:
158
+ app/business/prompts/__init__.py
159
+ app/generated/deps.py
160
+ app/generated/requests.py
161
+ app/generated/schemas.py
162
+ ...
163
+ ```
164
+
165
+ The counts are `created` / `updated` / `unchanged` / `kept`, so a no-op regeneration says so instead of looking
166
+ like progress.
167
+
168
+ ### When a generated file has been edited by hand
169
+
170
+ `gen` records a SHA-256 for every file it writes in `aid.manifest.json`. If a `generated/` file on disk no
171
+ longer matches its recorded hash, a human edited it, and `gen` **refuses to overwrite** it. It writes the new
172
+ content beside it as `<path>.aid-rej` and reports the conflict with both hashes.
173
+
174
+ The choice is yours to make explicitly:
175
+
176
+ ```sh
177
+ npx aid gen --force # discard the hand edit and overwrite
178
+ ```
179
+
180
+ or move the file into `business/` and point the business seam at it. Either way the edit is not lost silently,
181
+ which is the failure mode that matters: a generator that overwrites unconditionally destroys the only signal it
182
+ has that a human disagreed with it.
183
+
184
+ ## `aid eval`: gating behaviour, not bytes
185
+
186
+ A generated service varies between runs — the model is not deterministic, and pretending otherwise produces tests
187
+ that are either always red or measuring nothing. `aid eval` therefore samples and compares distributions:
188
+
189
+ ```sh
190
+ npx aid eval --record # record a baseline from the current behaviour
191
+ npx aid eval # gate the current behaviour against it
192
+ ```
193
+
194
+ ## What the CLI is not
195
+
196
+ It is not a runtime. It reads the spec, compiles it, and reports; the generated app imports the runtime package
197
+ for its target and runs without the CLI installed. `@aid-framework/cli` is a `devDependency` for exactly that
198
+ reason.
199
+
200
+ ## API
201
+
202
+ The CLI is also importable, so a build tool can drive it without a subprocess:
203
+
204
+ ```ts
205
+ import { main, runCli } from '@aid-framework/cli';
206
+
207
+ process.exitCode = await runCli(process.argv.slice(2));
208
+ ```
209
+
210
+ | Export | Purpose |
211
+ | --- | --- |
212
+ | `runCli(argv)` | Run a command and return its exit code |
213
+ | `main` | The command dispatcher |
214
+ | `EXIT_OK`, `EXIT_REJECTED`, `EXIT_USAGE` | The exit-code constants (`0`, `1`, `2`) |
215
+
216
+ ## Related packages
217
+
218
+ - [`@aid-framework/spec`](https://www.npmjs.com/package/@aid-framework/spec) — the spec language
219
+ - [`@aid-framework/ir`](https://www.npmjs.com/package/@aid-framework/ir) — the IR and its build-time gates
220
+ - [`@aid-framework/generator-py-fastapi`](https://www.npmjs.com/package/@aid-framework/generator-py-fastapi)
221
+ - [`@aid-framework/generator-ts-vercel`](https://www.npmjs.com/package/@aid-framework/generator-ts-vercel)
222
+
223
+ ## License
224
+
225
+ [Apache-2.0](https://github.com/aid-framework/aid-framework/blob/main/LICENSE). Code generated by AID Framework is
226
+ not subject to this license.