@ucsandman/legcli 0.7.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/CHANGELOG.md +560 -0
- package/LICENSE +58 -0
- package/NOTICE +4 -0
- package/README.md +558 -0
- package/bin/fake-agent.mjs +143 -0
- package/bin/leg.mjs +449 -0
- package/docs/DECISIONS.md +119 -0
- package/docs/DEMO.md +214 -0
- package/docs/DEVIATIONS.md +182 -0
- package/docs/ERRORS.md +326 -0
- package/docs/README.md +40 -0
- package/docs/REUSE.md +289 -0
- package/docs/ROADMAP-v2.md +55 -0
- package/docs/VOCABULARY.md +243 -0
- package/docs/adapters.md +300 -0
- package/docs/board-guide.md +579 -0
- package/docs/cli-contracts.md +401 -0
- package/docs/concepts.md +426 -0
- package/docs/configuration.md +220 -0
- package/docs/faq.md +206 -0
- package/docs/getting-started.md +251 -0
- package/docs/real-run.md +80 -0
- package/docs/review-2026-09-14.md +72 -0
- package/docs/screenshots/board-400px.png +0 -0
- package/docs/screenshots/board-details-open.png +0 -0
- package/docs/screenshots/board-done.png +0 -0
- package/docs/screenshots/board-drawer.png +0 -0
- package/docs/screenshots/board-empty.png +0 -0
- package/docs/screenshots/board-handoff.png +0 -0
- package/docs/screenshots/board-running.png +0 -0
- package/docs/screenshots/demo-1-claude-running.png +0 -0
- package/docs/screenshots/demo-2-limit-hit.png +0 -0
- package/docs/screenshots/demo-3-handoff-bundle.png +0 -0
- package/docs/screenshots/demo-4-codex-running.png +0 -0
- package/docs/screenshots/demo-5-done.png +0 -0
- package/docs/screenshots/floor-final-1280.png +0 -0
- package/docs/screenshots/floor-final-400.png +0 -0
- package/docs/screenshots/floor-landing.png +0 -0
- package/docs/screenshots/floor.png +0 -0
- package/docs/screenshots/share-guest-1280.png +0 -0
- package/docs/screenshots/share-owner-1280.png +0 -0
- package/docs/screenshots/terminals-1280.png +0 -0
- package/docs/screenshots/thumb-done.jpg +0 -0
- package/docs/screenshots/thumb-handoff-details.jpg +0 -0
- package/docs/screenshots/thumb-limit-hit.jpg +0 -0
- package/docs/screenshots/youtube-thumbnail.jpg +0 -0
- package/fixtures/limits/agy/agy-resource-exhausted.json +11 -0
- package/fixtures/limits/claude/claude-budget-limit.json +11 -0
- package/fixtures/limits/claude/claude-max-turns.json +11 -0
- package/fixtures/limits/claude/claude-model-limit.json +11 -0
- package/fixtures/limits/claude/claude-session-limit.json +11 -0
- package/fixtures/limits/claude/claude-weekly-limit.json +11 -0
- package/fixtures/limits/codex/codex-quota-exceeded.json +11 -0
- package/fixtures/limits/codex/codex-rate-limit-exceeded.json +11 -0
- package/fixtures/limits/codex/codex-skills-notice.json +11 -0
- package/fixtures/limits/codex/codex-usage-limit-reached.json +11 -0
- package/fixtures/limits/codex/codex-usage-limit.json +11 -0
- package/fixtures/limits/generic/generic-429.json +11 -0
- package/fixtures/limits/generic/generic-overloaded.json +11 -0
- package/fixtures/limits/generic/generic-quota.json +11 -0
- package/fixtures/limits/generic/generic-rate-limit.json +11 -0
- package/fixtures/limits/generic/generic-resource-exhausted.json +11 -0
- package/fixtures/limits/generic/generic-usage-limit.json +11 -0
- package/fixtures/limits/grok/grok-not-logged-in.json +11 -0
- package/fixtures/limits/negative/auth-source-set.json +11 -0
- package/fixtures/limits/negative/compile-error.json +11 -0
- package/fixtures/limits/negative/empty-stdout-exit-0.json +11 -0
- package/fixtures/live/agy/attempt-1-scratch-workspace.out.log +1 -0
- package/fixtures/live/agy/cmd.txt +1 -0
- package/fixtures/live/agy/err.log +0 -0
- package/fixtures/live/agy/out.log +1 -0
- package/fixtures/live/agy/parsed.json +5 -0
- package/fixtures/live/agy/run.json +18 -0
- package/fixtures/live/agy/supervisor.log +2 -0
- package/fixtures/live/claude/cmd.txt +1 -0
- package/fixtures/live/claude/err.log +0 -0
- package/fixtures/live/claude/limit-rate_limit.json +21 -0
- package/fixtures/live/claude/out.log +1 -0
- package/fixtures/live/claude/parsed.json +11 -0
- package/fixtures/live/claude/run.json +18 -0
- package/fixtures/live/claude/supervisor.log +2 -0
- package/fixtures/live/codex/cmd.txt +1 -0
- package/fixtures/live/codex/err.log +1 -0
- package/fixtures/live/codex/last.md +2 -0
- package/fixtures/live/codex/limit-usage_limit_exceeded.json +25 -0
- package/fixtures/live/codex/out.log +8 -0
- package/fixtures/live/codex/parsed.json +15 -0
- package/fixtures/live/codex/run.json +18 -0
- package/fixtures/live/codex/supervisor.log +2 -0
- package/fixtures/live/grok/cmd.txt +1 -0
- package/fixtures/live/grok/err.log +32 -0
- package/fixtures/live/grok/out.log +7 -0
- package/fixtures/live/grok/parsed.json +5 -0
- package/fixtures/live/grok/run.json +18 -0
- package/fixtures/live/grok/supervisor.log +2 -0
- package/fixtures/verified.json +34 -0
- package/package.json +68 -0
- package/scripts/board-shots.mjs +69 -0
- package/scripts/build-docs-site.mjs +308 -0
- package/scripts/check-claims.mjs +150 -0
- package/scripts/clean-clone-check.sh +42 -0
- package/scripts/license-sign.mjs +23 -0
- package/scripts/limits-table.mjs +38 -0
- package/scripts/live-limits.mjs +36 -0
- package/scripts/privacy-check.mjs +63 -0
- package/scripts/probe.mjs +92 -0
- package/scripts/seed-fake-cards.mjs +34 -0
- package/scripts/seed-floor-board.mjs +69 -0
- package/scripts/seed-wes-board.mjs +91 -0
- package/scripts/stripe-setup.mjs +99 -0
- package/scripts/vercel-env.mjs +40 -0
- package/src/accounts.mjs +119 -0
- package/src/adapters/agy.mjs +56 -0
- package/src/adapters/claude.mjs +54 -0
- package/src/adapters/codex.mjs +73 -0
- package/src/adapters/common.mjs +32 -0
- package/src/adapters/fake.mjs +48 -0
- package/src/adapters/grok.mjs +51 -0
- package/src/adapters/index.mjs +27 -0
- package/src/adapters/resolve.mjs +40 -0
- package/src/attach.mjs +592 -0
- package/src/auth.mjs +85 -0
- package/src/board/board.css +543 -0
- package/src/board/board.js +1307 -0
- package/src/board/favicon.svg +1 -0
- package/src/board/floor.html +127 -0
- package/src/board/floor.js +670 -0
- package/src/board/fonts/OFL-atkinson-hyperlegible-next.txt +93 -0
- package/src/board/fonts/OFL-azeret-mono.txt +94 -0
- package/src/board/fonts/atkinson-board.woff2 +0 -0
- package/src/board/fonts/azeret-board.woff2 +0 -0
- package/src/board/index.html +251 -0
- package/src/board/sessions.js +1451 -0
- package/src/bundle.mjs +103 -0
- package/src/cards.mjs +114 -0
- package/src/chain.mjs +264 -0
- package/src/commands.mjs +79 -0
- package/src/contract.mjs +73 -0
- package/src/env.mjs +19 -0
- package/src/fsx.mjs +72 -0
- package/src/git-snapshot.mjs +152 -0
- package/src/handoff.mjs +171 -0
- package/src/hook.mjs +49 -0
- package/src/land.mjs +106 -0
- package/src/launcher.mjs +272 -0
- package/src/leases.mjs +86 -0
- package/src/ledger.mjs +369 -0
- package/src/license.mjs +168 -0
- package/src/limits.mjs +97 -0
- package/src/live-capture.mjs +69 -0
- package/src/mergequeue.mjs +225 -0
- package/src/orchestrator.mjs +365 -0
- package/src/pipeline.mjs +100 -0
- package/src/preferences.mjs +47 -0
- package/src/presets.mjs +23 -0
- package/src/ratelimit.mjs +49 -0
- package/src/redact.mjs +47 -0
- package/src/resume.mjs +354 -0
- package/src/runner.mjs +438 -0
- package/src/scheduler.mjs +118 -0
- package/src/server.mjs +845 -0
- package/src/session-detail.mjs +129 -0
- package/src/sessions.mjs +202 -0
- package/src/share.mjs +163 -0
- package/src/stations/agent.mjs +42 -0
- package/src/stations/build.mjs +9 -0
- package/src/stations/human.mjs +5 -0
- package/src/stations/land.mjs +6 -0
- package/src/stations/plan.mjs +8 -0
- package/src/stations/pr.mjs +31 -0
- package/src/stations/review.mjs +8 -0
- package/src/stations/test.mjs +27 -0
- package/src/store.mjs +90 -0
- package/src/sync/dashclaw.mjs +70 -0
- package/src/sync/index.mjs +54 -0
- package/src/sync/workboard.mjs +54 -0
- package/src/taps/agy.mjs +64 -0
- package/src/taps/claude-usage.mjs +67 -0
- package/src/taps/claude.mjs +170 -0
- package/src/taps/codex.mjs +286 -0
- package/src/trust.mjs +323 -0
- package/src/usage.mjs +179 -0
- package/src/wait.mjs +30 -0
- package/src/worktree.mjs +207 -0
package/docs/faq.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# FAQ
|
|
2
|
+
|
|
3
|
+
Real questions, short answers, sourced from the code and the other docs in
|
|
4
|
+
this directory.
|
|
5
|
+
|
|
6
|
+
**Why subscription CLIs only, never a per-token API?**
|
|
7
|
+
Leg spawns each CLI's own logged-in session (`claude`, `codex`, `agy`).
|
|
8
|
+
Every child process has `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`,
|
|
9
|
+
`ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS`, `OPENAI_API_KEY`,
|
|
10
|
+
`OPENAI_BASE_URL`, `OPENAI_API_BASE`, `GEMINI_API_KEY`, `GOOGLE_API_KEY`,
|
|
11
|
+
`GOOGLE_GEMINI_BASE_URL`, `GOOGLE_GENAI_USE_VERTEXAI`,
|
|
12
|
+
`GOOGLE_GENAI_USE_ENTERPRISE`, `GOOGLE_CLOUD_PROJECT`,
|
|
13
|
+
`GOOGLE_CLOUD_LOCATION`, `GOOGLE_APPLICATION_CREDENTIALS`, `CLAUDECODE`,
|
|
14
|
+
`CLAUDE_CODE_*`, `CLAUDE_EFFORT`, and `CLAUDE_PLUGIN_DATA` stripped out
|
|
15
|
+
(`src/env.mjs`), then gets `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0` for a
|
|
16
|
+
detached Claude print session. A stray API key in your shell cannot silently
|
|
17
|
+
take over billing or shadow the subscription login. If stderr ever says
|
|
18
|
+
"another auth source is set", that leg is classified `auth_failed`, a failed
|
|
19
|
+
launch, and does not count as a usage limit.
|
|
20
|
+
|
|
21
|
+
**Why does Leg poll an endpoint for claude's usage instead of reading the
|
|
22
|
+
status line?**
|
|
23
|
+
Because Claude Code 2.1.268 did not run a custom status line from a settings
|
|
24
|
+
file Leg controls when this was tried on 2026-09-11 (recorded in
|
|
25
|
+
[DEVIATIONS.md](DEVIATIONS.md)); hooks from the same `--settings` file did
|
|
26
|
+
fire. So the numbers come from
|
|
27
|
+
`GET api.anthropic.com/api/oauth/usage` with the login Claude Code already
|
|
28
|
+
stored, which is the same data `/usage` shows. Leg still writes the
|
|
29
|
+
`statusLine` entry, so the endpoint poll becomes a fallback the moment a build
|
|
30
|
+
honours it, and your own status-line command is chained first either way. See
|
|
31
|
+
[adapters.md](adapters.md#claude) and `src/taps/claude-usage.mjs`.
|
|
32
|
+
|
|
33
|
+
**Why does codex get no hook when claude does?**
|
|
34
|
+
Because injecting one would put a prompt in your way. codex asks you to review
|
|
35
|
+
new hooks before it runs them, so a hook per Leg session would mean a
|
|
36
|
+
review prompt per Leg session. It is not needed: an interactive codex writes
|
|
37
|
+
the whole thread to a rollout file under `~/.codex/sessions/YYYY/MM/DD/`, and
|
|
38
|
+
flushes it per event. Leg finds the rollout whose `session_meta` cwd is the
|
|
39
|
+
session's directory and tails it for the rate limits, the prompts and the
|
|
40
|
+
edited files.
|
|
41
|
+
|
|
42
|
+
**Why does agy show no percentage?**
|
|
43
|
+
Because agy exposes none. agy 1.2.0 is a closed Go binary; its own status line
|
|
44
|
+
fetches a quota summary from the backend and writes it nowhere on disk. The
|
|
45
|
+
board shows "no % from agy" rather than an empty bar. The wall itself is still
|
|
46
|
+
caught: Leg passes `--log-file` per session and watches for
|
|
47
|
+
`RESOURCE_EXHAUSTED`, "it resets in …" and "out of quota". Those strings are
|
|
48
|
+
present in `agy.exe`. A real `RESOURCE_EXHAUSTED` wall was caught live on
|
|
49
|
+
2026-09-11 and handed the session off; the fixture in `fixtures/limits/agy/`
|
|
50
|
+
is still tagged docs-only in [cli-contracts.md](cli-contracts.md#agy-tap)
|
|
51
|
+
because the payload itself was never captured to `fixtures/live/agy/`.
|
|
52
|
+
|
|
53
|
+
**Am I allowed to add a second account?**
|
|
54
|
+
That is your call, and the terms are quoted in full in the README under
|
|
55
|
+
"Second accounts, and what the terms say" (`leg accounts terms` prints the
|
|
56
|
+
same summary). The short version: owning two paid subscriptions is not named
|
|
57
|
+
as prohibited by Anthropic or OpenAI, but rotating to a second account of the
|
|
58
|
+
same vendor because the first is rate-limited sits close to OpenAI's
|
|
59
|
+
"circumvent any rate limits" wording and Anthropic's "circumvent product
|
|
60
|
+
guardrails". Leg's default chain switches vendors (claude, codex, agy),
|
|
61
|
+
which is plainly fine. Same-vendor rotation only happens after you run
|
|
62
|
+
`leg accounts add`.
|
|
63
|
+
|
|
64
|
+
**What does `leg uninstall` remove?**
|
|
65
|
+
`~/.leg` and nothing else: sessions, usage files, the extra account
|
|
66
|
+
directories with their junctions, the v0.1 cards and runs, and the board
|
|
67
|
+
pidfile. It removes the junctions as links, never following them into your
|
|
68
|
+
real `~/.claude` or `~/.codex`. It does not touch any file of yours, any repo,
|
|
69
|
+
or the agent CLIs themselves. Run `leg uninstall` with no flag to print what
|
|
70
|
+
would go, `--yes` to do it; then `npm rm -g legcli` if you want the
|
|
71
|
+
package gone too.
|
|
72
|
+
|
|
73
|
+
**Can I run `leg claude` inside a Claude Code shell?**
|
|
74
|
+
Yes. A parent Claude Code session exports `CLAUDECODE` and `CLAUDE_CODE_*`
|
|
75
|
+
markers that make a nested Claude refuse to start; `sanitizeEnv`
|
|
76
|
+
(`src/env.mjs`) strips them along with the API-key variables, so the child
|
|
77
|
+
starts normally. It becomes its own session with its own card on the board,
|
|
78
|
+
unrelated to the parent's.
|
|
79
|
+
|
|
80
|
+
**Why are `--dangerously-skip-permissions` and similar flags never
|
|
81
|
+
available?**
|
|
82
|
+
Each adapter's `argv()` calls `assertAllowed()`
|
|
83
|
+
(`src/adapters/common.mjs`), which checks the requested mode against that
|
|
84
|
+
adapter's `allowed` list and rejects any flag on its `forbiddenFlags` list,
|
|
85
|
+
before anything spawns. Requesting `bypassPermissions`, `--yolo`,
|
|
86
|
+
`--full-auto`, `danger-full-access`, or similar throws immediately; nothing
|
|
87
|
+
ever runs with permission checks off.
|
|
88
|
+
|
|
89
|
+
**What actually happens when a pipeline leg hits a usage limit?**
|
|
90
|
+
(For an interactive session, see [concepts.md](concepts.md#handoff-interactive).)
|
|
91
|
+
`src/limits.mjs` classifies the leg's exit code, output and diff evidence as
|
|
92
|
+
`limit`. Leg writes a handoff bundle in the same worktree (task, done so
|
|
93
|
+
far, the diff, open findings) via `context-handoff-bundle`, then starts the
|
|
94
|
+
next adapter in that station's chain from the bundle's resume text. If the
|
|
95
|
+
chain has no next adapter, the card fails. See
|
|
96
|
+
[concepts.md](concepts.md#outcomes-and-the-classifier).
|
|
97
|
+
|
|
98
|
+
**Can two agents edit the same files at the same time?**
|
|
99
|
+
Not inside one card: a station runs one chain leg at a time. Across cards,
|
|
100
|
+
the scheduler only starts a queued card when its declared leases (path
|
|
101
|
+
globs) do not overlap any running card's leases (`src/leases.mjs`); the
|
|
102
|
+
default lease is `**` (the whole repo), so two cards with no leases set
|
|
103
|
+
always serialize. The overlap check is a deliberate approximation biased
|
|
104
|
+
toward false positives: an unnecessary serialization costs minutes, a
|
|
105
|
+
wrongly parallel card can corrupt a merge.
|
|
106
|
+
|
|
107
|
+
For terminals: a second `leg <agent>` in a checkout that already has a live
|
|
108
|
+
session gets its own worktree and branch, so the two never write over each
|
|
109
|
+
other's files. Whoever presses Land first fast-forwards trunk; the second one
|
|
110
|
+
rebases onto it, or bounces with the conflicting files named on the card.
|
|
111
|
+
`--no-worktree` shares the checkout when that is what you want.
|
|
112
|
+
|
|
113
|
+
**Can someone else watch my board?**
|
|
114
|
+
Only if you run `leg share on`, which is off by default. It binds your
|
|
115
|
+
Tailscale or LAN address and prints one link per human (`leg share add
|
|
116
|
+
<name>`), each with its own token. A guest sees the terminals lane read-only
|
|
117
|
+
and nothing a terminal has said, read or written: no prompt, no file names, no
|
|
118
|
+
paths, no bundle, no events, no logs, and none of the pipeline side. The
|
|
119
|
+
limit line keeps only the reason and the reset time, never the raw limit
|
|
120
|
+
text. The one thing they can do on your terminal is ask for a hand-off, which
|
|
121
|
+
you approve or dismiss on the card. `leg share off` ends it and every link
|
|
122
|
+
stops working. There is no TLS, so keep it on Tailscale or a network you trust.
|
|
123
|
+
|
|
124
|
+
**What if the CLI I want isn't installed?**
|
|
125
|
+
`node bin/leg.mjs up --dry` prints a preflight table with `ok`/`missing`
|
|
126
|
+
per adapter. A missing adapter only matters if a card's chain names it: that
|
|
127
|
+
leg fails to launch (`launch_failed`), which does not advance the chain,
|
|
128
|
+
install and log in, then press Rerun. A chain only needs the adapters it
|
|
129
|
+
names; leave a missing one out.
|
|
130
|
+
|
|
131
|
+
**How do I resume after a reboot or a crash?**
|
|
132
|
+
Every run's `run.json` under `$LEG_HOME/cards/<id>/runs/<n>/` is the
|
|
133
|
+
source of truth, not process memory. `leg down` kills active agents but
|
|
134
|
+
each run's supervisor still writes its final verdict; the next `leg up`
|
|
135
|
+
(or the scheduler) finds any unsettled run with no live driver and
|
|
136
|
+
re-attaches to apply that verdict, logging a `re-attached to run N` event.
|
|
137
|
+
Nothing manual to do beyond starting Leg again.
|
|
138
|
+
|
|
139
|
+
**Does Leg push to GitHub, or open pull requests?**
|
|
140
|
+
No. The default `land_mode: ff` only rebases and fast-forwards the local
|
|
141
|
+
trunk branch inside the repo you gave it; there is no remote write anywhere
|
|
142
|
+
in `src/worktree.mjs` or `src/mergequeue.mjs`. `land_mode: pr` builds a real
|
|
143
|
+
`gh pr create` argv but is stub-only: it refuses to run unless
|
|
144
|
+
`LEG_GH_BIN` points at a real `gh` (or a test stub), and even then it
|
|
145
|
+
never pushes or creates a remote for you, that is out of scope for this
|
|
146
|
+
build.
|
|
147
|
+
|
|
148
|
+
**Is there a hosted version of Leg?**
|
|
149
|
+
No. Leg is local-first: the board binds `127.0.0.1` by default, every
|
|
150
|
+
card's state lives in files under `LEG_HOME`, and there is no service to
|
|
151
|
+
sign into. See [configuration.md](configuration.md#network-exposure) for
|
|
152
|
+
what changes if you deliberately bind it to a shared address.
|
|
153
|
+
|
|
154
|
+
**How are secrets handled?**
|
|
155
|
+
`src/redact.mjs` holds one pattern list (API keys, bearer tokens, GitHub and
|
|
156
|
+
AWS tokens, `key=value` secrets); `scrub()` rewrites matches to
|
|
157
|
+
`[REDACTED]` in every log line the launcher prints and in handoff bundle
|
|
158
|
+
text, and the ledger's own writer refuses to record an event whose summary
|
|
159
|
+
or body matches a secret pattern at all. Every adapter's `env()` strips the
|
|
160
|
+
API-key/base-URL variables from the child process. `npm test` and the
|
|
161
|
+
pre-commit hook both run `scripts/privacy-check.mjs`, which additionally
|
|
162
|
+
scans the whole tree for a short list of strings specific to the private
|
|
163
|
+
codebase Leg's runner/ledger were ported from.
|
|
164
|
+
|
|
165
|
+
**What happens if I close the terminal instead of Ctrl-C?**
|
|
166
|
+
The board server and any running agents keep running as detached processes.
|
|
167
|
+
Run `node bin/leg.mjs down` (or `npm run stop`) from another terminal to
|
|
168
|
+
stop them cleanly, or just start `leg up` again later: it re-attaches to
|
|
169
|
+
any run left in progress rather than launching a duplicate.
|
|
170
|
+
|
|
171
|
+
**Can I run a card without the board?**
|
|
172
|
+
Yes: `node bin/leg.mjs card run <card-id>` drives one card through the
|
|
173
|
+
orchestrator directly and exits when it reaches a waiting or terminal
|
|
174
|
+
state, printing the final status. `node bin/leg.mjs card show <id>` and
|
|
175
|
+
`card events <id>` work without the server running too, since they read the
|
|
176
|
+
same on-disk ledger the board reads.
|
|
177
|
+
|
|
178
|
+
**What does `LEG_NO_SCHEDULER=1` do, and why would I set it?**
|
|
179
|
+
It boots the board server without its embedded scheduler, so no queued card
|
|
180
|
+
starts automatically, useful when you want to drive every card by hand
|
|
181
|
+
with `card run` (for example, inside a test) while still watching it on the
|
|
182
|
+
board.
|
|
183
|
+
|
|
184
|
+
**Is there a limit to how long a chain can be, or how many legs a card can
|
|
185
|
+
take?**
|
|
186
|
+
No fixed limit; the chain array can be as long as you like. A card fails
|
|
187
|
+
only when a leg's outcome needs to hand off and the chain has no next entry
|
|
188
|
+
left, or (separately) after `LEG_MAX_LAND_ATTEMPTS` test/land bounces.
|
|
189
|
+
|
|
190
|
+
**Windows vs macOS/Linux: what's actually verified?**
|
|
191
|
+
Built and tested on Windows (every worktree, git and taskkill path in the
|
|
192
|
+
source has Windows-specific handling: `MSYS_NO_PATHCONV=1`, `taskkill /T
|
|
193
|
+
/F`, native `.exe` resolution). CI (`.github/workflows/ci.yml`) runs the
|
|
194
|
+
full test suite on both `ubuntu-latest` and `windows-latest` on every push
|
|
195
|
+
and pull request.
|
|
196
|
+
|
|
197
|
+
**How do I report a bug?**
|
|
198
|
+
Open a GitHub issue using the bug report template
|
|
199
|
+
(`.github/ISSUE_TEMPLATE/bug_report.md`). For a security vulnerability, do
|
|
200
|
+
not open a public issue, see [SECURITY.md](../SECURITY.md) instead.
|
|
201
|
+
|
|
202
|
+
**Where do I find the exact word Leg uses for a given status, event, or
|
|
203
|
+
button?**
|
|
204
|
+
[VOCABULARY.md](VOCABULARY.md): one table per category (statuses, outcomes,
|
|
205
|
+
station kinds, event types, actor types, human actions, bounce reasons),
|
|
206
|
+
pulled straight from the source identifiers.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Getting started
|
|
2
|
+
|
|
3
|
+
For a developer setting up Leg for the first time on their own machine. The
|
|
4
|
+
first four sections get an interactive agent running with the board alongside
|
|
5
|
+
it. The v0.1 pipeline flow is section 7 onward.
|
|
6
|
+
|
|
7
|
+
## 1. Prerequisites
|
|
8
|
+
|
|
9
|
+
- Node 22 or newer (`package.json` sets `"engines": {"node": ">=22"}`).
|
|
10
|
+
- git.
|
|
11
|
+
- Python 3 with pip, for the `context-handoff-bundle` CLI. This is what writes
|
|
12
|
+
and reads the handoff bundles.
|
|
13
|
+
- At least one coding-agent CLI, logged in: `claude`, `codex` or `agy`. You do
|
|
14
|
+
not need all three. With one installed you get the board and the usage
|
|
15
|
+
tracking; with two or more you also get the hand-off.
|
|
16
|
+
|
|
17
|
+
## 2. Install
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
npm install -g @ucsandman/legcli
|
|
21
|
+
pip install -U context-handoff-bundle
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The source repository is private, so there is no clone to install from. The
|
|
25
|
+
source you run ships in the package: `$(npm root -g)/legcli/src`, plain
|
|
26
|
+
`.mjs`, nothing bundled. Read it before you trust it.
|
|
27
|
+
|
|
28
|
+
## 3. Run an agent
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
cd <any repo>
|
|
32
|
+
leg claude
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That is the whole setup. `leg claude` runs the real Claude Code in this
|
|
36
|
+
terminal with your own settings, hooks and skills. Anything after the agent
|
|
37
|
+
name passes straight through:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
leg claude --model haiku
|
|
41
|
+
leg codex -m gpt-5.3-codex-spark
|
|
42
|
+
leg agy
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The first `leg <agent>` starts the board on http://127.0.0.1:4747 and opens
|
|
46
|
+
it once in your browser. Later sessions reuse the same board. Set
|
|
47
|
+
`LEG_NO_OPEN=1` to skip the browser.
|
|
48
|
+
|
|
49
|
+
Leg strips `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`,
|
|
50
|
+
`ANTHROPIC_BASE_URL`, `ANTHROPIC_CUSTOM_HEADERS`, `OPENAI_API_KEY`,
|
|
51
|
+
`OPENAI_BASE_URL`, `OPENAI_API_BASE`, `GEMINI_API_KEY`, `GOOGLE_API_KEY`,
|
|
52
|
+
`GOOGLE_GEMINI_BASE_URL`, `GOOGLE_GENAI_USE_VERTEXAI`,
|
|
53
|
+
`GOOGLE_GENAI_USE_ENTERPRISE`, `GOOGLE_CLOUD_PROJECT`,
|
|
54
|
+
`GOOGLE_CLOUD_LOCATION`, `GOOGLE_APPLICATION_CREDENTIALS`, `CLAUDECODE`,
|
|
55
|
+
`CLAUDE_CODE_*`, `CLAUDE_EFFORT`, and `CLAUDE_PLUGIN_DATA` from the agent's
|
|
56
|
+
environment, then sets `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0` for a detached
|
|
57
|
+
Claude print session. The subscription login is always what runs. Nothing in
|
|
58
|
+
`~/.claude`, `~/.codex` or agy's home is edited.
|
|
59
|
+
|
|
60
|
+
## 4. What you get while it runs
|
|
61
|
+
|
|
62
|
+
- A card on the board for this terminal: agent, account, repo@branch, the first
|
|
63
|
+
prompt, turns, the files being touched, 5h and 7d usage.
|
|
64
|
+
- A warning at 85 % of either window (`LEG_WARN_PCT`): amber card, an event,
|
|
65
|
+
one terminal bell.
|
|
66
|
+
- For Codex, the board and active attach poll the read-only app-server
|
|
67
|
+
`account/rateLimits/read` response every 60 seconds. Leg maps the returned
|
|
68
|
+
300- and 10080-minute durations to 5h and 7d, shows `<n>% used`, and labels
|
|
69
|
+
old readings stale. Only an explicit backend available answer clears a prior
|
|
70
|
+
wall; no model turn or hardcoded quota is used.
|
|
71
|
+
- A `context-handoff-bundle` for this session, refreshed every two minutes and
|
|
72
|
+
at every warning, limit and hand-off.
|
|
73
|
+
- At the limit: the bundle is saved, the agent is stopped, and the next option
|
|
74
|
+
starts in the same terminal from `.leg/RESUME-<session-id>.md`, copied to
|
|
75
|
+
`.leg/RESUME.md`. Order is other logins of
|
|
76
|
+
the same agent first, then the remaining agents (claude, codex, agy). When
|
|
77
|
+
every option is out, Leg prints each reset time, soonest first, waits with
|
|
78
|
+
a countdown, and starts the first one back from the bundle. Ctrl-C quits.
|
|
79
|
+
|
|
80
|
+
Force a hand-off at any time with the **Hand off now** button on the card, or
|
|
81
|
+
`leg sessions handoff <id>`.
|
|
82
|
+
|
|
83
|
+
## Is the resume file still true?
|
|
84
|
+
|
|
85
|
+
```powershell
|
|
86
|
+
leg resume --check # exit 0 current, 1 stale or unstamped, 3 none here
|
|
87
|
+
leg resume # the same verdict, then the pointer itself
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Leg stamps every resume file with the commit, the working tree and the
|
|
91
|
+
terminals it was written against, and recomputes freshness from git when you
|
|
92
|
+
read it. A commit landing, the tree moving, or the terminal it described going
|
|
93
|
+
away all make it stale; the terminal card's drawer shows the same verdict under
|
|
94
|
+
"What happens next". A session ending rewrites `RESUME.md` to say nothing is in
|
|
95
|
+
flight, and the board does the same at start for a terminal that crashed, so
|
|
96
|
+
nothing is left describing work that has moved on.
|
|
97
|
+
|
|
98
|
+
## 5. Watching and steering from the CLI
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
leg sessions ls every session and its usage
|
|
102
|
+
leg sessions show <id>
|
|
103
|
+
leg sessions events <id>
|
|
104
|
+
leg sessions handoff <id> same as the board button
|
|
105
|
+
leg sessions end <id>
|
|
106
|
+
leg sessions rm <id> forget an ended session
|
|
107
|
+
leg sessions simulate-limit <id> drive the real limit path without a real wall (claude, agy)
|
|
108
|
+
leg accounts ls logins and their 5h/7d usage
|
|
109
|
+
leg open | down | status the board
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
On the board, **Remove** safely prunes an ended session only when its worktree
|
|
113
|
+
is clean and its branch is already merged into the base. For a session that
|
|
114
|
+
needs to be removed from the board while preserving its work, use the visible
|
|
115
|
+
**Remove record** button, read its confirmation, and keep the worktree, branch,
|
|
116
|
+
unmerged commits, and dirty files intact.
|
|
117
|
+
|
|
118
|
+
## 6. Where files live
|
|
119
|
+
|
|
120
|
+
Everything Leg writes goes under `LEG_HOME` (default `~/.leg`):
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
~/.leg/
|
|
124
|
+
sessions/<session-id>/
|
|
125
|
+
session.json the live record the board renders
|
|
126
|
+
events.jsonl the timeline
|
|
127
|
+
control.json board to runner requests
|
|
128
|
+
hook.log what Claude Code's hooks sent
|
|
129
|
+
claude-settings.json the per-session --settings file
|
|
130
|
+
agy.log agy's --log-file, agy sessions only
|
|
131
|
+
usage/<agent>--<account>.json
|
|
132
|
+
accounts/<agent>/<name>/ extra logins (see configuration.md)
|
|
133
|
+
landings.jsonl every Land, for the landed-on-trunk list
|
|
134
|
+
board.log the board server's output
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
In the repo you run in, Leg writes `.leg/` (session notes, `RESUME.md` and
|
|
138
|
+
one `RESUME-<session-id>.md` per hand-off) and `.context-handoffs/` (the
|
|
139
|
+
bundles). Both are added to `.git/info/exclude`, so they never show up in
|
|
140
|
+
`git status`.
|
|
141
|
+
|
|
142
|
+
`leg uninstall --yes` removes `~/.leg` and nothing else.
|
|
143
|
+
|
|
144
|
+
## 7. Pipelines (extras)
|
|
145
|
+
|
|
146
|
+
Version 0.1 worked the other way round: you dropped a task card on the board
|
|
147
|
+
and Leg ran the agents headless in a git worktree, one per card, with a
|
|
148
|
+
fallback chain, path leases, a scheduler and a merge queue. All of that still
|
|
149
|
+
works and lives below the Terminals lane. It is no longer the way in.
|
|
150
|
+
|
|
151
|
+
### Preflight
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
leg up --dry
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
One row per dependency (node, git, `context-handoff-bundle`, each registered
|
|
158
|
+
adapter) with `ok` or `missing`, then exit without spawning anything. A
|
|
159
|
+
`missing` adapter only matters if a card's chain names it.
|
|
160
|
+
|
|
161
|
+
### Start the board with the scheduler
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
npm start
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`npm start` runs the same preflight, boots the board server on
|
|
168
|
+
`http://127.0.0.1:4747`, opens it, and streams prefixed, redacted logs. Ctrl-C
|
|
169
|
+
stops the server and any agent it started. From another terminal: `npm run
|
|
170
|
+
stop` (which runs `leg down`).
|
|
171
|
+
|
|
172
|
+
### A card from the board
|
|
173
|
+
|
|
174
|
+
Click **New card**. The form asks for:
|
|
175
|
+
|
|
176
|
+
- **Repo path**: an absolute path to a local git repository (at least one
|
|
177
|
+
commit).
|
|
178
|
+
- **Task**: the text prompt every agent leg gets.
|
|
179
|
+
- **Pipeline**: `factory`, `build`, `build-land`, or `custom JSON`.
|
|
180
|
+
- **Chain**: one row per adapter, in fallback order.
|
|
181
|
+
- **Leases** (comma separated path globs), **Trunk branch** (default `main`),
|
|
182
|
+
**Land mode** (`ff` or `pr`), **Test command**, **Title**.
|
|
183
|
+
- **Queue immediately**: leave it checked to start the card right away.
|
|
184
|
+
|
|
185
|
+
### A card from the CLI
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
leg card add --repo <path-to-a-git-repo> --task "Add a LICENSE file" --chain claude --queue
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Other flags `card add` accepts: `--pipeline <preset|file>`, `--mode
|
|
192
|
+
<adapter>=<mode>`, `--max-turns <adapter>=<n>`, `--leases <glob,glob>`,
|
|
193
|
+
`--trunk <branch>`, `--land-mode ff|pr`, `--test-command "<cmd>"`, `--title
|
|
194
|
+
"<text>"`, `--slug <id>`, `--approve <adapter,...>`.
|
|
195
|
+
|
|
196
|
+
The command prints the new card id. Show it, or run it directly:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
leg card show <card-id>
|
|
200
|
+
leg card run <card-id>
|
|
201
|
+
leg card events <card-id>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Try a pipeline with no real agent
|
|
205
|
+
|
|
206
|
+
The `fake` adapter drives `bin/fake-agent.mjs`, a stand-in CLI, so you can see
|
|
207
|
+
a full run without a subscription login:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
leg card add --repo <path-to-a-git-repo> --task "demo" --chain fake --fake-mode fake=limit --queue
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`FAKE_MODE` (set per adapter with `--fake-mode <adapter>=<mode>`) picks its
|
|
214
|
+
behaviour: `success`, `incomplete`, `limit`, `stall`, `auth`, `crash`,
|
|
215
|
+
`no_progress`, and the land-demo modes `break-test` / `fix-test` /
|
|
216
|
+
`resolve-rebase`. A chain of two fake adapters (`--chain
|
|
217
|
+
fake-claude,fake-codex --fake-mode "fake-claude=limit,fake-codex=success"`)
|
|
218
|
+
shows a full handoff; see [DEMO.md](DEMO.md) for the walkthrough with
|
|
219
|
+
screenshots.
|
|
220
|
+
|
|
221
|
+
### Where a card's files live
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
~/.leg/
|
|
225
|
+
cards/<card-id>/
|
|
226
|
+
card.json the card's current state
|
|
227
|
+
events-<actor-key>.jsonl one append-only file per writer
|
|
228
|
+
driver.lock the process driving the card, only while it runs
|
|
229
|
+
unsynced.jsonl DashClaw records to retry (`ledger sync`), if any
|
|
230
|
+
runs/<n>/
|
|
231
|
+
run.json this run's status, outcome, exit code
|
|
232
|
+
prompt.txt the exact prompt sent to the adapter
|
|
233
|
+
out.log the adapter's stdout
|
|
234
|
+
err.log the adapter's stderr
|
|
235
|
+
supervisor.log the runner's own log for this run
|
|
236
|
+
locks/land-<hash>.json the merge queue's turn, one per repo root, while a land runs
|
|
237
|
+
ACTIVE.md generated summary of open cards
|
|
238
|
+
leg.pid written by `leg up`, removed on stop
|
|
239
|
+
scheduler.pid written by `leg scheduler start`
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
A card's own git worktree lives in the repo it targets, not under
|
|
243
|
+
`LEG_HOME`: `<repo>/.leg-worktrees/<card-id>` on branch `leg/<card-id>`.
|
|
244
|
+
|
|
245
|
+
## Next
|
|
246
|
+
|
|
247
|
+
- [concepts.md](concepts.md): sessions, accounts, usage windows, the
|
|
248
|
+
interactive hand-off, then cards, stations, chains, leases and the land
|
|
249
|
+
station.
|
|
250
|
+
- [board-guide.md](board-guide.md): every element of the board.
|
|
251
|
+
- [configuration.md](configuration.md): every environment variable.
|
package/docs/real-run.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Real run: claude → codex, no fakes
|
|
2
|
+
|
|
3
|
+
One task, two real logged-in CLIs, the real handoff path. Run on 2026-09-10 on
|
|
4
|
+
a scratch repo (`toy-real`: README, package.json with `"test": "node --test"`,
|
|
5
|
+
one commit). Evidence: `fixtures/real-run/` (run records, logs, bundle `show`,
|
|
6
|
+
events; home paths scrubbed to `~`).
|
|
7
|
+
|
|
8
|
+
## Command
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
node bin/leg.mjs card add --repo <toy-real> --chain claude,codex --max-turns claude=2 --mode codex=workspace-write --pipeline build --title "calc module (real run)" --task "Create src/calc.mjs exporting add, sub, mul and div (div throws on division by zero). Create test/calc.test.mjs using node:test covering all four functions including the divide-by-zero case. Run node --test and fix until green. Then write .leg/DONE."
|
|
12
|
+
node bin/leg.mjs card run card-20260911-0232-real-calc
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`--max-turns claude=2` is the handoff trigger: the task needs more than two
|
|
16
|
+
turns, so the first leg ends before the DONE marker.
|
|
17
|
+
|
|
18
|
+
## Timeline (UTC, from `fixtures/real-run/events.jsonl`)
|
|
19
|
+
|
|
20
|
+
| time | event | detail |
|
|
21
|
+
|------|-------|--------|
|
|
22
|
+
| 02:32:50 | card_created | actor human:local, chain claude > codex |
|
|
23
|
+
| 02:32:51 | leg_started | adapter `claude`, run 1, `mode=default`, `--max-turns 2` |
|
|
24
|
+
| 02:33:23 | leg_exited | **outcome `failed`**, exit 1, signal `claude-max-turns` (32 s) |
|
|
25
|
+
| 02:33:24 | handoff_written | bundle `20260911-023324-baton-card-20260911-0232-real-calc-build-leg0`, quality **strong (0.65)** |
|
|
26
|
+
| 02:33:25 | leg_started | adapter `codex`, run 2, `-s workspace-write` |
|
|
27
|
+
| 02:35:39 | leg_exited | **outcome `completed`**, exit 0 (2 m 14 s) |
|
|
28
|
+
| 02:35:40 | station_done, done | |
|
|
29
|
+
|
|
30
|
+
Wall time: 2 m 50 s. `grep -c fake fixtures/real-run/events.jsonl` = 0.
|
|
31
|
+
|
|
32
|
+
## Leg 1 (claude)
|
|
33
|
+
|
|
34
|
+
The result JSON on stdout (`fixtures/real-run/leg1/out.log`) is the signal that
|
|
35
|
+
was docs-only until this run and is now observed-live:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
{"type":"result","subtype":"error_max_turns","is_error":true,"stop_reason":"tool_use","terminal_reason":"max_turns","num_turns":3, …}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Exit code 1, empty stderr, no `.leg/DONE`; the diff evidence saw one changed
|
|
42
|
+
path (`.dashclaw-local/`, written by the machine's Claude Code hooks, not the
|
|
43
|
+
task). Classification: `budget` signal `claude-max-turns` + non-zero exit →
|
|
44
|
+
`failed`, handoff. (A budget cap is Leg's own setting, not a usage limit; it
|
|
45
|
+
still hands off.)
|
|
46
|
+
|
|
47
|
+
## The bundle
|
|
48
|
+
|
|
49
|
+
`fixtures/real-run/bundle-show.txt`: Findings carry "Done so far", "Diff since
|
|
50
|
+
leg start", the touched path; Open questions carry the outcome and exit code.
|
|
51
|
+
Leg 2 picked the handoff up: its first message is "Proceeding from the last
|
|
52
|
+
agent state" and its second tool call reads `.leg/CONTRACT.md` and the
|
|
53
|
+
handoff file (`fixtures/real-run/leg2/out.excerpt.log`, items 1-3).
|
|
54
|
+
|
|
55
|
+
## Leg 2 (codex)
|
|
56
|
+
|
|
57
|
+
`fixtures/real-run/leg2/out.excerpt.log` (JSONL): codex read the resume,
|
|
58
|
+
wrote `src/calc.mjs` and `test/calc.test.mjs`, ran the tests itself (its
|
|
59
|
+
sandbox allowed `node --test` here; LESSONS 07-13 recorded a policy block on a
|
|
60
|
+
different machine setup), wrote `.leg/PROGRESS.md` and `.leg/DONE`, and
|
|
61
|
+
printed a summary. Verified inside leg 2
|
|
62
|
+
(`fixtures/real-run/leg2/out.excerpt.log`, item 20), which ran the repo's
|
|
63
|
+
`npm test` -> `node --test` in the worktree:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
tests 5 pass 5 fail 0
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The worktree holds `src/calc.mjs`, `test/calc.test.mjs` and
|
|
70
|
+
`.leg/{CONTRACT.md, DONE, PROGRESS.md, handoff-build-leg0.md}`.
|
|
71
|
+
|
|
72
|
+
## What this proves
|
|
73
|
+
|
|
74
|
+
- The subscription logins were used: neither leg's stderr carries an
|
|
75
|
+
"another auth source" warning (`leg1/err.log` is empty, `leg2/err.log` is
|
|
76
|
+
one stdin notice), and `sanitizeEnv` strips ANTHROPIC_API_KEY and
|
|
77
|
+
OPENAI_API_KEY before any adapter starts (test/redact.test.mjs).
|
|
78
|
+
- The handoff is CLI-agnostic: the bundle written after a Claude leg was
|
|
79
|
+
consumed by a Codex leg through the same `load` + contract prompt.
|
|
80
|
+
- The DONE-marker contract classified both legs without parsing prose.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Review notes, 2026-09-14
|
|
2
|
+
|
|
3
|
+
This is a requested thorough-review deliverable. It records confirmed fixes
|
|
4
|
+
and the local evidence captured before publication. Current release outcomes
|
|
5
|
+
are tracked on npm; release contents are in the packaged `CHANGELOG.md`.
|
|
6
|
+
|
|
7
|
+
## Confirmed fixes
|
|
8
|
+
|
|
9
|
+
| area | confirmed behavior | source and regression references |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Codex quota | The board and an active Codex attach make a read-only app-server `account/rateLimits/read` request every 60 seconds. Durations identify windows: 300 minutes is 5h and 10080 minutes is 7d. An explicit available response, rather than a percentage, clears an old wall. | `src/server.mjs` `refreshCodexAccounts`, `src/attach.mjs` `refreshCodexUsage`, `src/taps/codex.mjs` `readCodexUsage`, `src/usage.mjs` `recordUsage`; `test/attach-core.test.mjs`, `test/server.test.mjs` |
|
|
12
|
+
| quota display | Account bars read `<n>% used`; readings older than five minutes are stale. No model turn and no hardcoded quota supply the reading. | `src/board/sessions.js`, `src/usage.mjs`; `test/server.test.mjs` |
|
|
13
|
+
| session removal | Normal removal is a safe prune. **Remove record** is a separate confirmed board action that preserves the worktree, branch, unmerged commits, and dirty files after its confirmation. | `src/board/sessions.js`, `src/server.mjs`, `src/worktree.mjs`; `test/server.test.mjs` |
|
|
14
|
+
| concurrent terminal control | End and Hand off write through the session file lock, preventing competing control-file updates. | `src/sessions.mjs` `requestControl`, `src/server.mjs`; `test/attach-core.test.mjs`, `test/attach-e2e.test.mjs` |
|
|
15
|
+
| landing | Landing rejects a wrong base branch, proves a branch is merged before safe pruning, and reruns tests after a retry rebase when trunk moves. | `src/land.mjs` `landSession` and `pruneSessionWorktree`, `src/mergequeue.mjs` `land`, `src/worktree.mjs`; `test/land.test.mjs`, `test/mergequeue.test.mjs`, `test/worktree.test.mjs` |
|
|
16
|
+
| card recovery | An orphaned run can recover a card left in `handing_off` instead of occupying the scheduler indefinitely. | `src/orchestrator.mjs` `orphanedRun`, `src/scheduler.mjs`, `src/runner.mjs`; `test/runner.test.mjs`, `test/scheduler.test.mjs` |
|
|
17
|
+
| board freshness | Guest-to-owner token changes restore pipeline controls; valid guest changes retain the terminal stream. Older card-list, drawer, log, Floor, and retired SSE responses cannot overwrite newer state. | `src/board/board.js`, `src/board/floor.js`; `test/board-updates.test.mjs` |
|
|
18
|
+
| loopback authorization | Tokenless owner access requires a loopback peer and a loopback hostname. An attacker-controlled hostname that resolves or rebinds to `127.0.0.1` is refused on reads and writes even when its `Origin` matches its `Host`. | `src/auth.mjs` `isLoopbackRequest`, `src/server.mjs`; `test/dns-rebinding.test.mjs` |
|
|
19
|
+
| Team refresh | A signed Team key is required for refresh. An expired signed key can refresh when its subscription remains active; this is subscription-level behavior, not individual-seat revocation. | `site/api/key.js`, `site/api/_lib.js`; `test/site-api.test.mjs`, `test/license.test.mjs` |
|
|
20
|
+
| webhook delivery | Stripe webhook verification accepts multiple rotated `v1` signatures. Resend is given a stable event-and-delivery idempotency key; Resend retains that key for 24 hours, so this is not a claim of permanent deduplication. | `site/api/webhook.js`, `site/api/_lib.js`; `test/site-api.test.mjs` |
|
|
21
|
+
|
|
22
|
+
## Evidence already observed
|
|
23
|
+
|
|
24
|
+
- The final independent `npm test` run exited 0: 391 tests, 390 passed, none
|
|
25
|
+
failed or cancelled, and one POSIX-permission test was skipped on Windows.
|
|
26
|
+
The privacy check reported 0 findings across 256 files. Final lint and
|
|
27
|
+
`git diff --check` both exited 0.
|
|
28
|
+
- The board-focused suite added deferred behavioral cases for guest/owner token
|
|
29
|
+
changes and stale card, drawer, log, and Floor responses. It reported 26
|
|
30
|
+
passing tests before this documentation change.
|
|
31
|
+
- A live Codex app-server probe returned a weekly-only window as `primary`,
|
|
32
|
+
confirming that positional primary-to-5h mapping was wrong. The check did
|
|
33
|
+
not start a model turn; account-specific readings are omitted here.
|
|
34
|
+
The same weekly-only fixture failed against an archived `HEAD` copy because
|
|
35
|
+
the old parser put the 10080-minute object in `five_hour`; it passes now with
|
|
36
|
+
`five_hour=null` and the object in `seven_day`.
|
|
37
|
+
- The DNS-rebinding regression failed against `HEAD` with `200 !== 401`, then
|
|
38
|
+
passed with the fix for both a read and a state-changing request. The focused
|
|
39
|
+
auth/share suite reported 35 passing tests.
|
|
40
|
+
- After a board-only restart, `/api/health` returned 200. Frontend verification
|
|
41
|
+
passed `/` and `/floor` with no console errors or failed requests; the live
|
|
42
|
+
Codex pill rendered the weekly percentage from the app-server source and no
|
|
43
|
+
5h value. The New card form opened and Escape dismissed it without creating
|
|
44
|
+
data.
|
|
45
|
+
- An independent security review passed the loopback authorization fix for the
|
|
46
|
+
normal API, SSE reauthorization, and the companion listener. Custom-host
|
|
47
|
+
bearer and share tokens remained valid while the attacker Host returned 401.
|
|
48
|
+
|
|
49
|
+
## Review conclusions and rejected findings
|
|
50
|
+
|
|
51
|
+
- Account labels expose intentional shared metadata, not an additional account
|
|
52
|
+
privacy defect.
|
|
53
|
+
- Removing a landing session after the landing operation completes is not a
|
|
54
|
+
concurrent-removal bug if the final verifier confirms the operation boundary.
|
|
55
|
+
- Resend retains an idempotency key for 24 hours while Stripe can retry live
|
|
56
|
+
webhooks for longer. A delayed retry can therefore resend the same license
|
|
57
|
+
email; it cannot create a duplicate charge or entitlement. Durable delivery
|
|
58
|
+
storage was not added because it would introduce new production state and
|
|
59
|
+
infrastructure beyond this local audit.
|
|
60
|
+
|
|
61
|
+
## Retro
|
|
62
|
+
|
|
63
|
+
Green baseline checks did not expose every race or operator-facing safety gap.
|
|
64
|
+
Inspecting actual records and running deferred behavioral reproductions did.
|
|
65
|
+
The permanent behavioral cases now cover the observed guest/owner and stale
|
|
66
|
+
response failures so they do not rely on a future review to be rediscovered.
|
|
67
|
+
|
|
68
|
+
## Deviation log
|
|
69
|
+
|
|
70
|
+
The approved test update required a small refresh-auth protocol change so a
|
|
71
|
+
valid guest token reconnects the sessions stream while pipeline controls remain
|
|
72
|
+
hidden.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|