@caddis/cli 0.4.13 → 0.4.15
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/caddis_statusline.py +30 -3
- package/bundles/antigravity-plugin/plugin.json +1 -1
- package/bundles/antigravity-plugin/repo_freshness.py +146 -0
- package/bundles/antigravity-plugin/scripts/caddis-run.ps1 +11 -0
- package/bundles/antigravity-plugin/scripts/caddis-run.sh +9 -0
- package/bundles/antigravity-plugin/scripts/caddis_gate.py +24 -3
- package/bundles/antigravity-plugin/scripts/caddis_governor.py +542 -0
- package/bundles/antigravity-plugin/scripts/caddis_host_guard.py +689 -0
- package/bundles/antigravity-plugin/scripts/caddis_lanes.py +1632 -0
- package/bundles/antigravity-plugin/scripts/caddis_statusline.py +30 -3
- package/bundles/antigravity-plugin/scripts/install-host-guard.ps1 +393 -0
- package/bundles/antigravity-plugin/skills/_registry.md +2 -0
- package/bundles/antigravity-plugin/skills/confluence-hla/SKILL.md +375 -0
- package/bundles/antigravity-plugin/skills/confluence-hla/scripts/convert_hla_to_confluence.py +1164 -0
- package/bundles/antigravity-plugin/skills/confluence-hla/scripts/discover_db_lineage.py +970 -0
- package/bundles/antigravity-plugin/skills/feature-plan/SKILL.md +30 -4
- package/bundles/antigravity-plugin/skills/implement/SKILL.md +48 -1
- package/bundles/antigravity-plugin/skills/pr-merge/SKILL.md +155 -0
- package/bundles/antigravity-plugin/warm_start_agy.py +38 -4
- package/bundles/codex/.codex/skills/docs/confluence-hla/SKILL.md +375 -0
- package/bundles/codex/.codex/skills/docs/confluence-hla/scripts/convert_hla_to_confluence.py +1164 -0
- package/bundles/codex/.codex/skills/docs/confluence-hla/scripts/discover_db_lineage.py +970 -0
- package/bundles/manifest.json +2 -2
- package/dist/claude-deepseek.js +50 -10
- package/dist/claude-glm.js +50 -10
- package/dist/claude-oss.js +50 -10
- package/dist/cli.js +173 -63
- package/package.json +1 -1
|
@@ -126,6 +126,24 @@ A tier can be served by an OSS provider instead of Anthropic (cheat sheet:
|
|
|
126
126
|
`claude-glm -p "/caddis:implement <plan-path> — Phase N only"` (from the repo root, on the
|
|
127
127
|
feature branch). The local-coder gate below exists exactly so these phases survive a weaker
|
|
128
128
|
implementer — a phase may only take this lane if it passes that gate.
|
|
129
|
+
- **codex-sol / codex-astra / agy / glm (via caddis_lanes)** — select one of these five task types
|
|
130
|
+
and its default chain:
|
|
131
|
+
- `backend`: `glm`, `codex-sol`, `claude`
|
|
132
|
+
- `hard`: `claude`, `codex-astra`
|
|
133
|
+
- `ui`: `claude`, `codex-astra`
|
|
134
|
+
- `tests`: `codex-sol`, `glm`, `claude`
|
|
135
|
+
- `docs`: `agy`, `glm`, `claude`
|
|
136
|
+
Use `python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_lanes.py" run --plan <plan-path> --phase N
|
|
137
|
+
--type <type>` as the launch command. The chains are defaults, not limits on Claude: while Claude
|
|
138
|
+
has budget, give any phase the `claude` lane. Override chains and model pins per project in
|
|
139
|
+
`.caddis/config.toml` `[lanes.chains]` and `[lanes.models]`.
|
|
140
|
+
|
|
141
|
+
**Evidence standard — never plan a load test on a shared host.** Prove timing robustness with
|
|
142
|
+
event-driven fakes, ordering assertions and an A/B check that fails against the racy version. A phase
|
|
143
|
+
must never ask for CPU burners, stress tests, load tests or "every core". `caddis_lanes.py` refuses
|
|
144
|
+
this wording. The host guard kills runaway agent processes. On 2026-09-13, 32 all-core busy loops
|
|
145
|
+
froze CI on the dev box.
|
|
146
|
+
|
|
129
147
|
- **review lane (every phase that touches code)** — cross-vendor review by a vendor that did NOT
|
|
130
148
|
write the code: `/caddis:cross-review --range main..HEAD`
|
|
131
149
|
(Claude or GLM implemented) or `--provider glm` / a Claude `code-reviewer` pass (DeepSeek-adjacent
|
|
@@ -219,6 +237,7 @@ Creating Model: <model-id>
|
|
|
219
237
|
**Milestone:** M1 *(omit on plans without milestones)*
|
|
220
238
|
**Model:** <tier> (`/model <alias>`) — <one-line rationale tied to this phase's difficulty>
|
|
221
239
|
**Lane:** claude — this session | glm-headless — `claude-glm -p "/caddis:implement <this plan's path> — Phase 1 only"`
|
|
240
|
+
**Type:** backend | hard | ui | tests | docs *(required when the Lane runs through caddis_lanes.py)*
|
|
222
241
|
**Session:** continue | **fresh** (start a new session for this phase — note why: lane change / heavy context)
|
|
223
242
|
**Risk:** high — token-verification seam; a wrong answer here is a silent auth bypass *(omit this line when normal)*
|
|
224
243
|
**Goal:** <one sentence>
|
|
@@ -273,6 +292,11 @@ yourself (relay + Tracker) before stopping — do not hand back "run `/caddis:ha
|
|
|
273
292
|
- **Ship gate:** NEVER push or merge to main from a phase — phases only commit to the feature branch.
|
|
274
293
|
When all phases are ✅: `/caddis:ship-pr` (stops at green CI), then STOP and wait for the human's
|
|
275
294
|
explicit go before `/caddis:ship-merge`.
|
|
295
|
+
- **Finish:** after the last phase, run `caddis_lanes.py finish --plan <plan>`; the plan is not done
|
|
296
|
+
until it prints `LEFTOVERS: 0` (merged lane worktrees and branches removed, unmerged or dirty ones
|
|
297
|
+
reported, feature branch pushed; merging to main stays with `/caddis:ship-merge`).
|
|
298
|
+
- **No load wording** — no phase asks for a CPU burner, stress test, load test or "every core"; prove
|
|
299
|
+
timing by design.
|
|
276
300
|
|
|
277
301
|
## Affected files
|
|
278
302
|
| File | Action |
|
|
@@ -303,16 +327,18 @@ done:
|
|
|
303
327
|
- **No abbreviation** — never "etc.", "similar to Phase 1", "and so on". Write every item in full.
|
|
304
328
|
- **Model tier named** — every phase names a tier (cheap/mid/frontier/ultra) + a one-line rationale; no
|
|
305
329
|
phase silently defaults to the most expensive model. Default mid; frontier/ultra only where justified.
|
|
306
|
-
- **Lane + reviewer named** — every phase names its execution lane (`claude
|
|
307
|
-
the literal launch command for
|
|
308
|
-
|
|
309
|
-
slack for reasoning out gaps.
|
|
330
|
+
- **Lane + reviewer named** — every phase names its execution lane (`claude`, `glm-headless`, or a
|
|
331
|
+
`caddis_lanes.py` lane with its `**Type:**`, with the literal launch command for every non-claude
|
|
332
|
+
phase) and a cross-review provider that is a different vendor than the implementer. A glm-headless
|
|
333
|
+
phase MUST pass every bullet of this gate — that lane has no slack for reasoning out gaps.
|
|
310
334
|
- **Launch command is spawnable** — every `glm-headless` phase's command carries **`-p`** (or
|
|
311
335
|
`--print`) and ends with **`— Phase N only`**. Both are load-bearing, not style: the launchers set
|
|
312
336
|
`CADDIS_HEADLESS` *only* on `-p`, and that variable is the guard that stops a spawned session
|
|
313
337
|
spawning another one forever; the `Phase N only` suffix is what stops the child running to the end
|
|
314
338
|
of the plan. The command is executed verbatim by a model with no slack to notice a missing flag,
|
|
315
339
|
and `/caddis:implement` now refuses a command without `-p` rather than running it.
|
|
340
|
+
- **caddis_lanes command carries --phase** — a `caddis_lanes.py` launch command must name `--phase N`
|
|
341
|
+
and `--type`; lane-check refuses one without `--phase`.
|
|
316
342
|
- **Gate-detectable** — if this phase's instruction were WRONG, its exit gate would go red. A phase
|
|
317
343
|
whose gates pass either way (copy, nav entries, badges, ordering, wording, config defaults) must
|
|
318
344
|
not take a lane that cannot escalate, no matter how completely it is specified. Every other bullet
|
|
@@ -101,6 +101,51 @@ the bug the lane routing exists to fix.
|
|
|
101
101
|
lane the plan planned. A deviation then shows up as a diff between the phase block and the tracker
|
|
102
102
|
row, visible to any later reader without an Advisory Hub having to catch it live.
|
|
103
103
|
|
|
104
|
+
**Multi-lane phases — `caddis_lanes.py`.** Use this procedure when the phase launch command is
|
|
105
|
+
`python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_lanes.py" run …`.
|
|
106
|
+
|
|
107
|
+
1. In an interactive session, run the phase's literal command. The script creates a temporary
|
|
108
|
+
worktree. It runs the lane or its fallback chain inside a resource-capped job. It re-runs the
|
|
109
|
+
gates with the repo interpreter. It runs the DeepSeek review. It then prints a `NEXT:` line.
|
|
110
|
+
2. Obey `NEXT:`:
|
|
111
|
+
- `review` → read the packet JSON and inspect `git -C <worktree> diff`. Then decide ACCEPT or REDO.
|
|
112
|
+
- `redo` → run `abandon`. Then run `run` with `--attempt 2 --findings <file with your findings>`.
|
|
113
|
+
- `takeover` → run `abandon`. Then implement the phase yourself on the `claude` lane.
|
|
114
|
+
- `claude-implement` → write the code in the printed worktree. Then run `check`.
|
|
115
|
+
- `stop` → report the result and halt. `stop (host guard killed the lane…)` means
|
|
116
|
+
`FAILED-RESOURCE`. The lane saturated the host. Never re-run it with more load. Fix the phase
|
|
117
|
+
so it proves timing by design.
|
|
118
|
+
3. **ACCEPT** means run `caddis_lanes.py accept --plan <plan> --phase N --attempt K --message
|
|
119
|
+
"<the phase's commit message>"`. It runs only on the programme branch, never on `main`. It
|
|
120
|
+
refuses a `BLOCKING` review. If you checked the finding and it is false, add
|
|
121
|
+
`--override-review "<why the finding is false>"`. The script records that reason in the packet.
|
|
122
|
+
4. Allow at most **2 attempts** per phase on a non-Claude lane. An out-of-budget switch is not an
|
|
123
|
+
attempt. The owner can request TAKEOVER at any time.
|
|
124
|
+
5. A UI phase needs a `ui-design-reviewer` screenshot review before ACCEPT. This rule applies
|
|
125
|
+
whichever lane wrote the phase.
|
|
126
|
+
6. A phase that Claude wrote needs a vendor review that is not MISSING. If `review.status` is
|
|
127
|
+
`MISSING`, run `codex exec -m gpt-5.6-sol -s read-only -C <worktree> --skip-git-repo-check -`.
|
|
128
|
+
Give the diff to the command on stdin.
|
|
129
|
+
6b. **Orchestrator artefacts live under `.caddis/orchestrator/`.** Store packets and locks in
|
|
130
|
+
`lane-runs/`. Store evidence notes as markdown directly in `.caddis/orchestrator/`. Follow its
|
|
131
|
+
`AGENTS.md`. Never write orchestration notes to `.caddis/kb/`.
|
|
132
|
+
7. **Never write a load or stress test into a lane prompt or findings file.** The script refuses
|
|
133
|
+
this wording without `--allow-load`. Only the owner gives that flag.
|
|
134
|
+
8. Record these Tracker values:
|
|
135
|
+
- `Model` = the packet's `model`.
|
|
136
|
+
- `Lane` = the lanes tried, joined with ` -> ` and followed by the reason. For example,
|
|
137
|
+
`glm -> codex-sol (glm out of budget)`.
|
|
138
|
+
- `Cross-review` = `CLEAN`, `BLOCKING` or `missing`.
|
|
139
|
+
- `Notes` = `attempt K, ACCEPT|TAKEOVER|FAILED-RESOURCE`.
|
|
140
|
+
9. The PR body lists every phase accepted with `review.status == MISSING`. It also lists every phase
|
|
141
|
+
accepted while Codex astra stood in for Claude.
|
|
142
|
+
9b. **Finish.** After the last phase, run `caddis_lanes.py finish --plan <plan>`. It removes merged
|
|
143
|
+
lane worktrees and branches. It reports unmerged or dirty ones. It pushes the feature branch.
|
|
144
|
+
The plan is not done until it prints `LEFTOVERS: 0`. Then run `/caddis:ship-pr`. Merging and
|
|
145
|
+
post-merge cleanup stay with `/caddis:ship-merge` after the owner's go.
|
|
146
|
+
10. **When Claude is out of budget**, the owner opens `caddis-run codex -m gpt-6-astra` and asks it
|
|
147
|
+
to follow this same subsection.
|
|
148
|
+
|
|
104
149
|
**Advisory-Hub mode (conditional — OFF by default).** Check whether a companion file
|
|
105
150
|
`<plan-dir>/<plan-stem>-advisory-context.md` exists (e.g. plan `.caddis/plans/foo.md` → look for
|
|
106
151
|
`.caddis/plans/foo-advisory-context.md`). **If it does not exist — the normal case, including every
|
|
@@ -187,7 +232,9 @@ report — do not redo completed work.
|
|
|
187
232
|
first, every phase, not just the one you resumed at. Step 1's check only ever saw the *resume*
|
|
188
233
|
phase: on a plan whose phase 1 is `claude` and phase 2 is `glm-headless`, it finds no mismatch and
|
|
189
234
|
this loop then carries you straight through phase 2 — so the lane silently executes nothing for
|
|
190
|
-
every phase after the first, which is the original bug wearing a different hat.
|
|
235
|
+
every phase after the first, which is the original bug wearing a different hat. For a
|
|
236
|
+
`caddis_lanes.py` launch command, follow "Multi-lane phases" above instead of spawning the command
|
|
237
|
+
blind.
|
|
191
238
|
- **RED** — write the failing test(s) the phase names (`<test file>::<case>`). Run them; confirm they
|
|
192
239
|
fail for the right reason (the missing behavior, not an import error). If a phase genuinely has no
|
|
193
240
|
testable surface, say so in the review file and implement the minimal change directly.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pr-merge
|
|
3
|
+
description: End-to-end pull request and merge lane — push feature branch, open or update PR, monitor CI to green, merge to default branch, and clean up the feature branch.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /pr-merge — the unified reviewed lane: branch → PR → green → merged → cleaned up
|
|
7
|
+
|
|
8
|
+
End-to-end delivery of feature-branch work through the reviewed lane in one shot: run preflight checks,
|
|
9
|
+
push safely, create or update the Pull Request, monitor CI checks to green, merge into the default
|
|
10
|
+
branch, validate the deployment, and clean up the feature branch.
|
|
11
|
+
|
|
12
|
+
For a hotfix straight to the default branch with no PR ceremony, use `/ship` (the express lane) instead.
|
|
13
|
+
To open a PR and stop at green for human review without merging, use `/ship-pr`.
|
|
14
|
+
To merge an already-opened PR, use `/ship-merge`.
|
|
15
|
+
|
|
16
|
+
Optional argument: **$ARGUMENTS** (PR title override; default = derived from the branch's
|
|
17
|
+
conventional-commit summary). An optional strategy override (`squash` | `merge` | `rebase`)
|
|
18
|
+
can be provided.
|
|
19
|
+
|
|
20
|
+
## Step 0 — Detect the lane (before anything else)
|
|
21
|
+
|
|
22
|
+
- **Gitea lane** — `.gitea/workflows/` exists. PR creation, CI monitoring, and merge via the Gitea API
|
|
23
|
+
(token loading and job-polling procedure: the `deploy-local` skill).
|
|
24
|
+
- **GitHub lane** — `.github/workflows/` exists (and no `.gitea/`). PR creation, checks monitoring,
|
|
25
|
+
and merge via the `gh` CLI (`gh pr create`, `gh pr checks --watch`, `gh pr merge` — see the `gh-cli` skill).
|
|
26
|
+
- **Local-only lane** — neither exists, or there is no push remote. There is no forge and no PR concept:
|
|
27
|
+
**refuse early**, tell the user to use `/ship`, and stop. Do not fabricate a PR flow.
|
|
28
|
+
|
|
29
|
+
Read `AGENTS.md` + the detected workflow file(s) first for the repo's real gate names, default
|
|
30
|
+
branch, and merge conventions — never assume `main`, never assume job names.
|
|
31
|
+
|
|
32
|
+
## Step 1 — Refuse on the default branch
|
|
33
|
+
|
|
34
|
+
A PR is *branch → default*. If HEAD **is** the default branch, stop and point at `/ship` — there is
|
|
35
|
+
nothing to open a PR from. (Offer to create a feature branch from the current work if the changes
|
|
36
|
+
are uncommitted.)
|
|
37
|
+
|
|
38
|
+
## Step 2 — Preflight (local gates mirroring CI)
|
|
39
|
+
|
|
40
|
+
- Backend: `ruff check .` clean (or repository linter).
|
|
41
|
+
- Frontend: `npm run typecheck && npm run build` if `frontend/` exists.
|
|
42
|
+
- Fix failures first. **Never open a PR over red preflight** — a PR that arrives red wastes CI resources
|
|
43
|
+
and reviewer time.
|
|
44
|
+
|
|
45
|
+
## Step 3 — Scope-guard pre-check (before the PR exists)
|
|
46
|
+
|
|
47
|
+
- If the repo has a **named scope-guard workflow** (e.g. a Gitea `pr-scope-guard` that blocks PRs
|
|
48
|
+
mixing infra `.gitea/`/`.github/` changes with product `src/`/`frontend/`/`tests/` changes): read
|
|
49
|
+
the workflow's own rule and check the branch diff against exactly that rule **before** creating
|
|
50
|
+
the PR. On a violation, **refuse with the specific offending files** — do not let the guard fail
|
|
51
|
+
the PR after the fact.
|
|
52
|
+
- Without a named guard, apply the generic infra-vs-product mixed-diff heuristic as a **warning
|
|
53
|
+
only** — surface it and proceed (repos without a guard allow mixed PRs by construction).
|
|
54
|
+
|
|
55
|
+
## Step 3b — Cross-review trigger (before anything is pushed)
|
|
56
|
+
|
|
57
|
+
<!-- shared:cross-review-trigger — keep byte-identical across commands; a test pins it -->
|
|
58
|
+
**Run the trigger, do not eyeball the diff:**
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
python "${CLAUDE_PLUGIN_ROOT}/scripts/caddis_gate.py" review-trigger --range <base>...HEAD
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Exit **0** — say nothing, carry on. Exit **2** — it prints which rule fired and on which files.
|
|
65
|
+
Run `/caddis:cross-review`, show the findings, and let the user decide. It **never blocks**: this
|
|
66
|
+
picks when to ask for a second opinion, it is not a verdict, and a review gate that blocks a ship
|
|
67
|
+
gets switched off inside a week.
|
|
68
|
+
|
|
69
|
+
It fires on SQL and repositories, `services/`, caches and refresh jobs, auth and RBAC, or a diff
|
|
70
|
+
over 400 changed lines. No judgement in any of those — deliberately, because judgement is exactly
|
|
71
|
+
what failed.
|
|
72
|
+
<!-- /shared:cross-review-trigger -->
|
|
73
|
+
|
|
74
|
+
## Step 4 — Rebase-safe currency
|
|
75
|
+
|
|
76
|
+
- If the branch is **behind** the default branch, offer to rebase onto it (stale branches are how
|
|
77
|
+
merges rot). Only when behind — never rebase gratuitously.
|
|
78
|
+
- If the branch was already pushed, a rebase rewrites history. **Snapshot first, then push safely:**
|
|
79
|
+
```
|
|
80
|
+
git branch backup/<branch>-preship # recovery ref BEFORE any rewrite
|
|
81
|
+
git rebase <default-branch>
|
|
82
|
+
git push --force-with-lease origin <branch> # never bare --force
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Step 5 — Push + create or update the PR
|
|
86
|
+
|
|
87
|
+
- Push the branch to remote (plain push if no rewrite happened).
|
|
88
|
+
- Create the PR: title from the branch's conventional-commit summary (or `$ARGUMENTS`); body = the
|
|
89
|
+
commit list + a "generated by /pr-merge" note.
|
|
90
|
+
- **Idempotent:** if a PR for this branch **already exists**, reuse it, update its title/body, and
|
|
91
|
+
report it — never error, never open a duplicate.
|
|
92
|
+
|
|
93
|
+
## Step 6 — Monitor PR CI checks
|
|
94
|
+
|
|
95
|
+
Watch the PR's checks job-by-job until completion:
|
|
96
|
+
- Gitea lane: poll the run for the PR's head SHA via the API (`deploy-local` skill procedure).
|
|
97
|
+
- GitHub lane: `gh pr checks <pr> --watch` / `gh run watch` (`gh-cli` skill).
|
|
98
|
+
- If any check fails (red check): report the failure, classify the cause, and **stop before merging**.
|
|
99
|
+
Do not merge a failing PR. Apply the minimum source fix and re-push.
|
|
100
|
+
|
|
101
|
+
## Step 7 — Merge to default branch
|
|
102
|
+
|
|
103
|
+
Once all required checks are **green**, there are no merge conflicts, and review requirements are satisfied:
|
|
104
|
+
- Use the repo's configured **merge strategy** — read it from repo settings (Gitea repo settings API /
|
|
105
|
+
`gh repo view --json squashMergeAllowed,mergeCommitAllowed,rebaseMergeAllowed`) and `AGENTS.md`;
|
|
106
|
+
an explicit argument wins. Options: **squash**, **merge-commit**, rebase-merge. **Don't assume** —
|
|
107
|
+
if detection is inconclusive, use merge-commit (never silently squash someone's history without evidence).
|
|
108
|
+
- Execute merge:
|
|
109
|
+
- GitHub: `gh pr merge <pr> --<strategy>` (e.g. `gh pr merge <pr> --squash`).
|
|
110
|
+
- Gitea: call the Gitea PR merge API with the chosen strategy.
|
|
111
|
+
- Record the merge SHA.
|
|
112
|
+
|
|
113
|
+
## Step 8 — Monitor deployment & validate (if applicable)
|
|
114
|
+
|
|
115
|
+
If the default branch triggers an automated deployment pipeline:
|
|
116
|
+
- Gitea: `lint_and_test` → `deploy_prod` → `release_metadata` (`deploy-local` skill).
|
|
117
|
+
- GitHub: `gh run watch <run-id> --exit-status` on the default branch. If CI-only, note that no deploy occurs.
|
|
118
|
+
- Validate health and deployed SHA if a live service is deployed. If deploy validation fails, report
|
|
119
|
+
immediately and **skip cleanup** (Step 9).
|
|
120
|
+
|
|
121
|
+
## Step 9 — Close feature-branch lifecycle (Clean up)
|
|
122
|
+
|
|
123
|
+
**Only after** the merge is complete and deployment (if any) is validated green:
|
|
124
|
+
- Switch to the default branch: `git switch <default-branch>`.
|
|
125
|
+
- Fast-forward to the merge commit: `git pull --ff-only origin <default-branch>`.
|
|
126
|
+
- Delete the **local** feature branch: `git branch -d <branch>` (use `-D` only if squashed after verifying tree contents match).
|
|
127
|
+
- Delete the **remote** feature branch: `git push origin --delete <branch>` (if not deleted by forge).
|
|
128
|
+
- Drop the temporary `backup/<branch>-preship` ref if one was created in Step 4.
|
|
129
|
+
|
|
130
|
+
If deploy validation failed: **skip cleanup** entirely and report why — never delete a branch whose
|
|
131
|
+
work may need re-work.
|
|
132
|
+
|
|
133
|
+
## Step 10 — Report
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
PR: #<n> <title> — merged (<strategy>) as <merge-sha>
|
|
137
|
+
Checks: <check ✓ per check>
|
|
138
|
+
Deploy: <job ✓ / ✗ per job | n/a>
|
|
139
|
+
Prod SHA: matches merge ✓ | n/a
|
|
140
|
+
Cleanup: remote branch deleted / local branch deleted / backup ref dropped | SKIPPED (<why>)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Rules
|
|
144
|
+
|
|
145
|
+
- Never merge a PR whose required checks are red or absent.
|
|
146
|
+
- Never delete a feature branch before its merge is confirmed and any deploy validated.
|
|
147
|
+
- Never `git add -A` without reviewing `git status` first (stage named files only).
|
|
148
|
+
- Never force-push without a `backup/*-preship` recovery ref; force pushes are `--force-with-lease` only.
|
|
149
|
+
- Never edit a workflow file (`.gitea/workflows/`, `.github/workflows/`) to make a gate pass — fix the source.
|
|
150
|
+
- Local-only lane: refuse + point at `/ship`. On the default branch: stop + point at `/ship`.
|
|
151
|
+
|
|
152
|
+
## Skill reference
|
|
153
|
+
|
|
154
|
+
Gitea API monitoring, deploy tracking, failure triage: the `deploy-local` skill (ships in optional `caddis-extras`).
|
|
155
|
+
GitHub `gh pr create`, `gh pr checks`, `gh pr merge`: the `gh-cli` skill.
|
|
@@ -50,6 +50,29 @@ RELAY_FRAME_FOOTER = "=== end session-context ==="
|
|
|
50
50
|
|
|
51
51
|
_TRUTHY = {"1", "true", "yes", "on"}
|
|
52
52
|
|
|
53
|
+
# The freshness report (fetch, then say how far this checkout is from the remote default
|
|
54
|
+
# branch) is the ONE piece here that is imported rather than duplicated: the agy bundle ships
|
|
55
|
+
# hooks/repo_freshness.py beside this file, listed in runtime-targets.json. In the source repo
|
|
56
|
+
# it sits one directory over, so both layouts resolve. Fail open — an older bundle without it
|
|
57
|
+
# simply reports no freshness.
|
|
58
|
+
try:
|
|
59
|
+
_HOOKS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "hooks")
|
|
60
|
+
if os.path.isdir(_HOOKS_DIR) and _HOOKS_DIR not in sys.path:
|
|
61
|
+
sys.path.append(_HOOKS_DIR)
|
|
62
|
+
import repo_freshness # noqa: E402
|
|
63
|
+
except Exception:
|
|
64
|
+
repo_freshness = None # type: ignore[assignment]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def freshness_lines(root: str) -> list[str]:
|
|
68
|
+
"""Where `root` stands against the remote default branch; [] on any problem."""
|
|
69
|
+
if repo_freshness is None:
|
|
70
|
+
return []
|
|
71
|
+
try:
|
|
72
|
+
return repo_freshness.report(root)
|
|
73
|
+
except Exception:
|
|
74
|
+
return []
|
|
75
|
+
|
|
53
76
|
|
|
54
77
|
def is_headless() -> bool:
|
|
55
78
|
"""True when no human is watching — a run given its task explicitly needs no resume pointer."""
|
|
@@ -268,11 +291,22 @@ def main() -> None:
|
|
|
268
291
|
if conversation:
|
|
269
292
|
_mark_injected(root, conversation)
|
|
270
293
|
|
|
271
|
-
|
|
294
|
+
# agy has no SessionStart event, so this first invocation is also where the freshness
|
|
295
|
+
# report belongs. It is emitted even when there is no relay to inject, and it stays
|
|
296
|
+
# silent in a repo that is clean, level and on the default branch.
|
|
297
|
+
fresh: list[str] = []
|
|
298
|
+
for root in roots:
|
|
299
|
+
fresh.extend(freshness_lines(root))
|
|
300
|
+
|
|
301
|
+
if not blocks and not fresh:
|
|
272
302
|
return
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
303
|
+
parts = []
|
|
304
|
+
if blocks:
|
|
305
|
+
parts.append(RELAY_FRAME_HEADER + "\n" + "\n\n".join(blocks)
|
|
306
|
+
+ "\n\n" + RELAY_FRAME_FOOTER)
|
|
307
|
+
if fresh:
|
|
308
|
+
parts.append("\n".join(fresh))
|
|
309
|
+
payload = json.dumps({"injectSteps": [{"ephemeralMessage": "\n\n".join(parts)}]})
|
|
276
310
|
sys.stdout.write(payload)
|
|
277
311
|
except Exception:
|
|
278
312
|
return # fail open: no injection is always better than a broken invocation
|