@caddis/cli 0.4.8 → 0.4.9
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/agents/preflight.md +1 -1
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/scripts/caddis_spawn.py +40 -9
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +8 -1
- package/bundles/antigravity-plugin/skills/handoff/SKILL.md +2 -0
- package/bundles/antigravity-plugin/skills/kb/SKILL.md +3 -1
- package/bundles/antigravity-plugin/skills/prd/SKILL.md +7 -0
- package/bundles/antigravity-plugin/skills/setup-project-ai/SKILL.md +1 -1
- package/bundles/antigravity-plugin/skills/ship/SKILL.md +2 -0
- package/bundles/antigravity-plugin/skills/ship-pr/SKILL.md +2 -0
- package/bundles/antigravity-plugin/skills/spawn-session/SKILL.md +30 -0
- package/bundles/antigravity-plugin/skills/usage-review/SKILL.md +16 -13
- package/bundles/antigravity-plugin/skills/use-model/SKILL.md +1 -1
- package/bundles/manifest.json +2 -2
- package/package.json +1 -1
|
@@ -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 (
|
|
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)?
|
|
@@ -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
|
-
|
|
474
|
-
|
|
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
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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")
|
|
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 "
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
|
@@ -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 `.
|
|
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
|
|
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
|
|
55
|
+
## Step 5 — do NOT offer to schedule this as a cloud routine
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
**This command cannot run as a cloud routine, and offering one produces a routine that fails.**
|
|
56
58
|
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/bundles/manifest.json
CHANGED
package/package.json
CHANGED