@caddis/cli 0.4.8 → 0.4.10

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.
@@ -31,7 +31,7 @@ You are a principal engineer doing a focused code review. You read the change an
31
31
  ## Return format (always end with this)
32
32
  ```
33
33
  review:
34
- verdict: approved | changes-requested
34
+ verdict: CLEAN | BLOCKING
35
35
  blocking:
36
36
  - file: <path:line> issue: <what> fix: <concrete suggestion>
37
37
  should_fix:
@@ -40,7 +40,7 @@ review:
40
40
  - file: <path:line> note: <what>
41
41
  good: <one line on what's done well, if anything>
42
42
  ```
43
- `verdict: approved` requires an empty `blocking` list. Be specific with `file:line`. Do not fix; report.
43
+ `verdict: CLEAN` requires an empty `blocking` list. Be specific with `file:line`. Do not fix; report.
44
44
 
45
45
  **Then, as the very last line of your output (nothing after it), emit the machine verdict:**
46
46
  `REVIEW: CLEAN` (empty `blocking` list) or `REVIEW: BLOCKING` (one or more blocking items). Automated
@@ -6,7 +6,7 @@ description: Use this agent to validate an implementation plan (or a phase of it
6
6
  You are a plan-validation specialist. Your job: catch the plan's wrong assumptions **before** they
7
7
  become wasted implementation. You diagnose; you do not fix and you do not write code.
8
8
 
9
- ## What to validate (7 categories)
9
+ ## What to validate (8 categories)
10
10
  For the plan (or the named phase) check each claim against the codebase:
11
11
  1. **File paths** — every file the plan says to create/edit: does the parent dir exist? Does an
12
12
  existing file already cover it (collision/duplication)?
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "caddis",
3
- "version": "1.3.95",
3
+ "version": "1.3.97",
4
4
  "description": "caddis — agent-agnostic dev harness as an Antigravity (agy) plugin: TDD skills + commands, single-sourced with the Claude Code plugin."
5
5
  }
@@ -470,8 +470,17 @@ def capture_order(repo_root: Path, spawn_id: str) -> tuple[list[str], list[str]]
470
470
  CHASE_AFTER_S = 1800
471
471
 
472
472
  # Ordered. Each event may only move the handshake forward.
473
- HANDSHAKE_STATES = ("awaiting-answers", "answered", "verdict-sent", "acknowledged")
474
- _EVENT_STATE = {"answers": "answered", "verdict": "verdict-sent", "ack": "acknowledged"}
473
+ # Ordered. An event may only move the handshake FORWARD along this list.
474
+ #
475
+ # Two entry points, one list. PARENT-FIRST (the product owner starts the child, then names it)
476
+ # begins at `awaiting-readback`, because the parent speaks first and must not take the answers on
477
+ # trust. CHILD-FIRST (008's original: print a prompt, a human pastes it) begins at
478
+ # `awaiting-answers`, because there the child's first message is itself the evidence of reading
479
+ # and a read-back would be asking for something already proven.
480
+ HANDSHAKE_STATES = ("awaiting-readback", "awaiting-answers", "answered", "verdict-sent",
481
+ "acknowledged")
482
+ _EVENT_STATE = {"readback": "awaiting-answers", "answers": "answered",
483
+ "verdict": "verdict-sent", "ack": "acknowledged"}
475
484
 
476
485
 
477
486
  def detect_transport(env: dict | None = None) -> tuple[str, str]:
@@ -511,7 +520,8 @@ def handshake_open(repo_root: Path, spawn_id: str, transport: str = "auto",
511
520
  chosen, why = transport, "forced with --transport"
512
521
  state = {
513
522
  "id": spawn_id,
514
- "state": HANDSHAKE_STATES[0],
523
+ # Parent-first only when we can actually reach the child: a name AND a transport.
524
+ "state": HANDSHAKE_STATES[0] if (peer and chosen == "message") else HANDSHAKE_STATES[1],
515
525
  "transport": chosen,
516
526
  "transport_reason": why,
517
527
  # HOW TO ADDRESS THE CHILD. `SendMessage` takes the name `ListAgents` shows, and until
@@ -545,10 +555,19 @@ def handshake_record(repo_root: Path, spawn_id: str, event: str,
545
555
  if state is None:
546
556
  return None, f"no handshake for {spawn_id} — run `handshake open --id {spawn_id}` first"
547
557
  target = _EVENT_STATE[event]
548
- if HANDSHAKE_STATES.index(target) <= HANDSHAKE_STATES.index(state["state"]):
549
- return state, (f"already at `{state['state']}`; `{event}` would not move it forward. "
550
- "Refusing rather than rewriting — an out-of-order event means the round "
551
- "is not the one you think it is.")
558
+ here = HANDSHAKE_STATES.index(state["state"])
559
+ there = HANDSHAKE_STATES.index(target)
560
+ # EXACTLY one step. Monotonic-only let `answers` jump straight over `awaiting-readback`,
561
+ # which silently skipped the read-back gate — the one thing the parent-first flow adds.
562
+ # Backwards is an operator who has lost track of the round; forwards-by-two is a gate not run.
563
+ if there != here + 1:
564
+ expected = HANDSHAKE_STATES[here + 1] if here + 1 < len(HANDSHAKE_STATES) else "nothing"
565
+ wanted = next((e for e, st in _EVENT_STATE.items() if st == expected), "—")
566
+ return state, (f"at `{state['state']}`, so the only event that fits is `{wanted}` "
567
+ f"(-> `{expected}`). `{event}` would "
568
+ + ("skip a step" if there > here + 1 else "move backwards")
569
+ + " — refusing rather than rewriting, because a skipped step here is a "
570
+ "gate that never ran.")
552
571
  if peer:
553
572
  state["peer"] = peer
554
573
  state["history"].append({"event": event, "at": int(time.time()),
@@ -562,6 +581,10 @@ def handshake_record(repo_root: Path, spawn_id: str, event: str,
562
581
  def chase_instruction(state: dict) -> str:
563
582
  """What to actually DO when a chase is due — addressed if we can, honest if we cannot."""
564
583
  peer = (state.get("peer") or "").strip()
584
+ if state.get("state") == HANDSHAKE_STATES[0] and peer:
585
+ return (f'No read-back from "{peer}". It was messaged and has not said what it READ, which '
586
+ "is the cheapest signal that it skipped the gate and started working. Ask again, "
587
+ "and ask for file paths — a summary can be written without opening anything.")
565
588
  if state.get("transport") != "message":
566
589
  return ("This handshake is on the paste route, so the chase goes through the human. Say "
567
590
  "so explicitly — otherwise they watch nothing happen and conclude it is broken.")
@@ -581,7 +604,7 @@ def handshake_chase_due(state: dict, now: int | None = None) -> int:
581
604
  pre-read in the handover file — a stronger test than the original set, and free, because the
582
605
  parent is holding the context anyway.
583
606
  """
584
- if state.get("state") != HANDSHAKE_STATES[0]:
607
+ if state.get("state") not in (HANDSHAKE_STATES[0], HANDSHAKE_STATES[1]):
585
608
  return 0
586
609
  now = int(time.time()) if now is None else now
587
610
  return max(0, now - int(state.get("opened_at", now)) - CHASE_AFTER_S)
@@ -708,7 +731,15 @@ def main(argv: list[str] | None = None) -> int:
708
731
  if a.action == "open":
709
732
  st = handshake_open(root, a.id, a.transport, a.peer)
710
733
  print(f"[spawn] handshake {a.id} open — transport `{st['transport']}` "
711
- f"({st['transport_reason']})")
734
+ f"({st['transport_reason']}), state `{st['state']}`")
735
+ if st["state"] == HANDSHAKE_STATES[0]:
736
+ print(f" PARENT-FIRST. You have the address, so message \"{st['peer']}\" now: "
737
+ "tell it what to read, then ask it to reply with WHICH FILES it opened "
738
+ "before it answers anything. Record that with `--event readback`.")
739
+ elif st["transport"] == "message" and not st["peer"]:
740
+ print(" No --peer given, so this is the child-first flow: print the prompt and "
741
+ "wait. If you already started the child, re-open with `--peer <its "
742
+ "ListAgents name>` to message it directly instead.")
712
743
  if st["transport"] == "paste":
713
744
  print(" The child cannot message you. Print the prompt for the human to carry, "
714
745
  "and say so — otherwise they watch nothing happen and conclude it is "
@@ -10,7 +10,7 @@ model change, context pressure, a cheaper lane, or a distracted session — and
10
10
  guarantees, which is worse than reading as advice.
11
11
 
12
12
  The things that actually held under pressure were the ones with exit codes: pytest,
13
- validate_pool, the hooks, oss_review's exit 2.
13
+ validate_pool, the hooks, oss_review's exit 4.
14
14
 
15
15
  So this script measures the ratio of rules that a machine can fail on, to rules that only a
16
16
  model can choose to honour. That number is the health metric for the plugin. It is expected to
@@ -53,10 +53,18 @@ import urllib.error
53
53
  import urllib.request
54
54
  from pathlib import Path
55
55
 
56
+ # ONE SCALE ACROSS caddis (see claude-harness/README.md):
57
+ # 0 clean · 1 blocked · 2 advisory note · 3 could not run · 4 malformed
58
+ # ERROR moved 2 -> 4 on 2026-09-09. It sat on 2, which caddis_gate uses for "proceed, this is
59
+ # worth saying" — the exact opposite, and in the dangerous direction: a caller treating 2 as
60
+ # advisory silently accepts a review that never ran. That is the 2026-08-10 shape, where a stale
61
+ # copy of this file returned CLEAN on a database write path nobody had reviewed.
62
+ # 2 is left FREE here. This tool has no advisory verdict; a slot that means one thing everywhere
63
+ # is worth more than a slot reused for something else.
56
64
  EXIT_CLEAN = 0
57
65
  EXIT_BLOCKING = 1
58
- EXIT_ERROR = 2
59
- EXIT_CONFIG = 3
66
+ EXIT_CONFIG = 3 # could not run: no key, no provider, bad configuration
67
+ EXIT_ERROR = 4 # ran, produced nothing usable: no verdict line, transport failure, unsplittable file
60
68
 
61
69
  # Provider presets — the SINGLE place a renamed model id or moved endpoint is edited. Adding a new
62
70
  # provider (Qwen, a local vLLM, …) is one new row. Callers can always bypass this via env/flags.
@@ -81,7 +81,7 @@ writes. This line used to prefix the flag itself, which turned that into a doubl
81
81
  Optional: `--base-url <url>`, `--model <id>` (env always overrides the preset).
82
82
 
83
83
  **A large diff is batched, not refused.** Over `REVIEW_MAX_DIFF_CHARS` (default 60,000) the tool splits
84
- on whole-file boundaries and reviews each batch, and the verdict is CLEAN only if every batch is. Exit 2
84
+ on whole-file boundaries and reviews each batch, and the verdict is CLEAN only if every batch is. Exit **4**
85
85
  now means the narrower case — a **single file** too big to split, or more batches than the cap. When you
86
86
  see it, the fix is usually to review that one file separately, not to narrow `--range`; don't raise
87
87
  `--max-diff-chars` blind.
@@ -102,7 +102,10 @@ as a diff that silently is not read. Name a provider explicitly only when you ha
102
102
 
103
103
  ## Rules
104
104
  - Read-only second opinion — the tool never edits, commits, or pushes. YOU apply fixes in the main thread.
105
- - Treat exit 2/3 as blocking-unknown, never as approval (the tool is fail-closed by design).
105
+ - Treat exit **3 or 4** as blocking-unknown, never as approval (the tool is fail-closed by
106
+ design). 3 = could not run, 4 = ran and produced nothing usable. **Exit 2 is not
107
+ emitted by this tool** — across caddis 2 always means an advisory note, and a review
108
+ has no advisory verdict.
106
109
  - A different vendor means a different style; weigh its findings on merit, don't cargo-cult them.
107
110
  - **Never use `find`, `Get-ChildItem -Recurse`, or any other filesystem-wide search to locate
108
111
  `oss_review.py`.** Check only the two paths above. On Git Bash under Windows, `find /` (or any
@@ -22,6 +22,13 @@ description**, and there may be **no codebase, PROJECT-FACTS, or prior PRD** to
22
22
  write a complete best-effort plan anyway.
23
23
 
24
24
  Absolute rules in this mode (they override everything below):
25
+
26
+ > **The same policy exists in `/caddis:prd`, and the two are deliberately NOT identical.**
27
+ > Each is specialised to the artefact its command writes — where the output goes, and
28
+ > where an unresolved decision is recorded. **Do not merge them, and do not replace
29
+ > either with a pointer:** headless mode is precisely when no human is present and the
30
+ > session will not go and read another command to find its own rules.
31
+
25
32
  - **NEVER ask a question, request clarification, or end your turn with questions.** Replying with a list
26
33
  of scoping questions is a hard failure. Never use AskUserQuestion, never pause, never wait for input.
27
34
  - **A bare title (or PRD path) is sufficient input.** Where information is missing, invent a reasonable,
@@ -112,7 +119,7 @@ escalation, never a planned default.
112
119
 
113
120
  ### Assign an execution LANE per phase (which harness runs it — not just which model)
114
121
  A tier can be served by an OSS provider instead of Anthropic (cheat sheet:
115
- `docs/guide/multi-model-workflow.md`; wiring: the caddis repo's `.caddis/plans/model-access.md`):
122
+ `docs/guide/multi-model-workflow.md` (in the caddis **source** repo — `docs/` is not shipped inside the installed plugin); wiring: the caddis repo's `.caddis/plans/model-access.md`):
116
123
  - **claude** (default) — this session, `/model <alias>` as above. Required for judgment-heavy,
117
124
  security-sensitive, or novel-architecture phases.
118
125
  - **glm-headless** — mechanical, fully-specced phases run on GLM without touching Claude quota:
@@ -87,6 +87,7 @@ Record the outcome in relay.md's `## Learnings captured` line (below).
87
87
 
88
88
  ## Step 2c — was any of it the harness?
89
89
 
90
+ <!-- shared:harness-friction-question — keep byte-identical across commands; a test pins it -->
90
91
  **One question, asked here because here is where you are already reflecting.** The observations
91
92
  that reach caddis today are the ones someone happened to recall days later — so small, frequent
92
93
  friction never arrives, which is exactly the friction worth fixing.
@@ -99,6 +100,7 @@ harness, not this repo.
99
100
  ```
100
101
 
101
102
  Nothing to say is the normal answer. Say nothing and move on — this must not become a ritual.
103
+ <!-- /shared:harness-friction-question -->
102
104
 
103
105
  ## Step 3 — write the resume doc (overwrite) with exactly these sections
104
106
  > **Where to write:** solo / single active branch → `.caddis/relay.md` (default).
@@ -46,6 +46,7 @@ surface the `[DOC-MAP]` "read the index first" pointer for future sessions in th
46
46
 
47
47
  ## Step 5 — was any of it the harness?
48
48
 
49
+ <!-- shared:harness-friction-question — keep byte-identical across commands; a test pins it -->
49
50
  **One question, asked here because here is where you are already reflecting.** The observations
50
51
  that reach caddis today are the ones someone happened to recall days later — so small, frequent
51
52
  friction never arrives, which is exactly the friction worth fixing.
@@ -58,6 +59,7 @@ harness, not this repo.
58
59
  ```
59
60
 
60
61
  Nothing to say is the normal answer. Say nothing and move on — this must not become a ritual.
62
+ <!-- /shared:harness-friction-question -->
61
63
 
62
64
  ## KB note format (OKF-lite — mandatory for every new note)
63
65
 
@@ -83,7 +85,7 @@ generated: { by: caddis/<model-id>, at: 2026-07-28T09:00:00Z }
83
85
 
84
86
  The full schema, the caddis↔OKF `status` mapping, and what caddis deliberately skips from OKF v0.2
85
87
  (`sources:`, `Attested Computation`) live in
86
- [`.github/instructions/document-frontmatter.instructions.md`](../../.github/instructions/document-frontmatter.instructions.md).
88
+ `.github/instructions/document-frontmatter.instructions.md` in the caddis source repo. (Not a link: a relative path out of the plugin directory resolves nowhere once installed.)
87
89
 
88
90
  **Backward compatible:** an existing note with no trust fields is still correct. Do not bulk-migrate
89
91
  old notes — add the fields as notes are next touched.
@@ -30,6 +30,13 @@ forbidden**. This mode exists for one-line ideas: the card may be nothing more t
30
30
  fully actionable — a terse title is enough to write a complete draft PRD.
31
31
 
32
32
  Absolute rules in this mode (they override everything above):
33
+
34
+ > **The same policy exists in `/caddis:feature-plan`, and the two are deliberately NOT identical.**
35
+ > Each is specialised to the artefact its command writes — where the output goes, and
36
+ > where an unresolved decision is recorded. **Do not merge them, and do not replace
37
+ > either with a pointer:** headless mode is precisely when no human is present and the
38
+ > session will not go and read another command to find its own rules.
39
+
33
40
  - **NEVER ask a question. NEVER request clarification. NEVER end your turn with questions.** Replying with
34
41
  something like *"a few questions to scope this before I write the PRD"* is a hard failure. Never use
35
42
  AskUserQuestion, never pause for approval, never wait for input.
@@ -35,7 +35,7 @@ alias claude-glm="<path-to>/claude-harness/scripts/claude-oss.sh"
35
35
  ```
36
36
  Resolve `<path-to>` per the same plugin-vs-source rule as the generator itself (`${CLAUDE_PLUGIN_ROOT}`
37
37
  for a plugin install, the harness checkout path for caddis). Also set `CADDIS_KEYS_FILE`
38
- (default `~/.caddis/keys.env`) with the provider keys — see `docs/guide/providers-and-keys.md`.
38
+ (default `~/.caddis/keys.env`) with the provider keys — see `docs/guide/providers-and-keys.md` (in the caddis **source** repo — `docs/` is not shipped inside the installed plugin).
39
39
 
40
40
  ## After the deterministic step — deploy private harness skills (optional)
41
41
 
@@ -46,6 +46,7 @@ Use conventional commits: `fix:`, `feat:`, `chore:`, `refactor:`, `docs:`. Scope
46
46
 
47
47
  **2b. CROSS-REVIEW TRIGGER** — `ship` has no review pause at all, which is how sixteen fixes reached production unreviewed in one day.
48
48
 
49
+ <!-- shared:cross-review-trigger — keep byte-identical across commands; a test pins it -->
49
50
  **Run the trigger, do not eyeball the diff:**
50
51
 
51
52
  ```bash
@@ -60,6 +61,7 @@ gets switched off inside a week.
60
61
  It fires on SQL and repositories, `services/`, caches and refresh jobs, auth and RBAC, or a diff
61
62
  over 400 changed lines. No judgement in any of those — deliberately, because judgement is exactly
62
63
  what failed.
64
+ <!-- /shared:cross-review-trigger -->
63
65
 
64
66
  **3. PUSH** — to the repo's default branch (confirm the branch first; don't assume `main`):
65
67
  ```
@@ -52,6 +52,7 @@ are uncommitted.)
52
52
 
53
53
  ## Step 3b — Cross-review trigger (before anything is pushed)
54
54
 
55
+ <!-- shared:cross-review-trigger — keep byte-identical across commands; a test pins it -->
55
56
  **Run the trigger, do not eyeball the diff:**
56
57
 
57
58
  ```bash
@@ -66,6 +67,7 @@ gets switched off inside a week.
66
67
  It fires on SQL and repositories, `services/`, caches and refresh jobs, auth and RBAC, or a diff
67
68
  over 400 changed lines. No judgement in any of those — deliberately, because judgement is exactly
68
69
  what failed.
70
+ <!-- /shared:cross-review-trigger -->
69
71
 
70
72
  > **Why this is mechanical and not "consider a review here".** serve-sight shipped four
71
73
  > production releases in one day — sixteen fixes across SQL, caches, refresh jobs and React — and
@@ -24,6 +24,36 @@ A long session does not forget. **It recalls superseded facts fluently.** Measur
24
24
  Each was stated with conviction and was wrong. Asking that agent what it knows cannot find any of
25
25
  it.
26
26
 
27
+ ## Start the successor FIRST, then name it
28
+
29
+ **Preferred flow.** Start the child with a name you choose, then pass that same name here:
30
+
31
+ ```
32
+ claude --name successor-9c2 # in a new terminal
33
+ /caddis:spawn-session successor-9c2 finish the harvester # here
34
+ ```
35
+
36
+ `claude -n/--name` sets the session's display name, and that is exactly the name `SendMessage`
37
+ addresses. You pick it, so nothing has to be looked up.
38
+
39
+ **Check the row before you trust the name.** Run `ListAgents` once and confirm the child is there
40
+ and reads `interactive`, not `offline` — a listing carries long-dead sessions, so presence alone
41
+ proves nothing. Names are also **not unique**: if two rows share one, `SendMessage` needs the
42
+ ` [ref]` shown beside it. Pick a short distinct name and this never arises.
43
+
44
+ **Why this ordering.** With the child already running you hold its address from the start, so the
45
+ parent messages it directly and nothing has to be pasted. The older flow printed a prompt for a
46
+ human to carry, which meant the child did not exist yet and its address could not be known.
47
+
48
+ **What you give up, and how it is replaced.** In the older flow the child spoke first, so its
49
+ first message was itself proof it had read the handover. Here the parent speaks first, so that
50
+ proof is a separate step: **your opening message asks the child to reply with WHICH FILES it
51
+ opened, before it answers anything.** Ask for paths — a summary can be written without opening
52
+ anything. Record it with `--event readback`; until you do, the handshake refuses `--event answers`.
53
+
54
+ **No name given?** The command falls back to the older printed-prompt flow. That is also the only
55
+ route on agy and Codex, which have no `SendMessage`.
56
+
27
57
  The successor is what to pick up first: **$ARGUMENTS** (if empty, derive it from the parking-lot
28
58
  and the active plan).
29
59
 
@@ -318,7 +348,7 @@ immediately.
318
348
 
319
349
  | Verdict | Condition | You write |
320
350
  |---|---|---|
321
- | **PASS** | every answer correct, citations check out | a short go-ahead naming the first task |
351
+ | **ACCEPT** | every answer correct, citations check out | a short go-ahead naming the first task |
322
352
  | **ACCEPT WITH CORRECTIONS** | broadly right; one or two facts stale or a citation wrong | the corrections, each with the file that proves it |
323
353
  | **REJECT** | a settled decision misremembered, a withdrawn figure quoted, or a hazard misunderstood | an instruction to re-read named sections and re-answer. **Do not let it start work** |
324
354
 
@@ -353,7 +383,7 @@ Round 1's verdict sets round 2's size:
353
383
 
354
384
  | Round 1 | Round 2 task |
355
385
  |---|---|
356
- | PASS | a normal next item; review the outcome |
386
+ | ACCEPT | a normal next item; review the outcome |
357
387
  | ACCEPT WITH CORRECTIONS | a **small, reversible** item that writes nothing permanent; review closely |
358
388
  | REJECT | no task — re-read and re-answer |
359
389
 
@@ -20,9 +20,11 @@ python "<resolved-path>" [--days $ARGUMENTS]
20
20
  ```
21
21
 
22
22
  - Default window is **7 days**. If the user passed a number (e.g. `/usage-review 14`), use it.
23
- - Run from the project root so `.claude/usage-log.jsonl` is found correctly.
23
+ - Run from the project root so `.caddis/usage-log.jsonl` is found correctly. (The legacy
24
+ the legacy `.claude/usage-log.jsonl` is still read as a fallback during the migration.)
24
25
  - If the script exits with "No sessions found", tell the user and stop — no further steps.
25
- - The script prints a markdown report to stdout and writes `.claude/usage-review.html`.
26
+ - The script prints a markdown report to stdout and writes
27
+ `.caddis/reviews/usage-review.html` (override with `--output-dir`).
26
28
 
27
29
  ## Step 2 — present the report
28
30
 
@@ -50,21 +52,22 @@ the two `---` delimiters) of the named agent file and change its value. Show a d
50
52
  After any applies, tell the user which files changed and suggest:
51
53
  "Run `/usage-review` again after a few sessions to see the effect."
52
54
 
53
- ## Step 5 — offer to schedule a recurring review (optional)
55
+ ## Step 5 — do NOT offer to schedule this as a cloud routine
54
56
 
55
- After reporting the outcome, ask once:
57
+ **This command cannot run as a cloud routine, and offering one produces a routine that fails.**
56
58
 
57
- > "Want me to schedule this to run automatically every Wednesday? I can set it up with `/schedule`."
59
+ `/schedule` creates a **cloud** agent. This command reads `.caddis/usage-log.jsonl`, which the
60
+ local `Stop` hook writes on THIS machine and which is **gitignored** — so it is neither on the
61
+ cloud runner's filesystem nor in the clone it makes. The routine would fire on time and find
62
+ nothing.
58
63
 
59
- If the user says yes, use the `schedule` skill to create a weekly routine:
64
+ That also settles a contradiction this file used to carry: the description says "Data stays
65
+ on-machine — no telemetry, no server", and the old Step 5 offered a cloud routine two screens
66
+ later. The description is the correct half.
60
67
 
61
- ```
62
- Schedule a recurring cloud routine: every Wednesday, run /usage-review
63
- ```
64
-
65
- The routine should fire mid-week so findings are fresh for the next work sprint. If the user
66
- declines, skip silently — the SessionStart nudge in `inject_relay.py` already covers the
67
- "you haven't reviewed in 7 days" case.
68
+ **The recurring nudge already exists and is local.** Each run updates
69
+ `.caddis/.last-usage-review`, and the SessionStart hook reads it to remind you when a review is
70
+ overdue. Nothing to schedule.
68
71
 
69
72
  ## Notes
70
73
 
@@ -7,7 +7,7 @@ description: Explain (or apply) the model lanes — claude / claude-glm / cross-
7
7
 
8
8
  Explain the model-switching lanes so the user (or you, on their behalf) can move a session onto an
9
9
  OSS provider (GLM, DeepSeek, OpenRouter, or a custom endpoint) in one command, with no hardcoded key
10
- path. Full detail lives in the **Providers & keys** guide (`docs/guide/providers-and-keys.md`) — this
10
+ path. Full detail lives in the **Providers & keys** guide (`docs/guide/providers-and-keys.md` (in the caddis **source** repo — `docs/` is not shipped inside the installed plugin)) — this
11
11
  command is the quick-reference + do-it-now version.
12
12
 
13
13
  ## The lanes
@@ -1,7 +1,7 @@
1
1
  {
2
- "poolVersion": "1.3.95",
2
+ "poolVersion": "1.3.97",
3
3
  "bundles": {
4
- "antigravity-plugin": "1.3.95",
4
+ "antigravity-plugin": "1.3.97",
5
5
  "antigravity-plugin-extras": "1.3.28",
6
6
  "codex": "unknown"
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@caddis/cli",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "description": "Install, update and diagnose the caddis dev harness across every coding agent you use (Claude Code, agy/Antigravity, and more).",
5
5
  "keywords": [
6
6
  "caddis",