@caddis/cli 0.4.52 → 0.4.54

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 (111) hide show
  1. package/bundles/antigravity-plugin/CADDIS-GLOSSARY.md +1 -0
  2. package/bundles/antigravity-plugin/guard_agy.py +184 -0
  3. package/bundles/antigravity-plugin/plugin.json +1 -1
  4. package/bundles/antigravity-plugin/repo_freshness.py +132 -5
  5. package/bundles/antigravity-plugin/scripts/caddis_budget.py +283 -0
  6. package/bundles/antigravity-plugin/scripts/caddis_checkout_guard.py +227 -0
  7. package/bundles/antigravity-plugin/scripts/caddis_docs_pr.py +284 -0
  8. package/bundles/antigravity-plugin/scripts/caddis_exit.py +2 -0
  9. package/bundles/antigravity-plugin/scripts/caddis_fleet.py +439 -0
  10. package/bundles/antigravity-plugin/scripts/caddis_frontmatter.py +1 -0
  11. package/bundles/antigravity-plugin/scripts/caddis_gate.py +255 -195
  12. package/bundles/antigravity-plugin/scripts/caddis_glossary.py +3 -3
  13. package/bundles/antigravity-plugin/scripts/caddis_governor.py +48 -4
  14. package/bundles/antigravity-plugin/scripts/caddis_inventory.py +38 -0
  15. package/bundles/antigravity-plugin/scripts/caddis_lanes.py +411 -127
  16. package/bundles/antigravity-plugin/scripts/caddis_lineage_publish.py +458 -0
  17. package/bundles/antigravity-plugin/scripts/caddis_merge_chain.py +237 -0
  18. package/bundles/antigravity-plugin/scripts/caddis_notify.py +125 -0
  19. package/bundles/antigravity-plugin/scripts/caddis_prod_check.py +380 -0
  20. package/bundles/antigravity-plugin/scripts/caddis_redact.py +99 -0
  21. package/bundles/antigravity-plugin/scripts/caddis_relay_git.py +2 -2
  22. package/bundles/antigravity-plugin/scripts/caddis_review.py +1309 -0
  23. package/bundles/antigravity-plugin/scripts/caddis_roadmap.py +2109 -0
  24. package/bundles/antigravity-plugin/scripts/caddis_roadmap_page.py +676 -0
  25. package/bundles/antigravity-plugin/scripts/caddis_roadmap_run.py +2605 -0
  26. package/bundles/antigravity-plugin/scripts/caddis_routing.py +7 -6
  27. package/bundles/antigravity-plugin/scripts/caddis_rows.py +476 -0
  28. package/bundles/antigravity-plugin/scripts/caddis_spawn.py +8 -9
  29. package/bundles/antigravity-plugin/scripts/caddis_spend.py +415 -0
  30. package/bundles/antigravity-plugin/scripts/caddis_tidy.py +357 -20
  31. package/bundles/antigravity-plugin/scripts/caddis_todo.py +55 -0
  32. package/bundles/antigravity-plugin/scripts/caddis_workstreams.py +178 -19
  33. package/bundles/antigravity-plugin/scripts/caddis_worktree_close.py +349 -0
  34. package/bundles/antigravity-plugin/scripts/check_doc_coverage.py +0 -56
  35. package/bundles/antigravity-plugin/scripts/claudster_doctor.py +1 -1
  36. package/bundles/antigravity-plugin/scripts/enforcement_inventory.py +0 -1
  37. package/bundles/antigravity-plugin/scripts/oss_review.py +2 -2
  38. package/bundles/antigravity-plugin/scripts/roadmap-recheck.ps1 +89 -0
  39. package/bundles/antigravity-plugin/scripts/roadmap-run.ps1 +88 -0
  40. package/bundles/antigravity-plugin/scripts/setup_project_ai.py +43 -1
  41. package/bundles/antigravity-plugin/session_end_agy.py +3 -3
  42. package/bundles/antigravity-plugin/skills/_registry.md +4 -6
  43. package/bundles/antigravity-plugin/skills/best-practices/agents/best-practices-referencer.md +1 -1
  44. package/bundles/antigravity-plugin/skills/best-practices/agents/codebase-context-builder.md +1 -1
  45. package/bundles/antigravity-plugin/skills/best-practices/agents/task-intent-analyzer.md +1 -1
  46. package/bundles/antigravity-plugin/skills/catchup/SKILL.md +11 -0
  47. package/bundles/antigravity-plugin/skills/cloud-phases/SKILL.md +1 -1
  48. package/bundles/antigravity-plugin/skills/cross-review/SKILL.md +5 -2
  49. package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +35 -83
  50. package/bundles/antigravity-plugin/skills/fleet-close/SKILL.md +167 -0
  51. package/bundles/antigravity-plugin/skills/fleet-open/SKILL.md +110 -0
  52. package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +2 -1
  53. package/bundles/antigravity-plugin/skills/handoff/SKILL.md +177 -48
  54. package/bundles/antigravity-plugin/skills/implement/SKILL.md +87 -147
  55. package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +4 -3
  56. package/bundles/antigravity-plugin/skills/mmr/SKILL.md +5 -2
  57. package/bundles/antigravity-plugin/skills/office-documents-official/SKILL.md +24 -0
  58. package/bundles/antigravity-plugin/skills/park/SKILL.md +0 -1
  59. package/bundles/antigravity-plugin/skills/pr-merge/SKILL.md +28 -20
  60. package/bundles/antigravity-plugin/skills/prd/SKILL.md +2 -10
  61. package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +2 -1
  62. package/bundles/antigravity-plugin/skills/review-pr/SKILL.md +1 -1
  63. package/bundles/antigravity-plugin/skills/roadmap/SKILL.md +260 -0
  64. package/bundles/antigravity-plugin/skills/roadmap/assets/plan-resume-prompt.md +77 -0
  65. package/bundles/antigravity-plugin/skills/roadmap/assets/render-check.template.mjs +68 -0
  66. package/bundles/antigravity-plugin/skills/roadmap/assets/review-protocol.md +78 -0
  67. package/bundles/antigravity-plugin/skills/roadmap/assets/roadmap.template.md +32 -0
  68. package/bundles/antigravity-plugin/skills/roadmap/assets/value-check.template.py +161 -0
  69. package/bundles/antigravity-plugin/skills/ship/SKILL.md +16 -16
  70. package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +15 -5
  71. package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +36 -16
  72. package/bundles/antigravity-plugin/skills/spawn-session/SKILL.md +4 -8
  73. package/bundles/antigravity-plugin/warm_start_agy.py +1 -3
  74. package/bundles/antigravity-plugin-extras/plugin.json +1 -1
  75. package/bundles/antigravity-plugin-extras/skills/_registry.md +1 -2
  76. package/bundles/antigravity-plugin-extras/skills/agent-orchestration/SKILL.md +5 -5
  77. package/bundles/antigravity-plugin-extras/skills/cross-review/SKILL.md +5 -2
  78. package/bundles/antigravity-plugin-extras/skills/runner-move/SKILL.md +214 -0
  79. package/bundles/codex/.codex/skills/coding/refactoring/SKILL.md +2 -1
  80. package/bundles/codex/.codex/skills/devops/gh-cli/SKILL.md +2 -1
  81. package/bundles/codex/.codex/skills/docs/office-documents-official/SKILL.md +24 -0
  82. package/bundles/codex/.codex/skills/workflow/best-practices/agents/best-practices-referencer.md +1 -1
  83. package/bundles/codex/.codex/skills/workflow/best-practices/agents/codebase-context-builder.md +1 -1
  84. package/bundles/codex/.codex/skills/workflow/best-practices/agents/task-intent-analyzer.md +1 -1
  85. package/bundles/codex/.codex/skills/workflow/roadmap/SKILL.md +260 -0
  86. package/bundles/codex/.codex/skills/workflow/roadmap/assets/plan-resume-prompt.md +77 -0
  87. package/bundles/codex/.codex/skills/workflow/roadmap/assets/render-check.template.mjs +68 -0
  88. package/bundles/codex/.codex/skills/workflow/roadmap/assets/review-protocol.md +78 -0
  89. package/bundles/codex/.codex/skills/workflow/roadmap/assets/roadmap.template.md +32 -0
  90. package/bundles/codex/.codex/skills/workflow/roadmap/assets/value-check.template.py +161 -0
  91. package/bundles/codex/AGENTS.md +5 -4
  92. package/bundles/manifest.json +3 -3
  93. package/package.json +1 -1
  94. package/bundles/antigravity-plugin/skills/advisory-hub/SKILL.md +0 -233
  95. package/bundles/antigravity-plugin/skills/advisory-hub/reference/context-template.md +0 -214
  96. package/bundles/antigravity-plugin/skills/advisory-hub/reference/hub-handbook.md +0 -233
  97. package/bundles/antigravity-plugin/skills/advisory-hub/reference/report-contract.md +0 -94
  98. package/bundles/antigravity-plugin/skills/advisory-hub/reference/succession.md +0 -118
  99. package/bundles/antigravity-plugin/skills/resume/SKILL.md +0 -6
  100. package/bundles/antigravity-plugin/skills/review/SKILL.md +0 -6
  101. package/bundles/antigravity-plugin/skills/spawn-hub/SKILL.md +0 -341
  102. package/bundles/antigravity-plugin/skills/statusline/SKILL.md +0 -6
  103. package/bundles/antigravity-plugin/skills/validate-phase/SKILL.md +0 -143
  104. package/bundles/antigravity-plugin-extras/skills/data-lineage/SKILL.md +0 -158
  105. package/bundles/antigravity-plugin-extras/skills/data-lineage/scripts/convert_doc_to_confluence.py +0 -1430
  106. package/bundles/antigravity-plugin-extras/skills/golden-plan/SKILL.md +0 -586
  107. package/bundles/codex/.codex/skills/workflow/advisory-hub/SKILL.md +0 -233
  108. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/context-template.md +0 -214
  109. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/hub-handbook.md +0 -233
  110. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/report-contract.md +0 -94
  111. package/bundles/codex/.codex/skills/workflow/advisory-hub/reference/succession.md +0 -118
@@ -0,0 +1,214 @@
1
+ ---
2
+ name: runner-move
3
+ description: >-
4
+ Move GitHub Actions jobs from self-hosted runners to GitHub-hosted runners, one repo at a time,
5
+ with a cost check and a pilot PR. Use when the user says "move CI to GitHub-hosted runners",
6
+ "stop using self-hosted runners", "runner migration", "move jobs off the runner box", or "run
7
+ CI on windows-latest or ubuntu-latest".
8
+ ---
9
+
10
+ # runner-move
11
+
12
+ Move the safe jobs of one repository from self-hosted runners to GitHub-hosted runners. Prove it
13
+ with one pilot PR. Then roll out repo by repo.
14
+
15
+ This skill is generic. It names no repository, host, path or organisation. The pilot at the end is
16
+ a labelled worked example only.
17
+
18
+ ## The rule
19
+
20
+ Move a job only if it needs nothing that exists on the runner machine alone. When in doubt, the job
21
+ stays. A wrong move fails in CI. A wrong deploy move can break production.
22
+
23
+ ## Step 1 - classify each job
24
+
25
+ Read the workflow file. For each job, read every `run:` step. Then read every script that the step
26
+ calls.
27
+
28
+ **Can move.** The job does only these things:
29
+
30
+ - checkout
31
+ - install dependencies
32
+ - lint and type-check
33
+ - unit and integration tests that use data in the repository
34
+ - builds
35
+
36
+ **Must stay.** The job does any of these things:
37
+
38
+ - writes to a server (deploy)
39
+ - restarts a service
40
+ - reads an internal host (a database, an internal git server, a file share, `localhost` of a server)
41
+ - uses a path that exists only on the runner (a drive path such as `C:\...` or `G:\...`, or a shared
42
+ cache folder)
43
+ - needs a licensed tool that is installed only on the runner
44
+
45
+ Search the workflow and the scripts it calls for these signs:
46
+
47
+ ```powershell
48
+ git grep -n -i -E '[A-Z]:\\|localhost|127\.0\.0\.1|nssm|Restart-Service|Stop-Service|\\\\[a-z]' -- .github scripts
49
+ git grep -n -E 'secrets\.' -- .github/workflows
50
+ ```
51
+
52
+ Check each secret name. A secret that holds an internal host name or an internal URL means the job
53
+ reads that host.
54
+
55
+ Workflow-level `env:` that only the deploy jobs read does not block the check jobs. Look at which
56
+ job reads each variable.
57
+
58
+ Write the result as a table: job, current `runs-on`, move or stay, and the evidence line.
59
+
60
+ If no job can move, stop here and say so.
61
+
62
+ ## Step 2 - cost
63
+
64
+ Do this before you edit. Hosted minutes can cost money.
65
+
66
+ ```powershell
67
+ gh api orgs/<org> --jq .plan.name
68
+ gh api "organizations/<org>/settings/billing/usage?year=<Y>&month=<M>" # group the items by sku
69
+ ```
70
+
71
+ The old `/settings/billing/actions` endpoint returns HTTP 410. Do not use it.
72
+
73
+ Measure the minutes per run from recent runs:
74
+
75
+ ```powershell
76
+ gh run list --workflow <file> --limit 50 --json databaseId,createdAt,conclusion
77
+ gh run view <id> --json jobs # startedAt and completedAt for each job
78
+ ```
79
+
80
+ Count the runs per month. Then compute: minutes per run, times runs per month, for each job you
81
+ will move.
82
+
83
+ Remember these facts:
84
+
85
+ - Windows minutes cost more than Linux minutes: about 1.7 times in the October 2026 usage data
86
+ (USD 0.010 against USD 0.006 a minute). Read the real rates from the usage call above.
87
+ - A private repository is billed after the included minutes of the plan.
88
+ - A public repository does not pay for hosted minutes.
89
+
90
+ **A change that adds cost needs owner approval.** Give the owner the numbers and wait for a yes.
91
+
92
+ ## Step 3 - edit
93
+
94
+ Change `runs-on` only for the jobs that can move.
95
+
96
+ | The job's scripts are | Use |
97
+ |---|---|
98
+ | Windows scripts (PowerShell, batch) | `windows-latest` |
99
+ | Clean on Linux (no Windows-only tool) | `ubuntu-latest` |
100
+
101
+ Then fix these three things.
102
+
103
+ **1. Tool paths.** A runner-only path to Python or Node does not exist on a hosted runner. Install
104
+ the tool in the job. Pin the version that production and the old runner use.
105
+
106
+ ```yaml
107
+ - uses: actions/setup-python@v5
108
+ id: py
109
+ with:
110
+ python-version: "<version of prod>"
111
+ - uses: actions/setup-node@v4
112
+ with:
113
+ node-version: "<version of prod>"
114
+ ```
115
+
116
+ If a script reads an interpreter path from an environment variable, set that variable to the new
117
+ path:
118
+
119
+ ```yaml
120
+ env:
121
+ PYTHON_EXE: ${{ steps.py.outputs.python-path }}
122
+ ```
123
+
124
+ **2. The default shell.** Look at `defaults: run: shell:` in the workflow first. On `windows-latest`
125
+ the default shell is `pwsh` (PowerShell 7). The old runner often used Windows PowerShell 5.1. If the
126
+ workflow sets no default and the steps were written for 5.1, add this to the moved jobs:
127
+
128
+ ```yaml
129
+ defaults:
130
+ run:
131
+ shell: powershell
132
+ ```
133
+
134
+ If the workflow already sets `shell: powershell`, add nothing. Say in the PR that you checked.
135
+
136
+ **3. Shared-cache scripts.** A dependency script may read packages from a cache folder on the
137
+ runner. Read the script. It must fall back to a normal install when the folder is missing. If it
138
+ does not, the job must stay, or a separate PR must fix the script first (see "Do not").
139
+
140
+ Also:
141
+
142
+ - Leave `clean: false` on checkout. It does no harm on a fresh hosted VM.
143
+ - Do not add caching in the same change. A change in speed must not hide a change in behaviour.
144
+ Add caching in a later PR.
145
+
146
+ ## Step 4 - pilot PR
147
+
148
+ Use one repository and one PR. A PR does not deploy, so it is a safe test.
149
+
150
+ **Exit gate.** Both of these must be true:
151
+
152
+ 1. CI is green on the hosted runners.
153
+ 2. The test counts equal the last self-hosted run. Compare the test summary lines of the old run
154
+ and the new run, line by line. Do this even for a suite that is allowed to fail. A suite that
155
+ "passes" with fewer tests ran less.
156
+
157
+ Then compare the job times with the last self-hosted run. Put both sets of numbers in the PR
158
+ description. The numbers decide whether the move is worth it.
159
+
160
+ If the counts differ, stop. Find the cause. A missing tool, a missing data folder, or a different
161
+ shell is the usual cause.
162
+
163
+ ## Step 5 - monitoring impact
164
+
165
+ Do this before you merge. Look at every tool that watches CI or runners: dashboards, alerts and
166
+ reports. Ask these questions:
167
+
168
+ - Does the tool derive the job kind or group from the runner labels? Hosted jobs may fall into an
169
+ "other" group.
170
+ - Does the queue depth count jobs that wait for a hosted VM? That number now means something
171
+ different.
172
+ - Does any text say that every runner is self-hosted?
173
+ - Do runner-offline alerts still work? They are not affected. Hosted runners are not listed in
174
+ `/orgs/<org>/actions/runners`.
175
+
176
+ Write down each effect. Fix them in that tool's own PR. Do not fix them in the pilot PR.
177
+
178
+ ## Step 6 - merge, watch, roll out
179
+
180
+ 1. Merge the pilot PR.
181
+ 2. Watch the first run on the default branch, **including the deploy job**. The deploy job stays on
182
+ the self-hosted runner. Check that it still starts and still works.
183
+
184
+ ```powershell
185
+ python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_lanes.py" ci-watch --run <run-id> --repo <owner/name>
186
+ ```
187
+ 3. Only after this run is green, take the next repository. Repeat steps 1 to 6 for each one. Never
188
+ do all repositories at once.
189
+
190
+ ## Do not
191
+
192
+ - Do not move deploy jobs.
193
+ - Do not move a job that reads an internal host.
194
+ - Do not change shared scripts in the same PR. Change them in an earlier PR, and merge it first.
195
+ - Do not roll out to all repositories before one pilot has passed.
196
+ - Do not add caching in the move PR.
197
+ - Do not delete the self-hosted runner. Other jobs and repositories may still use it.
198
+
199
+ ## Worked example (labelled: one real pilot)
200
+
201
+ One pilot repository (a Python and React app), 2026-10-15, PR #30, merge `432d385`. The numbers
202
+ here are an example, not a default. The repository name is left out on purpose.
203
+
204
+ - Step 1: the check jobs (change detection, lint and test, frontend checks) could move. The deploy
205
+ job wrote to a server, so it stayed.
206
+ - Step 3: the workflow already had `shell: powershell`, so no shell change was needed. A runner-only
207
+ Python path became `actions/setup-python` and `actions/setup-node`.
208
+ - Step 4: the Python suite is non-blocking. The summary was `31 failed, 33 passed, 27 skipped` on
209
+ the self-hosted run and on the hosted run. The counts matched.
210
+ - Job times, self-hosted to hosted: change detection 132 s to 17 s, lint and test 85 s to 99 s,
211
+ frontend checks 482 s to 402 s.
212
+ - Step 5: the monitoring tool grouped jobs by runner label, so hosted jobs fell into "other". Its
213
+ queue count also included jobs that waited for a hosted VM. Nothing broke and no alert fired. The
214
+ team noted the fix for that tool's own PR.
@@ -126,7 +126,8 @@ Activate when:
126
126
 
127
127
  4. **Commit current state**
128
128
  ```bash
129
- git add .
129
+ git status --short # stage the files you changed, by name
130
+ git add tests/test_<module>.py
130
131
  git commit -m "chore: add tests before refactoring"
131
132
  ```
132
133
 
@@ -167,7 +167,8 @@ gh alias set review 'pr list --search "review-requested:@me"'
167
167
 
168
168
  ```bash
169
169
  gh issue develop 42 --branch fix/issue-42
170
- git add . && git commit -m "fix: resolve login bug" && git push -u origin HEAD
170
+ git add src/auth/login.py tests/test_login.py # named files only, never `git add .`
171
+ git commit -m "fix: resolve login bug" && git push -u origin HEAD
171
172
  gh pr create --title "fix: resolve login bug" --body "Closes #42"
172
173
  gh pr checks --watch
173
174
  gh pr merge --squash --delete-branch
@@ -0,0 +1,24 @@
1
+ ---
2
+ name: office-documents-official
3
+ description: Hint for Office and PDF file work. Use when the user asks to create, edit, read or convert a Word .docx, PDF, PowerPoint .pptx or Excel .xlsx file. caddis does not ship these skills; points to Anthropic's official document-skills plugin.
4
+ ---
5
+
6
+ # Office and PDF files: use Anthropic's document-skills
7
+
8
+ caddis does not include skills for Word (.docx), PDF, PowerPoint (.pptx) or Excel (.xlsx) files.
9
+ These skills belong to Anthropic. Anthropic publishes them as the `document-skills` plugin.
10
+
11
+ ## Install (Claude Code)
12
+
13
+ ```
14
+ /plugin marketplace add anthropics/skills
15
+ /plugin install document-skills@anthropic-agent-skills
16
+ ```
17
+
18
+ Source: https://github.com/anthropics/skills (see its README, section "Claude Code").
19
+
20
+ ## What to do
21
+
22
+ 1. If the plugin is installed, use its `docx`, `pdf`, `pptx` or `xlsx` skill for the file type. Do not work from this note.
23
+ 2. If the plugin is not installed, tell the user the two commands above. Then ask if they want to install it first.
24
+ 3. If the user cannot install it, say what you can do without it. For example, write the content as Markdown and let the user convert it.
@@ -44,7 +44,7 @@ description: >-
44
44
  constraints pattern, and surfaces anti-patterns around breaking changes.
45
45
  </commentary>
46
46
  </example>
47
- model: inherit
47
+ model: sonnet
48
48
  ---
49
49
 
50
50
  **Note: The current year is 2026.** Use this when searching for recent documentation and patterns.
@@ -46,7 +46,7 @@ description: >-
46
46
  finds slow query examples, and identifies existing performance tests.
47
47
  </commentary>
48
48
  </example>
49
- model: inherit
49
+ model: sonnet
50
50
  ---
51
51
 
52
52
  **Note: The current year is 2026.** Use this when referencing recent patterns or documentation.
@@ -48,7 +48,7 @@ description: >-
48
48
  considerations: caching, N+1 queries, database indexes, async processing.
49
49
  </commentary>
50
50
  </example>
51
- model: inherit
51
+ model: sonnet
52
52
  ---
53
53
 
54
54
  **Note: The current year is 2026.** Use this when referencing recent patterns or documentation.
@@ -0,0 +1,260 @@
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 render --roadmap <slug> | --all [--out <html>] # page: ~/.caddis/roadmap-pages/<repo>/<slug>.html; hub of every repo: ~/.caddis/roadmap-pages/index.html
74
+ python scripts/caddis_roadmap.py next --roadmap <slug> [--json] # exit 0 found, 1 nothing ready (reasons)
75
+ python scripts/caddis_roadmap.py check --roadmap <slug> # parse errors, unknown refs or types
76
+ python scripts/caddis_roadmap.py set --roadmap <slug> --id S1 --col Status|Owner|Target --value <v> [--by-owner] [--reason "<r>"]
77
+ python scripts/caddis_roadmap.py block --roadmap <slug> --id S2 --type <type> --reason "<text>" [--check "<cmd>" --by-owner]
78
+ python scripts/caddis_roadmap.py unblock --roadmap <slug> --id S2 --type <type> --reason "<text>" [--check "<cmd>"] [--by-owner]
79
+ python scripts/caddis_roadmap.py rollup --roadmap <slug> --id S1
80
+ python scripts/caddis_roadmap.py recheck --roadmap <slug>
81
+ ```
82
+
83
+ `next` picks the first milestone in table order that is `todo` or `in-progress`, whose `Depends on` are all `done` or
84
+ `not-needed`, with no Blockers row, Owner `runner` or empty, and a phase not yet merged. It returns that phase.
85
+
86
+ ## Rules a runner trusts
87
+
88
+ - **Only `rollup` writes `done`.** `set --value done` exits 4. `rollup` needs every phase merged on origin, every
89
+ `Depends on` milestone `done` or `not-needed`, and no open blocker other than `data-condition` (exit 1 names it),
90
+ then runs the Exit check (300 s): pass -> `done`; fail with a `data-condition` blocker -> `code-done/data-pending`;
91
+ fail otherwise -> an `external` blocker
92
+ `exit check failing (exit <n>)` (never the command's output). A milestone with no phase refs and no Exit check is
93
+ refused (exit 4).
94
+ - **Owner-only changes:** `set` Status `not-needed`, any `set` Owner, `unblock` of an `owner-gate`, and `block --check`
95
+ (recheck runs that command unattended later). They need `--by-owner` and are refused headless: `CADDIS_HEADLESS`
96
+ or `CLAUDECODE` in the environment (even empty), stdin not a terminal, or on
97
+ Windows stdin not a console (a NUL stdin is headless).
98
+ - **`unblock` removes one blocker.** When two rows share milestone, type and reason, it exits 4 until `--check`
99
+ names the row's Check text.
100
+ - **`recheck` clears `data-condition` blockers**, each row whose own Check passed (120 s), **and `calendar`
101
+ blockers whose Check date has passed** (UTC; no command runs). A `calendar` row whose Check is `-` stays until
102
+ someone runs `unblock`. Other types stay.
103
+ - **`not-needed` needs a reason.** `set --col Status --value not-needed --by-owner` also needs
104
+ `--reason "dropped: <why>"` or `--reason .caddis/parking-lot/<file>.md` (the file must be on origin). A reason with
105
+ `|` or a line break exits 4. The same docs PR adds `- <id> not-needed: <reason> (YYYY-MM-DD)` under `## Notes`
106
+ (the section is added at the end when missing).
107
+ - **Old blockers are listed.** `status` (and so the runner's toast) ends with
108
+ `old blockers: <id> <type> (<N> days), ...`, oldest first, for each blocker row older than 14 days. The age is the
109
+ date of the newest commit on origin that added that exact row (`git log -S`): the roadmap never stores a date, and
110
+ a row that was cleared and added again counts from the new add.
111
+ - **Commands run the merged code.** Editing a check script or a Check in the working tree changes nothing until it
112
+ merges.
113
+
114
+ ## How a write lands
115
+
116
+ `set`, `block`, `unblock`, `rollup` and `recheck` take the write lock (after 30 s, a lock whose holder is dead on
117
+ this host, or an empty or non-JSON lock older than 60 s, is taken over), branch `roadmap/<slug>-<id>-<utc>` from
118
+ `origin/<default>` in a temp worktree, commit `docs(roadmap): <slug> <id> <change>`, push, open a PR, and hand it to
119
+ `caddis_merge_chain.py --docs-only`. `gh` uses the account in `GH_TOKEN` when it is set. Git network calls time out
120
+ 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
121
+ and `would run: <cmd>` lines, and runs and writes nothing. Exit codes: 0 clean, 1 blocked, 3 could not run, 4 error
122
+ (malformed roadmap, refused change, bad usage).
123
+
124
+ ## The runner
125
+
126
+ `roadmap-run.ps1 -Roadmap <slug>` (a shim over `scripts/caddis_roadmap_run.py --roadmap <slug>`) runs the roadmap
127
+ unattended; `-Plan <slug>` runs one plain plan instead (the two are exclusive). It starts only on the roadmap's
128
+ `Runner host` (else exit 3). Each iteration:
129
+
130
+ 1. `next`. Nothing ready: stop `BLOCKED nothing ready` (exit 1); the notification carries the `status` line. When
131
+ the roadmap is finished (every milestone `done` or `not-needed`) the runner also removes its worktree if it is
132
+ clean (only session relay files left), and keeps it, saying so in the stop line, if not. With the worktree gone it
133
+ also deletes the local `caddis/roadmap/<slug>/*` branches merged into `origin/<default>` and names the others.
134
+ 2. Print `next: <milestone> <plan>#<phase>` (`next` and the batch claim happen under one repo-level claim lock).
135
+ A phase whose `**Lane:**` line does not start with this engine (`claude`, `agy`, `codex`), names a cloud session
136
+ or the owner, or that has **no Lane line**, is not run (fail closed): the runner adds
137
+ `block --type owner-gate --reason "phase <n> lane: <lane>"` (`lane: none` when there is no line) and goes on.
138
+ 3. A `todo` milestone is set `in-progress` (a docs PR). Every session of a roadmap runs in ONE dedicated worktree,
139
+ `%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
140
+ session, reused on restart, recorded in the runner folder, never the `-Repo` checkout (plan mode keeps working in
141
+ the given checkout). In that worktree the runner makes a fresh branch `caddis/roadmap/<slug>/<plan>-p<n>` from
142
+ `origin/<default>` (it refuses a branch of that name already on origin that it did not make) and ONE session runs
143
+ `assets/plan-resume-prompt.md` on it (if a blocked session left uncommitted work in the worktree, the runner
144
+ stops `BLOCKED roadmap worktree has uncommitted work from <branch>` instead, and a session branch that already
145
+ holds commits not on `origin/<default>` is never reset: `BLOCKED <branch> has unpublished commits from an earlier
146
+ session`; the owner decides), with `{{slug}}` = the plan and
147
+ `{{phase}}` = the phase. Every session gets its own fresh branch (also after an OWNER-GATE or BLOCKED one), so a
148
+ blocked phase's commits never reach another PR. Sessions stay on that branch and never push.
149
+ 4. Relay `STATUS: CONTINUE` with new commits on that branch: the **runner** checks that the session is still on it
150
+ and did not touch `.caddis/roadmaps/` (else `FAILED`, nothing published), then, in this order: writes a persistent
151
+ claim `<git common dir>/caddis-runner/claims/<plan>#<n>.json`, pushes the branch, opens a PR (`gh api`, the account
152
+ from `GH_TOKEN`) titled `<plan> phase <n>: <phase title>`, writes the PR URL into the claim, records the wait in its
153
+ runner folder's `waiting.json` (ref, PR, milestone, branch, claim and its hash, the roadmap hash; never in the last
154
+ stop), and stops `BLOCKED waiting for merge <plan>#<phase> <pr url>` (exit 1). A run killed between the claim and
155
+ `waiting.json` leaves a claim nothing names: the next start stops on it as an orphaned claim. On restart, GitHub
156
+ must confirm the PR merged before any branch is deleted; a done Tracker row with the PR still open or closed
157
+ unmerged stops `BLOCKED <ref> Tracker row is done but PR <url> is not merged` and keeps the branch, the claim and
158
+ `waiting.json`. The claim keeps the phase held (`next`, other roadmaps and plan mode skip or refuse it) until the PR
159
+ merges or closes. A run started while the PR still waits stops at once (`BLOCKED still waiting for merge of <ref>:
160
+ <url>`) and changes nothing. Once it merges, the recheck task restarts the runner. The restart first checks that the merged
161
+ origin roadmap is the one it published against (a hash, then the frozen tables; any difference is `FAILED roadmap
162
+ changed`), rolls the milestone up FIRST (when every phase is merged), then removes the claim and the phase's
163
+ branch (locally and on origin: whoever makes a branch removes it when its PR merges), and goes on with `next`.
164
+ An own claim that neither the waiting record nor `pending-rollup.json` names (a run killed between the claim and
165
+ the wait) stops the start `BLOCKED orphaned claim <file> from a killed run`; the owner checks the branch and its PR
166
+ and deletes the claim.
167
+ CONTINUE with the phase already merged on origin: `rollup` when every phase is merged, then back to 1. CONTINUE
168
+ with no new commits and nothing merged stops FAILED. `STATUS: OWNER-GATE <r>` becomes an `owner-gate` blocker,
169
+ `STATUS: BLOCKED <r>` an `external` one; then back to 1. `STATUS: FAILED` stops.
170
+ 5. Every `**Risk:** high` phase done on the branch and not yet reviewed clean (record in the runner's own folder:
171
+ `<git common dir>/caddis-runner/roadmap-<slug>/reviewed/<plan>.json`; another runner's record does not count) is
172
+ reviewed BEFORE step 4 writes the claim and publishes
173
+ (`caddis_review.py --plan --phase --runs 2`): exit 0 publishes;
174
+ exit 1 starts one fix session, then one more review; exit 1 again stops `BLOCKED review`, exit 3, 4 or any other
175
+ code `BLOCKED review not run` (neither restartable); the review cost counts against the USD caps.
176
+
177
+ Stops (each one notifies and releases the lock): a STOP file (`~/.caddis/STOP` or `.local/STOP`), any cap (10 h,
178
+ 12 sessions, 120 min and USD 25 per session, USD 150 total; per run, in memory; the global agy spend cap of
179
+ `caddis_spend.py` still counts across runs), a lock or batch claim that cannot be written
180
+ (`BLOCKED claim not written`, before any session), the agy spend cap refusing a reservation, three sessions
181
+ with no newly merged phase, a roadmap write that fails, and the **snapshot guard**: one baseline for the whole run.
182
+ The Milestones and Blockers tables (every cell of every row, rows added, removed or moved) and the `Runner host` line
183
+ are frozen. Every origin read (each `next`, after the runner's own writes, after each session) is compared with the
184
+ previous one; the only differences allowed are the exact edits the runner itself made and recorded for that read: a
185
+ Status it set, a blocker row it added, and what a rollup's audit entry (`changes.jsonl`) says it actually wrote.
186
+ Anything else stops the run `FAILED roadmap changed`.
187
+ Each runner has its own state folder, `<git common dir>/caddis-runner/roadmap-<slug>/` (plan mode: `plan-<slug>/`):
188
+ lock, last stop, options, branches, worktree path, the saved roadmap text. `claims/` and `claim.lock` are shared at
189
+ the top level. The runner hashes its own folder (recursively) and its own claims before and after every session; a
190
+ 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
191
+ waiting-for-merge stop records is still there with the same content; a missing record, claim or hash is
192
+ `FAILED own claim missing or changed` (not restartable). During the upgrade window a plan-mode lock of the base
193
+ version (`.caddis/plans/.local/<slug>/runner.lock`, read only) still holds its plan while its pid runs and the lock
194
+ file is newer than that process's start (else it is ignored). Roadmap slugs
195
+ match `^[a-z0-9][a-z0-9-]{0,62}$` everywhere (both scripts and `caddis_roadmap.py`). The launch options are kept
196
+ in memory and re-written at every stop, so a changed options file can never raise the caps.
197
+
198
+ The runner never passes `--by-owner`. Every session and every `caddis_roadmap.py` call it starts gets
199
+ `CADDIS_HEADLESS=1`. The runner lock is `<git common dir>/caddis-runner/roadmap-<slug>/runner.lock` (a plan-mode
200
+ runner: `plan-<slug>/runner.lock`), so every worktree sees it. It holds `{"host", "pid", "started_utc", "batch", "max_hours"}`. On
201
+ this host it is stale when its pid is not running, or the process now holding that pid started after `started_utc`
202
+ (pid reuse after a reboot; when the start time cannot be read, it counts as live). Another host's lock is stale when it
203
+ 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
204
+ plan-mode lock holds the whole plan (`batch` `<plan>#*`). `next` skips a phase another live lock holds, and plan mode
205
+ refuses a plan that a roadmap lock names, at start and before every session. `-DryRun` prints the chosen batch,
206
+ the session command, the worktree and any pending rollup, skips the host check, writes no files and starts nothing; it
207
+ does run `git fetch`, which updates the remote-tracking refs. `-GhAccount <login>` (recorded in the launch options,
208
+ passed on by the recheck task and in the printed task) makes every gh call of the runner and of `--last-stop` use
209
+ GH_TOKEN from `gh auth token -u <login>`; on a machine with several gh logins, give the repo's account. A gh call that
210
+ cannot answer (gh missing, 401, 404) is an error, never "not merged": the run stops
211
+ `BLOCKED gh cannot see <repo> with account <login>`, notified and recorded.
212
+
213
+ `roadmap-recheck.ps1 -Roadmap <slug> -Repo <path>` starts `roadmap-run.ps1 -Roadmap <slug>` detached only when:
214
+ the runner's last recorded stop (next to its lock) is `BLOCKED nothing ready` or there is none, and `recheck`
215
+ cleared a blocker; or the last stop is `BLOCKED waiting for merge` and GitHub says that PR merged
216
+ (`--last-stop` asks GitHub only and fetches nothing; the restarted runner checks the Tracker row on origin under its
217
+ lock; in that tick recheck does NOT run `recheck`, because a cleared blocker would change the roadmap the restart
218
+ checks against its hash). If GitHub says merged but the Tracker row is not done, the restart
219
+ 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
220
+ another runner's lock or claim held; once every one of those holders is gone, `--last-stop` says `freed:` and recheck
221
+ restarts the runner even with no blocker cleared. A plan in flight anywhere (a live lock or a claim on any of its
222
+ phases) holds the whole plan for every other roadmap and for plan mode. At the top of each run the runner rolls up
223
+ every milestone that waits only for its rollup (Status not finished, every phase merged, and the same rule as
224
+ `next`: no blocker, dependencies finished, Owner the runner); `--last-stop` says `rollup-ready:` for such a milestone,
225
+ so recheck restarts the runner. A sweep rollup that writes nothing stops `BLOCKED rollup of <id> wrote nothing`
226
+ (recorded, not restartable) and is not retried or reported until the roadmap changes. The dry run lists them first. If
227
+ the owner edits the roadmap during a merge wait, the restart stops `FAILED roadmap changed ...` and names the
228
+ recovery: `roadmap-run.ps1 -Roadmap <slug> -AcceptRoadmap` (owner only, refused headless) takes the runner lock (a live
229
+ runner refuses it), prints the diff from the published baseline, asks you to type `accept` (or `--yes`), and accepts
230
+ 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
231
+ 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
232
+ claude-engine roadmap needs no spend-cap file). A restart after a merge writes `pending-rollup.json` in its runner
233
+ folder, runs the rollup first, then removes the claim and the branch, and only then the file. Every stop records
234
+ itself: a failed rollup, a crash or a STOP file before that is not restartable (the owner decides), and the next start
235
+ finds the file and does the rollup first. A merged
236
+ branch checked out in another checkout is kept locally (HEAD never moves outside the roadmap worktree).
237
+ Any FAILED stop (the snapshot guard, a crash), a cap or a STOP file needs the owner: recheck prints why and starts
238
+ nothing; the owner's next manual run records a new last stop. A runner records the last stop `running` as soon as it
239
+ holds the lock, so a run killed without a stop (a reboot, a kill) also needs the owner; an unexpected exception
240
+ records `FAILED crash: <type>` and notifies once. Recheck restarts with the launch options the last run recorded
241
+ (engine, model, caps), each cap clamped to at most the built-in default (the options file is writable), never higher.
242
+ `roadmap-run.ps1 -Roadmap <slug> -PrintRecheckTask` prints a PowerShell block (`New-ScheduledTaskAction`,
243
+ `New-ScheduledTaskTrigger` every 2 hours, `Register-ScheduledTask`) for the owner to paste; it registers nothing. The
244
+ task runs only while you are logged on unless you add `-User` and `-Password` to `Register-ScheduledTask` (or set that
245
+ option in Task Scheduler).
246
+
247
+ ## Limits
248
+
249
+ Sessions are cooperative (our own sessions): the runner guards against mistakes and runaway spend, not against a
250
+ deliberately hostile session, which runs as the same OS user and could change anything the runner can. The state
251
+ tripwire catches accidents in a runner's own state only. In particular, a session in another worktree of the repo can
252
+ delete or rewrite ANOTHER runner's claim or lock under `<git common dir>/caddis-runner/`; that is not caught while it
253
+ 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
254
+ worktree, removed when the roadmap is finished and the worktree is clean.
255
+
256
+ Owner-only rules are enforced by this tool; a direct PR that edits `roadmap.md` bypasses them; the runner stops
257
+ when an owner-gate row disappears; repos that need more protect their default branch.
258
+ On Windows, a check's grandchild started in the instant before the job object is attached can escape the tree kill;
259
+ checks are owner-written, so this is accepted. Headless detection cannot see a process started in a fresh console with
260
+ 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.