@hagicode/cli 0.3.0 → 0.6.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.
@@ -0,0 +1,363 @@
1
+ # CLI Usage
2
+
3
+ Use the published package contract first.
4
+ The repository exists to build and maintain `@hagicode/cli`, but normal command execution should start from the package surface rather than the source tree.
5
+
6
+ ## Installation paths
7
+
8
+ ### Zero-install
9
+
10
+ ```bash
11
+ npx @hagicode/cli --help
12
+ npx @hagicode/cli project list --json
13
+ ```
14
+
15
+ ### Project-local install
16
+
17
+ ```bash
18
+ npm install @hagicode/cli
19
+ npx @hagicode/cli proposal list --json
20
+ ```
21
+
22
+ ### Optional global install
23
+
24
+ ```bash
25
+ npm install -g @hagicode/cli
26
+ hagi --help
27
+ ```
28
+
29
+ ## Quick start
30
+
31
+ When `@hagicode/cli` is already available on `PATH`, these examples work as `hagi ...`.
32
+ For one-off execution, replace `hagi` with `npx @hagicode/cli`.
33
+
34
+ ```bash
35
+ hagi --help
36
+ hagi project list --json
37
+ hagi proposal list --json
38
+ hagi saga list --path .
39
+ hagi chat list --json
40
+ hagi hagitask list --json
41
+ hagi hagitask create --title "Auto commit" --project-id <project-id> --prompt-id auto-compose-commit.en-US
42
+ ```
43
+
44
+ ## Shared runtime flags
45
+
46
+ Every command family supports the same runtime flags:
47
+
48
+ - `--base-url <url>`
49
+ - `--token <token>`
50
+ - `--lang <lang>`
51
+ - `--json`
52
+
53
+ Example:
54
+
55
+ ```bash
56
+ hagi --base-url https://api.example.com --token "$HAGI_TOKEN" project list --json
57
+ ```
58
+
59
+ Saga commands using `--path` accept these flags but do not read backend configuration or contact the backend, except `saga session create`, which calls the backend to find the registered project.
60
+
61
+ ## Environment variables
62
+
63
+ Backend operations resolve runtime settings centrally before accessing the API.
64
+ Explicit flags always override environment variables.
65
+
66
+ | Variable | Purpose |
67
+ | --- | --- |
68
+ | `HAGI_API_BASE_URL` | Backend base URL, used when `--base-url` is not given. Overrides the config file. |
69
+ | `HAGI_API_TOKEN` | Bearer token attached as `Authorization: Bearer <token>`. |
70
+ | `HAGI_API_LANG` | Shared `Accept-Language` header value. |
71
+ | `HAGI_SEND_STEER` | Turns Steer on (`1`, `true`, `yes`, `on`) or off (`0`, `false`, `no`, `off`) for `chat`, `proposal` and `hagitask send`. Any other value exits with code `2`. See [Steer for send commands](#steer-for-send-commands). |
72
+
73
+ ## Config file
74
+
75
+ The base URL can also be stored in a local config file:
76
+
77
+ ```json
78
+ { "baseUrl": "https://api.example.com", "steer": false }
79
+ ```
80
+
81
+ The file is `$XDG_CONFIG_HOME/hagi/config.json`, or `~/.config/hagi/config.json` when `XDG_CONFIG_HOME` is unset. The `steer` key must be `true` or `false`; any other value exits with code `2` and names the file.
82
+
83
+ Base URL precedence, highest first: `--base-url`, then `HAGI_API_BASE_URL`, then `baseUrl` in the config file.
84
+ There is no default. When none of the three is set, a backend operation exits with code `2` and names all three sources.
85
+ The local Saga selector `--path` is the exception: it does not read this file, so malformed, unreachable, or absent backend
86
+ settings do not affect that local operation. The one exception is `saga session create`, which calls the backend even with `--path`.
87
+
88
+ ## Command overview
89
+
90
+ ```text
91
+ hagi
92
+ |-- project list | repositories | create | update | delete
93
+ | `-- monospecs show | init | set | validate | reload | clone | remove-checkout
94
+ |-- proposal list | create | optimize-description | generate | annotate | execute | archive | status | complete | send | get | messages
95
+ |-- saga init | new | list | status | items | claim | assert-owner | release | freeze | check | tick | block | set-status | amend | archive | session create
96
+ |-- vault list | create | update | delete
97
+ | `-- files list | preview
98
+ |-- chat list | create | archive | delete | send
99
+ |-- hagitask list | schema | create | send | get | messages
100
+ |-- plan list | get | create | update | enable | disable | delete | run
101
+ |-- skills local list | enable | disable | uninstall
102
+ | gallery search | detail | install
103
+ | trusted list | upsert | delete | reset
104
+ `-- agent lock acquire | status | renew | heartbeat | release | requisition | list
105
+ ```
106
+
107
+ ## Saga commands
108
+
109
+ Each command requires exactly one nonempty project selector:
110
+
111
+ - `--path <directory>` resolves a local directory on the CLI host. Relative paths use the current working directory. This
112
+ selector does not read backend configuration or make API calls, except in `saga session create`, which looks up the
113
+ registered project through the backend. Shared backend flags do not change its behavior otherwise.
114
+ - `--project-id <id>` reads the project list through the configured backend. Its project path must be a directory available
115
+ on the machine running the CLI; the CLI does not execute filesystem operations remotely.
116
+
117
+ Missing, empty, or conflicting selectors exit `2` before backend access or file writes. Paths with spaces should be quoted.
118
+ Shared `--base-url`, `--token`, `--lang`, and `--json` options are accepted.
119
+
120
+ ```bash
121
+ hagi saga init --path "./My Project"
122
+ hagi saga method run --json
123
+ hagi saga new demo --path "./My Project" --json
124
+ hagi saga list --path "./My Project"
125
+ hagi saga status demo --project-id <project-id> --json
126
+ hagi saga items demo --path "./My Project" --json
127
+ hagi saga claim demo --path "./My Project" --session orchestrator-1 --json
128
+ hagi saga check demo --path "./My Project" --phase run --json
129
+ hagi saga tick demo T1 --path "./My Project" --token "$SAGA_EXECUTION_TOKEN" --json
130
+ hagi saga session create --saga demo --path "./My Project" --title "T1 implementation" --role implementation --master orchestrator-1 --item T1 --json
131
+ ```
132
+
133
+ `saga session create` takes its execution token from `SAGA_EXECUTION_TOKEN` only; `--token` is refused. It creates a Worker
134
+ chat session and records it in `runtime/sessions.md`. See [`saga.md`](saga.md#worker-sessions) for its metadata, record and exit codes.
135
+
136
+ For offline use, invoke an installed package or a package already present in the `npx` cache. An uncached
137
+ `npx @hagicode/cli` command may need network access to download the package; the local `--path` operation itself does not
138
+ need backend access.
139
+
140
+ ## Project repositories
141
+
142
+ `project repositories --id <project-id>` is read-only. It reads the same aggregation snapshot as `project list` and prints one project's repositories: name, path relative to the project root (for example `repos/cli`), display name, type and branch, with the project's MonoSpecs flag. The `path` values are the ones a proposal's `Repository scope` names.
143
+
144
+ ```bash
145
+ npx @hagicode/cli project repositories --id <project-id> --json
146
+ ```
147
+
148
+ An unknown project id exits with code `24` and prints no list.
149
+
150
+ ## MonoSpecs commands
151
+
152
+ `project monospecs` manages a project's MonoSpecs configuration (`monospecs.yaml`) and the local checkouts of its repositories. The CLI only sends requests; the backend reads and writes files and runs Git on its own host.
153
+
154
+ - `show --id <id>` prints the document: version, repo directory, config path, commit-when-archive, and one row per repository with its local Git state. A repository that is not a valid Git checkout is marked `missing or not a Git repository`. `--yaml` adds the YAML preview; `--json` emits the same fields plus `code` and `metadata`.
155
+ - `init --id <id>` initializes the configuration; an already initialized project returns its document.
156
+ - `set --id <id>` changes the document. Options: `--version`, `--repo-directory`, `--commit-when-archive <true|false>`, repeatable `--add-repository <path>` and `--remove-repository <path>`, `--clone`. `--url`, `--name`, `--display-name` and `--type` require exactly one `--add-repository`. `--clone` needs at least one addition. The CLI reads the document, applies the change and saves the complete document; it strips local Git state fields before saving. Duplicate additions and unknown removals fail before any save.
157
+ - `validate --id <id>` validates the configuration. It exits `30` when the backend reports it invalid, after printing every error.
158
+ - `reload --id <id>` rereads `monospecs.yaml` on the backend host and refreshes Git state.
159
+ - `clone --id <id>` clones missing repositories. Repeat `--path <path>` to select some; `--include-optional` adds optional repositories when no `--path` is given. Every result row is printed; any `failed` row exits `30`.
160
+ - `remove-checkout --id <id> --path <path>` deletes one configured repository's local checkout. Dirty, unpushed or no-upstream checkouts are refused (exit `29`); the hint suggests `--force` when the refusal can be overridden. The configuration file is not changed.
161
+
162
+ Only configured repositories can be cloned or removed, and the backend confines paths to the project root. Exit codes: `2` usage (no request sent), `20` backend validation with field errors, `24` project not found, `29` conflict, `30` invalid configuration or failed clone.
163
+
164
+ ```bash
165
+ hagi project monospecs show --id <project-id> --json
166
+ hagi project monospecs set --id <project-id> --add-repository repos/api --url <git-url> --clone
167
+ hagi project monospecs remove-checkout --id <project-id> --path repos/api --force
168
+ ```
169
+
170
+ ## Proposal commands
171
+
172
+ `proposal get` reads one proposal session. With `--wait` it polls until the status settles, so a stage can be followed without guessing. `proposal messages` reads its messages, newest first.
173
+
174
+ ```bash
175
+ hagi proposal get --session-id <session-id> --wait --timeout 900 --json
176
+ hagi proposal messages --session-id <session-id> --last 1 --json
177
+ ```
178
+
179
+ - `--wait` settles on any status other than Optimizing, Openspecing, Executing or Archiving. WaitingForReply counts as settled, because a person must answer.
180
+ - `--timeout`, `--interval` and `--grace` are seconds and need `--wait`. `--interval` must be less than `--timeout`. A timeout exits `31`.
181
+ - Which stage each command starts, and how to follow a run, is in [openspec-workflow.md](openspec-workflow.md).
182
+
183
+ ## Session metadata
184
+
185
+ `chat create` and `proposal create` take write-once metadata entries. An orchestrator marks the sessions it starts with them, for example a master marker and worker markers, and finds them again through `userMetadata`.
186
+
187
+ ```bash
188
+ hagi chat create --title "Master" --project-id <project-id> --metadata role=master --metadata batch=2026-11-07 --json
189
+ hagi chat create --title "Worker 1" --project-id <project-id> --metadata role=worker --metadata master=<master-session-id> --json
190
+ printf '{"role":"worker","master":"<master-session-id>"}' | hagi chat create --title "Worker 2" --project-id <project-id> --metadata-json - --json
191
+ hagi proposal create --project-id <project-id> --chief-complaint "<text>" --default-title --metadata role=worker --metadata master=<master-session-id> --json
192
+ ```
193
+
194
+ - `--metadata key=value` is repeatable. The text is split at the first `=`, so a value may contain `=`.
195
+ - `--metadata-json <file|->` reads a JSON object of string values from a file, or from stdin with `-`. It is repeatable.
196
+ - A key may appear once across both options. A missing `=`, an empty key, a repeated key, or JSON that is not an object of string values exits `2` before any request is sent.
197
+ - Backend limits: at most 16 entries; a key of 1 to 64 characters matching `^[a-z][a-z0-9._-]{0,63}$`; a value of 1 to 512 characters with no control characters. The backend's message is printed as it is, and the session is not created.
198
+ - Write-once: the entries are set at creation and cannot be changed later. Do not put secrets in them: every reader of the session can see them.
199
+ - Output: `--json` of `chat create`, `chat list`, `proposal create` and `proposal get` carries `userMetadata` (an object of string values) when the session has entries. The human output adds one `userMetadata` line, keys sorted. A session without entries has no `userMetadata` field.
200
+
201
+ ## HagiTask commands
202
+
203
+ A HagiTask is a preset task with a structured input. Use the schema-first flow: list the tasks, read one task's schema, write the input file from the schema, then create the session.
204
+
205
+ ```bash
206
+ hagi hagitask list --json
207
+ hagi hagitask schema --task-id <task-id>
208
+ hagi hagitask schema --task-id <task-id> --json
209
+ hagi hagitask create --title "<title>" --project-id <project-id> --task-id <task-id> --input input.json --json
210
+ hagi hagitask get --session-id <session-id> --wait --json
211
+ hagi hagitask messages --session-id <session-id> --last 1 --json
212
+ hagi hagitask send --session-id <session-id> --content "Retry with a smaller diff"
213
+ ```
214
+
215
+ `hagi hagitask get` and `hagi hagitask messages` take the same options as the proposal commands above.
216
+
217
+ Super Commit is a prompt, not a preset task, so it has no schema and takes no `--input`. Start it with a prompt id:
218
+
219
+ ```bash
220
+ hagi hagitask create --title "Commit the CLI changes" --project-id <project-id> --prompt-id auto-compose-commit.en-US --json
221
+ ```
222
+
223
+ Start one only when the user asked for it. See [super-commit.md](super-commit.md) for its options, its result and when to start it.
224
+
225
+ - `schema` (human view) lists the target scope, commands and parameters, with each required parameter marked `yes`, then the requirements, scenes, endpoint and an example command. `--json` adds the input JSON Schema and the example request body.
226
+ - `create` needs `--title`, `--project-id`, and either `--task-id` or `--prompt-id`. `--input <file|->` needs `--task-id`. The input must be a JSON object. The CLI does not check its keys; the backend does, and it rejects a key that the schema does not list and a missing required value. A rejection is printed unchanged and no session is reported.
227
+ - `create --json` prints `{ session, presetTaskId, promptId, requested }`. Read the session id from `session.sessionId`.
228
+
229
+ Example `input.json` for a task with one required input:
230
+
231
+ ```json
232
+ {
233
+ "inputs": {
234
+ "commitScope": "cli"
235
+ }
236
+ }
237
+ ```
238
+
239
+ Migration: `hagi hagitask create` and `hagi hagitask send` replace the former `autotask` commands. There is no alias, so `hagi autotask` fails as an unknown command.
240
+
241
+ ## Steer for send commands
242
+
243
+ `hagi chat send`, `hagi proposal send` and `hagi hagitask send` have two delivery modes. A normal send waits behind the running turn, as before. A Steer inserts the text into the running turn through one `SteerSession` call. Steer is off by default.
244
+
245
+ Options: `--steer` requests Steer for one call; `--no-steer` forces a normal send for one call. Without either flag, the first set source decides: `HAGI_SEND_STEER` (values above), then the `steer` config key, then `false`. `--no-steer` overrides `HAGI_SEND_STEER=1`.
246
+
247
+ Outcomes:
248
+
249
+ - `steered`: a running task took the message. It is not proof the text was used; the backend can requeue it when the run ends.
250
+ - `queued`: no running task took it, so it was sent as the next message. Exit code `0`. An idle session also answers `queued`, and the message then starts a run at once, so do not read `queued` as "waiting".
251
+
252
+ ```bash
253
+ hagi chat send --session-id <session-id> --content "Check the diff" --steer --json
254
+ ```
255
+
256
+ The `--json` output keeps `sessionId`, `message` and `requested`, and adds a `steer` object: `{ requested, source, effective, outcome?, messageId?, reason? }`. `source` is `flag`, `env`, `config` or `default`. On the Steer path `message` is `Steered` or `Queued`. `reason` appears only when Steer was requested but not used.
257
+
258
+ Conflicts and fallback:
259
+
260
+ - `--steer` with `--mode` or `--executor-type` exits with code `2` and sends nothing.
261
+ - Steer from `HAGI_SEND_STEER` or the config file, combined with `--mode` or `--executor-type`, sends a normal message with those options and sets `reason`.
262
+ - Steer from `HAGI_SEND_STEER` or the config file, against a backend without `SteerSession`, sends a normal message with the reason `the backend does not support Steer`. An explicit `--steer` returns the backend error instead.
263
+
264
+ A queued Steer is always sent in `Edit` mode. To keep another mode, use `--no-steer --mode <mode>`.
265
+
266
+ For unattended session-to-session messages, set `HAGI_SEND_STEER=1` in the orchestrator's environment. Use `--no-steer` on a single call that must take the normal queue.
267
+
268
+ ## Plan commands
269
+
270
+ Scheduled plans save a proposal or HagiTask input with a schedule and, for HagiTask plans, an agent pool.
271
+
272
+ ```text
273
+ hagi plan
274
+ |-- list
275
+ |-- get --id <id>
276
+ |-- create --input <file|-> [--name <name>] [--hero-id <id>] [--disabled]
277
+ |-- update --id <id> [--input <file|->] [--name <name>] [--hero-id <id>] [--enabled | --disabled]
278
+ |-- enable --id <id>
279
+ |-- disable --id <id>
280
+ |-- delete --id <id>
281
+ `-- run --id <id> [--background]
282
+ ```
283
+
284
+ - `create` enables the plan unless the document sets `enabled` or `--disabled` is passed.
285
+ - `update`, `enable` and `disable` read the current plan and send it back whole, so unchanged fields are kept. `update --input` replaces each top-level key it contains as a whole value.
286
+ - `run` creates a session now and waits for it. `run --background` returns `{ planId, accepted: true }` once the backend accepts the run, before the session exists.
287
+ - A manual run does not change the plan's state or next run time, and works for disabled and completed plans.
288
+
289
+ A one-time proposal plan document for `plan create --input`:
290
+
291
+ ```json
292
+ {
293
+ "type": "proposal",
294
+ "name": "Weekly triage",
295
+ "enabled": true,
296
+ "schedule": { "mode": "once", "runAt": "2026-06-12T10:30:00Z" },
297
+ "payload": {
298
+ "title": "Weekly triage",
299
+ "chiefComplaint": "Triage the open issues and propose the next sprint",
300
+ "projectId": "<project-id>"
301
+ }
302
+ }
303
+ ```
304
+
305
+ The README's `## Plan Commands` section has a recurring HagiTask example and the full `update`, `enable`, `disable` and `delete` workflow.
306
+
307
+ ## Skills commands
308
+
309
+ `skills local list` shows each installed skill with its origin (`user` or `built-in`), its enabled state for built-in skills (`yes` or `no`, shown as `-` for user skills), its version, scope, agents and path. `--json` returns the same data with the `origin` and `builtIn` fields.
310
+
311
+ ```bash
312
+ hagi skills local list
313
+ hagi skills local list --json
314
+ hagi skills local enable --skill-name hagi
315
+ hagi skills local disable --skill-name hagi --json
316
+ ```
317
+
318
+ - `enable` and `disable` take `--skill-name` and change one built-in skill. They apply to agent runs that start afterwards.
319
+ - `uninstall` removes a user skill. Built-in skills cannot be uninstalled; use `disable` to stop one from loading.
320
+ - Skill recommendation and install progress streaming are not part of the CLI. `skills gallery search`, `detail` and `install` are the discovery and install path.
321
+ - `skills gallery detail` adds skills.sh audit ratings. `--json` keeps the detail fields and adds `audit: { providers: [...] }`, one entry per provider with `provider` and, when supplied, `risk`, `alerts`, `score` and `analyzedAt`. Human output appends one `skills.sh audit (<provider>)` line per provider, with `--` for missing values. If the audit fails or has no data, the command still exits `0` and `audit.providers` is `[]`. A detail failure fails the command as before. The audit never changes the install command preview or installability.
322
+
323
+ ## Agent lock commands
324
+
325
+ `hagi agent lock` takes turns on a shared resource with other agents. The full protocol, the numbers and the seven agent rules are in [`resource-lock.md`](resource-lock.md); read it before using these commands. There is no top-level `hagi lock`.
326
+
327
+ ```bash
328
+ hagi agent lock acquire --lock-id eu-1/duckov --owner sess-7f2c
329
+ hagi agent lock acquire --server eu-1 --game duckov --owner sess-7f2c --wait --timeout 900
330
+ hagi agent lock status --lock-id eu-1/duckov --owner sess-7f2c --json
331
+ hagi agent lock heartbeat --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
332
+ hagi agent lock renew --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
333
+ hagi agent lock release --lock-id eu-1/duckov --owner sess-7f2c --epoch 7
334
+ hagi agent lock requisition --lock-id eu-1/duckov --owner sess-7f2c --reason "User order: take the lock now"
335
+ hagi agent lock list --prefix eu-1/
336
+ ```
337
+
338
+ - Name the lock with `--lock-id <id>` or with `--server <name> --game <name>` (not both forms). The owner is your own session ID, passed as `--owner`; without it the CLI reads `HAGI_LOCK_OWNER`, then `HAGI_SESSION_ID`, and otherwise exits `2`.
339
+ - `acquire` takes `--wait`, `--timeout <seconds>` (default 900, needs `--wait`), `--lease-seconds <n>` and `--label <text>`. Without `--wait` it exits `0` for `Holding` and `Queued`. With `--wait` it exits `0` only when the owner holds the lock, `31` on timeout and `32` when the queue entry was dropped.
340
+ - `status` takes `--wait-seconds <n>`. `renew` and `heartbeat` take `--epoch <n>` and `--lease-seconds <n>`; `heartbeat` also takes `--interval <seconds>` (default 120) and `--verbose`. `release` takes `--epoch <n>`. `requisition` requires `--reason <text>` and takes `--label <text>`. `list` takes `--prefix <text>`.
341
+ - `heartbeat` is long-running and quiet on success. Run it under a watcher; it exits `32` when the lock is lost and `0` when stopped, without releasing.
342
+ - Timing: 10-minute feedback timeout, 24-hour maximum hold, 2-minute heartbeat interval, 30-second claim window, 60-second waiter TTL.
343
+ - Never run `requisition` unless the user explicitly ordered a forced take.
344
+
345
+ ## Exit codes
346
+
347
+ Stable non-zero exits used by the CLI. The HTTP numbers are the equivalents of the gRPC status the backend answered with:
348
+
349
+ | Exit code | Meaning |
350
+ | --- | --- |
351
+ | `2` | CLI usage or validation failure |
352
+ | `20` | HTTP 400 |
353
+ | `21` | HTTP 401 |
354
+ | `23` | HTTP 403 |
355
+ | `24` | HTTP 404 |
356
+ | `29` | HTTP 409 |
357
+ | `30` | Operation failed: the backend returned a refusal, for example `skills local enable` or `disable` on a name that is not a built-in skill, an invalid MonoSpecs configuration, or a failed MonoSpecs clone |
358
+ | `31` | Wait timed out: `--wait` on `proposal get`, `hagitask get` or `agent lock acquire` reached `--timeout` before the session settled. The last status is printed. Not a success, and not a backend error |
359
+ | `32` | Lock lost: a `hagi agent lock` command learned that the owner no longer holds the lock (expired, taken back at the 24-hour cap, stale epoch, requisitioned). Stop using the resource. Distinct from `31` |
360
+ | `40` | Other HTTP 4xx |
361
+ | `50` | HTTP 5xx |
362
+ | `60` | Transport or connection failure |
363
+ | `70` | Unknown error |
@@ -0,0 +1,91 @@
1
+ # Local Development
2
+
3
+ This document is maintainer-only.
4
+ Use it when you are editing or validating the CLI source tree inside `repos/cli`.
5
+ Normal users should stay on the published package path.
6
+
7
+ ## Working directory rule
8
+
9
+ Always change into `repos/cli` before running install, build, or test commands.
10
+ Do not run npm commands at the monorepo root.
11
+
12
+ ```bash
13
+ cd repos/cli
14
+ ```
15
+
16
+ ## Development modes
17
+
18
+ ### Source-level development
19
+
20
+ Use the TypeScript entrypoint directly while changing behavior:
21
+
22
+ ```bash
23
+ cd repos/cli
24
+ pnpm run dev proposal list --json
25
+ pnpm run dev chat list --json
26
+ ```
27
+
28
+ ### Release-style verification
29
+
30
+ Build the local distribution and run the bundled CLI:
31
+
32
+ ```bash
33
+ cd repos/cli
34
+ pnpm run build
35
+ node ./dist/cli.js --help
36
+ node ./dist/cli.js proposal --help
37
+ ```
38
+
39
+ ## Repository map
40
+
41
+ Inspect these areas when implementation details matter:
42
+
43
+ - `src/commands/` - command registration, flags, and request shaping
44
+ - `src/runtime/` - API runtime, shared context, and error handling
45
+ - `src/formatters/` - human-readable and JSON output formatting
46
+ - `buildTools/` - gRPC stub generation and DTO type generation scripts
47
+ - `scripts/verify-package.mjs` - publish-surface verification
48
+
49
+ ## API client generation
50
+
51
+ The CLI calls the backend over gRPC-Web (`<base-url>/grpc`). The stubs in `src/generated/grpc` come from the proto contracts in `repos/hagicode-core/protos`.
52
+ Use these commands when a proto changes:
53
+
54
+ ```bash
55
+ cd repos/cli
56
+ pnpm run generate:grpc
57
+ pnpm run generate:grpc:check
58
+ ```
59
+
60
+ `HAGICODE_PROTO_DIR` points generation at another checkout of the protos.
61
+
62
+ The DTO types in `src/generated/api/models` are committed and maintained by hand (types only, no REST client). They are no longer
63
+ generated: the backend's OpenAPI document now lists only the retained REST routes, so regenerating from it would delete the
64
+ types of every removed route. When a proto DTO changes, edit or add the matching type there.
65
+
66
+ ## Saga asset bundle
67
+
68
+ The Saga resources are owned by this repository. `resources/saga/` contains the six method prompt files, `.gitignore`,
69
+ and `manifest.json`; there is no standalone Saga tool and no sync step. Keep the bytes raw: do not render templates or
70
+ normalize line endings. The manifest uses schema version `2` with two maps: `install` lists `.gitignore`, and `methods`
71
+ maps each method name (`goal`, `research`, `acceptance`, `plan`, `run`, `amend`) to the digest of `prompts/<name>.md`.
72
+ Paths are sorted POSIX relative paths and every digest is lowercase SHA-256. The manifest does not hash itself. Only
73
+ `.gitignore` is installed into a project; the methods are read from the package with `hagi saga method`.
74
+
75
+ After editing a prompt, update its digest in `manifest.json`. `tests/runtime/saga/assets.test.ts` fails on any mismatch
76
+ between the files and the manifest, and `pnpm run pack:check` verifies the published contents.
77
+
78
+ ## Release automation
79
+
80
+ GitHub workflows under `.github/workflows/` handle release drafting and npm publishing.
81
+ For a stable release dry run, use:
82
+
83
+ ```bash
84
+ cd repos/cli
85
+ version="$(pnpm run --silent publish:verify-release -- v0.1.0)"
86
+ npm version --no-git-tag-version "$version"
87
+ pnpm run build
88
+ pnpm run typecheck
89
+ pnpm test
90
+ pnpm run pack:check
91
+ ```
@@ -0,0 +1,130 @@
1
+ # OpenSpec workflow behind `hagi proposal`
2
+
3
+ Use this reference when a user asks what a `hagi proposal` command does on the OpenSpec side, which stage it starts, where it stops, or how to follow a run until it settles.
4
+
5
+ Source of these facts: `ProposalSessionGrain` in `repos/hagicode-core` (`PCode.Orleans/Grains/ProposalSessionGrain.cs`, `ProposalSessionHandler.cs`) and the `openspec-v1-*` stage prompts (`new`, `explore`, `ff`, `apply`, `archive`). The command descriptions come from `hagi proposal <command> --help`. When these change, update this file.
6
+
7
+ ## Stage map
8
+
9
+ Each command starts at most one stage. "Does not" lists what the command must not be assumed to do.
10
+
11
+ | Command | Starts | Does not |
12
+ | --- | --- | --- |
13
+ | `proposal create` | no stage; creates the session | generate or execute |
14
+ | `proposal optimize-description` | no stage; rewrites the title, description and name in one backend step | generate |
15
+ | `proposal generate` | planning stages: `new`, then `explore` when explore mode is on, then `ff` | implement, execute or archive |
16
+ | `proposal annotate` | no stage; submits review notes, which return the session to planning | execute |
17
+ | `proposal execute` | implementation stage: `apply` | archive, or commit code |
18
+ | `proposal archive` | archive stage: `archive` | complete the session |
19
+ | `proposal complete` | no stage; marks the session completed through its own endpoint | start any stage |
20
+ | `proposal status` | no stage; sets the status directly with a read-before-write merge | start any stage |
21
+ | `proposal send` | no stage; sends a follow-up message on the active session | start a stage by itself, except as noted under "Who drives each step" |
22
+ | `proposal get` | no stage; reads one session, and with `--wait` polls until its status settles | change the session |
23
+ | `proposal messages` | no stage; reads the session's messages | change the session |
24
+
25
+ Explore mode is chosen with `--enable-explore-mode` or `--disable-explore-mode` on `proposal generate`.
26
+
27
+ A command returning means the request was accepted and the stage was queued. It does not mean the stage finished. Follow the run with `proposal get` (see "Who drives each step").
28
+
29
+ ## Boundaries of each stage
30
+
31
+ - **Planning** (`generate`) writes the proposal artifacts. It implements no source files. The next step is `annotate` or `execute`, and the user chooses which.
32
+ - **Implementation** (`execute`) implements the change. It skips tasks that are not implementation, such as archiving or committing, and reports them as out of scope. It does not archive.
33
+ - **Archive** (`archive`) archives the change. It does not implement anything.
34
+
35
+ No command starts the next stage on its own. An agent that has finished one lifecycle command runs the next command only when the user asked for that step.
36
+
37
+ ## Two kinds of commit
38
+
39
+ Two different things are called "commit" in this workflow. Neither replaces the other.
40
+
41
+ - **Archive commit.** The archive stage may make one commit, only in the MonoSpecs repository that holds `openspec/`, and only when the project's option to commit on archive is on. The backend runs it inside the archive stage. It never covers code repositories, and the agent does not run it as a command.
42
+ - **Super Commit.** A separate session that commits the uncommitted changes in a code repository. It is started with `hagi hagitask create --prompt-id auto-compose-commit.<locale>`, and the agent starts it only on request. See [super-commit.md](super-commit.md).
43
+
44
+ After `proposal execute`, the code changes are still uncommitted. The archive commit does not commit them. To commit code, use Super Commit, and only when the user asks.
45
+
46
+ ## Confirm a stage before moving on
47
+
48
+ 1. Take the session id from the command's `--json` output.
49
+ 2. Pass that same id to the next command.
50
+ 3. Treat the suggested next command in the output as a suggestion. Do not run it unless the user asked for that step.
51
+
52
+ The repositories a proposal may change are listed in [Repository scope](proposal-flow.md#repository-scope). Read that section; this file does not restate it.
53
+
54
+ ## Who drives each step
55
+
56
+ Every step is one of five kinds:
57
+
58
+ - **Agent-issued command**: the agent runs it, and only when the user asked for that step.
59
+ - **AI run inside the backend**: the backend runs an AI agent for the stage. The agent does not run it by hand.
60
+ - **Automatic**: the backend moves the status with no command.
61
+ - **Needs a person**: the run stops until a person answers or decides.
62
+ - **Needs monitoring**: the agent follows the run with `proposal get --wait` and reports the result.
63
+
64
+ | Step | Kind | Notes |
65
+ | --- | --- | --- |
66
+ | `proposal create` | agent-issued command | creates the session |
67
+ | `proposal optimize-description` | agent-issued command | moves Init to Optimizing |
68
+ | Optimizing to Drafting | automatic | when the optimization finishes |
69
+ | `proposal generate` | agent-issued command | starts the planning run, an AI run inside the backend |
70
+ | Planning run (Openspecing) | AI run inside the backend, needs monitoring | Openspecing |
71
+ | Openspecing to Reviewing | automatic | when planning ends |
72
+ | `proposal annotate` | agent-issued command | moves Reviewing back to Openspecing |
73
+ | `proposal execute` | agent-issued command | moves Reviewing to Executing, and starts the implementation run |
74
+ | Implementation run (Executing) | AI run inside the backend, needs monitoring | Executing |
75
+ | Run asks a question | needs a person | Executing moves to WaitingForReply |
76
+ | A person answers | needs a person | WaitingForReply moves back to Executing |
77
+ | Executing to ExecutionCompleted | automatic | when the implementation run ends |
78
+ | `proposal archive` | agent-issued command | moves ExecutionCompleted to Archiving |
79
+ | Archiving to Archived | automatic | when the archive run ends |
80
+ | `proposal complete` | agent-issued command | marks the session completed |
81
+ | `proposal status` | agent-issued command | sets the status directly |
82
+ | `proposal send` | agent-issued command | on a session in ExecutionCompleted or Completed, it starts a new execution run; see the warning below |
83
+ | `proposal get --wait` | needs monitoring | reads the status until it settles |
84
+ | `proposal messages` | agent-issued command | reads the messages, for example the final result |
85
+
86
+ Status moves, and who causes each one:
87
+
88
+ | From | To | Trigger |
89
+ | --- | --- | --- |
90
+ | Init | Optimizing | agent runs `optimize-description` |
91
+ | Optimizing | Drafting | automatic |
92
+ | Drafting | Openspecing | agent runs `generate` |
93
+ | Openspecing | Reviewing | automatic when planning ends |
94
+ | Reviewing | Openspecing | agent runs `annotate` |
95
+ | Reviewing | Executing | agent runs `execute` |
96
+ | Executing | WaitingForReply | the run asks a question |
97
+ | WaitingForReply | Executing | a person answers |
98
+ | Executing | ExecutionCompleted | automatic when the implementation run ends |
99
+ | ExecutionCompleted | Archiving | agent runs `archive` |
100
+ | ExecutionCompleted | Executing | agent runs `send` (a new execution run) |
101
+ | Archiving | Archived | automatic when the archive run ends |
102
+
103
+ Only the moves labelled with a command are caused by the agent. The rest are caused by the backend, or wait for a person.
104
+
105
+ ### Warnings
106
+
107
+ - `proposal send` on a session in **ExecutionCompleted** or **Completed** starts a new execution run. Use it only when the user asks to run again.
108
+ - `proposal annotate` on a session in **Reviewing** returns it to planning. The session goes back to Openspecing and is planned again. Use it only when the user asks for revisions.
109
+
110
+ ### The monitoring rule
111
+
112
+ After any start command (`generate`, `execute`, `archive`), follow the run:
113
+
114
+ ```bash
115
+ hagi proposal get --session-id <id> --wait --json
116
+ ```
117
+
118
+ Read the `status` and the `wait` object in the output:
119
+
120
+ - **Openspecing, Executing or Archiving**: the run is still going. The wait ends on its own at the timeout.
121
+ - **WaitingForReply**: a person must answer. Tell the user; waiting longer does not help.
122
+ - **Reviewing, ExecutionCompleted or Archived**: the stage has settled.
123
+ - **Timeout (exit code `31`)**: report the last status to the user. Do not retry in a loop.
124
+
125
+ A returned start command accepts the request. It does not prove the run succeeded. Confirm the outcome from the settled status and, when the result matters, from `proposal messages`.
126
+
127
+ Other options of the monitoring commands:
128
+
129
+ - `--timeout <seconds>`, `--interval <seconds>`, `--grace <seconds>`: only with `--wait`. `--interval` must be less than `--timeout`.
130
+ - `proposal messages --session-id <id> --last <n>`: reads the n newest messages. `--json` prints the list unchanged.