@caddis/cli 0.4.53 → 0.4.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundles/antigravity-plugin/guard_agy.py +184 -0
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_budget.py +283 -0
- package/bundles/antigravity-plugin/scripts/caddis_checkout_guard.py +5 -1
- package/bundles/antigravity-plugin/scripts/caddis_fleet.py +439 -0
- package/bundles/antigravity-plugin/scripts/caddis_gate.py +29 -0
- package/bundles/antigravity-plugin/scripts/caddis_lanes.py +127 -10
- package/bundles/antigravity-plugin/scripts/caddis_notify.py +5 -1
- package/bundles/antigravity-plugin/scripts/caddis_roadmap.py +56 -3
- package/bundles/antigravity-plugin/scripts/caddis_roadmap_page.py +676 -0
- package/bundles/antigravity-plugin/scripts/caddis_roadmap_run.py +39 -6
- package/bundles/antigravity-plugin/scripts/caddis_routing.py +4 -4
- package/bundles/antigravity-plugin/skills/_registry.md +2 -0
- package/bundles/antigravity-plugin/skills/catchup/SKILL.md +4 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +6 -0
- package/bundles/antigravity-plugin/skills/fleet-close/SKILL.md +167 -0
- package/bundles/antigravity-plugin/skills/fleet-open/SKILL.md +110 -0
- package/bundles/antigravity-plugin/skills/gh-cli/SKILL.md +2 -1
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +101 -1
- package/bundles/antigravity-plugin/skills/migrate-dir/SKILL.md +3 -2
- package/bundles/antigravity-plugin/skills/pr-merge/SKILL.md +20 -6
- package/bundles/antigravity-plugin/skills/refactoring/SKILL.md +2 -1
- package/bundles/antigravity-plugin/skills/review-pr/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/roadmap/SKILL.md +1 -0
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +2 -2
- package/bundles/antigravity-plugin/skills/ship-merge/SKILL.md +9 -5
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +3 -2
- package/bundles/codex/.codex/skills/coding/refactoring/SKILL.md +2 -1
- package/bundles/codex/.codex/skills/devops/gh-cli/SKILL.md +2 -1
- package/bundles/codex/.codex/skills/workflow/roadmap/SKILL.md +1 -0
- package/bundles/manifest.json +2 -2
- package/package.json +1 -1
|
@@ -144,7 +144,10 @@ ENGINES: dict[str, dict] = {
|
|
|
144
144
|
PROMPT_ON_STDIN = {"claude"}
|
|
145
145
|
STUCK_SESSIONS = 3
|
|
146
146
|
PLAN_PROMPT_NAME = "plan-resume-prompt.md"
|
|
147
|
-
|
|
147
|
+
# CLAUDE_CODE_FORK_SUBAGENT=0: since 2.1.302 fork subagents are on by default in `claude -p`. A fork can run in the
|
|
148
|
+
# background, and a print-mode session can end before it finishes, so its work is lost; the session prompt already
|
|
149
|
+
# asks for foreground work only. Normal subagents stay available.
|
|
150
|
+
HEADLESS_ENV = {"CADDIS_HEADLESS": "1", "CLAUDE_CODE_FORK_SUBAGENT": "0"}
|
|
148
151
|
ROADMAP_WRITE_TIMEOUT_S = caddis_roadmap.MERGE_CHAIN_TIMEOUT + 600 # a backstop: every call inside is bounded
|
|
149
152
|
RUNNING = "running" # the last-stop record while a runner holds the lock; a killed run leaves it (needs the owner)
|
|
150
153
|
GH_STOP_PREFIX = "BLOCKED gh cannot see" # a gh error at a restart: waiting.json stays, a later tick may resume
|
|
@@ -192,7 +195,11 @@ def build_command(engine: str, model: str, prompt: str, *, usd: float, timeout_m
|
|
|
192
195
|
raise ValueError(f"unknown engine {engine!r}; known: {', '.join(ENGINES)}")
|
|
193
196
|
exe = list(engine_cmd) if engine_cmd else [engine]
|
|
194
197
|
if engine == "claude":
|
|
195
|
-
|
|
198
|
+
# The mode is explicit: since Claude Code 2.1.302 a headless run with no mode starts in `default` (third-party
|
|
199
|
+
# providers) and no longer in auto mode, so a machine without `defaultMode` in its settings would stop at
|
|
200
|
+
# every permission prompt. The session runs in its own worktree under the governor, and the guard still runs.
|
|
201
|
+
tail = [*exe, "-p", "--permission-mode", "bypassPermissions", "--model", model, "--output-format", "json",
|
|
202
|
+
"--max-budget-usd", _usd(usd)]
|
|
196
203
|
elif engine == "agy": # probe 5: --model must come before --print
|
|
197
204
|
tail = [*exe, "--model", model, "--print", prompt, "--output-format", "json",
|
|
198
205
|
"--print-timeout", f"{max(1, int(timeout_min))}m"]
|
|
@@ -481,6 +488,11 @@ def _create_with(path: Path, text: str) -> None:
|
|
|
481
488
|
os.link(tmp, path)
|
|
482
489
|
except FileExistsError:
|
|
483
490
|
raise
|
|
491
|
+
except PermissionError as exc:
|
|
492
|
+
if not caddis_roadmap._WINDOWS:
|
|
493
|
+
raise
|
|
494
|
+
# Windows refuses while the old lock's delete is still pending: the name is taken, for now.
|
|
495
|
+
raise FileExistsError(str(path)) from exc
|
|
484
496
|
except OSError: # no hard links on this file system: O_EXCL, then the text
|
|
485
497
|
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
486
498
|
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
@@ -1187,13 +1199,33 @@ def run(*, repo: Path, plan: str | None = None, roadmap: str | None = None, caps
|
|
|
1187
1199
|
if value:
|
|
1188
1200
|
ctx.masks.append(value)
|
|
1189
1201
|
if roadmap is not None:
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1202
|
+
rm_cmd = list(roadmap_cmd or default_roadmap_cmd())
|
|
1203
|
+
try:
|
|
1204
|
+
return _run_roadmap(ctx, template, dry_run, after_session, rm_cmd,
|
|
1205
|
+
hostname or socket.gethostname(), publish or default_publish,
|
|
1206
|
+
pr_merged or default_pr_merged, worktree_root,
|
|
1207
|
+
delete_remote_branch or default_delete_remote_branch, pr_closed or default_pr_closed)
|
|
1208
|
+
finally:
|
|
1209
|
+
if not dry_run:
|
|
1210
|
+
_render_page(ctx, rm_cmd) # the page shows where the run stopped and what waits for the owner
|
|
1194
1211
|
return _run_plan(ctx, template, dry_run, after_session)
|
|
1195
1212
|
|
|
1196
1213
|
|
|
1214
|
+
PAGE_RENDER_TIMEOUT_S = 120
|
|
1215
|
+
|
|
1216
|
+
|
|
1217
|
+
def _render_page(ctx: "_Ctx", roadmap_cmd: Sequence[str]) -> None:
|
|
1218
|
+
"""Re-render the local roadmap page (roadmap-page Phase 3). Best effort: a failure is one stderr line, never a stop."""
|
|
1219
|
+
argv = [*roadmap_cmd, "render", "--roadmap", ctx.target.slug, "--root", str(ctx.repo)]
|
|
1220
|
+
try:
|
|
1221
|
+
proc = subprocess.run(argv, cwd=ctx.repo, env=headless_env(), stdin=subprocess.DEVNULL, capture_output=True,
|
|
1222
|
+
text=True, encoding="utf-8", errors="replace", timeout=PAGE_RENDER_TIMEOUT_S)
|
|
1223
|
+
if proc.returncode != 0:
|
|
1224
|
+
print(f"caddis_roadmap_run: page not rendered (exit {proc.returncode})", file=sys.stderr)
|
|
1225
|
+
except Exception as exc: # a page must never change how a run ends
|
|
1226
|
+
print(f"caddis_roadmap_run: page not rendered: {type(exc).__name__}", file=sys.stderr)
|
|
1227
|
+
|
|
1228
|
+
|
|
1197
1229
|
def _dry_run_tail(ctx: _Ctx, template: Path, prompt: str, cwd: Path | None = None) -> None:
|
|
1198
1230
|
caps = ctx.caps
|
|
1199
1231
|
cmd = build_command(ctx.engine, ctx.model, "<resume prompt>", usd=min(caps.max_usd_per_session, caps.max_usd_total),
|
|
@@ -2042,6 +2074,7 @@ def _run_roadmap(ctx: _Ctx, template: Path, dry_run: bool, after_session: Callab
|
|
|
2042
2074
|
finally:
|
|
2043
2075
|
s.record.setdefault("status", "FAILED")
|
|
2044
2076
|
ctx.log(s.record)
|
|
2077
|
+
_render_page(ctx, roadmap_cmd)
|
|
2045
2078
|
if after_session is not None:
|
|
2046
2079
|
after_session(sessions)
|
|
2047
2080
|
except _Stop as stop:
|
|
@@ -16,14 +16,14 @@ import oss_model
|
|
|
16
16
|
|
|
17
17
|
# agy is Flash-only on some machines and is good only at small, exact, mechanical cards (112 of 114 passed
|
|
18
18
|
# in the 2026-10-08 UI-upgrade run) and CI watching. Give design-fidelity, cross-file refactor and judgment
|
|
19
|
-
# work to
|
|
19
|
+
# work to claude. Codex left the routing on 2026-10-12 (owner); a config may still name it. See .caddis/kb/agy-headless-implementer-limits.md. A separate `design` role is an
|
|
20
20
|
# owner decision: it changes the routing line every session prints.
|
|
21
21
|
DEFAULT_ROUTING: dict[str, list[str]] = {
|
|
22
22
|
"critical": ["opus"],
|
|
23
23
|
"light": ["sonnet"],
|
|
24
|
-
"coding": ["agy"
|
|
25
|
-
"review": ["
|
|
26
|
-
"consult": ["agy", "glm"
|
|
24
|
+
"coding": ["agy"],
|
|
25
|
+
"review": ["deepseek", "glm"], # owner order 2026-10-16; an agy review runs through caddis_spend.py
|
|
26
|
+
"consult": ["agy", "glm"],
|
|
27
27
|
"ci-watch": ["agy"],
|
|
28
28
|
"mechanical": ["haiku"],
|
|
29
29
|
}
|
|
@@ -84,6 +84,8 @@
|
|
|
84
84
|
| Digress | `digress/` | Park the current workstream on the stack and switch to a new task — without losing the original |
|
|
85
85
|
| Excalidraw Db | `excalidraw-db/` | Turn a SQL artifact into an Excalidraw diagram for a design review / ARB pack / slide — higher-level, drag-the-boxes format |
|
|
86
86
|
| Feature Plan | `feature-plan/` | Create a phased, TDD-structured implementation plan that acts as the durable spine for multi-session work |
|
|
87
|
+
| Fleet Close | `fleet-close/` | Close a fleet of sessions — every session writes its handoff and acks, then print one restart sheet for the owner |
|
|
88
|
+
| Fleet Open | `fleet-open/` | Restore one role from the fleet restart sheet in a new session — name check, relay, tasks, peer greeting, watchers |
|
|
87
89
|
| Gate Preflight | `gate-preflight/` | Deterministic headless preflight — validates a plan against the actual codebase, self-contained (never depends on a skill activating) |
|
|
88
90
|
| Gate Review | `gate-review/` | Deterministic headless code review — adversarial diff review, self-contained (never depends on a skill activating) |
|
|
89
91
|
| Glossary | `glossary/` | Define the words this project invented, in one place — .caddis/kb/GLOSSARY.md |
|
|
@@ -46,6 +46,10 @@ Keep it short enough to read at a glance. Roughly:
|
|
|
46
46
|
- **The next step**, if one is recorded. Say which file it came from.
|
|
47
47
|
- **Uncommitted work**, if `git status` is dirty — this is the thing most likely to be lost, and
|
|
48
48
|
the state file does not track it. Check it; do not assume clean.
|
|
49
|
+
- **The roadmap page**, when the repo has a roadmap (`.caddis/roadmaps/`): re-render it with
|
|
50
|
+
`python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_roadmap.py" render --all` (best effort) and give the path it prints
|
|
51
|
+
(`~/.caddis/roadmap-pages/<repo>/<slug>.html`); it shows every session waiting for the owner. The hub of every
|
|
52
|
+
repo on this machine is `~/.caddis/roadmap-pages/index.html`.
|
|
49
53
|
|
|
50
54
|
Leave out anything already done unless the user asks. "What is finished" is a different question
|
|
51
55
|
from "where are we", and the completed list is usually the longest part of both files.
|
|
@@ -54,6 +54,12 @@ points), read it first. It's a free, token-zero project fingerprint that anchors
|
|
|
54
54
|
exists instead of assumed. If the work fits comfortably in one session, say so and offer to just do it
|
|
55
55
|
instead of planning. Only produce a plan for genuinely multi-phase work.
|
|
56
56
|
|
|
57
|
+
**Check upstream first when the plan builds on Claude Code behaviour** (hooks, headless `claude -p`, permission
|
|
58
|
+
modes, subagents, worktrees, plugins and skills, Remote Control, background sessions). Read
|
|
59
|
+
`.caddis/kb/claude-code-upstream-watch.md` if the repo has it, and the latest Claude Code changelog for that area
|
|
60
|
+
(Improvements, Bug fixes, In development). Do not plan a fix or a workaround for something Claude Code has already
|
|
61
|
+
changed, fixed or announced; cite the release in **Constraints & decisions** instead.
|
|
62
|
+
|
|
57
63
|
### Evidence gate — do not write plan content while a BLOCKER is unresolved
|
|
58
64
|
This is the direct counter to the failure
|
|
59
65
|
mode that makes a plan dangerous: **you write the spec, so you cannot see its gaps.** A phase built
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fleet-close
|
|
3
|
+
description: Close a fleet of sessions — every session writes its handoff and acks, then print one restart sheet for the owner
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /caddis:fleet-close — close every session, collect acks, print the restart sheet
|
|
7
|
+
|
|
8
|
+
Use this when **several** sessions must stop together (a machine restart, a long break, a context
|
|
9
|
+
reset for the whole fleet). For one session, run `/caddis:handoff` instead. This session is the
|
|
10
|
+
**coordinator**: it asks every other session to write its own handoff, collects one ack from each,
|
|
11
|
+
writes its own handoff last, and prints a restart sheet. The owner starts each new session from that
|
|
12
|
+
sheet, and each new session runs `/caddis:fleet-open <role>`.
|
|
13
|
+
|
|
14
|
+
Every fleet rule (names, ack format, order, checks) lives in
|
|
15
|
+
`${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py`; this command only calls it. Run the steps in order.
|
|
16
|
+
|
|
17
|
+
**Safety rules for the whole command** (a wrong close loses another session's relay or tasks):
|
|
18
|
+
- Never write, edit or move another session's relay, tasks file or watcher files. Each session writes
|
|
19
|
+
its own; you only collect acks.
|
|
20
|
+
- Never stop, end or kill another session, and never tell one to exit. The owner closes terminals
|
|
21
|
+
after reading the sheet.
|
|
22
|
+
- Never wait with a foreground `sleep`. Every wait is a background timer (`run_in_background`).
|
|
23
|
+
- A session that does not ack is never guessed: its row says `no ack`, and the owner runs
|
|
24
|
+
`/caddis:handoff` in it by hand.
|
|
25
|
+
|
|
26
|
+
The ack format (one line in a fenced block, then the session's full resume prompt):
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
FLEET-ACK name=<n> role=<r> machine=<m> folder=<abs> relay=<path> tasks=<path|none> mode=<new|keeps-running|restore-on:<machine>|nothing> owns-watchers=<yes|no> worktrees=<abs;abs|none>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Step 1 — list the fleet
|
|
33
|
+
|
|
34
|
+
Run `ListAgents`. Your own name is in its header line (`This session is <name>`): you are the
|
|
35
|
+
coordinator. Candidates:
|
|
36
|
+
- every local interactive or background session, and every Remote Control session that is not
|
|
37
|
+
`offline`: each gets the close message in Step 3;
|
|
38
|
+
- every cloud session: it is listed as `keeps-running` and gets **no** message (it keeps running in
|
|
39
|
+
the cloud). In Step 4 you save its ack yourself:
|
|
40
|
+
`FLEET-ACK name=<n> role=<role-of n> machine=cloud folder=- relay=- tasks=none mode=keeps-running owns-watchers=no worktrees=none`,
|
|
41
|
+
then the prompt line `Cloud session: keeps running; nothing to restore.`;
|
|
42
|
+
- `offline` rows and your own row are not candidates.
|
|
43
|
+
|
|
44
|
+
For each candidate, and for yourself, get the role:
|
|
45
|
+
`python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" role-of <name>`. **Two sessions with the same role**
|
|
46
|
+
would get one start line and one new name, and one of them would be lost. So when roles repeat, keep the
|
|
47
|
+
role for the first name (sorted), and give each other one a distinct role `<role>-b`, `<role>-c`, ...; tell
|
|
48
|
+
that session its role in Step 3 (rule 5).
|
|
49
|
+
|
|
50
|
+
Print the candidate list (name, kind, machine, role) before you send anything. If there are no candidates,
|
|
51
|
+
say so, run `/caddis:handoff` for this session only, and **stop**.
|
|
52
|
+
|
|
53
|
+
## Step 2 — make the fleet dir
|
|
54
|
+
|
|
55
|
+
The fleet dir is `.caddis/relay/fleet/<MMDD>-<HH>/` in this repo, at the coordinator's local time:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" new-name fleet
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
prints `fleet-<MMDD>-<HH>`; the dir name is the `<MMDD>-<HH>` part. Create it. It is local and
|
|
62
|
+
gitignored (it holds machine paths). Use the same dir for every later step, even if the hour changes.
|
|
63
|
+
|
|
64
|
+
## Step 3 — send the close message
|
|
65
|
+
|
|
66
|
+
Send each candidate (from Step 1) one `SendMessage`. The first line must be exactly:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
FLEET CLOSE from <coordinator>: write your handoff and ack.
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Then these close rules, in the message:
|
|
73
|
+
1. Run `/caddis:handoff` for **your own** relay only. Never touch another session's files. Skip its
|
|
74
|
+
Step 7 (the `Push this note for another machine?` question): the restart sheet carries your prompt,
|
|
75
|
+
and a question nobody answers would stop you before you ack.
|
|
76
|
+
2. In a shared checkout (its `AGENTS.md` says `Shared checkout: yes`, or `git status` shows changes you
|
|
77
|
+
did not make), skip the steps the handoff checklist skips there.
|
|
78
|
+
3. If you run watchers: copy each watcher script to `.caddis/relay/<your name>-files/watchers/`, and fill
|
|
79
|
+
the relay's `## Watchers` section (per watcher: script path, exact start command, stop condition,
|
|
80
|
+
the action after the stop).
|
|
81
|
+
4. Your resume prompt must load your tasks file, greet peers with `<role> is now <your new name>`, and,
|
|
82
|
+
in a shared checkout, say: "read code facts from origin/main; a worktree needs its own fresh
|
|
83
|
+
dependencies".
|
|
84
|
+
5. Reply to `<coordinator>` with this block (one line, every field filled), then your full resume prompt:
|
|
85
|
+
```
|
|
86
|
+
FLEET-ACK name=<n> role=<r> machine=<m> folder=<abs> relay=<path> tasks=<path|none> mode=<new|keeps-running|restore-on:<machine>|nothing> owns-watchers=<yes|no> worktrees=<abs;abs|none>
|
|
87
|
+
```
|
|
88
|
+
Use `name=<your current name>` exactly as `ListAgents` shows it, and `role=<role>` as given in this
|
|
89
|
+
message (a role that repeats in the fleet gets `<role>-b`, ...). `mode=new` if your work continues
|
|
90
|
+
in a new session; `mode=nothing` if it needs no new session (for example a finished read-only review).
|
|
91
|
+
6. Remote sessions (another machine): also put your open task rows inline after the prompt, as a table
|
|
92
|
+
with exactly this header line (other headers are not read):
|
|
93
|
+
```
|
|
94
|
+
| Old # | Status | Subject |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
```
|
|
97
|
+
and say whether your machine restarts (`mode=restore-on:<machine>`) or not (`mode=keeps-running`).
|
|
98
|
+
7. Do not exit or stop after the ack; the owner closes sessions.
|
|
99
|
+
|
|
100
|
+
## Step 4 — collect acks (first wait: 10 minutes)
|
|
101
|
+
|
|
102
|
+
Start a background timer, never a foreground wait:
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
sleep 600
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
with `run_in_background`. Each ack arrives as a cross-session message. For each one:
|
|
109
|
+
1. Write the message text (from the `FLEET-ACK` line to the end) to a temp file in your scratchpad.
|
|
110
|
+
2. Save it:
|
|
111
|
+
```
|
|
112
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" ack add --dir <fleet dir> --text-file <temp file>
|
|
113
|
+
```
|
|
114
|
+
3. Exit 0 → acked. An error names the missing or bad field: send that session **one** reply that
|
|
115
|
+
quotes the error and asks for a corrected ack. Do not fix the ack yourself.
|
|
116
|
+
4. The ack's `name=` must be the name of the candidate that sent it, and its `role=` the role you gave
|
|
117
|
+
it. If not, send **one** reply asking for a corrected ack (a wrong name makes a phantom row and marks
|
|
118
|
+
the real session `no ack`). A second ack from the same session replaces the first (`ack add` saves by
|
|
119
|
+
name).
|
|
120
|
+
|
|
121
|
+
Also save the cloud-session acks from Step 1 here, the same way.
|
|
122
|
+
|
|
123
|
+
## Step 5 — remind once (second wait: 20 minutes)
|
|
124
|
+
|
|
125
|
+
When the 10-minute timer ends, send each silent candidate **one** reminder:
|
|
126
|
+
`FLEET CLOSE reminder from <coordinator>: your ack is still missing.` Start a 20-minute background
|
|
127
|
+
timer (`sleep 1200`, `run_in_background`) and keep saving acks as in Step 4. When it ends (or every
|
|
128
|
+
candidate has acked), stop waiting. Each session still silent is a `no ack` row (Step 7).
|
|
129
|
+
|
|
130
|
+
**A late ack is never dropped.** Until Step 8 ends, keep saving every ack that arrives, as in Step 4, and
|
|
131
|
+
remove that session from the `--no-ack` list before the next `write`.
|
|
132
|
+
|
|
133
|
+
## Step 6 — your own handoff and ack
|
|
134
|
+
|
|
135
|
+
Run `/caddis:handoff` for this session (the coordinator's own relay). Then write your own ack (the
|
|
136
|
+
`FLEET-ACK` block with your fields and your resume prompt) to a temp file and save it with
|
|
137
|
+
`ack add`, as in Step 4. The coordinator's ack is never skipped. Then record this session's presence
|
|
138
|
+
(your session id is printed at session start as `[caddis] this session's id: <id>`; never guess one):
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" whoami --session-id <id> --name <your name> --root .
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Step 7 — write and check the sheet
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" write --dir <fleet dir> --coordinator <your name> --no-ack <name> [--no-ack <name> ...]
|
|
148
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" check --dir <fleet dir>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Pass `--no-ack <name>` once for each silent session from Step 5, **on every `write`**, and leave it out
|
|
152
|
+
when every session acked (a placeholder would become a fake row) (the no-ack rows are not
|
|
153
|
+
saved between runs). `write` saves `roster.md` and `restart-sheet.md` in the fleet dir.
|
|
154
|
+
|
|
155
|
+
- `check` exit 0 → go to Step 8.
|
|
156
|
+
- `check` exit 1 → send each named session **one** message that quotes its error and asks for a
|
|
157
|
+
corrected ack. Start one 5-minute background timer (`sleep 300`, `run_in_background`) and save the
|
|
158
|
+
corrected acks that arrive. Then run `write` (same `--no-ack` flags) and `check` once more. Errors that
|
|
159
|
+
remain go at the end of `restart-sheet.md` under a heading `## Needs the owner:`, one line each,
|
|
160
|
+
quoted exactly.
|
|
161
|
+
- `write` or `check` exit 3 (no acks, or a file it cannot read) → do not print a sheet. Report the exact
|
|
162
|
+
error, list every ack file in `<fleet dir>/acks/`, and **stop**: the owner decides.
|
|
163
|
+
|
|
164
|
+
## Step 8 — final reply
|
|
165
|
+
|
|
166
|
+
Reply with the whole text of `restart-sheet.md`, then its absolute path, and nothing else. The
|
|
167
|
+
restart closes this session too, so the sheet must be on screen and on disk.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fleet-open
|
|
3
|
+
description: Restore one role from the fleet restart sheet in a new session — name check, relay, tasks, peer greeting, watchers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /caddis:fleet-open — restore a role from the restart sheet
|
|
7
|
+
|
|
8
|
+
A fleet close (`/caddis:fleet-close`) wrote a restart sheet: one row per role, with the folder to
|
|
9
|
+
start `claude` in, a start line that names the new session, and the role's resume prompt. You are the new session
|
|
10
|
+
for one of those rows. This command restores that role in one go.
|
|
11
|
+
|
|
12
|
+
The argument is the role: `$ARGUMENTS`. If it is empty, list the roles of the newest sheet (Step 1)
|
|
13
|
+
and ask which one — do not guess.
|
|
14
|
+
|
|
15
|
+
Every fleet rule lives in `${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py`; this command only calls it.
|
|
16
|
+
Run each step in order. A step that says "stop" ends the command: print why, and nothing else.
|
|
17
|
+
|
|
18
|
+
## Step 1 — find the newest restart sheet that holds the role
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
ls -d .caddis/relay/fleet/*/restart-sheet.md
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The folders are named `<MMDD>-<HH>` (the coordinator's local time). Go from the newest folder to the
|
|
25
|
+
oldest and take the first `restart-sheet.md` with a section `### <n>. <role>`. Read the
|
|
26
|
+
`roster.md` beside it too: its row for the role gives the relay, the tasks file, the folder and the
|
|
27
|
+
mode.
|
|
28
|
+
|
|
29
|
+
- No sheet holds the role → say `No restart sheet holds <role>.` and **stop**.
|
|
30
|
+
- The sheet row says `no new session` (mode `keeps-running`, `nothing` or `no ack`) → say so, quote
|
|
31
|
+
the row's section, and **stop**. A `no ack` row needs `/caddis:handoff` run by hand in the old
|
|
32
|
+
session first.
|
|
33
|
+
|
|
34
|
+
## Step 2 — check this session's name before loading anything
|
|
35
|
+
|
|
36
|
+
Run `ListAgents`. Its header line says `This session is <name>`; that is your name. The sheet row's
|
|
37
|
+
Start line already names the session: `claude --name <expected> "/caddis:fleet-open <role>"`, where
|
|
38
|
+
`<expected>` is `new_name` of the role at the sheet's hour.
|
|
39
|
+
|
|
40
|
+
- `<name>` equals `<expected>` → go on.
|
|
41
|
+
- Otherwise it must at least be a name of this role:
|
|
42
|
+
```
|
|
43
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" role-of <name>
|
|
44
|
+
```
|
|
45
|
+
prints `<role>` (the name is `<role>-<MMDD>-<HH>`) → go on, and note the name differs from the sheet.
|
|
46
|
+
- Anything else → tell the owner to run `/rename <expected>` and then `/caddis:fleet-open <role>`
|
|
47
|
+
again, and **stop**. A session cannot rename itself, and a role loaded under the wrong name is a
|
|
48
|
+
role its peers never reach.
|
|
49
|
+
|
|
50
|
+
Nothing is read into this session (no relay, no tasks, no watcher) until this step passes.
|
|
51
|
+
|
|
52
|
+
## Step 3 — read the relay and run its resume prompt
|
|
53
|
+
|
|
54
|
+
Read the role's relay (the roster's Relay column, `.caddis/relay/<old name>.md`), then follow the
|
|
55
|
+
resume prompt in the sheet section — the fenced block under `### <n>. <role>`. It is the same prompt
|
|
56
|
+
the old session wrote; do what it says, in order.
|
|
57
|
+
|
|
58
|
+
Load the open tasks: every row of the tasks file (roster Tasks file column,
|
|
59
|
+
`.caddis/relay/<old name>.tasks.md`, table `| Old # | Status | Subject |`) becomes one `TaskCreate`
|
|
60
|
+
with the Subject as its subject. A row with Status `in_progress` then gets
|
|
61
|
+
`TaskUpdate` to `in_progress`. A remote role's rows are inline on the sheet under
|
|
62
|
+
`Tasks (inline, remote session):` — load those the same way. Tasks file `none` → 0 tasks.
|
|
63
|
+
Count the tasks you created; Step 6 reports it.
|
|
64
|
+
|
|
65
|
+
## Step 4 — greet the live local peers
|
|
66
|
+
|
|
67
|
+
From the `ListAgents` output of Step 2 (run it again if more than a few minutes passed), take each
|
|
68
|
+
live local session whose role is in the roster (`caddis_fleet.py role-of <their name>`), other than
|
|
69
|
+
your own role. When a role has several names, address the newest:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" newest <their role> --names-file <file with one name per line>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Send each one message, exactly: `<role> is now <name>` (your role, your name). Skip offline sessions
|
|
76
|
+
and cloud sessions. Keep the list of names you greeted for Step 6.
|
|
77
|
+
|
|
78
|
+
## Step 5 — restart the watchers
|
|
79
|
+
|
|
80
|
+
Read the relay's `## Watchers` section. For each entry:
|
|
81
|
+
|
|
82
|
+
1. Print the entry as written (script path, start command, stop condition, after-action).
|
|
83
|
+
2. Check the script path exists (handoff copied it to `.caddis/relay/<old name>-files/watchers/`)
|
|
84
|
+
and that the stop condition and the after-action are both in the relay. If either is missing,
|
|
85
|
+
do not start it: report it as `watcher not started: <reason>` in Step 6.
|
|
86
|
+
3. Start it with its exact start command through Bash with `run_in_background`. Never a foreground
|
|
87
|
+
`sleep` or loop.
|
|
88
|
+
|
|
89
|
+
No `## Watchers` section → 0 watchers.
|
|
90
|
+
|
|
91
|
+
## Step 6 — record the name and report
|
|
92
|
+
|
|
93
|
+
Record this session's presence (your session id is printed at session start as
|
|
94
|
+
`[caddis] this session's id: <id>`; never guess one):
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_fleet.py" whoami --session-id <id> --name <name> --root .
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then print one line, and any `watcher not started:` lines under it:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
[FLEET-OPEN] <role> restored: <n> tasks, <k> watchers, peers greeted: <names>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Rules
|
|
107
|
+
|
|
108
|
+
- One role per session. To restore another role, start another session from the sheet.
|
|
109
|
+
- Never `claude --resume` the old session: the sheet exists so every role starts fresh.
|
|
110
|
+
- Never edit the restart sheet or the roster; they are the record of the close.
|
|
@@ -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 .
|
|
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
|
|
@@ -8,6 +8,9 @@ description: End-of-session handoff — capture exact state so the next session
|
|
|
8
8
|
You are ending a work session. Produce/refresh this workstream's resume doc (`.caddis/relay/<name>.md`) so the next session
|
|
9
9
|
(you, a future you, or another agent on any tool) can resume immediately. This is the anti-context-rot checkpoint.
|
|
10
10
|
|
|
11
|
+
Several sessions to close together? Run `/caddis:fleet-close` in one of them instead; it asks each session to run this
|
|
12
|
+
command and collects one restart sheet.
|
|
13
|
+
|
|
11
14
|
## Step 0 — is this a shared checkout?
|
|
12
15
|
Another session may have uncommitted edits in this checkout. A handoff that commits here can carry
|
|
13
16
|
them into its commit, so check first, before Step 1.
|
|
@@ -33,9 +36,12 @@ them into its commit, so check first, before Step 1.
|
|
|
33
36
|
- Step 3, point 4: run `caddis_rows.py set --file <worktree>/.caddis/open-work.md ...`, and make the
|
|
34
37
|
plan's Tracker edit in `<worktree>/.caddis/plans/<feature>.md`, not in this checkout.
|
|
35
38
|
- Step 4: skip `caddis_tidy.py --apply` (report `skipped: shared checkout`).
|
|
39
|
+
- Step 6: skip `caddis_todo.py reconcile` (report `skipped: shared checkout`; it writes tracked
|
|
40
|
+
`.caddis/todo-list/` files and has no worktree option).
|
|
36
41
|
- Step 6b: skip `session_state.py prune` (report `skipped: shared checkout`).
|
|
42
|
+
- Step 6c: run `--reindex` in the worktree (it edits the tracked `DOC-MAP.md`).
|
|
37
43
|
- The relay stays in this checkout: `.caddis/relay/` is local and gitignored.
|
|
38
|
-
After Step
|
|
44
|
+
After Step 6c, run:
|
|
39
45
|
```
|
|
40
46
|
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_docs_pr.py" finish --worktree <path> --title "docs(handoff): <name> session notes" --body "Handoff doc edits from a shared checkout."
|
|
41
47
|
```
|
|
@@ -44,6 +50,30 @@ them into its commit, so check first, before Step 1.
|
|
|
44
50
|
it never merges. On a failure it prints `PATCH: <path>` with the edits; report that path. Put the
|
|
45
51
|
`PR:` line and the file list in your final report, under `Docs PR needed:`.
|
|
46
52
|
|
|
53
|
+
## Checklist
|
|
54
|
+
The handoff is a fixed checklist. Every row ends as `done`, `skipped: <reason>` or `failed: <error>`, and
|
|
55
|
+
Step 8 prints all 13 in this order. Never drop a row from the report, even when its step did not apply.
|
|
56
|
+
|
|
57
|
+
| # | Row | What it is | Shared checkout (Step 0) |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| 1 | `knowledge-transfer` | Step 1 | edits only in the Step 0 worktree |
|
|
60
|
+
| 2 | `plan-tracker` | the plan's Tracker row (Rules) | edit in the Step 0 worktree |
|
|
61
|
+
| 3 | `open-work-row` | Step 3, point 4: `caddis_rows.py set` | `--file <worktree>/...` |
|
|
62
|
+
| 4 | `relay` | Step 3: the relay, written from scratch with current state only | the relay is local; runs |
|
|
63
|
+
| 5 | `tasks-file` | Step 3c | runs |
|
|
64
|
+
| 6 | `kb-index` | Step 6c: refresh the KB index | in the Step 0 worktree |
|
|
65
|
+
| 7 | `violations-report` | Step 6c: report standing violations, each with a one-line fix command | runs (report only) |
|
|
66
|
+
| 8 | `todo-reconcile` | Step 6: `caddis_todo.py reconcile` | `skipped: shared checkout` |
|
|
67
|
+
| 9 | `usage-record` | Step 6b: `session_state.py usage` | runs |
|
|
68
|
+
| 10 | `tidy-apply` | Step 4: `caddis_tidy.py --apply` | `skipped: shared checkout` |
|
|
69
|
+
| 11 | `state-prune` | Step 6b: `session_state.py prune` | `skipped: shared checkout` |
|
|
70
|
+
| 12 | `prune-list` | Step 6b: `caddis_tidy.py --prune`, list only | runs (lists, never applies) |
|
|
71
|
+
| 13 | `handover-check` | Step 5 | runs |
|
|
72
|
+
|
|
73
|
+
`tidy-apply`, `state-prune` and `todo-reconcile` run only on `SHARED: no`. Handoff never runs
|
|
74
|
+
`caddis_tidy.py --prune --apply` on its own, in any checkout; only the user can ask for it (Step 6b). In a
|
|
75
|
+
shared checkout the report also lists every tracked file Step 1 changed, under `Docs PR needed:`.
|
|
76
|
+
|
|
47
77
|
## Step 1 — capture learnings FIRST (knowledge-transfer)
|
|
48
78
|
**Before** gathering git signals or writing relay.md, decide on `knowledge-transfer` — and the default
|
|
49
79
|
is to run it.
|
|
@@ -107,6 +137,9 @@ Nothing to say is the normal answer. Say nothing and move on — this must not b
|
|
|
107
137
|
<!-- /shared:harness-friction-question -->
|
|
108
138
|
|
|
109
139
|
## Step 3 — write the resume doc (overwrite) with exactly these sections
|
|
140
|
+
Write the relay from scratch with the current state only. Never append to the old text: git keeps the
|
|
141
|
+
history, and the relay has a 4,000-character budget (`caddis_budget.py`).
|
|
142
|
+
|
|
110
143
|
> **Where to write:** this session's own workstream relay, `.caddis/relay/<name>.md`, through the
|
|
111
144
|
> locked script — never with a plain file edit, which would skip the conflict check. `.caddis/relay.md`
|
|
112
145
|
> is a generated index of all workstreams; do not write it by hand.
|
|
@@ -184,7 +217,9 @@ knowledge-transfer: <✓ ran → files written | ✗ skipped → reason>
|
|
|
184
217
|
## Resume prompt
|
|
185
218
|
\`\`\`
|
|
186
219
|
Read .caddis/relay/<name>.md, then the plan it points to. Continue from <phase/step>. Next action: <exact>.
|
|
220
|
+
Load every row of .caddis/relay/<name>.tasks.md with TaskCreate.
|
|
187
221
|
\`\`\`
|
|
222
|
+
(Keep the `Load every row` sentence only when Step 3c wrote `.caddis/relay/<name>.tasks.md`.)
|
|
188
223
|
|
|
189
224
|
STATUS: <CONTINUE | OWNER-GATE <P>-OG<n> | BLOCKED <owner> | FAILED <reason>>
|
|
190
225
|
```
|
|
@@ -195,6 +230,20 @@ Small work that does not meet a trigger skips its block.
|
|
|
195
230
|
|
|
196
231
|
**Trigger: Always.** The relay ends with a status line, the last non-empty line, and it is exactly one of `STATUS: CONTINUE`, `STATUS: OWNER-GATE <P>-OG<n>`, `STATUS: BLOCKED <owner>` or `STATUS: FAILED <reason>`. In Step 3, point 5, write the relay with `python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_workstreams.py" put <name> <temp-file> --require-status --expected-hash <hash>` instead of `write` (in short: put --require-status), with the same hash Step 3 read, so a peer's newer relay is never overwritten. It rejects a relay with no valid status line, and renders the index. When the repo has `.caddis/open-work.md` with `type: open-work` in its frontmatter, also set each open-work row's status with `python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_rows.py" set --file <open-work.md> --id <row id> --col Status=<status>` (in a shared checkout, the copy in the Step 0 worktree). When the status is `done`, also pass `--col Evidence=<commit, PR or test>`: caddis_rows.py refuses `done` with no Evidence.
|
|
197
232
|
|
|
233
|
+
## Step 3c - Save the open tasks
|
|
234
|
+
The task list dies with the session unless it is saved. Write this session's open tasks beside
|
|
235
|
+
the relay:
|
|
236
|
+
```
|
|
237
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/session_state.py" tasks-file --session-id <session-id> --relay <name> --root .
|
|
238
|
+
```
|
|
239
|
+
It reads the `## Tasks` table of `.caddis/session-state/<session-id>.md`, keeps the rows that are
|
|
240
|
+
not `done` or `deleted`, and prints `[TASKS] <n> open task(s) -> <path>` or `[TASKS] none open`
|
|
241
|
+
(with no open task it deletes an old `.caddis/relay/<name>.tasks.md`). When it wrote a file, the
|
|
242
|
+
relay's resume prompt must hold the sentence
|
|
243
|
+
`Load every row of .caddis/relay/<name>.tasks.md with TaskCreate.`: Step 5 blocks a relay whose
|
|
244
|
+
tasks file exists and is not named. If you wrote the relay before running this, rewrite it with
|
|
245
|
+
Step 3, point 5.
|
|
246
|
+
|
|
198
247
|
## Step 4 — tidy finished artifacts
|
|
199
248
|
Flip any plan/prompt's frontmatter `status:` to `done` (or `superseded`) if this session actually
|
|
200
249
|
finished it — that is your judgment call; the script below never makes it and never auto-flips
|
|
@@ -260,6 +309,34 @@ It only lists: session-state files older than 7 days, logs over 5 MB to rotate,
|
|
|
260
309
|
only, never deleted) orphan `.lock` files and lane worktrees older than 7 days. Include its report. **Never
|
|
261
310
|
add `--apply` yourself**; deleting is the user's choice. If they ask, run `--prune --apply`.
|
|
262
311
|
|
|
312
|
+
## Step 6c — KB index and standing violations
|
|
313
|
+
Both scripts fall back to `claude-harness/scripts/check_doc_coverage.py` and `scripts/caddis_tidy.py` in a
|
|
314
|
+
source checkout; a script that is missing is `skipped: <script> not installed`.
|
|
315
|
+
|
|
316
|
+
Checklist row 6, `kb-index` (in a shared checkout, run it in the Step 0 worktree, before the Step 0
|
|
317
|
+
`finish`; it edits `DOC-MAP.md`):
|
|
318
|
+
```
|
|
319
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/check_doc_coverage.py" --reindex
|
|
320
|
+
```
|
|
321
|
+
Checklist row 7, `violations-report` (all three only report; never fix files here):
|
|
322
|
+
```
|
|
323
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_tidy.py" --check
|
|
324
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/check_doc_coverage.py"
|
|
325
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_budget.py" check --root . --report
|
|
326
|
+
```
|
|
327
|
+
`caddis_budget.py` lists each handoff-grown file over its size budget (`OVER <path>: ...; fix: <rule>`); the
|
|
328
|
+
`fix:` text is the one-line fix for that row.
|
|
329
|
+
List each violation they print with a one-line command that fixes it (for example `/caddis:kb` for an
|
|
330
|
+
un-indexed note, or `caddis_tidy.py --apply` for a finished artifact in the wrong folder). A violation is
|
|
331
|
+
not a failure of the handoff: the row is `done` when all three ran. If a script is missing, the row is
|
|
332
|
+
`skipped: <script> not installed`.
|
|
333
|
+
|
|
334
|
+
Then, when the repo has a roadmap (`.caddis/roadmaps/`), re-render its page so phases built by hand show too
|
|
335
|
+
(best effort, not a checklist row; it writes only under `~/.caddis/roadmap-pages/`, never in this checkout):
|
|
336
|
+
```
|
|
337
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_roadmap.py" render --all
|
|
338
|
+
```
|
|
339
|
+
|
|
263
340
|
## Step 7 — push prompt (interactive only)
|
|
264
341
|
If this is an interactive session, ask the user every time — never reuse an earlier answer:
|
|
265
342
|
`Push this note for another machine? (y/N)`
|
|
@@ -272,6 +349,29 @@ index or staged files — after the secret filter passes. Report its one-line re
|
|
|
272
349
|
`y` means do nothing. Headless runs never ask and never push (the script also refuses headless).
|
|
273
350
|
On the other machine, `/catchup <name>` fetches it.
|
|
274
351
|
|
|
352
|
+
## Step 8 — the checklist report
|
|
353
|
+
End the handoff with this block, every row filled with `done`, `skipped: <reason>` or `failed: <error>`:
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
=== HANDOFF CHECKLIST ===
|
|
357
|
+
1 knowledge-transfer done | skipped: <reason> | failed: <error>
|
|
358
|
+
2 plan-tracker <same three forms>
|
|
359
|
+
3 open-work-row <same three forms>
|
|
360
|
+
4 relay <same three forms>
|
|
361
|
+
5 tasks-file <same three forms>
|
|
362
|
+
6 kb-index <same three forms>
|
|
363
|
+
7 violations-report <same three forms>
|
|
364
|
+
8 todo-reconcile <same three forms>
|
|
365
|
+
9 usage-record <same three forms>
|
|
366
|
+
10 tidy-apply <same three forms>
|
|
367
|
+
11 state-prune <same three forms>
|
|
368
|
+
12 prune-list <same three forms>
|
|
369
|
+
13 handover-check <same three forms>
|
|
370
|
+
Shared checkout: yes|no <reason>
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Then, in a shared checkout, `Docs PR needed:` with the `PR:` line and the file list from Step 0.
|
|
374
|
+
|
|
275
375
|
## Rules
|
|
276
376
|
- **One next action, by priority ladder.** `## Next step` names exactly ONE action — not a menu. Pick it in
|
|
277
377
|
order: (1) interrupted mid-phase work → finish + commit it; else (2) the active plan's next not-started
|
|
@@ -24,8 +24,9 @@ It ships with the harness. Try, in order:
|
|
|
24
24
|
git -C <target> status --short
|
|
25
25
|
```
|
|
26
26
|
The migration **refuses a dirty tree** (a rename mixed into unrelated edits is unreviewable, and
|
|
27
|
-
these working trees are shared with parallel sessions). If it's dirty
|
|
28
|
-
|
|
27
|
+
these working trees are shared with parallel sessions). If it's dirty with your own changes: commit
|
|
28
|
+
them first. If it holds changes you did not make, stop and ask the owner: never commit, stash or reset
|
|
29
|
+
them, and never pass `--allow-dirty` on such a tree.
|
|
29
30
|
|
|
30
31
|
## Step 3 — dry run
|
|
31
32
|
```
|