@caddis/cli 0.4.51 → 0.4.53

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.
Files changed (100) hide show
  1. package/bundles/antigravity-plugin/CADDIS-GLOSSARY.md +1 -0
  2. package/bundles/antigravity-plugin/plugin.json +1 -1
  3. package/bundles/antigravity-plugin/repo_freshness.py +132 -5
  4. package/bundles/antigravity-plugin/scripts/caddis_checkout_guard.py +223 -0
  5. package/bundles/antigravity-plugin/scripts/caddis_docs_pr.py +284 -0
  6. package/bundles/antigravity-plugin/scripts/caddis_exit.py +2 -0
  7. package/bundles/antigravity-plugin/scripts/caddis_frontmatter.py +1 -0
  8. package/bundles/antigravity-plugin/scripts/caddis_gate.py +226 -195
  9. package/bundles/antigravity-plugin/scripts/caddis_glossary.py +3 -3
  10. package/bundles/antigravity-plugin/scripts/caddis_governor.py +48 -4
  11. package/bundles/antigravity-plugin/scripts/caddis_inventory.py +38 -0
  12. package/bundles/antigravity-plugin/scripts/caddis_lanes.py +294 -127
  13. package/bundles/antigravity-plugin/scripts/caddis_lineage_publish.py +458 -0
  14. package/bundles/antigravity-plugin/scripts/caddis_merge_chain.py +237 -0
  15. package/bundles/antigravity-plugin/scripts/caddis_notify.py +121 -0
  16. package/bundles/antigravity-plugin/scripts/caddis_prod_check.py +380 -0
  17. package/bundles/antigravity-plugin/scripts/caddis_redact.py +99 -0
  18. package/bundles/antigravity-plugin/scripts/caddis_relay_git.py +2 -2
  19. package/bundles/antigravity-plugin/scripts/caddis_review.py +1309 -0
  20. package/bundles/antigravity-plugin/scripts/caddis_roadmap.py +2056 -0
  21. package/bundles/antigravity-plugin/scripts/caddis_roadmap_run.py +2572 -0
  22. package/bundles/antigravity-plugin/scripts/caddis_routing.py +3 -2
  23. package/bundles/antigravity-plugin/scripts/caddis_rows.py +476 -0
  24. package/bundles/antigravity-plugin/scripts/caddis_spawn.py +8 -9
  25. package/bundles/antigravity-plugin/scripts/caddis_spend.py +415 -0
  26. package/bundles/antigravity-plugin/scripts/caddis_tidy.py +357 -20
  27. package/bundles/antigravity-plugin/scripts/caddis_todo.py +55 -0
  28. package/bundles/antigravity-plugin/scripts/caddis_workstreams.py +178 -19
  29. package/bundles/antigravity-plugin/scripts/caddis_worktree_close.py +349 -0
  30. package/bundles/antigravity-plugin/scripts/check_doc_coverage.py +0 -56
  31. package/bundles/antigravity-plugin/scripts/claudster_doctor.py +1 -1
  32. package/bundles/antigravity-plugin/scripts/enforcement_inventory.py +0 -1
  33. package/bundles/antigravity-plugin/scripts/oss_review.py +2 -2
  34. package/bundles/antigravity-plugin/scripts/roadmap-recheck.ps1 +89 -0
  35. package/bundles/antigravity-plugin/scripts/roadmap-run.ps1 +88 -0
  36. package/bundles/antigravity-plugin/scripts/setup_project_ai.py +86 -2
  37. package/bundles/antigravity-plugin/session_end_agy.py +3 -3
  38. package/bundles/antigravity-plugin/skills/_registry.md +2 -6
  39. package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +1 -1
  40. package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +1 -1
  41. package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +1 -1
  42. package/bundles/antigravity-plugin/skills/catchup/SKILL.md +7 -0
  43. package/bundles/antigravity-plugin/skills/cloud-phases/SKILL.md +1 -1
  44. package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +5 -2
  45. package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +29 -83
  46. package/bundles/antigravity-plugin/skills/handoff/SKILL.md +77 -48
  47. package/bundles/antigravity-plugin/skills/implement/SKILL.md +87 -147
  48. package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +1 -1
  49. package/bundles/antigravity-plugin/skills/mmr/SKILL.md +5 -2
  50. package/bundles/antigravity-plugin/skills/office-documents-official/SKILL.md +24 -0
  51. package/bundles/antigravity-plugin/skills/park/SKILL.md +0 -1
  52. package/bundles/antigravity-plugin/skills/pr-merge/SKILL.md +8 -14
  53. package/bundles/antigravity-plugin/skills/prd/SKILL.md +2 -10
  54. package/bundles/antigravity-plugin/skills/roadmap/SKILL.md +259 -0
  55. package/bundles/antigravity-plugin/skills/roadmap/assets/plan-resume-prompt.md +77 -0
  56. package/bundles/antigravity-plugin/skills/roadmap/assets/render-check.template.mjs +68 -0
  57. package/bundles/antigravity-plugin/skills/roadmap/assets/review-protocol.md +78 -0
  58. package/bundles/antigravity-plugin/skills/roadmap/assets/roadmap.template.md +32 -0
  59. package/bundles/antigravity-plugin/skills/roadmap/assets/value-check.template.py +161 -0
  60. package/bundles/antigravity-plugin/skills/ship/SKILL.md +14 -14
  61. package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +6 -0
  62. package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +33 -14
  63. package/bundles/antigravity-plugin/skills/spawn-session/SKILL.md +4 -8
  64. package/bundles/antigravity-plugin/warm_start_agy.py +1 -3
  65. package/bundles/antigravity-plugin-extras/plugin.json +1 -1
  66. package/bundles/antigravity-plugin-extras/skills/_registry.md +1 -2
  67. package/bundles/antigravity-plugin-extras/skills/agent-orchestration/SKILL.md +5 -5
  68. package/bundles/antigravity-plugin-extras/skills/cross-review/SKILL.md +5 -2
  69. package/bundles/antigravity-plugin-extras/skills/runner-move/SKILL.md +214 -0
  70. package/bundles/codex/.codex/skills/docs/office-documents-official/SKILL.md +24 -0
  71. package/bundles/codex/.codex/skills/workflow/best-practices/agents/best-practices-referencer.md +1 -1
  72. package/bundles/codex/.codex/skills/workflow/best-practices/agents/codebase-context-builder.md +1 -1
  73. package/bundles/codex/.codex/skills/workflow/best-practices/agents/task-intent-analyzer.md +1 -1
  74. package/bundles/codex/.codex/skills/workflow/roadmap/SKILL.md +259 -0
  75. package/bundles/codex/.codex/skills/workflow/roadmap/assets/plan-resume-prompt.md +77 -0
  76. package/bundles/codex/.codex/skills/workflow/roadmap/assets/render-check.template.mjs +68 -0
  77. package/bundles/codex/.codex/skills/workflow/roadmap/assets/review-protocol.md +78 -0
  78. package/bundles/codex/.codex/skills/workflow/roadmap/assets/roadmap.template.md +32 -0
  79. package/bundles/codex/.codex/skills/workflow/roadmap/assets/value-check.template.py +161 -0
  80. package/bundles/codex/AGENTS.md +5 -4
  81. package/bundles/manifest.json +3 -3
  82. package/package.json +1 -1
  83. package/bundles/antigravity-plugin/skills/advisory-hub/SKILL.md +0 -233
  84. package/bundles/antigravity-plugin/skills/advisory-hub/reference/context-template.md +0 -214
  85. package/bundles/antigravity-plugin/skills/advisory-hub/reference/hub-handbook.md +0 -233
  86. package/bundles/antigravity-plugin/skills/advisory-hub/reference/report-contract.md +0 -94
  87. package/bundles/antigravity-plugin/skills/advisory-hub/reference/succession.md +0 -118
  88. package/bundles/antigravity-plugin/skills/resume/SKILL.md +0 -6
  89. package/bundles/antigravity-plugin/skills/review/SKILL.md +0 -6
  90. package/bundles/antigravity-plugin/skills/spawn-hub/SKILL.md +0 -341
  91. package/bundles/antigravity-plugin/skills/statusline/SKILL.md +0 -6
  92. package/bundles/antigravity-plugin/skills/validate-phase/SKILL.md +0 -143
  93. package/bundles/antigravity-plugin-extras/skills/data-lineage/SKILL.md +0 -158
  94. package/bundles/antigravity-plugin-extras/skills/data-lineage/scripts/convert_doc_to_confluence.py +0 -1430
  95. package/bundles/antigravity-plugin-extras/skills/golden-plan/SKILL.md +0 -586
  96. package/bundles/codex/.codex/skills/workflow/advisory-hub/SKILL.md +0 -233
  97. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/context-template.md +0 -214
  98. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/hub-handbook.md +0 -233
  99. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/report-contract.md +0 -94
  100. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/succession.md +0 -118
@@ -0,0 +1,259 @@
1
+ ---
2
+ name: roadmap
3
+ description: "Track milestones across plans in .caddis/roadmaps/<slug>/: next batch, typed blockers, rollup. Every change is a docs PR via caddis_roadmap.py."
4
+ ---
5
+
6
+ # Roadmap
7
+
8
+ **Core rule:** the roadmap file on `origin/main` is the only truth; every change to it, by a tool or a human, is a
9
+ small docs PR, so it is ordered and recorded in git history. The tool's PRs are merged automatically on green checks
10
+ by `caddis_merge_chain.py --docs-only`; a repo that wants a human to review them protects its default branch.
11
+
12
+ `scripts/` below is the caddis scripts folder: `${CLAUDE_PLUGIN_ROOT}/scripts` in an installed plugin, `scripts/` in
13
+ the caddis source repo.
14
+
15
+ ## The folder
16
+
17
+ `.caddis/roadmaps/<slug>/` holds:
18
+
19
+ | Path | What |
20
+ |---|---|
21
+ | `roadmap.md` | Milestones and Blockers tables (start from `assets/roadmap.template.md`) |
22
+ | `decisions.md` | decision register; a `Depends on` change cites an id from here |
23
+ | `cards/` | one **card** per unit of work handed to a lane or a reviewer: scope, files, pass criteria |
24
+ | `reports/` | what comes back from a card |
25
+ | `.local/` | machine-local: `changes.jsonl` (audit of the tool's PRs), the runner's `runner.jsonl` and `STOP` (its lock and other state are in `<git common dir>/caddis-runner/roadmap-<slug>/`) |
26
+
27
+ Plans stay in `.caddis/plans/`. Add `.caddis/roadmaps/*/.local/` to the repo's `.gitignore`. The write lock is not in
28
+ the folder: it is `<git common dir>/caddis-roadmaps/<slug>.write.lock`, so every worktree of the repo shares it.
29
+
30
+ ## The format
31
+
32
+ ```markdown
33
+ Runner host: <runner-host>
34
+
35
+ ## Milestones
36
+ | ID | Milestone | Phases | Depends on | Exit check | Status | Owner | Target |
37
+ |---|---|---|---|---|---|---|---|
38
+ | S1 | Lines load | p-alpha#1, p-alpha#2 | - | `python scripts/check_s1.py` | todo | runner | WD3 |
39
+
40
+ ## Blockers
41
+ | Milestone | Type | Reason | Check |
42
+ |---|---|---|---|
43
+ | S2 | data-condition | GPU rows not loaded | `python scripts/rows_ready.py` |
44
+ ```
45
+
46
+ - **Status:** `todo`, `in-progress`, `code-done/data-pending`, `done`, `not-needed`. There is no `blocked` status: a
47
+ milestone is blocked when it has a Blockers row.
48
+ - **Blocker types:** `owner-gate`, `external`, `shared` (a shared file, the GPU, the merge line, another session),
49
+ `calendar`, `data-condition`. A new `calendar` blocker needs an ISO date `YYYY-MM-DD` in Check
50
+ (`block --type calendar --check 2026-11-01`); a date is not a command, so it needs no `--by-owner`.
51
+ - **Owner:** `runner` (or empty) lets the runner take it; a session name means that session does it by hand.
52
+ - **Phase ref** `p-alpha#2` = Phase 2 in the Tracker of `.caddis/plans/p-alpha.md` or `.caddis/plans/done/p-alpha.md`.
53
+ It is done only when its Tracker Status starts with `done` or a check mark **on origin**. Refs work inside one repo.
54
+ - **Runner host:** the one machine whose runner may run this roadmap. Write a placeholder such as `HOSTNAME` in
55
+ shared examples.
56
+ - A `Depends on` change cites a decision id in the Milestone cell: `S9 (dep waived: TC94)`. `check` warns otherwise.
57
+ - **Strict sections.** `## Milestones` and `## Blockers` hold only blank lines, one table whose every line starts with
58
+ `|`, and prose with no unescaped `|` set apart from the table by a blank line. Anything else is a parse error.
59
+ A roadmap allows only headings, plain paragraphs, lists and pipe tables: no code fences, no HTML, no setext
60
+ headings, no control or non-ASCII whitespace characters.
61
+ - **A malformed roadmap fails closed.** A row with the wrong cell count, a `|` line outside a table, a near-miss
62
+ heading such as `## Blockers (open)`, an id that is not `[A-Za-z0-9_-]+`, an unknown Status or blocker type, or a
63
+ phase ref to a missing plan: every command but `check` exits 4 and prints the errors. Fix the file by a PR.
64
+
65
+ ## Commands
66
+
67
+ Every command runs `git fetch origin` and reads `origin/<default>`, never the working tree. Exit check and Check
68
+ commands run in a temporary worktree of `origin/<default>` (the merged code), with no stdin, killed with their whole
69
+ process tree on timeout.
70
+
71
+ ```bash
72
+ python scripts/caddis_roadmap.py status --roadmap <slug> [--json] # "<done> of <total> done; next <id>; ..."
73
+ python scripts/caddis_roadmap.py next --roadmap <slug> [--json] # exit 0 found, 1 nothing ready (reasons)
74
+ python scripts/caddis_roadmap.py check --roadmap <slug> # parse errors, unknown refs or types
75
+ python scripts/caddis_roadmap.py set --roadmap <slug> --id S1 --col Status|Owner|Target --value <v> [--by-owner] [--reason "<r>"]
76
+ python scripts/caddis_roadmap.py block --roadmap <slug> --id S2 --type <type> --reason "<text>" [--check "<cmd>" --by-owner]
77
+ python scripts/caddis_roadmap.py unblock --roadmap <slug> --id S2 --type <type> --reason "<text>" [--check "<cmd>"] [--by-owner]
78
+ python scripts/caddis_roadmap.py rollup --roadmap <slug> --id S1
79
+ python scripts/caddis_roadmap.py recheck --roadmap <slug>
80
+ ```
81
+
82
+ `next` picks the first milestone in table order that is `todo` or `in-progress`, whose `Depends on` are all `done` or
83
+ `not-needed`, with no Blockers row, Owner `runner` or empty, and a phase not yet merged. It returns that phase.
84
+
85
+ ## Rules a runner trusts
86
+
87
+ - **Only `rollup` writes `done`.** `set --value done` exits 4. `rollup` needs every phase merged on origin, every
88
+ `Depends on` milestone `done` or `not-needed`, and no open blocker other than `data-condition` (exit 1 names it),
89
+ then runs the Exit check (300 s): pass -> `done`; fail with a `data-condition` blocker -> `code-done/data-pending`;
90
+ fail otherwise -> an `external` blocker
91
+ `exit check failing (exit <n>)` (never the command's output). A milestone with no phase refs and no Exit check is
92
+ refused (exit 4).
93
+ - **Owner-only changes:** `set` Status `not-needed`, any `set` Owner, `unblock` of an `owner-gate`, and `block --check`
94
+ (recheck runs that command unattended later). They need `--by-owner` and are refused headless: `CADDIS_HEADLESS`
95
+ or `CLAUDECODE` in the environment (even empty), stdin not a terminal, or on
96
+ Windows stdin not a console (a NUL stdin is headless).
97
+ - **`unblock` removes one blocker.** When two rows share milestone, type and reason, it exits 4 until `--check`
98
+ names the row's Check text.
99
+ - **`recheck` clears `data-condition` blockers**, each row whose own Check passed (120 s), **and `calendar`
100
+ blockers whose Check date has passed** (UTC; no command runs). A `calendar` row whose Check is `-` stays until
101
+ someone runs `unblock`. Other types stay.
102
+ - **`not-needed` needs a reason.** `set --col Status --value not-needed --by-owner` also needs
103
+ `--reason "dropped: <why>"` or `--reason .caddis/parking-lot/<file>.md` (the file must be on origin). A reason with
104
+ `|` or a line break exits 4. The same docs PR adds `- <id> not-needed: <reason> (YYYY-MM-DD)` under `## Notes`
105
+ (the section is added at the end when missing).
106
+ - **Old blockers are listed.** `status` (and so the runner's toast) ends with
107
+ `old blockers: <id> <type> (<N> days), ...`, oldest first, for each blocker row older than 14 days. The age is the
108
+ date of the newest commit on origin that added that exact row (`git log -S`): the roadmap never stores a date, and
109
+ a row that was cleared and added again counts from the new add.
110
+ - **Commands run the merged code.** Editing a check script or a Check in the working tree changes nothing until it
111
+ merges.
112
+
113
+ ## How a write lands
114
+
115
+ `set`, `block`, `unblock`, `rollup` and `recheck` take the write lock (after 30 s, a lock whose holder is dead on
116
+ this host, or an empty or non-JSON lock older than 60 s, is taken over), branch `roadmap/<slug>-<id>-<utc>` from
117
+ `origin/<default>` in a temp worktree, commit `docs(roadmap): <slug> <id> <change>`, push, open a PR, and hand it to
118
+ `caddis_merge_chain.py --docs-only`. `gh` uses the account in `GH_TOKEN` when it is set. Git network calls time out
119
+ after 120 s, `gh` after 60 s and the merge chain after 1800 s (exit 3); none can wait on a credential prompt. `--dry-run` prints the diff
120
+ and `would run: <cmd>` lines, and runs and writes nothing. Exit codes: 0 clean, 1 blocked, 3 could not run, 4 error
121
+ (malformed roadmap, refused change, bad usage).
122
+
123
+ ## The runner
124
+
125
+ `roadmap-run.ps1 -Roadmap <slug>` (a shim over `scripts/caddis_roadmap_run.py --roadmap <slug>`) runs the roadmap
126
+ unattended; `-Plan <slug>` runs one plain plan instead (the two are exclusive). It starts only on the roadmap's
127
+ `Runner host` (else exit 3). Each iteration:
128
+
129
+ 1. `next`. Nothing ready: stop `BLOCKED nothing ready` (exit 1); the notification carries the `status` line. When
130
+ the roadmap is finished (every milestone `done` or `not-needed`) the runner also removes its worktree if it is
131
+ clean (only session relay files left), and keeps it, saying so in the stop line, if not. With the worktree gone it
132
+ also deletes the local `caddis/roadmap/<slug>/*` branches merged into `origin/<default>` and names the others.
133
+ 2. Print `next: <milestone> <plan>#<phase>` (`next` and the batch claim happen under one repo-level claim lock).
134
+ A phase whose `**Lane:**` line does not start with this engine (`claude`, `agy`, `codex`), names a cloud session
135
+ or the owner, or that has **no Lane line**, is not run (fail closed): the runner adds
136
+ `block --type owner-gate --reason "phase <n> lane: <lane>"` (`lane: none` when there is no line) and goes on.
137
+ 3. A `todo` milestone is set `in-progress` (a docs PR). Every session of a roadmap runs in ONE dedicated worktree,
138
+ `%TEMP%/caddis-roadmap-<slug>-<8 hex of the repo's git common dir>`: made with `git worktree add --detach` from `origin/<default>` on the first
139
+ session, reused on restart, recorded in the runner folder, never the `-Repo` checkout (plan mode keeps working in
140
+ the given checkout). In that worktree the runner makes a fresh branch `caddis/roadmap/<slug>/<plan>-p<n>` from
141
+ `origin/<default>` (it refuses a branch of that name already on origin that it did not make) and ONE session runs
142
+ `assets/plan-resume-prompt.md` on it (if a blocked session left uncommitted work in the worktree, the runner
143
+ stops `BLOCKED roadmap worktree has uncommitted work from <branch>` instead, and a session branch that already
144
+ holds commits not on `origin/<default>` is never reset: `BLOCKED <branch> has unpublished commits from an earlier
145
+ session`; the owner decides), with `{{slug}}` = the plan and
146
+ `{{phase}}` = the phase. Every session gets its own fresh branch (also after an OWNER-GATE or BLOCKED one), so a
147
+ blocked phase's commits never reach another PR. Sessions stay on that branch and never push.
148
+ 4. Relay `STATUS: CONTINUE` with new commits on that branch: the **runner** checks that the session is still on it
149
+ and did not touch `.caddis/roadmaps/` (else `FAILED`, nothing published), then, in this order: writes a persistent
150
+ claim `<git common dir>/caddis-runner/claims/<plan>#<n>.json`, pushes the branch, opens a PR (`gh api`, the account
151
+ from `GH_TOKEN`) titled `<plan> phase <n>: <phase title>`, writes the PR URL into the claim, records the wait in its
152
+ runner folder's `waiting.json` (ref, PR, milestone, branch, claim and its hash, the roadmap hash; never in the last
153
+ stop), and stops `BLOCKED waiting for merge <plan>#<phase> <pr url>` (exit 1). A run killed between the claim and
154
+ `waiting.json` leaves a claim nothing names: the next start stops on it as an orphaned claim. On restart, GitHub
155
+ must confirm the PR merged before any branch is deleted; a done Tracker row with the PR still open or closed
156
+ unmerged stops `BLOCKED <ref> Tracker row is done but PR <url> is not merged` and keeps the branch, the claim and
157
+ `waiting.json`. The claim keeps the phase held (`next`, other roadmaps and plan mode skip or refuse it) until the PR
158
+ merges or closes. A run started while the PR still waits stops at once (`BLOCKED still waiting for merge of <ref>:
159
+ <url>`) and changes nothing. Once it merges, the recheck task restarts the runner. The restart first checks that the merged
160
+ origin roadmap is the one it published against (a hash, then the frozen tables; any difference is `FAILED roadmap
161
+ changed`), rolls the milestone up FIRST (when every phase is merged), then removes the claim and the phase's
162
+ branch (locally and on origin: whoever makes a branch removes it when its PR merges), and goes on with `next`.
163
+ An own claim that neither the waiting record nor `pending-rollup.json` names (a run killed between the claim and
164
+ the wait) stops the start `BLOCKED orphaned claim <file> from a killed run`; the owner checks the branch and its PR
165
+ and deletes the claim.
166
+ CONTINUE with the phase already merged on origin: `rollup` when every phase is merged, then back to 1. CONTINUE
167
+ with no new commits and nothing merged stops FAILED. `STATUS: OWNER-GATE <r>` becomes an `owner-gate` blocker,
168
+ `STATUS: BLOCKED <r>` an `external` one; then back to 1. `STATUS: FAILED` stops.
169
+ 5. Every `**Risk:** high` phase done on the branch and not yet reviewed clean (record in the runner's own folder:
170
+ `<git common dir>/caddis-runner/roadmap-<slug>/reviewed/<plan>.json`; another runner's record does not count) is
171
+ reviewed BEFORE step 4 writes the claim and publishes
172
+ (`caddis_review.py --plan --phase --runs 2`): exit 0 publishes;
173
+ exit 1 starts one fix session, then one more review; exit 1 again stops `BLOCKED review`, exit 3, 4 or any other
174
+ code `BLOCKED review not run` (neither restartable); the review cost counts against the USD caps.
175
+
176
+ Stops (each one notifies and releases the lock): a STOP file (`~/.caddis/STOP` or `.local/STOP`), any cap (10 h,
177
+ 12 sessions, 120 min and USD 25 per session, USD 150 total; per run, in memory; the global agy spend cap of
178
+ `caddis_spend.py` still counts across runs), a lock or batch claim that cannot be written
179
+ (`BLOCKED claim not written`, before any session), the agy spend cap refusing a reservation, three sessions
180
+ with no newly merged phase, a roadmap write that fails, and the **snapshot guard**: one baseline for the whole run.
181
+ The Milestones and Blockers tables (every cell of every row, rows added, removed or moved) and the `Runner host` line
182
+ are frozen. Every origin read (each `next`, after the runner's own writes, after each session) is compared with the
183
+ previous one; the only differences allowed are the exact edits the runner itself made and recorded for that read: a
184
+ Status it set, a blocker row it added, and what a rollup's audit entry (`changes.jsonl`) says it actually wrote.
185
+ Anything else stops the run `FAILED roadmap changed`.
186
+ Each runner has its own state folder, `<git common dir>/caddis-runner/roadmap-<slug>/` (plan mode: `plan-<slug>/`):
187
+ lock, last stop, options, branches, worktree path, the saved roadmap text. `claims/` and `claim.lock` are shared at
188
+ the top level. The runner hashes its own folder (recursively) and its own claims before and after every session; a
189
+ change there stops the run `FAILED runner state changed` (not restartable). Another runner's writes never stop it. A restart checks that the claim its
190
+ waiting-for-merge stop records is still there with the same content; a missing record, claim or hash is
191
+ `FAILED own claim missing or changed` (not restartable). During the upgrade window a plan-mode lock of the base
192
+ version (`.caddis/plans/.local/<slug>/runner.lock`, read only) still holds its plan while its pid runs and the lock
193
+ file is newer than that process's start (else it is ignored). Roadmap slugs
194
+ match `^[a-z0-9][a-z0-9-]{0,62}$` everywhere (both scripts and `caddis_roadmap.py`). The launch options are kept
195
+ in memory and re-written at every stop, so a changed options file can never raise the caps.
196
+
197
+ The runner never passes `--by-owner`. Every session and every `caddis_roadmap.py` call it starts gets
198
+ `CADDIS_HEADLESS=1`. The runner lock is `<git common dir>/caddis-runner/roadmap-<slug>/runner.lock` (a plan-mode
199
+ runner: `plan-<slug>/runner.lock`), so every worktree sees it. It holds `{"host", "pid", "started_utc", "batch", "max_hours"}`. On
200
+ this host it is stale when its pid is not running, or the process now holding that pid started after `started_utc`
201
+ (pid reuse after a reboot; when the start time cannot be read, it counts as live). Another host's lock is stale when it
202
+ is older than its own `max_hours` plus 1 h. A stale lock is removed with a notification; a live lock is exit 3. A
203
+ plan-mode lock holds the whole plan (`batch` `<plan>#*`). `next` skips a phase another live lock holds, and plan mode
204
+ refuses a plan that a roadmap lock names, at start and before every session. `-DryRun` prints the chosen batch,
205
+ the session command, the worktree and any pending rollup, skips the host check, writes no files and starts nothing; it
206
+ does run `git fetch`, which updates the remote-tracking refs. `-GhAccount <login>` (recorded in the launch options,
207
+ passed on by the recheck task and in the printed task) makes every gh call of the runner and of `--last-stop` use
208
+ GH_TOKEN from `gh auth token -u <login>`; on a machine with several gh logins, give the repo's account. A gh call that
209
+ cannot answer (gh missing, 401, 404) is an error, never "not merged": the run stops
210
+ `BLOCKED gh cannot see <repo> with account <login>`, notified and recorded.
211
+
212
+ `roadmap-recheck.ps1 -Roadmap <slug> -Repo <path>` starts `roadmap-run.ps1 -Roadmap <slug>` detached only when:
213
+ the runner's last recorded stop (next to its lock) is `BLOCKED nothing ready` or there is none, and `recheck`
214
+ cleared a blocker; or the last stop is `BLOCKED waiting for merge` and GitHub says that PR merged
215
+ (`--last-stop` asks GitHub only and fetches nothing; the restarted runner checks the Tracker row on origin under its
216
+ lock; in that tick recheck does NOT run `recheck`, because a cleared blocker would change the roadmap the restart
217
+ checks against its hash). If GitHub says merged but the Tracker row is not done, the restart
218
+ removes the claim and stops `BLOCKED <ref> merged but its Tracker row is not done` (the owner decides). A `BLOCKED nothing ready` stop also records the batches that
219
+ another runner's lock or claim held; once every one of those holders is gone, `--last-stop` says `freed:` and recheck
220
+ restarts the runner even with no blocker cleared. A plan in flight anywhere (a live lock or a claim on any of its
221
+ phases) holds the whole plan for every other roadmap and for plan mode. At the top of each run the runner rolls up
222
+ every milestone that waits only for its rollup (Status not finished, every phase merged, and the same rule as
223
+ `next`: no blocker, dependencies finished, Owner the runner); `--last-stop` says `rollup-ready:` for such a milestone,
224
+ so recheck restarts the runner. A sweep rollup that writes nothing stops `BLOCKED rollup of <id> wrote nothing`
225
+ (recorded, not restartable) and is not retried or reported until the roadmap changes. The dry run lists them first. If
226
+ the owner edits the roadmap during a merge wait, the restart stops `FAILED roadmap changed ...` and names the
227
+ recovery: `roadmap-run.ps1 -Roadmap <slug> -AcceptRoadmap` (owner only, refused headless) takes the runner lock (a live
228
+ runner refuses it), prints the diff from the published baseline, asks you to type `accept` (or `--yes`), and accepts
229
+ the origin roadmap as the new baseline of the wait; the next start goes on. With `-DryRun` it prints the diff only. `--last-stop` uses `--gh-account`, else the account the run recorded
230
+ in its launch options. In all cases the lock must be absent or stale; for the agy engine `caddis_spend.py check` must exit 0 too (a
231
+ claude-engine roadmap needs no spend-cap file). A restart after a merge writes `pending-rollup.json` in its runner
232
+ folder, runs the rollup first, then removes the claim and the branch, and only then the file. Every stop records
233
+ itself: a failed rollup, a crash or a STOP file before that is not restartable (the owner decides), and the next start
234
+ finds the file and does the rollup first. A merged
235
+ branch checked out in another checkout is kept locally (HEAD never moves outside the roadmap worktree).
236
+ Any FAILED stop (the snapshot guard, a crash), a cap or a STOP file needs the owner: recheck prints why and starts
237
+ nothing; the owner's next manual run records a new last stop. A runner records the last stop `running` as soon as it
238
+ holds the lock, so a run killed without a stop (a reboot, a kill) also needs the owner; an unexpected exception
239
+ records `FAILED crash: <type>` and notifies once. Recheck restarts with the launch options the last run recorded
240
+ (engine, model, caps), each cap clamped to at most the built-in default (the options file is writable), never higher.
241
+ `roadmap-run.ps1 -Roadmap <slug> -PrintRecheckTask` prints a PowerShell block (`New-ScheduledTaskAction`,
242
+ `New-ScheduledTaskTrigger` every 2 hours, `Register-ScheduledTask`) for the owner to paste; it registers nothing. The
243
+ task runs only while you are logged on unless you add `-User` and `-Password` to `Register-ScheduledTask` (or set that
244
+ option in Task Scheduler).
245
+
246
+ ## Limits
247
+
248
+ Sessions are cooperative (our own sessions): the runner guards against mistakes and runaway spend, not against a
249
+ deliberately hostile session, which runs as the same OS user and could change anything the runner can. The state
250
+ tripwire catches accidents in a runner's own state only. In particular, a session in another worktree of the repo can
251
+ delete or rewrite ANOTHER runner's claim or lock under `<git common dir>/caddis-runner/`; that is not caught while it
252
+ happens, and the victim runner fails closed only at its next restart (own claim missing or changed, orphaned claim). Caps are per run; the agy spend cap is the only limit across runs. Each roadmap has one
253
+ worktree, removed when the roadmap is finished and the worktree is clean.
254
+
255
+ Owner-only rules are enforced by this tool; a direct PR that edits `roadmap.md` bypasses them; the runner stops
256
+ when an owner-gate row disappears; repos that need more protect their default branch.
257
+ On Windows, a check's grandchild started in the instant before the job object is attached can escape the tree kill;
258
+ checks are owner-written, so this is accepted. Headless detection cannot see a process started in a fresh console with
259
+ the markers removed: anything that runs this tool unattended (the runner, scheduled tasks) must set `CADDIS_HEADLESS=1`.
@@ -0,0 +1,77 @@
1
+ ---
2
+ type: prompt
3
+ title: Plan resume prompt for {{slug}}
4
+ description: The prompt for one fresh runner session of the plan {{slug}} (runner plan mode). It does one batch and ends with a status line.
5
+ status: current
6
+ timestamp: {{date}}
7
+ ---
8
+
9
+ You are a fresh session for the plan `{{slug}}`. Do ONE batch, then stop.
10
+
11
+ In the commands below, `<scripts>` is the caddis scripts folder `{{scripts}}` (put it in quotes if it holds a space).
12
+ Use the scripts in that folder; do not look for them in another caddis install.
13
+
14
+ ## 1. Read
15
+
16
+ 1. The plan `.caddis/plans/{{slug}}.md`: its goal, its decisions, its tracker. Every decision binds you.
17
+ 2. The relay `.caddis/relay/{{slug}}.md`. Its "Next step" says where to start.
18
+ 3. The open-work list `.caddis/open-work.md`, when it exists.
19
+
20
+ ## 2. Step 0: reconcile
21
+
22
+ The relay can be old. Before you act, compare it with the real state: the plan tracker, worktrees, local branches, open
23
+ pull requests and remote branches. If they disagree, trust the real state. Fix the relay and the tracker. Do not build on a
24
+ guess.
25
+
26
+ ## 3. Pick the batch
27
+
28
+ A batch is ONE tracker phase (the first row that is not done and not blocked), or ONE named batch that the plan or the relay
29
+ names. If no row can start, end with the status line that says why (an owner gate, a block, or FAILED).
30
+
31
+ The phase number chosen by the roadmap runner (empty: choose the batch yourself as above): {{phase}}
32
+ When it holds a number, that phase is the batch. Do only that phase.
33
+
34
+ Roadmap mode (a number above): the runner, not you, publishes the work. The runner made the branch you are on
35
+ (`caddis/roadmap/<roadmap>/<plan>-p<n>`, fresh from the default branch). Stay on it: never switch or create a branch,
36
+ never push, never open a pull request, and never edit anything under `.caddis/roadmaps/` (the runner refuses to
37
+ publish a branch that does). Commit the phase there, with its tracker row set to `done`. When you end with
38
+ `STATUS: CONTINUE` and new commits on that branch, the runner pushes it, opens the pull request and stops with
39
+ `BLOCKED waiting for merge`; the recheck task restarts the runner after the merge.
40
+
41
+ ## 4. Work
42
+
43
+ - Do ONE batch with `/caddis:implement` discipline: tests first, the phase's exit gate, a commit per phase.
44
+ - agy sessions only: run `python {{scripts}}/caddis_spend.py check` before each agy call. Stop at the cap (exit 1 over
45
+ the cap, 3 not configured).
46
+ - You may do everything except pass an owner gate. Never pass an owner gate. Never raise a cap. Never delete a STOP file.
47
+ - The runner reviews `risk: high` phases. A phase marked `**Risk:** high`: do not halt for it and do not run the
48
+ review yourself. When its exit gate is green, set its row `done` and end with `STATUS: CONTINUE`. The runner then
49
+ reviews it twice and, on findings, starts one fix session.
50
+ - Set the tracker row of the batch to `done` (with its evidence) when its exit gate is green. Set open-work rows with
51
+ `python <scripts>/caddis_rows.py set` (by row id and column name).
52
+
53
+ ## 5. Write the relay
54
+
55
+ Write the relay with `python <scripts>/caddis_workstreams.py put --require-status {{slug}} <file>`. Write `<file>` outside
56
+ the repo (for example in the temp folder). Read the current relay first with `python <scripts>/caddis_workstreams.py read {{slug}}`;
57
+ keep its frontmatter shape (`type: relay`, `workstream: {{slug}}`, `branch`, `session_id`, `plan`, `generated.at`). The LAST
58
+ non-empty line must be exactly one of these four:
59
+
60
+ ```
61
+ STATUS: CONTINUE
62
+ STATUS: OWNER-GATE <P>-OG<n>
63
+ STATUS: BLOCKED <owner>
64
+ STATUS: FAILED <reason>
65
+ ```
66
+
67
+ Use `STATUS: CONTINUE` only when the next batch can start with no owner input. A missing or bad line counts as FAILED.
68
+ An owner gate id has the form `<P>-OG<n>`: the plan's 2 to 4 capital-letter prefix (for example `PM-OG1`). Name the gate
69
+ in the plan's decisions with that id. Any other form is a bad line.
70
+
71
+ ## 6. Notify and end
72
+
73
+ When the status is not CONTINUE, call the `PushNotification` tool as your last action. Put the plan, the status line and the
74
+ next step in the message. After the call succeeds, print the line `PUSH-SENT`. The runner looks for that marker. Without it,
75
+ the runner sends the notification itself.
76
+
77
+ Then end the session.
@@ -0,0 +1,68 @@
1
+ // Render check template. Copy it into the app (for example frontend/render-check.mjs) so CI and prod checks can run it.
2
+ //
3
+ // Usage:
4
+ // node render-check.mjs --base http://localhost:5173 --path /dashboard --expect kpi-total --expect chart-main
5
+ // node render-check.mjs --prod https://app.example.com --path /dashboard --expect kpi-total --channel msedge \
6
+ // --allow-auth app.example.com
7
+ // node render-check.mjs --base http://localhost:5173 --path /dashboard --expect kpi-total --prove-it-can-fail
8
+ //
9
+ // Options:
10
+ // --base <url> | --prod <url> Origin to test (local or prod). One of them is required.
11
+ // --path <path> Page path. Default "/".
12
+ // --expect <testid> A data-testid that must be visible. Repeat it.
13
+ // --channel <name> Browser channel, for example msedge. Default: bundled chromium.
14
+ // --allow-auth <host> Host that may use the current Windows login (NTLM). Repeat it.
15
+ // --settle <ms> Wait after network idle. Default 3000.
16
+ // --prove-it-can-fail Add a test id that does not exist. The run must then exit 1.
17
+ // Exit codes: 0 all checks pass, 1 a check failed, 4 bad arguments.
18
+ import { chromium } from 'playwright'; // or: import { chromium } from '@playwright/test';
19
+
20
+ const args = process.argv.slice(2);
21
+ const opt = (name) => { const i = args.indexOf(name); return i >= 0 ? args[i + 1] : undefined; };
22
+ const all = (name) => args.flatMap((a, i) => (a === name ? [args[i + 1]] : []));
23
+
24
+ const origin = opt('--prod') ?? opt('--base');
25
+ if (!origin) { console.error('give --base <url> or --prod <url>'); process.exit(4); }
26
+ const path = opt('--path') ?? '/';
27
+ const settle = Number(opt('--settle') ?? 3000);
28
+ const expected = all('--expect');
29
+ const allowAuth = all('--allow-auth');
30
+ if (args.includes('--prove-it-can-fail')) expected.push('testid-that-does-not-exist');
31
+ if (expected.length === 0) { console.error('give at least one --expect <testid>'); process.exit(4); }
32
+
33
+ const launch = { headless: true };
34
+ if (opt('--channel')) launch.channel = opt('--channel');
35
+ if (allowAuth.length) launch.args = [`--auth-server-allowlist=${allowAuth.join(',')}`];
36
+
37
+ const failures = [];
38
+ const browser = await chromium.launch(launch);
39
+ for (const width of [1440, 400]) {
40
+ const context = await browser.newContext({ viewport: { width, height: 900 } });
41
+ // Dark theme seeded before any page script runs. Change the key and value to match the app.
42
+ await context.addInitScript(() => { try { localStorage.setItem('theme', 'dark'); } catch { /* ignore */ } });
43
+ const page = await context.newPage();
44
+ let consoleErrors = 0;
45
+ page.on('console', (m) => { if (m.type() === 'error') consoleErrors += 1; });
46
+ page.on('pageerror', () => { consoleErrors += 1; });
47
+
48
+ await page.goto(new URL(path, origin).toString());
49
+ await page.waitForLoadState('networkidle');
50
+ await page.waitForTimeout(settle);
51
+
52
+ if (consoleErrors > 0) failures.push(`${width}px: ${consoleErrors} console error(s)`);
53
+ const overflow = await page.evaluate(() => document.documentElement.scrollWidth > window.innerWidth);
54
+ if (overflow) failures.push(`${width}px: horizontal scroll`);
55
+ for (const id of expected) {
56
+ const visible = await page.getByTestId(id).first().isVisible().catch(() => false);
57
+ if (!visible) failures.push(`${width}px: data-testid "${id}" is not visible`);
58
+ }
59
+ console.log(`${width}px: console errors ${consoleErrors}, horizontal scroll ${overflow ? 'yes' : 'no'}`);
60
+ await context.close();
61
+ }
62
+ await browser.close();
63
+
64
+ if (failures.length) {
65
+ for (const f of failures) console.error(`FAIL ${f}`);
66
+ process.exit(1);
67
+ }
68
+ console.log('PASS render check');
@@ -0,0 +1,78 @@
1
+ ---
2
+ type: reference
3
+ title: Review protocol
4
+ description: The review method one model can rely on - invariants, inputs and writers, shared state, execute what the change promises, tests, docs, then severity (only blocking stops the merge; each finding needs a reproduction), one FINDING line per finding and an END REVIEW line. caddis_review.py sends it to every review run.
5
+ status: current
6
+ ---
7
+
8
+ # Review protocol (generic, any diff)
9
+
10
+ You review a diff. You do NOT edit, create or delete files, and you do not commit. You may read files and run commands
11
+ (tests, dry runs, the commands that plans, cards and docs name). Work through ALL steps in order. Do not stop at the
12
+ first finding. Include a short section per step, in order.
13
+
14
+ ## Step 1 - Purpose and invariants
15
+ Read the card, the plan or design section it cites, and the diff. Write the list of INVARIANTS this change must keep:
16
+ safety limits, gates, money caps, isolation between runs or users, path containment, "X can never cause Y". Include
17
+ invariants that the surrounding system relies on, not only the ones the card names.
18
+
19
+ ## Step 2 - Inputs and who controls them
20
+ List every input the changed code reads: files, file sections, env vars, CLI args, other processes' output, the clock,
21
+ the home folder. For EACH input write: who or what can write it (the owner, an unattended session, another runner,
22
+ a test, a template), and when. Then, for each invariant from step 1, ask: can any writer of any input break it, by
23
+ accident or on purpose? This includes the agent or session the code is meant to limit.
24
+
25
+ ## Step 3 - Shared state and concurrency
26
+ List state that two runs, two kinds of target, or two processes could share (files, locks, relays, logs, caches).
27
+ Can they collide? Can one read the other's output as its own?
28
+
29
+ ## Step 4 - Execute what the change promises
30
+ Run the tests the card names. Then run every command that the plan, its exit gates, the docs or the skill text say
31
+ should work for this change (dry runs, the documented usage lines), exactly as written. Use the repo's OWN fixtures
32
+ and example files, not ones you build, and run unattended scripts (hooks, runners) as they are really run (as a script,
33
+ with a real payload). Compare risky behaviour with the parent commit when the change claims to fix or tighten
34
+ something. Report any that fail or that cannot work as documented.
35
+
36
+ ## Step 5 - Tests
37
+ For each new or changed test: could it pass for the wrong reason (no assertion that can fail, mocked away the thing
38
+ under test)? Does it depend on the real machine (home folder, real STOP files, network, clock, cwd)? Which new
39
+ behaviour has no test, especially the negative cases (placeholders, malformed input, edge values)?
40
+
41
+ ## Step 6 - Docs and contracts
42
+ Compare every doc, skill, prompt asset and usage line touched by or describing this feature with the code. List each
43
+ mismatch (paths, flags, names, steps that do not exist or no longer apply). Check that every tool the new mode is told
44
+ to use supports the new mode.
45
+
46
+ ## Step 7 - Report
47
+ Severity rule. Only **blocking** stops the merge, so use it with care:
48
+ - **blocking** = a defect that breaks a stated invariant from step 1 (safety, security, isolation, a gate passing when
49
+ it should fail, a crash of something that runs unattended), or loses or corrupts work or data, or breaks a money cap,
50
+ or makes a documented command fail. Use it even if other limits make the defect unlikely.
51
+ - **should-fix** = a real defect that does not stop the merge: it goes on a follow-up list. Examples: a missing
52
+ negative test, a doc that is wrong in a way that does not make a command fail, an error message that misleads.
53
+ - **nit** = style and wording.
54
+
55
+ Every finding needs a concrete reproduction: a command, an input or a test that shows the failure, written in the
56
+ failure scenario. A finding without a reproduction is a **nit**, whatever its severity would otherwise be. Do not
57
+ write "might", "could in theory" or "a future change may": show it or call it a nit.
58
+
59
+ The gate exits by the findings. A **blocking** finding fails it. **should-fix** and **nit** findings do not: they are
60
+ listed in the report under "Follow-up (not blocking)". The tool allows 3 completed review rounds for a phase; the 4th completed round
61
+ ends with "stop, the owner decides". Report every real defect once, with its true severity, so that one round is
62
+ enough.
63
+
64
+ End your answer with the findings, one line per finding, in exactly this form (no bullets, no bold, no code block):
65
+
66
+ ```
67
+ FINDING <n> | <severity> | <file>:<line> | <one-sentence failure scenario>
68
+ ```
69
+
70
+ `<n>` counts from 1, `<severity>` is `blocking`, `should-fix` or `nit`. Put the reproduction in the one sentence
71
+ (for example `... ; reproduce: python x.py --flag with an empty file`). If you have no findings, write the line
72
+ `NO FINDINGS` instead. The very last line of your answer is `END REVIEW <count>`, where `<count>` is the number of
73
+ FINDING lines (`END REVIEW 0` after `NO FINDINGS`). A review whose last line is missing or whose count is wrong is
74
+ treated as not run. Do not write a verdict line: the gate reads the findings only.
75
+ No other line may contain the word FINDING followed by a number, anywhere in the line (in any case, also as
76
+ `finding-1`, `finding #1`, `finding (1)`, after a bullet, a `>`, a table `|` or any other text): such a line that is
77
+ not a complete FINDING line also makes the review count as not run. In your step sections, write "issue 1", not
78
+ "finding 1".
@@ -0,0 +1,32 @@
1
+ # <slug> roadmap
2
+
3
+ Runner host: HOSTNAME
4
+
5
+ The roadmap file on `origin/main` is the only truth. Change it with `scripts/caddis_roadmap.py` (each change is a small
6
+ docs PR), never by a direct push. Only `rollup` sets a milestone `done`.
7
+
8
+ ## Milestones
9
+ | ID | Milestone | Phases | Depends on | Exit check | Status | Owner | Target |
10
+ |---|---|---|---|---|---|---|---|
11
+ | S1 | <what is true when it is done> | <plan>#1, <plan>#2 | - | `<command that exits 0 when S1 holds>` | todo | runner | <date> |
12
+
13
+ ## Blockers
14
+ | Milestone | Type | Reason | Check |
15
+ |---|---|---|---|
16
+
17
+ ## Notes
18
+
19
+ - Status: todo, in-progress, code-done/data-pending, done or not-needed (blocked = has a Blockers row).
20
+ - Blocker types: owner-gate, external, shared, calendar or data-condition (Check: a command, added by the owner
21
+ only; recheck runs it in a worktree of origin/main and clears that row on exit 0). A calendar blocker's Check is
22
+ an ISO date YYYY-MM-DD; recheck clears the row once that date has passed.
23
+ - Status not-needed needs --reason "dropped: why" or --reason .caddis/parking-lot/file.md; the reason is added
24
+ below as "- id not-needed: reason (date)".
25
+ - status lists each blocker row older than 14 days as "old blockers: id type (N days)"; the age comes from git.
26
+ - A malformed section (wrong cell count, a row without a leading pipe, a line right after the table, a pipe in
27
+ prose, a second table, a renamed heading) stops every command: fix it by a PR. Escape a pipe in prose as \|.
28
+ - This file allows only headings, plain paragraphs, lists and pipe tables: no code fences, no HTML, no setext
29
+ headings, no control or non-ASCII whitespace characters. GitHub would hide or move lines the parser reads, so
30
+ each one is a parse error.
31
+ - Owner: runner (or empty) = the runner may take it; a session name = that session does it by hand.
32
+ - A Depends on change cites a decision id from decisions.md in the Milestone cell: `S9 (dep waived: D4)`.