@hagicode/cli 0.8.0 → 0.9.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/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./chunks/main-BUHthS3C.js");s.runCli().then(e=>{e!==0&&(process.exitCode=e)}).catch(e=>{const n=e instanceof Error?e.stack??e.message:String(e);process.stderr.write(`${n}
2
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("./chunks/main-GxA0gS86.js");s.runCli().then(e=>{e!==0&&(process.exitCode=e)}).catch(e=>{const n=e instanceof Error?e.stack??e.message:String(e);process.stderr.write(`${n}
3
3
  `),process.exitCode=s.EXIT_CODES.UNKNOWN});
package/dist/main.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/main-BUHthS3C.js");exports.createCliApp=e.createCliApp;exports.runCli=e.runCli;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./chunks/main-GxA0gS86.js");exports.createCliApp=e.createCliApp;exports.runCli=e.runCli;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hagicode/cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "HagiCode CLI for project, proposal, vault, chat, and AutoTask management.",
5
5
  "homepage": "https://github.com/HagiCode-org/cli#readme",
6
6
  "bugs": {
@@ -7,8 +7,8 @@
7
7
  "goal": "a545b01c9f6010df661f02cf62ef9bcae527ccf329f304ea82ffdda239b118df",
8
8
  "research": "2504283ea845440c7909ff1da20bfb70f846d55ca16565740168848cfb8060b1",
9
9
  "acceptance": "11ca06794d0ec19e87d1a4e883a67014563e9750b9fa62a8bedd43a69d52ca65",
10
- "plan": "bcc4c0509f2971083775db2b3678f41c55f16e69deba2d994d3ed636f314d917",
11
- "run": "ea3fc7f5c54230cef95b09595cb296432f733bdbe8cbe42c40cf73f6f6b900e4",
10
+ "plan": "6573a02a36ab2d6995c2e4740d2c8771a41e77e5179145d0a2b8e30023816916",
11
+ "run": "3a3ae5bc64ab66faefda08fabe97c41b990266740fe749aee4ea29d822d5c131",
12
12
  "amend": "be621d1221a85656851548aee1e9e9d78480288bd856baf1bdbf50cb3317f49b"
13
13
  }
14
14
  }
@@ -3,14 +3,34 @@
3
3
  ## Instruction
4
4
  List the work items, one line each: `- [ ] <ID> <name> | `. The evidence path is added when an item is ticked.
5
5
 
6
- - Items are small enough to verify with one command, mostly independent, and ordered so that earlier items do not depend on later ones.
6
+ - Items are small enough to verify with one command, and ordered so that earlier items do not depend on later ones.
7
7
  - Keep the list short enough to review (roughly 3 to 15 items). Use the user's work items as a start; add or split items only when needed.
8
8
  - Do not add items for committing, pushing, opening a pull request, releasing or archiving: they are outside the contract.
9
9
  - IDs match the headings of `acceptance.md`.
10
- - In a revision an item may be added or superseded, never deleted. A superseded item is written without brackets: `- ~ <ID> <name> | superseded in r<N>: <reason>`.
10
+ - In a revision an item may be added or superseded, never deleted. A superseded item is written without brackets: `- ~ <ID> <name> | superseded in r<N>: <reason>`. A superseded item is not active: it has no node in the graph and no edges.
11
11
  - Planning ends here. Do not run `freeze`, do not start the run, do not edit project code.
12
12
 
13
+ ## Dependency graph
14
+ `progress.md` holds exactly one Mermaid `flowchart` that describes every dependency between the active items. Write it in its own section, outside the item rows. It is the only place dependencies are declared.
15
+
16
+ - Each active item is one node. The node ID equals the item ID, and the node label holds the item's name in quotes.
17
+ - An edge `A --> B` means B cannot start until A is ticked. Write an edge only when that order is required.
18
+ - Two items with no path between them are independent and may run together. Independence is expressed only by the absence of a path.
19
+ - Group nodes with one `subgraph` per module, repository or area the item changes, titled in quotes (for example `subgraph CLI["repos/cli"]`). A subgraph states ownership only. Two items in one subgraph are not dependent because of it, and items in different subgraphs are not dependent because of it either.
20
+ - Item IDs must be valid Mermaid node IDs: start with a letter and use only letters, digits and underscores (`T1`, `CLI_2`; not `1.2`, not `T-1`). Never use the word `end`. Put the readable name in the node label, not in the ID.
21
+ - Keep every dependency in this one graph. Do not declare a dependency in prose, in the note column after `|` of an item row (that text is evidence only), in a separate list, or in a second diagram.
22
+ - Item rows keep their format `- [ ] <ID> <name> | <evidence>` unchanged, and the graph lines are never edited by ticking an item.
23
+
13
24
  ## Template
14
25
  ```
15
26
  - [ ] <ID> <name> |
16
27
  ```
28
+
29
+ ```mermaid
30
+ flowchart TD
31
+ subgraph <MODULE>["<module or repository>"]
32
+ <ID>["<ID> <name>"]
33
+ end
34
+ <ID> --> <ID>
35
+ ```
36
+ </content>
@@ -11,7 +11,7 @@ The run's options may name these, with the defaults shown:
11
11
 
12
12
  0. **Claim first.** `hagi saga claim <saga> --session <your session id> --path <root> --json`. Keep the printed token for every later command. If it refuses because another owner holds the Saga, stop and report it. Never take over by age, and never write a paused snapshot over another owner's progress.
13
13
  1. **Freeze.** `hagi saga freeze <saga> --path <root> --token <token>`. If it refuses (open questions, open amendments, a changed contract), run `hagi saga set-status <saga> --state paused --note "<reason>" --path <root> --token <token>`, stop and report the reasons. Do not edit contract files and do not decide open questions. A first freeze writes revision 1. After an amendment was merged it writes the next revision, chained to the previous one, and reopens the items whose acceptance command changed.
14
- 2. **Check before every item and at every heartbeat.** `hagi saga assert-owner <saga> --path <root> --token <token>` before each dispatch, then `hagi saga check <saga> --phase run --path <root>`. If a check fails, pause the same way and report. An open amendment also means: pause.
14
+ 2. **Check before every dispatch and at every heartbeat.** `hagi saga assert-owner <saga> --path <root> --token <token>` before each dispatch, then `hagi saga check <saga> --phase run --path <root>`. If a check fails, pause the same way and report. An open amendment also means: pause.
15
15
  - **Quota before every Worker.** Before you create any Worker (step 3, and a tuning worker in step 4), resolve the provider that Worker spends, in this order, and use the first that applies:
16
16
  1. the hint for the Worker's hero id, when the run input names the hero that Worker runs;
17
17
  2. the hint for the Worker's role;
@@ -23,15 +23,24 @@ The run's options may name these, with the defaults shown:
23
23
  - exit `34`, no usable figure: dispatch the Worker, log `quota unknown` with the provider in `orchestrator-log.md`, and read again before the next Worker.
24
24
  - Any other exit code: pause the same way as a failed check, and report it.
25
25
  - The JSON output's `windows` lists each window with `remainingPercent` and `resetsAtUtc`. Record the provider, the remaining percent and the reset time for every dispatch and every held dispatch (step 6).
26
- 3. **Work one item at a time** (`hagi saga items <saga> --path <root>` lists them with their commands), unless the goal allows parallel items. For each item:
26
+ - **Concurrent Workers.** Each Worker that runs alongside others gets its own ownership assert, its own check, its own quota check and its own logged reading (step 6), in that order, before it is created. A held dispatch (exit `0`, `guard.state` `low`) holds only that Worker: the Workers already running keep running, and between two runs of the held quota check you handle their reports, ticks and archiving. A held dispatch never stops a running Worker; only the wait total of that dispatch can pause the Saga.
27
+ 3. **Dispatch by the dependency graph.** Read the Mermaid flowchart in `<root>/.hagicode/sagas/<saga>/progress.md` and the items (`hagi saga items <saga> --path <root>` lists them with their commands). Then:
28
+ - **Ready set.** An item is ready when it is unticked, not blocked, and every predecessor of it in the graph (every node with an edge `P --> <ITEM>`) is ticked. Compute the ready set from the graph and the Saga record, not from memory, and recompute it after every tick or block.
29
+ - **No graph, serial.** If `progress.md` holds no Mermaid flowchart, the Saga runs serially: one item at a time, in the listed order, with no inferred parallelism.
30
+ - **Graph problems pause.** If an unticked active item has no node in the graph, or an edge names a node that is not an active item, pause the Saga with that reason (step 9). Do not guess an order.
31
+ - **Goal forces serial.** If the goal says items run one at a time, dispatch one item at a time regardless of the graph.
32
+ - **Hold back overlap.** Among ready items, do not run two at once when they share a subgraph module or are likely to edit the same files, judged from the subgraph, the item name and its acceptance command. Dispatch one of them and hold the others until the running item is ticked or blocked. Ready items in disjoint modules with no shared files are not held back.
33
+ - **Cap.** At most 3 Workers run at once by default, counting implementation and verification Workers together. The goal may name a different cap; never exceed it. When more items are ready than there are free slots, start the rest as Workers settle.
34
+ - **Fan out.** Dispatch each remaining ready item by creating its implementation Worker, one after another, each behind its own quota check (step 2). Workers never tick or block items themselves: the orchestrator is the only writer of the Saga record.
35
+ For each Worker:
27
36
  - Before each implementation, verification and tuning Worker, not once per item, run the quota check of step 2 for the provider that Worker spends.
28
- - Create each Worker with `hagi saga session create --saga <saga> --path <root> --title "<ITEM> <role>" --role <role> --master <your session id> --item <ITEM>`, where `<role>` is `implementation` or `verification`. This command takes the token from `SAGA_EXECUTION_TOKEN`, set to the token that `claim` printed, and refuses `--token`. It creates the chat session with its Saga metadata and records it in `sessions.md`. Then give the Worker its instructions below.
37
+ - Create each Worker with `hagi saga session create --saga <saga> --path <root> --title "<ITEM> <role>" --role <role> --master <your session id> --item <ITEM>`, plus `--hero <hero>` when the run input names the hero that Worker runs, where `<role>` is `implementation` or `verification`. This command takes the token from `SAGA_EXECUTION_TOKEN`, set to the token that `claim` printed, and refuses `--token`. It creates the chat session with its Saga metadata and records it in `sessions.md`. Then give the Worker its instructions below.
29
38
  - An implementation worker writes code and tests only. Give it the goal, the item, the scope and the safety rules, the project root and its report path.
30
39
  - A separate verification worker runs the item's command from `acceptance.md` in the project root, writes the output to `<root>/.hagicode/sagas/<saga>/runtime/evidence/<ITEM>.log`, and writes `<root>/.hagicode/sagas/<saga>/runtime/reports/<ITEM>.md` with the line `Status: PASS` (or `状态: PASS`) only if the exit code is 0. It changes no files.
31
40
  - Only `hagi saga tick <saga> <ITEM> --path <root> --token <token>` ticks an item. It refuses without a PASS report and an evidence file.
32
- 4. **On a failed verification, classify it.** An environment fault (missing module, port in use) does not count. A quota fault does not count either: a worker that stopped because its provider's quota was exhausted is not given to a tuning worker and is not counted. Re-dispatch the same Worker role with the same instructions, unchanged, once the step-2 quota check for that Worker's own provider (the provider it used, not the default) returns exit `0` with `guard.state` `ready`. A real failure counts: give a tuning worker the failure log path, then verify again. After 3 real failures run `hagi saga block <saga> <ITEM> "<reason>" --path <root> --token <token>` and move on.
41
+ 4. **On a failed verification, classify it.** An environment fault (missing module, port in use) does not count. A quota fault does not count either: a worker that stopped because its provider's quota was exhausted is not given to a tuning worker and is not counted. Re-dispatch the same Worker role with the same instructions, unchanged, once the step-2 quota check for that Worker's own provider (the provider it used, not the default) returns exit `0` with `guard.state` `ready`. A real failure counts: give a tuning worker the failure log path, then verify again. After 3 real failures run `hagi saga block <saga> <ITEM> "<reason>" --path <root> --token <token>` and move on. A blocked item blocks everything downstream of it: every item reachable from it in the graph is never dispatched and is reported as waiting on that item. Items with no path from the blocked item continue as in step 3.
33
42
  5. **After a revision reopened items,** also re-verify any item whose command is the full regression: its own command did not change, so it was not reopened.
34
- 6. **Keep the record.** Append a line to `<root>/.hagicode/sagas/<saga>/runtime/orchestrator-log.md` after every dispatch, held dispatch, verification and tick. Every dispatch line and every held dispatch line carries the quota reading it was made on: the provider, the remaining percent, the `resetsAtUtc` and the seconds waited. `hagi saga session create` records each Worker session it creates in `<root>/.hagicode/sagas/<saga>/runtime/sessions.md`, so do not append those sessions to that file by hand. Run `hagi saga set-status <saga> --state running --note "<heartbeat>" --path <root> --token <token>` at every heartbeat, and put the latest quota reading (provider, remaining percent, reset time and seconds waited) in that note. Run `hagi saga status <saga> --path <root> --json` to read the projection at the end.
43
+ 6. **Keep the record.** Append a line to `<root>/.hagicode/sagas/<saga>/runtime/orchestrator-log.md` after every dispatch, held dispatch, verification and tick. Every dispatch line and every held dispatch line carries the quota reading it was made on: the provider, the remaining percent, the `resetsAtUtc` and the seconds waited. `hagi saga session create` records each Worker session it creates in `<root>/.hagicode/sagas/<saga>/runtime/sessions.md`, so do not append those sessions to that file by hand. Run `hagi saga set-status <saga> --state running --note "<heartbeat>" --path <root> --token <token>` at every heartbeat, and put the latest quota reading (provider, remaining percent, reset time and seconds waited) in that note. While several Workers run, heartbeat at every check of a running Worker and name the running items in the note. Run `hagi saga status <saga> --path <root> --json` to read the projection at the end.
35
44
  7. **Archive each settled item's sessions.** Once an item is ticked or blocked and its workers have stopped, archive the worker sessions you created for it: `hagi chat archive --session-id <session id> --json`. Take the IDs from `<root>/.hagicode/sagas/<saga>/runtime/sessions.md`. Archive only sessions you created in this run: never your own orchestrator session, a session that is still running, a session you did not create, or the Saga itself (`hagi saga archive` is not part of a run). Archiving leaves the reports, evidence and `sessions.md` record in `runtime/` untouched. Log each archived ID in `orchestrator-log.md`. If an archive call fails, log the error and go on: do not retry in a loop and do not pause for it.
36
45
  8. **Never leave a command hanging.** Every shell command must end on its own: never read from standard input, and give waits a limit. If something does not answer, report it instead of waiting forever. Every quota check is bounded as in step 2, and a dispatch that has waited six hours in total on quota pauses the Saga.
37
- 9. **Stop** when every item is ticked or blocked, when a pause condition appears, or when the human's run options say so. Write the terminal snapshot first (`set-status --state done` only when every active item is verified, otherwise `paused` with the reason; a quota pause names the provider, the remaining percent and the reset time). Once every worker has stopped, archive any remaining worker sessions as in step 7, then run `hagi saga release <saga> --path <root> --token <token> --workers-settled`. Never archive the Saga itself and do not commit. Report the revision, every item with its state and evidence path, the blocked items with reasons, the sessions you created and which of them you archived, and the next step for a human.
46
+ 9. **Stop** when every item is ticked, blocked or waiting on a blocked item, when a pause condition appears (including an item missing from the graph or an unknown node, as in step 3), or when the human's run options say so. Write the terminal snapshot first (`set-status --state done` only when every active item is verified, otherwise `paused` with the reason; a quota pause names the provider, the remaining percent and the reset time). Once every worker has stopped, archive any remaining worker sessions as in step 7, then run `hagi saga release <saga> --path <root> --token <token> --workers-settled`. Never archive the Saga itself and do not commit. Report the revision, every item with its state and evidence path, the blocked items with reasons, the items waiting on a blocked item with the blocked item each one waits on, the sessions you created and which of them you archived, and the next step for a human.
@@ -90,7 +90,7 @@ The current top-level command families are:
90
90
  - `agent`
91
91
  - `saga`
92
92
 
93
- `hagi saga` provides `init`, `new`, `list`, `status`, and `items`, plus the write and check commands `claim`, `assert-owner`, `release`, `freeze`, `check`, `tick`, `block`, `set-status`, `amend`, and `archive`, and `session create`, which creates a Saga Worker chat session and records it. Mutating commands take `--token` or `SAGA_EXECUTION_TOKEN`; `session create` takes only `SAGA_EXECUTION_TOKEN`. With `--path`, these operate on host-local files without reading backend configuration or calling the API, except `session create`, which calls the backend to find the registered project; `--project-id` resolves a project through the backend. Creating a Worker changes state, so run `session create` only when the user or a Saga run explicitly asks for it. See [`references/saga.md`](references/saga.md) for initialization, creation, offline behavior, and read-only query guarantees.
93
+ `hagi saga` provides `init`, `new`, `list`, `status`, and `items`, plus the write and check commands `claim`, `assert-owner`, `release`, `freeze`, `check`, `tick`, `block`, `set-status`, `amend`, and `archive`, `session create`, which creates a Saga Worker chat session and records it; `session list`, which reads the Worker records without a token; and `log append`, which appends one line to the orchestrator log under the execution token. Mutating commands take `--token` or `SAGA_EXECUTION_TOKEN`; `session create` takes only `SAGA_EXECUTION_TOKEN`. With `--path`, these operate on host-local files without reading backend configuration or calling the API, except `session create`, which calls the backend to find the registered project; `--project-id` resolves a project through the backend. Creating a Worker changes state, so run `session create` only when the user or a Saga run explicitly asks for it. See [`references/saga.md`](references/saga.md) for initialization, creation, offline behavior, and read-only query guarantees.
94
94
 
95
95
  `project repositories --id <project-id>` is read-only. It lists one project's repositories with each path relative to the project root (for example `repos/cli`). Those paths are the values a proposal's `Repository scope` names; see [`references/proposal-flow.md`](references/proposal-flow.md#repository-scope).
96
96
 
@@ -92,7 +92,7 @@ hagi
92
92
  |-- project list | repositories | create | update | delete
93
93
  | `-- monospecs show | init | set | validate | reload | clone | remove-checkout
94
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
95
+ |-- saga init | new | list | status | items | claim | assert-owner | release | freeze | check | tick | block | set-status | amend | archive | session create | session list | log append
96
96
  |-- vault list | create | update | delete
97
97
  | `-- files list | preview
98
98
  |-- chat list | create | archive | delete | send
@@ -225,7 +225,8 @@ Start one only when the user asked for it. See [super-commit.md](super-commit.md
225
225
 
226
226
  - `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.
227
227
  - `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.
228
- - `create --json` prints `{ session, presetTaskId, promptId, requested }`. Read the session id from `session.sessionId`.
228
+ - `create --json` prints `{ session, presetTaskId, promptId, requested }`. Read the session id from `session.sessionId`. `requested` carries `heroId` or `heroName` when a Hero was given, and `session.chatHero` names the Hero the backend bound when it returns one.
229
+ - `create --hero <name-or-id>` picks the Hero. A GUID is sent as `heroId`; any other value is sent as `heroName` (trimmed, matched without regard to case). Give `--hero` or `--hero-id`, not both: that is a usage error (exit `2`) and sends nothing. A blank value is also exit `2`. An unknown name, or both fields in one request, fails with the backend's message and creates no session. Needs a backend that includes the `hero_name` field; an older backend ignores the name and runs its default Hero.
229
230
 
230
231
  Example `input.json` for a task with one required input:
231
232
 
@@ -184,6 +184,12 @@ Set `SAGA_EXECUTION_TOKEN` to the token that `claim` printed, then create a Work
184
184
  hagi saga session create --saga demo --path "./My Project" --title "T1 implementation" --role implementation --master orchestrator-1 --item T1 --json
185
185
  ```
186
186
 
187
+ To bind the Worker to a Hero, add `--hero <name-or-id>`, for example `--hero Nova` or a Hero ID:
188
+
189
+ ```bash
190
+ hagi saga session create --saga demo --path "./My Project" --title "T1 implementation" --role implementation --master orchestrator-1 --item T1 --hero Nova --json
191
+ ```
192
+
187
193
  - The command creates one ordinary chat session in the project and appends one line to `runtime/sessions.md`. It sends
188
194
  no message and does not archive the session; `hagi chat archive --session-id <id>` stays a separate step.
189
195
  - Required and nonblank: `--saga <id>`, `--title <title>`, `--role implementation|verification`, and `--master <session-id>`
@@ -198,6 +204,12 @@ hagi saga session create --saga demo --path "./My Project" --title "T1 implement
198
204
  names the path or the matching IDs, and creates nothing. `--project-id` looks the project up directly.
199
205
  - After the project and the Saga are resolved, the owner check of `assert-owner` runs with the token. A Saga without a lock
200
206
  still passes once a token is set. A wrong token is a refusal (code `1`), and an unknown Saga is code `24`.
207
+ - `--hero <name-or-id>` is optional. A GUID is sent as `heroId`; any other nonblank value is sent as `heroName`, matched
208
+ like Hero names (trimmed, case-insensitive). A blank value is a usage error (code `2`), and no file is touched. Without
209
+ `--hero` the request is the same as before. The Hero is never written to `userMetadata` or `sessions.md`. A Hero the
210
+ backend rejects (an unknown name, a name and an ID together, or a disabled Hero) fails with the backend's message, the
211
+ record line is not written, and no Worker is recorded. With `--json` the `session.chatHero` object names the Hero the
212
+ backend bound, and the human result shows it on a `hero` line. An older backend ignores the name and runs its default Hero.
201
213
  - The new session's `userMetadata` holds `saga.id` (the Saga), `saga.role` (always `worker`), `saga.worker` (the `--role`
202
214
  value), `saga.master` (the `--master` value), and `saga.item` only when `--item` is given. The backend checks the key
203
215
  format (`^[a-z][a-z0-9._-]{0,63}$`), at most 16 entries, and value length (1 to 512 characters). The metadata is
@@ -210,7 +222,14 @@ hagi saga session create --saga demo --path "./My Project" --title "T1 implement
210
222
  - If the append fails after the session was created, the command exits `1` and names the created session ID, in the
211
223
  human output and as `session.sessionId` in `--json` with `ok` false.
212
224
  - `--json` success is one object: `{ "sagaId": "demo", "command": "session create", "ok": true, "session": { "sessionId": "...", "userMetadata": { ... } }, "record": "<path to sessions.md>" }`.
213
- - Exit codes: `0` created and recorded; `1` refused, or the record failed after creation; `2` usage error; `24` Saga not found.
225
+ - Exit codes: `0` created and recorded; `1` refused, or the record failed after creation; `2` usage error; `20` the backend rejected the request, as with a Hero it cannot bind; `24` Saga not found.
226
+
227
+ ### Run records
228
+
229
+ Two commands replace the hand-written steps of the run prompt. Both take the Saga's path, as the other local Saga commands do.
230
+
231
+ - `hagi saga session list <saga-id> --path <root> [--json]` lists the Worker records in `runtime/sessions.md`: session ID, UTC creation time, role, item (`null` for `-`) and title, in file order. It is read-only and needs no token. A Saga with no `sessions.md` lists nothing and exits `0`. An unknown Saga exits `24`. `--json` returns `{ "sagaId", "command": "session list", "ok": true, "sessions": [...] }`.
232
+ - `hagi saga log append <saga-id> --message <text> --path <root>` appends one line, `- <UTC time> | <message>`, to `runtime/orchestrator-log.md`. It needs the execution token of the Saga owner, from `SAGA_EXECUTION_TOKEN` or `--token`, and refuses a token that does not match the lock with exit `1`. Whitespace and line breaks in the message collapse to one line. A blank message is a usage error (exit `2`) before any file or token check. The runtime directory is created when missing, and a redirected log file is refused before any byte is written.
214
233
 
215
234
  ## Items, amendments, and archive
216
235