forked from bchanot/claude
Merge release/1.3.0 into main
This commit is contained in:
@@ -1065,3 +1065,6 @@ Supersedes BDR-076 scope + amends BDR-066. Doctrine: session model (Fable) = mai
|
|||||||
### BDR-078 — ctx7 coverage: central fast-libs list + once-per-session reminder hook; every code path covered [accepted] (2026-07-20)
|
### BDR-078 — ctx7 coverage: central fast-libs list + once-per-session reminder hook; every code path covered [accepted] (2026-07-20)
|
||||||
Refines BDR-053 (single surface). Audit 2026-07-20: coverage PARTIAL — find-docs fired on user doc-questions only; ship-feature 0c / init-project 5c pre-fetched; /feat //bugfix executors + ad-hoc coding NEVER consulted ctx7; fast-libs list hardcoded 3× (drift risk). 4 closures shipped: (a) find-docs description += BEFORE-writing-code trigger (fast-moving lib, even without doc question, unless fresh cache) + cache-first rule in body (tee fetched docs to .ctx7-cache/); (b) feater+bugfixer briefs += fast-lib docs rule — read fresh `.ctx7-cache/<lib>*.md`, else `npx ctx7@latest` fetch max 2 topics, else `ctx7 cache miss: <lib>` in NOTES + proceed (executors lack Skill tool → Bash path); (c) hooks/ctx7-reminder.sh UserPromptSubmit — ONE fire/session (sentinel on session_id), only when project manifest carries fast-libs; reports cache state; skips <task-notification> turns; always exit 0; (d) lib/fast-libs.sh = SINGLE SOURCE (detect / cache-status verbs, JS package.json anchored full-key match + Python requirements/pyproject, 7-day freshness, LC_ALL=C sort locale-independent) consumed by hook + 3 pipeline skills + 2 briefs. 2nd session surface DELIBERATE, not a BDR-053 reversal: 053 killed a 490-tok ALWAYS-ON rule duplicate; hook costs ~0 quiet, 1 line once when fast-libs present. Alternatives rejected: PreToolUse Edit/Write gate (fires per-edit = noise); description-only fix (probabilistic, executors unreachable). Tests: lib/tests/fast-libs.test.sh 11 checks (anchored/near-miss/py/none, cache fresh/stale/missing, hook fire/sentinel/quiet×2); shellcheck + full make test green. Branch feature/ctx7-coverage, unmerged (human gate).
|
Refines BDR-053 (single surface). Audit 2026-07-20: coverage PARTIAL — find-docs fired on user doc-questions only; ship-feature 0c / init-project 5c pre-fetched; /feat //bugfix executors + ad-hoc coding NEVER consulted ctx7; fast-libs list hardcoded 3× (drift risk). 4 closures shipped: (a) find-docs description += BEFORE-writing-code trigger (fast-moving lib, even without doc question, unless fresh cache) + cache-first rule in body (tee fetched docs to .ctx7-cache/); (b) feater+bugfixer briefs += fast-lib docs rule — read fresh `.ctx7-cache/<lib>*.md`, else `npx ctx7@latest` fetch max 2 topics, else `ctx7 cache miss: <lib>` in NOTES + proceed (executors lack Skill tool → Bash path); (c) hooks/ctx7-reminder.sh UserPromptSubmit — ONE fire/session (sentinel on session_id), only when project manifest carries fast-libs; reports cache state; skips <task-notification> turns; always exit 0; (d) lib/fast-libs.sh = SINGLE SOURCE (detect / cache-status verbs, JS package.json anchored full-key match + Python requirements/pyproject, 7-day freshness, LC_ALL=C sort locale-independent) consumed by hook + 3 pipeline skills + 2 briefs. 2nd session surface DELIBERATE, not a BDR-053 reversal: 053 killed a 490-tok ALWAYS-ON rule duplicate; hook costs ~0 quiet, 1 line once when fast-libs present. Alternatives rejected: PreToolUse Edit/Write gate (fires per-edit = noise); description-only fix (probabilistic, executors unreachable). Tests: lib/tests/fast-libs.test.sh 11 checks (anchored/near-miss/py/none, cache fresh/stale/missing, hook fire/sentinel/quiet×2); shellcheck + full make test green. Branch feature/ctx7-coverage, unmerged (human gate).
|
||||||
Amendment (same session): skills/find-docs = machine-owned dist (gitignored, ctx7 regenerates on fresh clone) → durable copy of closure (a) lives in install-plugins.sh STEP ctx7 (idempotent grep-guarded python patch, fixture-verified); live SKILL.md carries the same edit uncommitted by design.
|
Amendment (same session): skills/find-docs = machine-owned dist (gitignored, ctx7 regenerates on fresh clone) → durable copy of closure (a) lives in install-plugins.sh STEP ctx7 (idempotent grep-guarded python patch, fixture-verified); live SKILL.md carries the same edit uncommitted by design.
|
||||||
|
|
||||||
|
### BDR-079 — profile `set` symmetric on managed externals + MCPs [accepted] (2026-07-20)
|
||||||
|
Audit (user ask "profile toggles externals both ways?"): ASYMMETRIC. Enable side OK — gstack on-demand from submodule when pack off (shared `skills-disabled/gstack__*` convention with toggle-external.sh, interoperable), externals restored from parked, magic delegated to toggle-external. Disable side MISSING: `cmd_set` trimmed only gstack + MANAGED_PLUGINS → `set backend` left emil/frontend-design/design-motion/impeccable active + magic registered; SKILL.md claimed both-ways toggle (true only at enable). Shipped: (1) `MANAGED_EXTERNALS` (emil-design-eng, frontend-design, design-motion-principles, impeccable = exact union of profile `external` usage; darwin-skill excluded — not task-type-driven) + `MANAGED_MCPS` (magic) allowlists, same doctrine as MANAGED_PLUGINS; (2) cmd_set refactored to 4 trim helpers (`disable_{gstack,plugins,externals,mcps}_not_in`) — symmetric, nothing outside allowlists ever auto-touched; (3) enable_skill external += from-source fallback (`ln -sf skills-external/<name>`, mirrors toggle-external) — closes the "missing symlink" warn; (4) stale usage() NOTE ("NOT toggled automatically") + SKILL.md fixed. Hermetic test profile-set-managed.test.sh 16 checks: fixture repo (both *_REPO_OVERRIDE), fake `claude` shim on PATH logging calls + flat-file MCP registry — gstack on-demand, external from-source, park/restore round-trip, magic add/remove calls, non-managed untouched. shellcheck + make test green. Branch feature/profile-managed-externals, unmerged (human gate).
|
||||||
|
|||||||
@@ -417,3 +417,4 @@ rules:
|
|||||||
## 2026-07-20
|
## 2026-07-20
|
||||||
- ctx7 coverage audit (user ask "ctx7 appelé à chaque techno ?") → verdict PARTIAL. 4 gaps: find-docs question-only, /feat //bugfix executors blind, ad-hoc coding uncovered, fast-libs hardcoded 3×. All 4 closed → BDR-078 (fast-libs.sh single source + ctx7-reminder hook + description trigger + executor-brief rule). fast-libs test 11/0, make test + review-guards green. feature/ctx7-coverage, UNMERGED.
|
- ctx7 coverage audit (user ask "ctx7 appelé à chaque techno ?") → verdict PARTIAL. 4 gaps: find-docs question-only, /feat //bugfix executors blind, ad-hoc coding uncovered, fast-libs hardcoded 3×. All 4 closed → BDR-078 (fast-libs.sh single source + ctx7-reminder hook + description trigger + executor-brief rule). fast-libs test 11/0, make test + review-guards green. feature/ctx7-coverage, UNMERGED.
|
||||||
- v1.2.0 cut + pushed (release-candidate flow: prep/finish via release-executor, tag on main 51b6572). CHANGELOG backfilled at prep: 10 entries added to Unreleased (plan-challenge, seo-data verbs, model-tiering v2, integrity pass, safe_fetch/url-guard) — was ctx7-only. /doc full post-release: README model-routing table v1→v2 reframe + ctx7 two-surface wording, chore/doc-sync-v1.2.0 merged. All pushed on explicit go.
|
- v1.2.0 cut + pushed (release-candidate flow: prep/finish via release-executor, tag on main 51b6572). CHANGELOG backfilled at prep: 10 entries added to Unreleased (plan-challenge, seo-data verbs, model-tiering v2, integrity pass, safe_fetch/url-guard) — was ctx7-only. /doc full post-release: README model-routing table v1→v2 reframe + ctx7 two-surface wording, chore/doc-sync-v1.2.0 merged. All pushed on explicit go.
|
||||||
|
- profile↔toggle-external audit (user) → enable side already symmetric (gstack on-demand LIVE), disable side missing → BDR-079: MANAGED_EXTERNALS+MANAGED_MCPS trim at set, external from-source fallback, 16-check hermetic test (claude shim). feature/profile-managed-externals, UNMERGED.
|
||||||
|
|||||||
+33
-5
@@ -1,5 +1,31 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
## 2026-07-20 — pending merge gates (reconcile)
|
||||||
|
- [x] merge feature/profile-managed-externals → develop (BDR-079 profile
|
||||||
|
symmetry + /doc clean pass: README/USAGE/ARCHITECTURE.md) — 37c79f0
|
||||||
|
- [x] merge chore/purge-transient-docs → develop (docs/ transient purge
|
||||||
|
655e364 + reconcile e75ea79) — reaches main at next release
|
||||||
|
- [ ] Makefile help text: profiles 5/10 listed (:57) + test glob missing
|
||||||
|
run-*.sh (:31) — 2-line hotfix (flagged by /doc audit)
|
||||||
|
|
||||||
|
## 2026-07-20 — profile ↔ toggle-external symmetry (feature/profile-managed-externals, BDR-079)
|
||||||
|
Audit verdict: gstack on-demand + design enable already work; DISABLE side
|
||||||
|
missing — `set backend` leaves emil/frontend-design/design-motion/impeccable
|
||||||
|
active + magic registered. Doc claims auto-toggle both ways (only enable true).
|
||||||
|
- [x] profile.sh: `MANAGED_EXTERNALS` (emil-design-eng, frontend-design,
|
||||||
|
design-motion-principles, impeccable — union of profile usage) +
|
||||||
|
`MANAGED_MCPS` (magic) allowlists; cmd_set refactored to 4 trim
|
||||||
|
helpers (disable_{gstack,plugins,externals,mcps}_not_in).
|
||||||
|
- [x] profile.sh enable_skill external: from-source fallback
|
||||||
|
(`ln -sf skills-external/<name>`) mirroring toggle-external.
|
||||||
|
- [x] Texts: cmd_set info line, usage() NOTE (stale "NOT toggled
|
||||||
|
automatically"), header; skills/profile/SKILL.md Mechanism+tradeoffs.
|
||||||
|
- [x] Hermetic test lib/tests/profile-set-managed.test.sh — 16/0: gstack
|
||||||
|
on-demand, external from-source, park/restore round-trip, magic
|
||||||
|
add/remove via claude shim, non-managed untouched.
|
||||||
|
- [x] Gate: shellcheck OK + make test exit 0 (review-guards 5/0). BDR-079 +
|
||||||
|
journal + CHANGELOG done. Merged 37c79f0 (2026-07-20).
|
||||||
|
|
||||||
## 2026-07-20 — ctx7 coverage extension (feature/ctx7-coverage, BDR-078)
|
## 2026-07-20 — ctx7 coverage extension (feature/ctx7-coverage, BDR-078)
|
||||||
Close the 4 gaps from the ctx7 coverage audit: /feat //bugfix + ad-hoc coding
|
Close the 4 gaps from the ctx7 coverage audit: /feat //bugfix + ad-hoc coding
|
||||||
never consult ctx7; fast-libs list hardcoded 3×; zero deterministic backstop.
|
never consult ctx7; fast-libs list hardcoded 3×; zero deterministic backstop.
|
||||||
@@ -18,7 +44,7 @@ never consult ctx7; fast-libs list hardcoded 3×; zero deterministic backstop.
|
|||||||
- [x] `lib/tests/fast-libs.test.sh` (lib verbs + hook fire/sentinel/quiet)
|
- [x] `lib/tests/fast-libs.test.sh` (lib verbs + hook fire/sentinel/quiet)
|
||||||
— 11/0, auto-discovered by the make test glob.
|
— 11/0, auto-discovered by the make test glob.
|
||||||
- [x] Gate: shellcheck + make test green (review-guards 5/0). BDR-078 +
|
- [x] Gate: shellcheck + make test green (review-guards 5/0). BDR-078 +
|
||||||
journal + CHANGELOG done. Committed on branch, NO merge (human gate).
|
journal + CHANGELOG done. Merged 8ee7d19, shipped v1.2.0.
|
||||||
|
|
||||||
## 2026-07-19 — Opus-pin dispatched judgment agents (branch feature/opus-pin-audit-agents)
|
## 2026-07-19 — Opus-pin dispatched judgment agents (branch feature/opus-pin-audit-agents)
|
||||||
|
|
||||||
@@ -45,8 +71,8 @@ on audits). User approved: opus for judgment agents, drop local opus pin.
|
|||||||
- [x] `.claude/settings.local.json` — drop `"model": "opus-4-8[1m]"`
|
- [x] `.claude/settings.local.json` — drop `"model": "opus-4-8[1m]"`
|
||||||
(local, gitignored; Fable default from settings.json applies).
|
(local, gitignored; Fable default from settings.json applies).
|
||||||
- [x] Tests: model-routing + loops-light + shellcheck + make test.
|
- [x] Tests: model-routing + loops-light + shellcheck + make test.
|
||||||
- [x] Memory: BDR-076 append + journal line. Commit (feat + chore),
|
- [x] Memory: BDR-076 append + journal line. Commit (feat + chore);
|
||||||
NO merge (human gate).
|
merged 17fbe51, shipped v1.2.0 (reconcile 2026-07-20).
|
||||||
|
|
||||||
## 2026-07-17 — STATUS seo/geo parity (branch bugfix/seo-geo-integrity — MERGED to develop, 92301fe; "UNMERGED" note was stale, corrected 2026-07-19 W0)
|
## 2026-07-17 — STATUS seo/geo parity (branch bugfix/seo-geo-integrity — MERGED to develop, 92301fe; "UNMERGED" note was stale, corrected 2026-07-19 W0)
|
||||||
PHASE 1 — integrity: **DONE 7/7**. I3 8b0c98c · I1 57c67f2 · I2 4ea2fb8 ·
|
PHASE 1 — integrity: **DONE 7/7**. I3 8b0c98c · I1 57c67f2 · I2 4ea2fb8 ·
|
||||||
@@ -54,8 +80,8 @@ I5 64f175f · I4 e70e1d6 · I6 9da1dec · I8 acd452b. Plus 9cd7b51 (A1+A2, two
|
|||||||
process anomalies surfaced by dogfooding /harden at zenquality.fr from the
|
process anomalies surfaced by dogfooding /harden at zenquality.fr from the
|
||||||
wrong CWD).
|
wrong CWD).
|
||||||
PHASE 2 — free wins: W3 fe93b79 · W1 a6d423b · **W2 DEFERRED** (see below).
|
PHASE 2 — free wins: W3 fe93b79 · W1 a6d423b · **W2 DEFERRED** (see below).
|
||||||
NEXT: H1 (SSRF/injection guard) → C1 (sitemap crawl). Human merge gate: all
|
H1 DONE (url-guard 7d6aa09) · C1 DONE (sitemap verb, C1a/b/c). Branch MERGED
|
||||||
10 commits await review; nothing merged to develop.
|
to develop (92301fe), shipped in v1.2.0 (reconcile 2026-07-20).
|
||||||
|
|
||||||
### Plan corrections made while executing (the plan was wrong 4×)
|
### Plan corrections made while executing (the plan was wrong 4×)
|
||||||
- **B3 KILLED** — GSC Links API does not exist. Verified against the API
|
- **B3 KILLED** — GSC Links API does not exist. Verified against the API
|
||||||
@@ -478,6 +504,8 @@ manipuler une valeur de secret — edits sur les mécanismes seulement.
|
|||||||
Transcript `f1c9c474-...jsonl` (generic-api-key, 8) — PAS choisi
|
Transcript `f1c9c474-...jsonl` (generic-api-key, 8) — PAS choisi
|
||||||
par l'utilisateur parmi les options (auto-inspect / TODO / rm) →
|
par l'utilisateur parmi les options (auto-inspect / TODO / rm) →
|
||||||
**laissé intact, à trancher** ; ni lu ni caractérisé (règle job7).
|
**laissé intact, à trancher** ; ni lu ni caractérisé (règle job7).
|
||||||
|
[sans objet : transcript auto-roté (cleanupPeriodDays=7), absent
|
||||||
|
du disque — reconcile 2026-07-20]
|
||||||
- [x] **NOUVEAU (bruit, pas un item D)** : transcript de CETTE session
|
- [x] **NOUVEAU (bruit, pas un item D)** : transcript de CETTE session
|
||||||
(`4b5c02a9-...jsonl`, aws-access-token, 2) = mes propres fixtures
|
(`4b5c02a9-...jsonl`, aws-access-token, 2) = mes propres fixtures
|
||||||
synthétiques de test (AKIA random) loggées dans mon propre
|
synthétiques de test (AKIA random) loggées dans mon propre
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# Architecture — claude-config
|
||||||
|
|
||||||
|
Repo layout and structural principles. Command workflows live in
|
||||||
|
[`USAGE.md`](./USAGE.md); version history in [`CHANGELOG.md`](./CHANGELOG.md).
|
||||||
|
|
||||||
|
## Project layout
|
||||||
|
|
||||||
|
```
|
||||||
|
claude-config/
|
||||||
|
├── CLAUDE.global.md # Global coding preferences — deployed as ~/.claude/CLAUDE.md
|
||||||
|
├── CLAUDE.md # Project-scope instructions (this repo only)
|
||||||
|
├── settings.json # Global permissions (deny / ask / allow rules)
|
||||||
|
├── install.sh # Bootstrap: Claude Code CLI + auth + submodules + link + plugins
|
||||||
|
├── install-plugins.sh # One-shot installer: prerequisites + all plugins
|
||||||
|
├── link.sh # Symlinks this repo into ~/.claude/
|
||||||
|
├── doctor.sh # Setup diagnostic
|
||||||
|
├── update-all.sh # One-command update for all components
|
||||||
|
├── Makefile # Unified entry point: make install / doctor / update
|
||||||
|
├── plugins.lock.json # Version pinning for non-marketplace dependencies
|
||||||
|
├── hooks/ # Session start, statusline, RTK rewrite + ctx7 + design-toolchain reminders
|
||||||
|
├── agents/ # Execution units called by skills (never invoked directly)
|
||||||
|
├── skills/ # Entry points invoked via /skill-name
|
||||||
|
├── skills-external/ # Vendored skill packs (gstack submodule + installer-fetched design packs)
|
||||||
|
├── templates/ # Per-project templates (CLAUDE.md, settings, memory registries, deploy runbook, gitignore)
|
||||||
|
└── lib/ # Shared shell libs (gitflow, profiles, commit helpers, archetypes, tests)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture principles
|
||||||
|
|
||||||
|
- `skills/` = entry points you invoke via `/skill-name`
|
||||||
|
- `agents/` = execution units called by skills (never invoked directly by user)
|
||||||
|
- `templates/` = symlinked to `~/.claude/templates/` — copy into projects via `/onboard` or manually
|
||||||
|
- **Graphify** builds a knowledge graph of any codebase (`/graphify query`), producing a navigable wiki in `graphify-out/wiki/`. This map helps Claude understand project structure, find relevant code faster, and reason across files. Essential for large-scope tasks (multi-file features, complex bugs, architectural changes). Small tasks should skip it and read files directly.
|
||||||
@@ -6,6 +6,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/).
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [1.3.0] — 2026-07-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Profile switches now toggle external packs and MCPs both ways (BDR-079)** — `profile.sh set` was asymmetric: it enabled what a profile listed (including gstack skills on demand when the whole pack is off, and the `magic` MCP) but never disabled the managed leftovers, so `set backend` after design work kept emil-design-eng / frontend-design / design-motion-principles / impeccable active and magic registered. `set` now trims managed externals (`MANAGED_EXTERNALS`) and managed MCPs (`MANAGED_MCPS`, delegated to `toggle-external.sh`) not listed in the profile — same allowlist doctrine as `MANAGED_PLUGINS`, nothing outside the allowlists is ever auto-touched (darwin-skill stays manual). Also: an `external` entry whose symlink never existed is now created from `skills-external/` (mirroring toggle-external's from-source path), and the stale "NOT toggled automatically" note in `profile.sh` usage was corrected. Covered by a hermetic 16-check test (`lib/tests/profile-set-managed.test.sh`) with a fake `claude` shim.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **README restructured for public readers** — the project-layout tree and architecture principles moved verbatim to a new `ARCHITECTURE.md` (README links it); bare decision-registry citations (`BDR-XXX`) stripped from README prose, meaning preserved; `/profile` documentation corrected in three places to the real 10-profile set (web / seo / web-full / full / backend / design / dev / qa / audit / minimal); fresh-install block now uses the real clone URL + `make install` / `make doctor`; new "SEO data layer" subsection documents the `GOOGLE_OAUTH_CLIENT_ID` / `GOOGLE_OAUTH_CLIENT_SECRET` / `CRUX_API_KEY` vars in `~/.claude/.env` (mirrors `.env.example`, `make seo-connect` one-time consent).
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Transient planning artifacts purged from the repo** — `docs/plans`, `docs/specs`, `docs/superpowers/{plans,specs}` (deploy-skill 2026-06-27, model-routing 2026-07-15) were run-time pipeline artifacts that should have been deleted in their chantiers' post-merge cleanup and slipped through (one pair predates the lifecycle rule, one missed the purge step of a 6-wave chantier). Git history at the feature commits remains their archive; `docs/` no longer exists.
|
||||||
|
|
||||||
## [1.2.1] — 2026-07-20
|
## [1.2.1] — 2026-07-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -11,33 +11,11 @@ Global Claude Code configuration — agents, skills, plugins, and project templa
|
|||||||
|
|
||||||
This repo is your personal Claude Code setup, versioned and reproducible across machines.
|
This repo is your personal Claude Code setup, versioned and reproducible across machines.
|
||||||
|
|
||||||
```
|
See [`ARCHITECTURE.md`](./ARCHITECTURE.md) for the full project layout and
|
||||||
claude-config/
|
structural principles (skills = entry points, agents = execution units,
|
||||||
├── CLAUDE.global.md # Global coding preferences — deployed as ~/.claude/CLAUDE.md
|
templates = per-project scaffolding, graphify = codebase knowledge graph).
|
||||||
├── CLAUDE.md # Project-scope instructions (this repo only)
|
|
||||||
├── settings.json # Global permissions (deny / ask / allow rules)
|
|
||||||
├── install.sh # Bootstrap: Claude Code CLI + auth + submodules + link + plugins
|
|
||||||
├── install-plugins.sh # One-shot installer: prerequisites + all plugins
|
|
||||||
├── link.sh # Symlinks this repo into ~/.claude/
|
|
||||||
├── doctor.sh # Setup diagnostic
|
|
||||||
├── update-all.sh # One-command update for all components
|
|
||||||
├── Makefile # Unified entry point: make install / doctor / update
|
|
||||||
├── plugins.lock.json # Version pinning for non-marketplace dependencies
|
|
||||||
├── hooks/ # Session start, statusline, RTK rewrite + ctx7 + design-toolchain reminders
|
|
||||||
├── agents/ # Execution units called by skills (never invoked directly)
|
|
||||||
├── skills/ # Entry points invoked via /skill-name
|
|
||||||
├── skills-external/ # Vendored skill packs (gstack submodule + installer-fetched design packs)
|
|
||||||
├── templates/ # Per-project templates (CLAUDE.md, settings, memory registries, deploy runbook, gitignore)
|
|
||||||
└── lib/ # Shared shell libs (gitflow, profiles, commit helpers, archetypes, tests)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Architecture principle:**
|
### Agent model routing (model-tiering v2)
|
||||||
- `skills/` = entry points you invoke via `/skill-name`
|
|
||||||
- `agents/` = execution units called by skills (never invoked directly by user)
|
|
||||||
- `templates/` = symlinked to `~/.claude/templates/` — copy into projects via `/onboard` or manually
|
|
||||||
- **Graphify** builds a knowledge graph of any codebase (`/graphify query`), producing a navigable wiki in `graphify-out/wiki/`. This map helps Claude understand project structure, find relevant code faster, and reason across files. Essential for large-scope tasks (multi-file features, complex bugs, architectural changes). Small tasks should skip it and read files directly.
|
|
||||||
|
|
||||||
### Agent model routing (BDR-076/077 — model-tiering v2)
|
|
||||||
|
|
||||||
Doctrine: the session model (Fable) does main-loop reflection ONLY —
|
Doctrine: the session model (Fable) does main-loop reflection ONLY —
|
||||||
brainstorm, plan, contract, audit judgment, gates, loop decisions — enforced
|
brainstorm, plan, contract, audit judgment, gates, loop decisions — enforced
|
||||||
@@ -73,14 +51,14 @@ children are dispatched `model:"fable"` (they carry reflection).
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Clone with submodules
|
# 1. Clone with submodules
|
||||||
git clone --recurse-submodules git@github.com:youruser/claude-config.git
|
git clone --recurse-submodules https://github.com/bchanot/claude
|
||||||
cd claude-config
|
cd claude
|
||||||
|
|
||||||
# 2. Bootstrap (CLI + auth + symlinks + plugins)
|
# 2. Bootstrap (CLI + auth + symlinks + plugins)
|
||||||
bash install.sh
|
make install
|
||||||
|
|
||||||
# 3. Verify setup
|
# 3. Verify setup
|
||||||
bash doctor.sh
|
make doctor
|
||||||
|
|
||||||
# 4. Restart Claude Code — plugins load automatically
|
# 4. Restart Claude Code — plugins load automatically
|
||||||
```
|
```
|
||||||
@@ -90,11 +68,11 @@ The plugins step logs to `install-YYYYMMDD-HHMMSS.log`.
|
|||||||
|
|
||||||
**Optional — Context7** (fast doc lookup for React / Next.js / Prisma…): the plugins
|
**Optional — Context7** (fast doc lookup for React / Next.js / Prisma…): the plugins
|
||||||
step installs the `ctx7` CLI and wires it into Claude Code. The doc-fetch surface is
|
step installs the `ctx7` CLI and wires it into Claude Code. The doc-fetch surface is
|
||||||
the `find-docs` skill alone (BDR-053 — the generated `rules/context7.md` is purged by
|
the `find-docs` skill alone (the generated `rules/context7.md` is purged by
|
||||||
design; if you run `ctx7 setup` manually, delete that rule or re-run `make plugin`).
|
design; if you run `ctx7 setup` manually, delete that rule or re-run `make plugin`).
|
||||||
A once-per-session `ctx7-reminder` hook nudges toward it when the current project
|
A once-per-session `ctx7-reminder` hook nudges toward it when the current project
|
||||||
carries fast-moving libs (`lib/fast-libs.sh`) — a scoped second surface refining
|
carries fast-moving libs (`lib/fast-libs.sh`) — a scoped second surface, a
|
||||||
BDR-053, not reversing it (BDR-078).
|
refinement of the single-surface rule, not a reversal.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ctx7 login # optional: OAuth / API key for higher rate limits
|
ctx7 login # optional: OAuth / API key for higher rate limits
|
||||||
@@ -160,7 +138,7 @@ a different package, ships its own conflicting `graphify` bin) — see
|
|||||||
| `/web-validate` | W3C HTML/CSS validity + WCAG 2.1 accessibility audit |
|
| `/web-validate` | W3C HTML/CSS validity + WCAG 2.1 accessibility audit |
|
||||||
| `/geo` | GEO-only audit — AI-search visibility (ChatGPT, Perplexity, Claude, Gemini…) |
|
| `/geo` | GEO-only audit — AI-search visibility (ChatGPT, Perplexity, Claude, Gemini…) |
|
||||||
| `/client-handover` | Final project delivery — audits + branded deliverable (Markdown / HTML / PDF) |
|
| `/client-handover` | Final project delivery — audits + branded deliverable (Markdown / HTML / PDF) |
|
||||||
| `/profile` | Activate a skill profile (design / dev / qa / audit / minimal) |
|
| `/profile` | Activate a skill profile (web / seo / web-full / full / backend / design / dev / qa / audit / minimal) |
|
||||||
| `/tour` | Grouped all-axes sweep — cleanup + security + reconcile + doc, fix and loop until clean |
|
| `/tour` | Grouped all-axes sweep — cleanup + security + reconcile + doc, fix and loop until clean |
|
||||||
|
|
||||||
> This table lists personal skills. Gstack skills (investigate, review, retro,
|
> This table lists personal skills. Gstack skills (investigate, review, retro,
|
||||||
@@ -236,17 +214,15 @@ See [`templates/settings/SETTINGS.md`](templates/settings/SETTINGS.md) for the f
|
|||||||
`~/.claude.json` (or the project's `.mcp.json`) — if you pass the real secret
|
`~/.claude.json` (or the project's `.mcp.json`) — if you pass the real secret
|
||||||
on that command line, it materializes as a second plaintext copy outside
|
on that command line, it materializes as a second plaintext copy outside
|
||||||
`~/.claude/.env`, invisible to the repo's `.gitignore`/allowlist reach (this
|
`~/.claude/.env`, invisible to the repo's `.gitignore`/allowlist reach (this
|
||||||
bit us once: job7/BDR-026).
|
bit us once).
|
||||||
|
|
||||||
Claude Code expands `${VAR}` and `${VAR:-default}` in `mcpServers` config —
|
Claude Code expands `${VAR}` and `${VAR:-default}` in `mcpServers` config —
|
||||||
in `env`, `command`, `args`, `url`, and `headers` — for both project (`.mcp.json`)
|
in `env`, `command`, `args`, `url`, and `headers` — for both project (`.mcp.json`)
|
||||||
and user (`~/.claude.json`) scope. Use that instead of a literal value:
|
and user (`~/.claude.json`) scope. Use that instead of a literal value:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# WRONG — plaintext key lands in ~/.claude.json:
|
MAGIC_API_KEY=<Enter your magic api key here from https://21st.dev/settings/api-keys >
|
||||||
claude mcp add magic --scope user --env API_KEY="$MAGIC_API_KEY" -- npx -y @21st-dev/magic@latest
|
# single-quoted so bash doesn't expand it; Claude Code expands it at
|
||||||
|
|
||||||
# RIGHT — single-quoted so bash doesn't expand it; Claude Code expands it at
|
|
||||||
# launch, reading the var from its own process environment:
|
# launch, reading the var from its own process environment:
|
||||||
claude mcp add magic --scope user --env 'API_KEY=${MAGIC_API_KEY}' -- npx -y @21st-dev/magic@latest
|
claude mcp add magic --scope user --env 'API_KEY=${MAGIC_API_KEY}' -- npx -y @21st-dev/magic@latest
|
||||||
```
|
```
|
||||||
@@ -264,6 +240,26 @@ There is no `claude mcp add` flag that writes the reference form for you —
|
|||||||
the `${VAR}` syntax has to be typed by hand (or via a wrapper script), same as
|
the `${VAR}` syntax has to be typed by hand (or via a wrapper script), same as
|
||||||
above.
|
above.
|
||||||
|
|
||||||
|
### SEO data layer (`/seo` FULL) — Google OAuth + CrUX keys
|
||||||
|
|
||||||
|
The same `~/.claude/.env` also feeds `lib/seo-data`, which pulls real Google
|
||||||
|
Search Console and Chrome UX Report data into `/seo` FULL audits. Add these
|
||||||
|
three vars (template with the GCP console steps in `.env.example`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# OAuth Desktop client — GCP console → APIs & Services → Credentials →
|
||||||
|
# OAuth client (Desktop). Consent scope: webmasters.readonly only.
|
||||||
|
GOOGLE_OAUTH_CLIENT_ID=<your-client-id.apps.googleusercontent.com>
|
||||||
|
GOOGLE_OAUTH_CLIENT_SECRET=<your-client-secret>
|
||||||
|
# CrUX + PageSpeed API key — GCP console → Credentials → API key,
|
||||||
|
# restricted to those two APIs. https://developer.chrome.com/docs/crux/api
|
||||||
|
CRUX_API_KEY=<your-crux-api-key>
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run the one-time consent flow: `make seo-connect` (per-label token
|
||||||
|
store, multi-site safe). Missing credentials never break an audit — `/seo`
|
||||||
|
degrades gracefully to anonymous PageSpeed lab data.
|
||||||
|
|
||||||
### magic MCP (`@21st-dev/magic`) — known callback-injection risk
|
### magic MCP (`@21st-dev/magic`) — known callback-injection risk
|
||||||
|
|
||||||
`21st_magic_component_builder` opens an **unauthenticated** local callback
|
`21st_magic_component_builder` opens an **unauthenticated** local callback
|
||||||
@@ -273,7 +269,7 @@ can `POST` to it and that body is injected **verbatim** into the tool result
|
|||||||
the model consumes (job8 audit, `dist/utils/callback-server.js:36`). This is
|
the model consumes (job8 audit, `dist/utils/callback-server.js:36`). This is
|
||||||
in the third-party package's code, not this repo's config — **we don't patch
|
in the third-party package's code, not this repo's config — **we don't patch
|
||||||
it**. The mitigation lives entirely on our side: `settings.json`
|
it**. The mitigation lives entirely on our side: `settings.json`
|
||||||
`permissions.ask` explicitly lists all 4 `mcp__magic__*` tools ([[BDR-059]]),
|
`permissions.ask` explicitly lists all 4 `mcp__magic__*` tools,
|
||||||
so every call — builder included — requires a live confirmation and can
|
so every call — builder included — requires a live confirmation and can
|
||||||
never auto-execute. Don't allowlist
|
never auto-execute. Don't allowlist
|
||||||
`21st_magic_component_builder` or `21st_magic_component_refiner` (arbitrary
|
`21st_magic_component_builder` or `21st_magic_component_refiner` (arbitrary
|
||||||
@@ -299,10 +295,10 @@ make plugin # install plugins only
|
|||||||
make link # create/update symlinks into ~/.claude/
|
make link # create/update symlinks into ~/.claude/
|
||||||
make doctor # diagnostic
|
make doctor # diagnostic
|
||||||
make update # update Claude Code, config, submodules, plugins, and verify
|
make update # update Claude Code, config, submodules, plugins, and verify
|
||||||
make test # run deterministic tests (lib/tests/*.test.sh + lib/seo-data/*.test.sh + lib/gitflow-test.sh)
|
make test # run deterministic tests (lib/tests/*.test.sh + lib/seo-data/*.test.sh + lib/gitflow-test.sh + lib/tests/run-*.sh)
|
||||||
make onboard # onboard an existing project (run from its dir)
|
make onboard # onboard an existing project (run from its dir)
|
||||||
make seo-connect # connect a Google account for /seo FULL (OAuth consent)
|
make seo-connect # connect a Google account for /seo FULL (OAuth consent)
|
||||||
make profile cmd="set X" # activate a skill profile (design/dev/qa/audit/minimal/full)
|
make profile cmd="set X" # activate a skill profile (web/seo/web-full/full/backend/design/dev/qa/audit/minimal)
|
||||||
make profile-list # list skill profiles
|
make profile-list # list skill profiles
|
||||||
make profile-current # show the active profile
|
make profile-current # show the active profile
|
||||||
make profile-reset # re-enable all gstack skills
|
make profile-reset # re-enable all gstack skills
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ Tu veux...
|
|||||||
| `/pdf-translate` | Traduire un PDF vers une autre langue | Sortie HTML fidèle (images, layout, style préservés) |
|
| `/pdf-translate` | Traduire un PDF vers une autre langue | Sortie HTML fidèle (images, layout, style préservés) |
|
||||||
| `/impeccable` | Audit/polish design + détecteur anti-slop déterministe | 23 verbes ; `npx impeccable detect` (exit 0/2) |
|
| `/impeccable` | Audit/polish design + détecteur anti-slop déterministe | 23 verbes ; `npx impeccable detect` (exit 0/2) |
|
||||||
| `/tour` | Sweep groupé sur un ou plusieurs projets | Sécu + nettoyage + reconcile + doc, boucle jusqu'à un pass propre |
|
| `/tour` | Sweep groupé sur un ou plusieurs projets | Sécu + nettoyage + reconcile + doc, boucle jusqu'à un pass propre |
|
||||||
| `/profile` | Changer le profil de skills | design / dev / qa / audit / minimal |
|
| `/profile` | Changer le profil de skills | web / seo / web-full / full / backend / design / dev / qa / audit / minimal |
|
||||||
|
|
||||||
> Cette table couvre les skills personnels principaux. Les plugins (gstack,
|
> Cette table couvre les skills personnels principaux. Les plugins (gstack,
|
||||||
> pr-review-toolkit…) et marketplaces externes en ajoutent beaucoup d'autres —
|
> pr-review-toolkit…) et marketplaces externes en ajoutent beaucoup d'autres —
|
||||||
|
|||||||
@@ -1,385 +0,0 @@
|
|||||||
# Deploy Skill — Implementation Plan
|
|
||||||
|
|
||||||
> **Superseded by BDR-054** (`52f6678`): the shipped skill has NO `NEXT.sh` file and NO
|
|
||||||
> AskUserQuestion hand-back — see `skills/deploy/SKILL.md` for current behavior. This
|
|
||||||
> plan is kept as historical record; do not implement its NEXT.sh/hand-back sections.
|
|
||||||
|
|
||||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
||||||
|
|
||||||
**Goal:** Build a `deploy` skill — a per-project shell runbook that re-instantiates from the delta since the last deploy, hands control to the user for out-of-band execution, resumes cold (even in a new session), and learns from deploy errors in place.
|
|
||||||
|
|
||||||
**Architecture:** A surgical-commit helper (`lib/deploy-commit.sh`, allowlist-scoped to `.claude/deploy/`) is the foundation. Five per-project artifacts under `.claude/deploy/` carry runbook, incident ledger, deploy oracle, in-flight bridge, and the instantiated checklist. The skill is a two-moment SKILL.md (before → user deploys out-of-band → after, on the user's report), resumable cold from the JSON bridge per the `audit-delta` state-file convention. Bootstrap scaffolds the runbook for a project that has none.
|
|
||||||
|
|
||||||
**Tech Stack:** Bash (helper + git), Markdown (SKILL.md + runbook + ledger), JSON (oracle + bridge). No new runtime deps — Claude reads JSON natively in skill steps; the helper never parses JSON.
|
|
||||||
|
|
||||||
## Global Constraints
|
|
||||||
|
|
||||||
- Surgical commits only: `deploy-commit.sh` commits via explicit argv pathspec, never `git add -A`. (mirror BDR-034/036)
|
|
||||||
- Allowlist scope = `.claude/deploy/` ONLY; any other path is a loud rc-4 refusal. Inverse of `doc-commit.sh`'s `.claude/**` exclusion (BDR-022). Verified: real `doc-commit.sh` returns rc 4 on `.claude/deploy/PROCEDURE.md`.
|
|
||||||
- Delta = `git diff --name-only <base_sha> HEAD` — **explicit two endpoints, no dots** (two-dot ≡ this; three-dot undercounts — verified). Never `git rev-list` ancestry (phantom deltas on rebase — verified).
|
|
||||||
- First-deploy detection = `[ -f .claude/deploy/STATE.json ]` (deterministic). NEVER `git describe` (hard-errors rc 128 on no tag — verified).
|
|
||||||
- Resume convention = `audit-delta`: "the state file is the only memory between runs; never infer prior scope from context." Bridge read at STEP 0.
|
|
||||||
- Helper inherits from `lib/memory-commit.sh`/`lib/doc-commit.sh`: rc 3 on unsafe git state (detached/merge/rebase/cherry-pick), short-hash on stdout only on a real commit, per-file changed-paths filter, diagnostics to stderr.
|
|
||||||
- User executes the deploy out-of-band (prod ssh) — the skill NEVER runs deploy commands itself.
|
|
||||||
- Registries/spec language English; the spec of record is `docs/specs/2026-06-27-deploy-skill-design.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decisions resolved at plan time
|
|
||||||
|
|
||||||
**§10 (cross-session state) — TRANCHÉ: separate bridge artifact.**
|
|
||||||
- Bridge = `.claude/deploy/PENDING.json` (JSON), **distinct from the ephemeral `NEXT.sh`**, **uncommitted** (transient local working state; gitignored). Schema:
|
|
||||||
```json
|
|
||||||
{ "base_sha": "<deployed STATE sha>", "target_sha": "<HEAD at instantiation>",
|
|
||||||
"delta": ["supabase/migrations/0033_x.sql", "docker-compose.yml"],
|
|
||||||
"step_reached": "awaiting-user", "started_at": "<ISO-8601>", "runbook_rev": "<PROCEDURE.md commit sha>" }
|
|
||||||
```
|
|
||||||
- Follows `audit-delta` ("state file is the only memory between runs"). Resolves the n°1↔n°3 coupling: NEXT.sh stays ephemeral per §3; the bridge persists and carries base+target+delta so moment 3 lays the correct marker and capitalizes the correct incident — **without re-parsing shell**, readable cold.
|
|
||||||
- Form-novelty (mid-flow pause-resume) is new → `writing-skills` formalizes the convention in Task 3.
|
|
||||||
- **LIMIT (acknowledged, not to be discovered):** `PENDING.json` is gitignored ⇒ cold-resume is **same-machine only** — it does not survive a clone or a move to another machine. Acceptable because a project's deploys run from one local; recorded as a constraint, not assumed away.
|
|
||||||
|
|
||||||
**§8 item 1 — tag push:** annotated tag `git tag -a deploy/<YYYY-MM-DD> <target_sha> -m "<summary>"` laid in MARK (success). **Project knob `# @config push_deploy_tags=true|false`** in the `PROCEDURE.md` header (default `false`): when true, MARK runs `git push origin deploy/<date>` — always **best-effort/non-fatal** (the push never blocks the deploy; tag is a bookmark, STATE.json is the oracle). Same-day re-deploy → suffix `-N`.
|
|
||||||
|
|
||||||
**§8 item 2 — INCIDENTS ID/name:** `.claude/deploy/INCIDENTS.md`, append-only, entries `DEP-NNN` (next = `grep '^## DEP-' | max+1`), fields mirror `blockers.md`: date, step, error (verbatim), root cause, fix. Resolution derivable from git: the commit that adds the entry IS the fix (atomic patch+incident); recover via `git log -S 'DEP-NNN' -- .claude/deploy/INCIDENTS.md`. Name confirmed `INCIDENTS.md` (not `ERRORS-LEARNED.md`).
|
|
||||||
|
|
||||||
**§8 item 3 — `@delta:` grammar:** directives on a runbook step's preceding comment line, patterns matched against the delta file list. `glob=` carries TWO required semantics (a single "checklist-only" reading was REJECTED — it breaks the game example, where step 3 runs `psql -f 0033` THEN `psql -f 0034` = one command PER file):
|
|
||||||
- `# @delta:<name> glob=<pat>:each` — **repeat**: emit the step's command once per delta file matching `<pat>` (e.g. `psql -f <each>`).
|
|
||||||
- `# @delta:<name> glob=<pat>:list` — **checklist**: emit the command once, with matching files as `# VERIFY:` items (e.g. `supabase migration up`).
|
|
||||||
- `# @delta:<name> when=<pat>[,<pat>...]` — **conditional**: include the step only if the delta intersects any pattern (e.g. rebuild when compose/Dockerfile changed).
|
|
||||||
- Patterns are git-pathspec/shell-glob; comma-separates alternatives. **Un-annotated step = fixed**, always emitted verbatim. The exact `:each`/`:list` keyword spelling is DEFERRED to `writing-skills` (Task 3); both semantics are mandatory.
|
|
||||||
|
|
||||||
**§8 item 4 — frontmatter / gates:**
|
|
||||||
```yaml
|
|
||||||
name: deploy
|
|
||||||
description: |
|
|
||||||
Use when deploying a project via its per-project runbook — instantiates the
|
|
||||||
delta since last deploy, hands off for out-of-band execution, resumes cold,
|
|
||||||
learns from errors.
|
|
||||||
Triggers: "deploy", "déploie", "run the deploy", "ship to prod", "deploy runbook".
|
|
||||||
allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, AskUserQuestion]
|
|
||||||
```
|
|
||||||
Gate vocabulary reused from `capitalize`/`client-handover`: `all / pick <IDs> / edit <ID> / skip-all`. Gates marked **[GATE]** in Task 3.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
- Create `lib/deploy-commit.sh` — surgical commit helper, allowlist `.claude/deploy/`. (Task 1)
|
|
||||||
- Create `lib/tests/deploy-commit.test.sh` — real-git behavioral tests. (Task 1)
|
|
||||||
- Create `skills/deploy/SKILL.md` — the two-moment skill. (Task 3)
|
|
||||||
- Create `templates/deploy/PROCEDURE.md` — annotated starter runbook (scaffold source). (Task 2/4)
|
|
||||||
- Create `templates/deploy/INCIDENTS.md` — empty ledger header. (Task 2)
|
|
||||||
- Modify `.gitignore` — ignore `.claude/deploy/NEXT.sh` and `.claude/deploy/PENDING.json`. (Task 2)
|
|
||||||
- Per-project, created at runtime (NOT in this repo): `.claude/deploy/{PROCEDURE.md, INCIDENTS.md, STATE.json, PENDING.json, NEXT.sh}`.
|
|
||||||
|
|
||||||
**Artifact lifecycle:**
|
|
||||||
|
|
||||||
| Artifact | Committed? | Lifecycle |
|
|
||||||
|---|---|---|
|
|
||||||
| `PROCEDURE.md` | yes (deploy-commit) | in-place edits (learning) |
|
|
||||||
| `INCIDENTS.md` | yes (deploy-commit) | append-only `DEP-NNN` |
|
|
||||||
| `STATE.json` | yes (deploy-commit) | overwritten on success = oracle |
|
|
||||||
| `PENDING.json` | **no** (gitignored) | written at hand-back, deleted on success = cold-resume bridge |
|
|
||||||
| `NEXT.sh` | **no** (gitignored) | regenerated per deploy, ephemeral checklist |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 1: `lib/deploy-commit.sh` — surgical commit helper (FOUNDATION, TDD)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `lib/deploy-commit.sh`
|
|
||||||
- Test: `lib/tests/deploy-commit.test.sh`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces: `deploy-commit.sh pending <file>...` → exit 0 if any passed file in-scope has changes, else 1. `deploy-commit.sh commit "<msg>" <file>...` → commits ONLY passed in-scope files, prints short hash on stdout; rc 0 success, rc 1 clean/no-op, rc 3 unsafe git state, rc 4 out-of-scope path.
|
|
||||||
- Consumes: nothing (foundation).
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write the failing test harness**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# lib/tests/deploy-commit.test.sh
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -u
|
|
||||||
H="$(cd "$(dirname "$0")/.." && pwd)/deploy-commit.sh"
|
|
||||||
pass=0; fail=0
|
|
||||||
mkrepo() { local d; d=$(mktemp -d); git -C "$d" init -q; git -C "$d" config user.email t@t;
|
|
||||||
git -C "$d" config user.name t; mkdir -p "$d/.claude/deploy"; printf 'x\n' >"$d/seed";
|
|
||||||
git -C "$d" add seed; git -C "$d" commit -q -m seed; printf '%s' "$d"; }
|
|
||||||
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1));
|
|
||||||
printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; }
|
|
||||||
|
|
||||||
d=$(mkrepo); printf 'run\n' >"$d/.claude/deploy/PROCEDURE.md"
|
|
||||||
out=$( cd "$d" && bash "$H" commit "docs(deploy): t" .claude/deploy/PROCEDURE.md ); rc=$?
|
|
||||||
check T1-rc "$rc" 0
|
|
||||||
check T1-committed-only "$(git -C "$d" show --name-only --format= HEAD)" ".claude/deploy/PROCEDURE.md"
|
|
||||||
check T1-hash-nonempty "$([ -n "$out" ] && echo y || echo n)" y
|
|
||||||
|
|
||||||
d=$(mkrepo); printf 'b\n' >"$d/src.txt"
|
|
||||||
( cd "$d" && bash "$H" commit "x" src.txt ) >/dev/null 2>&1; check T2-out-of-scope-rc "$?" 4
|
|
||||||
|
|
||||||
d=$(mkrepo)
|
|
||||||
( cd "$d" && bash "$H" commit "x" ".claude/deploy/../memory/secret" ) >/dev/null 2>&1
|
|
||||||
check T3-traversal-rc "$?" 4
|
|
||||||
|
|
||||||
d=$(mkrepo); printf 'p\n' >"$d/.claude/deploy/PROCEDURE.md"; printf 's\n' >"$d/src.txt"
|
|
||||||
( cd "$d" && bash "$H" commit "x" .claude/deploy/PROCEDURE.md src.txt ) >/dev/null 2>&1
|
|
||||||
check T4-mixed-refuses-all "$?" 4
|
|
||||||
check T4-nothing-committed "$(git -C "$d" rev-list --count HEAD)" 1
|
|
||||||
|
|
||||||
d=$(mkrepo); git -C "$d" checkout -q --detach
|
|
||||||
printf 'p\n' >"$d/.claude/deploy/PROCEDURE.md"
|
|
||||||
( cd "$d" && bash "$H" commit "x" .claude/deploy/PROCEDURE.md ) >/dev/null 2>&1
|
|
||||||
check T5-unsafe-rc "$?" 3
|
|
||||||
|
|
||||||
d=$(mkrepo)
|
|
||||||
( cd "$d" && bash "$H" pending .claude/deploy/PROCEDURE.md ); check T6-pending-clean-rc "$?" 1
|
|
||||||
|
|
||||||
d=$(mkrepo); printf 'p\n' >"$d/.claude/deploy/PROCEDURE.md"
|
|
||||||
printf 'i\n' >"$d/.claude/deploy/INCIDENTS.md"; printf '{}\n' >"$d/.claude/deploy/STATE.json"
|
|
||||||
( cd "$d" && bash "$H" commit "docs(deploy): learn" .claude/deploy/PROCEDURE.md \
|
|
||||||
.claude/deploy/INCIDENTS.md .claude/deploy/STATE.json ) >/dev/null 2>&1
|
|
||||||
check T7-atomic-rc "$?" 0
|
|
||||||
check T7-three-files "$(git -C "$d" show --name-only --format= HEAD | grep -c deploy)" 3
|
|
||||||
|
|
||||||
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Run the test, verify it FAILS**
|
|
||||||
|
|
||||||
Run: `bash lib/tests/deploy-commit.test.sh`
|
|
||||||
Expected: FAIL (helper absent) — every check fails or the harness errors on missing `lib/deploy-commit.sh`.
|
|
||||||
|
|
||||||
- [ ] **Step 3: Implement `lib/deploy-commit.sh`**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
# deploy-commit.sh — surgical commit for the .claude/deploy/ runbook family.
|
|
||||||
# Allowlist scope = .claude/deploy/ ONLY (inverse of doc-commit's .claude exclusion).
|
|
||||||
set -u
|
|
||||||
|
|
||||||
_in_git_repo() { git rev-parse --is-inside-work-tree >/dev/null 2>&1; }
|
|
||||||
|
|
||||||
_unsafe_state() { # 0 = unsafe
|
|
||||||
local g; g=$(git rev-parse --git-dir 2>/dev/null) || return 0
|
|
||||||
git symbolic-ref -q HEAD >/dev/null 2>&1 || return 0 # detached HEAD
|
|
||||||
[ -e "$g/MERGE_HEAD" ] || [ -d "$g/rebase-merge" ] || \
|
|
||||||
[ -d "$g/rebase-apply" ] || [ -e "$g/CHERRY_PICK_HEAD" ] && return 0
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_out_of_scope() { # 0 = forbidden, 1 = in scope
|
|
||||||
case "$1" in
|
|
||||||
*..*) return 0 ;; # traversal — forbidden FIRST
|
|
||||||
.claude/deploy/*) return 1 ;; # allowed
|
|
||||||
*) return 0 ;; # everything else forbidden
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
_scope_violations() { local p; for p in "$@"; do _out_of_scope "$p" && printf '%s\n' "$p"; done; }
|
|
||||||
|
|
||||||
_changed_only() { # echo passed files that actually have changes
|
|
||||||
local p; for p in "$@"; do
|
|
||||||
[ -n "$(git status --porcelain -- "$p" 2>/dev/null)" ] && printf '%s\n' "$p"; done
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd="${1:-}"; shift || true
|
|
||||||
_in_git_repo || { echo "deploy-commit: not a git repo" >&2; exit 2; }
|
|
||||||
|
|
||||||
case "$cmd" in
|
|
||||||
pending)
|
|
||||||
[ "$#" -gt 0 ] || { echo "deploy-commit: pending needs file args" >&2; exit 2; }
|
|
||||||
[ -n "$(_changed_only "$@")" ] && exit 0 || exit 1 ;;
|
|
||||||
commit)
|
|
||||||
msg="${1:-}"; shift || true
|
|
||||||
[ -n "$msg" ] && [ "$#" -gt 0 ] || { echo "deploy-commit: commit needs <msg> <file>..." >&2; exit 2; }
|
|
||||||
viol=$(_scope_violations "$@")
|
|
||||||
if [ -n "$viol" ]; then
|
|
||||||
{ echo "deploy-commit: REFUSED — path(s) outside .claude/deploy/ allowlist:";
|
|
||||||
printf ' - %s\n' $viol;
|
|
||||||
echo "deploy-commit: NOTHING committed. Caller must pass only .claude/deploy/ files."; } >&2
|
|
||||||
exit 4
|
|
||||||
fi
|
|
||||||
_unsafe_state && { echo "deploy-commit: unsafe git state (detached/merge/rebase) — not committing" >&2; exit 3; }
|
|
||||||
mapfile -t changed < <(_changed_only "$@")
|
|
||||||
[ "${#changed[@]}" -gt 0 ] || exit 1
|
|
||||||
git commit -q -m "$msg" -- "${changed[@]}" || { echo "deploy-commit: git commit failed" >&2; exit 1; }
|
|
||||||
git rev-parse --short HEAD ;;
|
|
||||||
*) echo "usage: deploy-commit.sh pending <file>... | commit \"<msg>\" <file>..." >&2; exit 2 ;;
|
|
||||||
esac
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 4: Run the test, verify it PASSES**
|
|
||||||
|
|
||||||
Run: `bash lib/tests/deploy-commit.test.sh`
|
|
||||||
Expected: `PASS=12 FAIL=0` (exit 0).
|
|
||||||
|
|
||||||
- [ ] **Step 5: shellcheck**
|
|
||||||
|
|
||||||
Run: `shellcheck lib/deploy-commit.sh lib/tests/deploy-commit.test.sh`
|
|
||||||
Expected: clean (matches repo Health Stack norm).
|
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add lib/deploy-commit.sh lib/tests/deploy-commit.test.sh
|
|
||||||
git commit -m "feat(deploy): deploy-commit.sh — allowlist surgical commit for .claude/deploy/"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 2: Artifacts + bridge formats (§10 materialized)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `templates/deploy/PROCEDURE.md`, `templates/deploy/INCIDENTS.md`
|
|
||||||
- Modify: `.gitignore`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Produces: the on-disk shapes the skill reads/writes — `PROCEDURE.md` annotation grammar, `INCIDENTS.md` `DEP-NNN` template, `STATE.json` and `PENDING.json` schemas.
|
|
||||||
- Consumes: nothing.
|
|
||||||
|
|
||||||
- [ ] **Step 1: Write `templates/deploy/PROCEDURE.md`** (annotated starter — fixed steps verbatim, dynamic steps annotated)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/usr/bin/env bash
|
|
||||||
# === deploy runbook (reference) — NOT run directly. Instantiated to NEXT.sh per delta. ===
|
|
||||||
# Fixed steps run every deploy; `# @delta:` steps re-instantiate from the delta.
|
|
||||||
# @config push_deploy_tags=false
|
|
||||||
# NOTE grammar: glob=<pat>:each repeats the command per matching file (e.g. psql -f <each>);
|
|
||||||
# glob=<pat>:list runs once + lists matching files as VERIFY items; when=<pat,...> is conditional.
|
|
||||||
|
|
||||||
# 1) backup BEFORE any forward-only migration
|
|
||||||
ssh "$DEPLOY_HOST" 'pg_dump "$DB" > ~/backups/pre-deploy-$(date +%F-%H%M).sql' # VERIFY: dump size > 0
|
|
||||||
|
|
||||||
# @delta:migrations glob=supabase/migrations/*.sql:list
|
|
||||||
# 2) apply NEW migrations (one command; skill lists the delta migrations to VERIFY)
|
|
||||||
ssh "$DEPLOY_HOST" 'supabase migration up' # VERIFY: "Applied" for each
|
|
||||||
|
|
||||||
# @delta:rebuild when=docker-compose*.yml,Dockerfile,Dockerfile.*
|
|
||||||
# 3) rebuild + restart services (only if build inputs changed)
|
|
||||||
ssh "$DEPLOY_HOST" 'docker compose up -d --build' # VERIFY: docker compose ps healthy
|
|
||||||
|
|
||||||
# @delta:deps when=package.json,*lock*,requirements.txt,pyproject.toml
|
|
||||||
# 4) install deps (only if manifests changed)
|
|
||||||
ssh "$DEPLOY_HOST" 'cd app && npm ci' # VERIFY: exit 0
|
|
||||||
|
|
||||||
# 5) reload cache + smoke test (fixed)
|
|
||||||
ssh "$DEPLOY_HOST" 'systemctl reload app'
|
|
||||||
curl -fsS https://$DEPLOY_HOST/health # VERIFY: HTTP 200
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 2: Write `templates/deploy/INCIDENTS.md`** (ledger header)
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Deploy incidents (append-only) — DEP-NNN
|
|
||||||
|
|
||||||
<!-- One entry per incident. Next ID = grep '^## DEP-' | max+1. Mirrors blockers.md. -->
|
|
||||||
<!-- Resolution = the commit that adds this entry (atomic patch+incident). Recover: git log -S 'DEP-NNN' -- .claude/deploy/INCIDENTS.md -->
|
|
||||||
<!-- ## DEP-NNN — <step> failed
|
|
||||||
- date: YYYY-MM-DD
|
|
||||||
- step: <runbook step + label>
|
|
||||||
- error: `<verbatim error>`
|
|
||||||
- cause: <root cause>
|
|
||||||
- fix: <what changed in PROCEDURE.md> -->
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 3: Record the JSON schemas** (no parsing in shell — Claude reads them in skill steps)
|
|
||||||
|
|
||||||
`STATE.json` (committed oracle, overwritten on success):
|
|
||||||
```json
|
|
||||||
{ "deployed_sha": "<sha>", "deployed_at": "<ISO-8601>", "outcome": "ok",
|
|
||||||
"tag": "deploy/<YYYY-MM-DD>" }
|
|
||||||
```
|
|
||||||
`PENDING.json` (gitignored bridge, deleted on success): schema as in "Decisions resolved at plan time / §10".
|
|
||||||
|
|
||||||
- [ ] **Step 4: Update `.gitignore`**
|
|
||||||
|
|
||||||
```gitignore
|
|
||||||
# deploy: transient per-deploy state (the runbook/ledger/oracle ARE committed)
|
|
||||||
.claude/deploy/NEXT.sh
|
|
||||||
.claude/deploy/PENDING.json
|
|
||||||
```
|
|
||||||
|
|
||||||
- [ ] **Step 5: Verify templates are well-formed**
|
|
||||||
|
|
||||||
Run: `bash -n templates/deploy/PROCEDURE.md && grep -c '^# @delta:' templates/deploy/PROCEDURE.md`
|
|
||||||
Expected: no syntax error; `3` annotations.
|
|
||||||
|
|
||||||
- [ ] **Step 6: Commit**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add templates/deploy/PROCEDURE.md templates/deploy/INCIDENTS.md .gitignore
|
|
||||||
git commit -m "feat(deploy): runbook/ledger templates + bridge schemas + gitignore transient state"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 3: `skills/deploy/SKILL.md` — the two-moment skill (REQUIRES writing-skills)
|
|
||||||
|
|
||||||
> **At this task, invoke `superpowers:writing-skills`** to shape SKILL.md to house conventions AND to formalize the **cross-session cold-resume** form (deploy's defining novelty; `audit-delta` is the state-file precedent, `client-handover` only an in-context pause). The step behaviors below are the contract; writing-skills governs structure/frontmatter/spine.
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Create: `skills/deploy/SKILL.md`
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes: `lib/deploy-commit.sh` (Task 1); artifact shapes (Task 2).
|
|
||||||
- Produces: the runtime behavior. STEP spine below.
|
|
||||||
|
|
||||||
**STEP spine (each = a SKILL.md section; [GATE] = mandatory stop):**
|
|
||||||
|
|
||||||
- [ ] **STEP 0 — PRE-FLIGHT + RESUME BRANCH.** Read `.claude/deploy/PENDING.json` FIRST (state file = only memory between runs).
|
|
||||||
- `PENDING.json` present → **RESUME**: jump to STEP 3 with its `{base, target, delta, step_reached}` (do not recompute).
|
|
||||||
- else `PROCEDURE.md` absent → **BOOTSTRAP** (Task 4).
|
|
||||||
- else → FRESH: continue STEP 1.
|
|
||||||
- [ ] **STEP 1 — DELTA.** `base = STATE.json.deployed_sha` (or, if `STATE.json` absent, first-deploy = full runbook). `git diff --name-only <base> HEAD` → delta file list. `target = git rev-parse HEAD`.
|
|
||||||
- [ ] **STEP 2 — INSTANTIATE + [GATE].** Expand `PROCEDURE.md`: emit fixed steps verbatim; expand `@delta:glob=…:each` steps by repeating the command per matching delta file, and `@delta:glob=…:list` steps once with matching files as `# VERIFY:` items; include `@delta:when=` steps only if the delta intersects. Read `INCIDENTS.md` and prepend matching `# PRE-WARN: DEP-NNN …` notes. Write `NEXT.sh`. **[GATE]** present `NEXT.sh` → `all / edit / skip-all`. On approve: write `PENDING.json` (`step_reached: awaiting-user`), then **hand back** (AskUserQuestion: "Run NEXT.sh step by step. Report back: Deployed OK / Failed at step X / Not yet").
|
|
||||||
- [ ] **STEP 3 — RESUME / REACT** (entry point on the user's report; may be a fresh session).
|
|
||||||
- "Deployed OK" → STEP 5.
|
|
||||||
- "Failed at step X: <err>" → STEP 4.
|
|
||||||
- "Not yet" → re-state pending, stop.
|
|
||||||
- [ ] **STEP 4 — LEARN + [GATE] + ATOMIC COMMIT.** Diagnose. Draft: (a) in-place `PROCEDURE.md` patch to step X; (b) `INCIDENTS.md` append `DEP-NNN` (error verbatim). **[GATE]** `all / pick / edit / skip-all` (significant edit). On approve: write both, then **one atomic** `bash lib/deploy-commit.sh commit "docs(deploy): patch <step> — recovered from <err>" .claude/deploy/PROCEDURE.md .claude/deploy/INCIDENTS.md`. The commit that adds `DEP-NNN` IS its resolution (derive via git later). Then bump `PENDING.json.runbook_rev` to the new `PROCEDURE.md` commit sha (keep `step_reached` at X). **Resume = REGENERATE `NEXT.sh` from `step_reached` against the PATCHED runbook** (steps X…end — X+1…end never ran), NOT replay a single step. The bumped `runbook_rev` is exactly the trigger: runbook changed ⇒ prior `NEXT.sh` is stale ⇒ regenerate. Re-present via STEP 2's hand-back.
|
|
||||||
- [ ] **STEP 5 — MARK (success).** Write `STATE.json` (`deployed_sha = PENDING.target_sha`, outcome ok, tag). `git tag -a deploy/<date> <target> -m "<summary>"`; **if `@config push_deploy_tags=true`** then `git push origin deploy/<date>` (best-effort, non-fatal). `bash lib/deploy-commit.sh commit "chore(deploy): mark <date> @ <short>" .claude/deploy/STATE.json`. **Delete `PENDING.json`** (+ `NEXT.sh`). Report.
|
|
||||||
|
|
||||||
- [ ] **Verification scenarios** (dry-run walkthroughs, no prod):
|
|
||||||
- First deploy (no `STATE.json`): full runbook fires; STATE laid; PENDING deleted.
|
|
||||||
- Delta deploy: only changed-bucket steps instantiate; `git diff` form is `<base> HEAD`.
|
|
||||||
- **Cold resume**: write a `PENDING.json` by hand, start `deploy` in a *fresh* context → STEP 0 detects it, resumes at STEP 3 from disk alone (no conversation memory).
|
|
||||||
- Failure→learn: report "failed at step X" → patch + DEP append committed atomically (one sha, both files).
|
|
||||||
- [ ] **Commit:** `git add skills/deploy/SKILL.md && git commit -m "feat(deploy): two-moment cross-session skill (resumes cold from PENDING.json)"`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Task 4: Bootstrap (project without a runbook)
|
|
||||||
|
|
||||||
**Files:**
|
|
||||||
- Modify: `skills/deploy/SKILL.md` (STEP 0 BOOTSTRAP branch)
|
|
||||||
|
|
||||||
**Interfaces:**
|
|
||||||
- Consumes: `templates/deploy/*` (Task 2); STEP spine (Task 3).
|
|
||||||
|
|
||||||
- [ ] **Step 1 — BOOTSTRAP branch + [GATE].** When `PROCEDURE.md` absent, offer two paths (AskUserQuestion):
|
|
||||||
- **Paste** — user provides an existing runbook → adopt verbatim, then propose `@delta:` annotations for migration/build/deps steps.
|
|
||||||
- **Scaffold** — detect artifacts (`supabase/migrations/`, `docker-compose*.yml`/`Dockerfile`, `package.json`/lockfiles, `.env*`) + short interview (ssh host, backup cmd, health URL, rollback note) → fill `templates/deploy/PROCEDURE.md`.
|
|
||||||
- **[GATE]** present drafted `PROCEDURE.md` → `all / edit / skip-all`. On approve: write `PROCEDURE.md` + empty `INCIDENTS.md`; `bash lib/deploy-commit.sh commit "feat(deploy): bootstrap runbook" .claude/deploy/PROCEDURE.md .claude/deploy/INCIDENTS.md`. First deploy then proceeds (no STATE.json ⇒ full runbook).
|
|
||||||
- [ ] **Step 2 — Verify:** dry-run on a repo with `supabase/migrations/` + `docker-compose.yml` present → scaffold proposes migration + rebuild steps annotated; on a bare repo → interview-only path.
|
|
||||||
- [ ] **Commit:** `git add skills/deploy/SKILL.md && git commit -m "feat(deploy): bootstrap — paste-or-scaffold initial runbook"`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Gates identified
|
|
||||||
|
|
||||||
- **[GATE] STEP 2** — approve instantiated `NEXT.sh` before hand-back.
|
|
||||||
- **[GATE] STEP 4** — approve runbook patch + `DEP-NNN` incident before the atomic learning commit.
|
|
||||||
- **[GATE] STEP 0/Task 4** — approve scaffolded `PROCEDURE.md` before first write.
|
|
||||||
- **Hand-back (STEP 2→3)** — AskUserQuestion is the resume point; the user executes out-of-band.
|
|
||||||
- **Task gates** — each Task ends test-green + shellcheck-clean + committed before the next (deps: 1 → 2 → 3 → 4).
|
|
||||||
|
|
||||||
## Self-review
|
|
||||||
|
|
||||||
- **Spec coverage:** 4 artifacts + bridge (§3/§10) → Task 2; STATE-oracle + `<base> HEAD` delta (§4) → Task 1 constraints + STEP 1; runbook+INCIDENTS learning, atomic couple (§5) → STEP 4; `deploy-commit.sh` inverse allowlist (§6) → Task 1; bootstrap (§7) → Task 4; two-moment cold resume (§10) → STEP 0/2/3 + PENDING.json. All §8 items resolved above. ✓
|
|
||||||
- **Placeholder scan:** none — helper code, test code, schemas, annotation grammar all concrete.
|
|
||||||
- **Type consistency:** `STATE.json.deployed_sha` (STEP 1 base, STEP 5 write), `PENDING.json.{base_sha,target_sha,delta,step_reached}` (STEP 0 read, STEP 2 write, STEP 4 update), `deploy-commit.sh commit "<msg>" <file>...` (Tasks 1/3/4) — names align.
|
|
||||||
- **Open at execution (not assumed):** the `writing-skills` consultation in Task 3 may rename/restructure SKILL.md sections to match the formalized cold-resume convention, and finalizes the `@delta:` `:each`/`:list` keyword spelling (both semantics mandatory); STEP behaviors and the §6 helper contract above are fixed regardless.
|
|
||||||
|
|
||||||
## Execution Handoff
|
|
||||||
|
|
||||||
Build order is strict by dependency: **Task 1 (helper, foundation) → Task 2 (formats) → Task 3 (skill, writing-skills) → Task 4 (bootstrap)**.
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
# Deploy skill — design spec
|
|
||||||
|
|
||||||
> **Superseded by BDR-054** (`52f6678`): the shipped skill has NO `NEXT.sh` file and NO
|
|
||||||
> AskUserQuestion hand-back — see `skills/deploy/SKILL.md` for current behavior. This
|
|
||||||
> spec is kept as historical record; do not implement its NEXT.sh/hand-back sections.
|
|
||||||
|
|
||||||
- **Date:** 2026-06-27
|
|
||||||
- **Status:** Design approved (5 knobs settled). **No skill code written yet.** Next step = implementation plan.
|
|
||||||
- **Scope:** A new `deploy` skill = a per-project shell RUNBOOK that lives in `.claude/deploy/`, gets re-instantiated from the delta since the last deploy, and LEARNS from deploy errors in place.
|
|
||||||
|
|
||||||
## 1. Vision — deployment memory that learns
|
|
||||||
|
|
||||||
Three moments:
|
|
||||||
|
|
||||||
1. **BEFORE** — produce the *instantiated* runbook: reference runbook + delta since last deploy, parameterized steps rewritten with the real artifacts (e.g. the migration step lists the migrations actually added since last deploy, not the runbook's examples).
|
|
||||||
2. **DURING** — the **user executes out-of-band** (prod ssh — Claude must not run it) and reports `deployed and tested` OR `failed at step X, here is the error` → fix together until success.
|
|
||||||
3. **AFTER** — on confirmed success: (a) if errors were hit + fixed, update the reference runbook so the next deploy does not repeat them; (b) lay the marker "deployed up to here" for the next diff.
|
|
||||||
|
|
||||||
Structural ancestor in the corpus: `client-handover` (BEFORE baseline → DURING user-deploy gate via `AskUserQuestion` → AFTER validate + react). No existing skill owns a learning per-project runbook — clean gap, no `.claude/deploy/` precedent.
|
|
||||||
|
|
||||||
## 2. Locked decisions
|
|
||||||
|
|
||||||
| # | Knob | Decision |
|
|
||||||
|---|------|----------|
|
|
||||||
| 1 | Marker / oracle | **STATE file is the oracle** (deployed SHA), **annotated tag** added as a human bookmark only |
|
|
||||||
| 2 | Learning storage | **In-place runbook edits + append-only `INCIDENTS.md`** (distinct jobs, atomic coupling) |
|
|
||||||
| 3 | Parameterization | **`# @delta:` annotations** bind dynamic steps to path-patterns; un-annotated steps are fixed |
|
|
||||||
| 4 | Bootstrap | **Offer both** — user pastes an existing runbook OR skill scaffolds via artifact detection + interview |
|
|
||||||
| 5 | Execution model | **`NEXT.sh` is a step-by-step CHECKLIST** — runnable shell, but driven by hand with manual `# VERIFY:` gates; never `bash NEXT.sh` unattended |
|
|
||||||
|
|
||||||
**Why #5 is design-time, not impl:** the execution model is load-bearing for moments 2 and 3. Moment 2 is defined as "user reports *failed at step X*", and moment 3's LEARN loop must know *which* step failed to patch it. A single `bash NEXT.sh` blob collapses both into "exited non-zero somewhere" and can strand a prod deploy (migrations, restarts) in partial state with no step control. Checklist is *entailed* by the three-moment structure, not merely safer.
|
|
||||||
|
|
||||||
Treated as settled corollaries: user executes out-of-band; a **new** `lib/deploy-commit.sh` helper (existing helpers cannot commit the runbook — see §6, verified).
|
|
||||||
|
|
||||||
## 3. Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
.claude/deploy/
|
|
||||||
PROCEDURE.md reference runbook — fixed shell + `# @delta:` annotated steps (edited IN-PLACE)
|
|
||||||
INCIDENTS.md DEP-NNN incident ledger: date, step, error verbatim, root cause,
|
|
||||||
fix (APPEND-ONLY; resolution = introducing commit, derive via git)
|
|
||||||
STATE.json deployed SHA + timestamp + outcome — the diff oracle (overwritten each deploy)
|
|
||||||
NEXT.sh instantiated runbook — EPHEMERAL, not committed ; run STEP-BY-STEP
|
|
||||||
(checklist, manual # VERIFY: gates) — never `bash NEXT.sh` unattended
|
|
||||||
|
|
||||||
lib/deploy-commit.sh surgical commit, allowlist = .claude/deploy/ , rc3 unsafe-git guard, short-hash stdout
|
|
||||||
|
|
||||||
Skill STEP spine (PRE-FLIGHT -> PROPOSE+GATE -> WRITE+COMMIT, house style):
|
|
||||||
0 PRE-FLIGHT runbook present? absent -> bootstrap (paste | scaffold+interview)
|
|
||||||
1 DELTA STATE absent -> first deploy = full runbook ; else diff <STATE_SHA> HEAD
|
|
||||||
2 INSTANTIATE expand @delta steps + read INCIDENTS pre-warns -> NEXT.sh -> GATE
|
|
||||||
3 (user executes out-of-band; reports "done" | "failed at step X: <err>")
|
|
||||||
4 LEARN on failure: patch PROCEDURE step + append DEP-NNN -> GATE -> deploy-commit (ATOMIC)
|
|
||||||
5 MARK on success: write STATE@sha ; annotate + push tag ; optional doc
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. Delta mechanism — verified (git 2.53.0)
|
|
||||||
|
|
||||||
All three facts re-run live before writing this spec; observed output recorded, not assumed.
|
|
||||||
|
|
||||||
**First-deploy detection = STATE-absent, deterministic. `describe` is off the detection path.**
|
|
||||||
```
|
|
||||||
[ -f .claude/deploy/STATE.json ] => exit 1 (absent = first deploy) <- THE detector
|
|
||||||
git describe --tags --match 'deploy/*' => fatal: No names found ; exit 128 <- only the reason NOT to use describe
|
|
||||||
[ -f .claude/deploy/STATE.json ] => exit 0 (present = delta path)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Delta = `git diff --name-only <STATE_SHA> HEAD`** (two explicit endpoints; no dots, so it cannot be misread as three-dot).
|
|
||||||
```
|
|
||||||
LINEAR git diff --name-only <sha> HEAD => 0033_new.sql, svc.yml (== two-dot == three-dot; merge-base == STATE)
|
|
||||||
DIVERGED two-dot sideA sideB => fileA.txt, fileB.txt (both endpoints = true tree delta)
|
|
||||||
DIVERGED three-dot sideA...sideB => fileB.txt (merge-base — UNDERCOUNTS)
|
|
||||||
```
|
|
||||||
Two-dot/explicit-endpoints is the literal tree difference between the deployed tree and HEAD = what deploy needs. It is also rebase-robust: an orphaned marker still yields the correct tree diff, whereas `git rev-list A..B` (ancestry) reports phantom deltas after history rewrite (LRN-054's trap; verified in an earlier run). **Never use `rev-list` ancestry for the artifact list.**
|
|
||||||
|
|
||||||
**delta -> steps:** `# @delta:<kind>` annotations bind a dynamic step to the path-pattern that feeds it; the diff buckets straight into steps:
|
|
||||||
```
|
|
||||||
# @delta:migrations glob=supabase/migrations/*.sql
|
|
||||||
# @delta:rebuild when=docker-compose*.yml,Dockerfile
|
|
||||||
# @delta:deps when=package.json,*lock*
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Learning model — runbook + INCIDENTS, non-redundant
|
|
||||||
|
|
||||||
| Artifact | Job | Lifecycle |
|
|
||||||
|---|---|---|
|
|
||||||
| `PROCEDURE.md` | The corrected procedure you run. A fix is baked into the step so the next run cannot repeat it. | in-place |
|
|
||||||
| `INCIDENTS.md` | The incident ledger; **read at BEFORE-time to pre-warn** ("0033 hit a lock timeout last deploy; runbook already carries `--timeout`, watch for it"). | append-only |
|
|
||||||
|
|
||||||
The pre-warn read is the function `git log` serves badly — that is why the ledger is not duplication. This mirrors the memory system's own split (append-only `journal.md`/`blockers.md` alongside in-place TODO/code).
|
|
||||||
|
|
||||||
**Coupling invariant:** one incident → **one in-place `PROCEDURE.md` patch + one `INCIDENTS.md` append, committed atomically in a single `deploy-commit.sh` call.** Never one without the other (mirrors BDR-034/036 "couple the commit to the integration step"). Significant patch (changes a prod path) → surface + approve before writing.
|
|
||||||
|
|
||||||
## 6. `lib/deploy-commit.sh` — new helper, inverse `.claude/` rule (verified)
|
|
||||||
|
|
||||||
Neither existing helper can commit the runbook — confirmed live:
|
|
||||||
```
|
|
||||||
REAL doc-commit.sh .claude/deploy/PROCEDURE.md => rc 4 "REFUSED — out-of-scope ... BDR-022 ... NOTHING committed"
|
|
||||||
REAL memory-commit.sh pending (deploy changed) => rc 1 (ignores it; allowlist = .claude/memory|tasks only)
|
|
||||||
```
|
|
||||||
`doc-commit.sh` is built to keep `.claude/**` *out* of public-doc commits; `.claude/deploy/` is under `.claude/`, so reuse is not just blocked, it is semantically wrong. `deploy-commit.sh` needs the **inverse** rule: a TARGET allowlist for `.claude/deploy/*`, modeled on `memory-commit.sh` (rc 3 unsafe-git guard, short-hash on stdout, `chore(deploy):`/`docs(deploy):` messages).
|
|
||||||
|
|
||||||
Allowlist guard — traversal reject ordered FIRST. Prototype matrix verified live:
|
|
||||||
```sh
|
|
||||||
_in_deploy_scope() {
|
|
||||||
case "$1" in
|
|
||||||
*..*) return 1 ;; # reject path traversal FIRST
|
|
||||||
.claude/deploy/*) return 0 ;; # ALLOW the deploy family only
|
|
||||||
*) return 1 ;; # reject everything else
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
```
|
|
||||||
```
|
|
||||||
ALLOW .claude/deploy/{PROCEDURE.md,INCIDENTS.md,STATE}
|
|
||||||
REJECT .claude/memory/* .claude/tasks/* .claude/secret CLAUDE.md src/*
|
|
||||||
REJECT .claude/deploy (bare dir, no slash)
|
|
||||||
REJECT .claude/deploy-other/x (trailing-slash requirement closes prefix confusion)
|
|
||||||
REJECT .claude/deploy/../memory/secret (traversal closed by *..* matched first)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 7. Bootstrap
|
|
||||||
|
|
||||||
`STEP 0 PRE-FLIGHT`: `PROCEDURE.md` present? Absent → bootstrap, two offered paths:
|
|
||||||
1. **Paste** — user supplies an existing runbook (the game example); skill adopts + annotates it.
|
|
||||||
2. **Scaffold** — skill detects deploy artifacts (migrations dir, compose/Dockerfile, package scripts, `.env`) + a short interview (ssh target, backup cmd, rollback note) → writes an annotated `PROCEDURE.md`.
|
|
||||||
|
|
||||||
First deploy has no marker → STATE-absent ⇒ full runbook fires; then lay STATE at the deployed SHA. The first deploy *is* the creation of the runbook + the first marker.
|
|
||||||
|
|
||||||
## 8. Open items (for the implementation plan)
|
|
||||||
|
|
||||||
> `NEXT.sh` execution model resolved → decision #5 (checklist), promoted to design-time.
|
|
||||||
|
|
||||||
- Tag push: tags don't push by default → AFTER step should `git push --tag deploy/<date>` or remind.
|
|
||||||
- `INCIDENTS.md` ID/format detail (mirror `blockers.md` `DEP-NNN`); confirm name vs `ERRORS-LEARNED.md`.
|
|
||||||
- `@delta:` annotation grammar (glob= vs when=) — finalize the small DSL.
|
|
||||||
- Frontmatter `allowed-tools` set; STEP gate wording reuse from `capitalize`/`client-handover`.
|
|
||||||
|
|
||||||
## 9. Build sequencing & a structural flag
|
|
||||||
|
|
||||||
**Two distinct disciplines, in order — do not conflate:**
|
|
||||||
1. `writing-plans` — global task ordering (helper → skill → bootstrap), dependencies, gates. The build plan.
|
|
||||||
2. → execution →
|
|
||||||
3. At the *skill* task ONLY: `writing-skills` — the discipline for the SKILL.md itself (structure, frontmatter, spine, config conventions). Used WHEN we reach the skill task, **not before** (it does not fire at plan time).
|
|
||||||
|
|
||||||
**Structural flag for `writing-skills` to resolve — do NOT assume the linear-spine convention suffices:**
|
|
||||||
deploy's spine is unusual — **two parts split by out-of-band execution**: STEP 0–2 before → *user deploys by hand* → STEP 4–5 after, on the `done`/`failed` report. A skill that **hands back control mid-run and resumes**.
|
|
||||||
|
|
||||||
Preliminary recon (confirm at the skill task — NOT verified now):
|
|
||||||
- The 6 completion flux (close, ship-feature, feat, bugfix, hotfix, commit-change) appear linear one-shot — synchronous gates at most, no out-of-band hand-back.
|
|
||||||
- The relevant precedent is OUTSIDE those 6: `client-handover` already hands back — a synchronous "Deploy done?" `AskUserQuestion` pause (STEP 5) — but it holds state in *conversation context*, not on disk.
|
|
||||||
- deploy's genuinely-new bit *may* be **disk-bridged resume** (`NEXT.sh` + `STATE` on disk as the bridge) — but **whether `NEXT.sh` alone suffices to resume cross-session is an OPEN design question, not a settled answer** (see §10). An earlier draft of this spec framed it as resolved; it is not. `writing-skills` must establish the convention (how to mark "I wait for your return here", detect + resume a pending deploy, hold state across the gap) — confirm there, do not assume the linear mould suffices.
|
|
||||||
|
|
||||||
## 10. Open design question (DESIGN-TIME, unresolved) — state across the two moments
|
|
||||||
|
|
||||||
deploy is a **two-moment skill**: moments 0–2 (BEFORE) → user deploys out-of-band → moment 3 (AFTER) on the `done`/`failed` report. **The report may arrive in a different session.** So the design must answer how state crosses the gap and what moment 3 must know to resume correctly.
|
|
||||||
|
|
||||||
> **`skill deux-temps, état entre temps = [à concevoir : NEXT.sh seul suffit-il pour reprendre cross-session ?]`**
|
|
||||||
|
|
||||||
Sub-questions (to settle when we resume — NOT now, NOT assumed):
|
|
||||||
- **What must the bridge record?** Moment 3 must (a) lay the correct marker = `STATE ← target sha`, and (b) capitalize the correct incident (which step, which delta). HEAD may have moved since NEXT.sh was generated → "current HEAD" is unsafe. The bridge must persist at least **{base STATE sha, target sha, delta manifest}** — inside NEXT.sh (header block) or a sidecar (`.claude/deploy/PENDING`)? Undecided.
|
|
||||||
- **Resume detection (re-entrancy):** STEP 0 PRE-FLIGHT must detect "a deploy is pending, awaiting your report" — likely *pending-bridge present + STATE not advanced to target* — and branch RESUME (ask done/failed) vs FRESH. Is moment 3 a new `deploy` call that re-detects from disk, or a `deploy --report`? Undecided.
|
|
||||||
- **Ephemeral vs persistent tension — LINKED to sub-question 1 (not independent).** §3 calls NEXT.sh "EPHEMERAL, not committed", yet a cross-session bridge MUST survive on disk. So: **if the bridge must persist, NEXT.sh-as-bridge is impossible while NEXT.sh stays ephemeral.** Likely *binary* resolution at plan time — either (a) NEXT.sh becomes persistent (contradicts §3), or (b) the bridge is a **separate** "deploy-in-progress" artifact `{base/target/delta}` distinct from NEXT.sh. Settle with `writing-skills`. (Uncommitted local state is fine; note the single-machine assumption — an uncommitted bridge won't follow a clone.)
|
|
||||||
- **Form-novelty — deploy's DEFINING characteristic: cross-session COLD resume.** `client-handover` is a *near* precedent, not exact: it hands back **in-context** (same conversation, state held in memory). deploy must resume with the **context lost** — so the **disk alone must carry everything to resume cold**. No existing skill resumes without context; that is what sets deploy apart, and it makes sub-question 1 **load-bearing** (disk must suffice for a cold restart). deploy likely introduces a NEW skill form → `writing-skills` establishes the convention. Confirm there.
|
|
||||||
|
|
||||||
**Next step:** `writing-plans` to turn this spec into an implementation plan (helper first, then skill); at the skill task, `writing-skills` to shape it to convention and **resolve the §10 two-moment state question** — which is design-time, deferred only because we are stopped here, not because it is impl detail.
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,143 +0,0 @@
|
|||||||
# Model routing — reflection inline (big model) / execution pinned (Sonnet) — design
|
|
||||||
|
|
||||||
**Date**: 2026-07-15 · **Status**: approved (user, 2026-07-15) · **Branch**: `feature/model-routing`
|
|
||||||
**Lifecycle**: transient planning artifact (BDR-065) — committed during the run, deleted post-merge.
|
|
||||||
|
|
||||||
## Principle
|
|
||||||
|
|
||||||
The session model is assumed to be a big reasoning model (Fable 5, or Opus when
|
|
||||||
Fable is unavailable). Everything that **thinks** — brainstorming, planning,
|
|
||||||
technical decisions, audits, loop decisions — runs INLINE in the main
|
|
||||||
conversation, or in subagents that inherit the session model. Everything that
|
|
||||||
**executes** a ready-made plan — writing code, applying fix bundles, commits,
|
|
||||||
deliverable rendering — runs on Sonnet-pinned subagents. A blocking gate
|
|
||||||
enforces the "session = big model" assumption at the entry of every reflection
|
|
||||||
orchestrator.
|
|
||||||
|
|
||||||
User verdicts baked in (2026-07-14/15):
|
|
||||||
- Scope = hybrid: ship-feature/init-project execution → sonnet; `/feat`
|
|
||||||
re-architected (plan inline → dispatch executor); bugfix/hotfix stay fully
|
|
||||||
inline (BDR-050 conserved for them).
|
|
||||||
- Gate = BLOCKING, not advisory.
|
|
||||||
- Audit agents inherit the session model (no opus pin); the gate extends to
|
|
||||||
audit orchestrators.
|
|
||||||
- verifier + security-auditor KEEP `model: sonnet` (job9 decision confirmed).
|
|
||||||
- client-handover-writer → sonnet (requires converting its inline-load to a
|
|
||||||
true dispatch; human gates relocate to the main loop).
|
|
||||||
|
|
||||||
## 1. Blocking model gate
|
|
||||||
|
|
||||||
New `lib/model-check.sh`: resolves the current session model from
|
|
||||||
`settings.json` (physical path resolution — LRN-023 class), normalizes
|
|
||||||
(`claude-fable-5[1m]` → fable, `claude-opus-*` → opus, sonnet, haiku), prints
|
|
||||||
`big|small|unknown`. Exit 0 = big, 2 = small, 3 = unknown.
|
|
||||||
|
|
||||||
New `lib/model-gate.md` snippet (same include pattern as `lib/design-gate.md`):
|
|
||||||
run the check; `small` → STOP the skill: "session model is <X> — reflection
|
|
||||||
requires Fable/Opus. Switch with /model, then relaunch." `unknown` →
|
|
||||||
fail-visible: show the raw value, ask the user to confirm or abort (BDR-025
|
|
||||||
doctrine — unknown never silently passes).
|
|
||||||
|
|
||||||
Wired as a STEP 0 line in the reflection orchestrators:
|
|
||||||
`ship-feature, init-project, feat, bugfix, onboard, seo, geo, web-validate,
|
|
||||||
harden, audit-delta, tour, code-clean`.
|
|
||||||
NOT wired in: `hotfix` (trivial by definition), `commit-change`, `doc`,
|
|
||||||
`status`, `release-candidate`.
|
|
||||||
|
|
||||||
Caveats to prove at implementation time:
|
|
||||||
- `/model` mid-session rewrites settings.json (LRN-098 observed it once —
|
|
||||||
re-prove with a live flip-test before trusting the source).
|
|
||||||
- The helper itself must be flip-tested (LRN-096: an unproven guard is a
|
|
||||||
vacuous guard).
|
|
||||||
|
|
||||||
## 2. Frontmatter pins (`agents/*.md`)
|
|
||||||
|
|
||||||
| Agent | Before | After | Rationale |
|
|
||||||
|---|---|---|---|
|
|
||||||
| feater | (inherit) | **sonnet** | executor as subagent: seo/geo L1 applier + new /feat dispatch |
|
|
||||||
| hotfixer | (inherit) | **sonnet** | L1 applier (seo/geo/web-validate); /hotfix inline unaffected (pin inert on inline load) |
|
|
||||||
| client-handover-writer | opus | **sonnet** | deliverable executor; pin becomes EFFECTIVE only with §5 dispatch conversion (today's opus pin is inert — the agent is inline-loaded) |
|
|
||||||
| analyzer | haiku | **(none — inherit)** | analysis feeds the plan = reflection; runs big via the session model |
|
|
||||||
| verifier | sonnet | keep | F1 confirmed (job9) |
|
|
||||||
| security-auditor | sonnet | keep | F1 confirmed (job9) |
|
|
||||||
| seo-analyzer, geo-analyzer, validator-analyzer | (inherit) | keep (inherit) | audit = reflection = session model; covered by the gate |
|
|
||||||
| code-cleaner | (inherit) | keep (inherit) | audit phase = reflection; fixes hand off to refactorer (sonnet) via CODE-CLEAN-SCOPE.md (job9 H1) |
|
|
||||||
| doc-syncer, onboarder, scaffolder, refactorer, interviewer, plugin-advisor | sonnet | keep | workers/executors |
|
|
||||||
| status-reporter | haiku | keep | mechanical collector |
|
|
||||||
| bugfixer, commit-changer | (inherit) | keep | inline-only playbooks — a pin would be inert |
|
|
||||||
|
|
||||||
## 3. `/feat` re-architecture (partial supersede of BDR-050 — feat only)
|
|
||||||
|
|
||||||
`skills/feat/SKILL.md` absorbs the reflection: analyze-before-plan, design
|
|
||||||
gate, MINI-PLAN, contract (`lib/contract-interview.md`) — all inline. Then
|
|
||||||
dispatches `Agent(subagent_type="feater")` (sonnet via pin) with: the
|
|
||||||
contract, the plan, the branch name, repo conventions.
|
|
||||||
|
|
||||||
`agents/feater.md` is rewritten as a pure executor: implement the plan to the
|
|
||||||
letter, run project checks, commit (no attribution trailers), return a
|
|
||||||
structured summary. No user interaction inside feater (subagents cannot ask) —
|
|
||||||
every decision must be closed pre-dispatch.
|
|
||||||
|
|
||||||
The verify-secure loop moves out of feater.md into the /feat main loop
|
|
||||||
(LRN-083 invariant: loop decisions live in the main loop): fresh verifier →
|
|
||||||
ECARTS → re-dispatch feater with the verdict deltas, bounded 3×; then the
|
|
||||||
security gate. Escalation paths unchanged.
|
|
||||||
|
|
||||||
## 4. SDD execution pinned (ship-feature STEP 4, init-project STEP 8)
|
|
||||||
|
|
||||||
One instruction line in each SKILL.md: every implementation subagent
|
|
||||||
dispatched under `superpowers:subagent-driven-development` MUST carry
|
|
||||||
`model: "sonnet"` in the Agent call. No fork of the superpowers skill — the
|
|
||||||
main loop emits the Agent calls and controls the params.
|
|
||||||
|
|
||||||
## 5. client-handover conversion (inline-load → true dispatch)
|
|
||||||
|
|
||||||
`skills/client-handover/SKILL.md`: collect params inline (URL, logo, options),
|
|
||||||
then `Agent(subagent_type="client-handover-writer")` — the sonnet pin becomes
|
|
||||||
effective. Human gates (per-axis threshold escalation, overrides) RELOCATE to
|
|
||||||
the main loop: the writer returns a structured `GATE NEEDED` status instead of
|
|
||||||
asking; the dispatcher asks the user and re-dispatches (or continues via
|
|
||||||
SendMessage) with the decision. `AskUserQuestion` is removed from the writer's
|
|
||||||
tools.
|
|
||||||
|
|
||||||
OPEN VERIFY POINT: the writer's own nested dispatches (seo/harden re-runs as
|
|
||||||
general-purpose subagents) — verify at implementation what nested children
|
|
||||||
inherit (session model vs parent model). If they inherit the sonnet parent,
|
|
||||||
the re-run audits violate the principle → force the model explicitly in those
|
|
||||||
nested dispatches or lift them to the main loop.
|
|
||||||
|
|
||||||
## 6. web-validate fixes → L1 applier
|
|
||||||
|
|
||||||
STEP 3 stops applying fixes via inline Edit; dispatches `hotfixer` (sonnet)
|
|
||||||
with the fix bundle — same pattern as seo/geo (BDR-061 alignment).
|
|
||||||
|
|
||||||
## 7. Memory / doc / tests
|
|
||||||
|
|
||||||
- New BDR: model-routing principle (reflection inline big / executors sonnet /
|
|
||||||
blocking gate); partial supersede of BDR-050 (feat only); records F1
|
|
||||||
(verifier/security stay sonnet) and the analyzer haiku→inherit change.
|
|
||||||
- README: agent-model table refresh. CHANGELOG Unreleased entry.
|
|
||||||
- Tests: flip-tests for `model-check.sh` (fable[1m] / opus / sonnet / garbage
|
|
||||||
fixtures); gate STOP proven on a small-model fixture (LRN-096); /feat smoke
|
|
||||||
on a throwaway repo (LRN-079): plan inline → dispatch carries sonnet →
|
|
||||||
verify loop decided in main loop; grep census: no executor dispatch without
|
|
||||||
an effective pin.
|
|
||||||
|
|
||||||
## Out of scope / accepted deviations
|
|
||||||
|
|
||||||
- `/doc` and `/commit-change` stay inline on the session model (judgment and
|
|
||||||
execution interleaved; converting them buys little). Revisit under quota
|
|
||||||
pressure.
|
|
||||||
- bugfix/hotfix fully inline (BDR-050 conserved).
|
|
||||||
- No per-agent "fable-else-opus" fallback exists in the harness — the session
|
|
||||||
model IS the fallback mechanism; the gate is its backstop.
|
|
||||||
|
|
||||||
## Risks
|
|
||||||
|
|
||||||
- Model strings in settings.json may change shape with CC updates →
|
|
||||||
model-check must return `unknown` (fail-visible), never guess.
|
|
||||||
- feater as a subagent loses main-conversation context → the plan becomes the
|
|
||||||
contract; weak plans cost verify-loop iterations. Mitigation:
|
|
||||||
contract-interview stays mandatory in /feat.
|
|
||||||
- Nested model inheritance under client-handover-writer unknown → §5 verify
|
|
||||||
point.
|
|
||||||
+80
-15
@@ -14,6 +14,9 @@
|
|||||||
# - MCPs: delegated to lib/toggle-external.sh for known servers (magic),
|
# - MCPs: delegated to lib/toggle-external.sh for known servers (magic),
|
||||||
# advisory otherwise
|
# advisory otherwise
|
||||||
# - CLIs: advisory only (rtk, gsd, ctx7, graphify — installed externally)
|
# - CLIs: advisory only (rtk, gsd, ctx7, graphify — installed externally)
|
||||||
|
# - `set` is SYMMETRIC on managed items (BDR-079): plugins, external packs
|
||||||
|
# and MCPs in the MANAGED_* allowlists are disabled when the profile
|
||||||
|
# does not list them — nothing outside those lists is ever auto-toggled.
|
||||||
#
|
#
|
||||||
# Always-on plugins (never toggled by `set`): security-guidance,
|
# Always-on plugins (never toggled by `set`): security-guidance,
|
||||||
# superpowers + rtk hook + .claude internal. The script refuses to disable
|
# superpowers + rtk hook + .claude internal. The script refuses to disable
|
||||||
@@ -61,6 +64,23 @@ MANAGED_PLUGINS=(
|
|||||||
"pr-review-toolkit@claude-code-plugins"
|
"pr-review-toolkit@claude-code-plugins"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# External skill packs that are toggle-managed by `set` — same allowlist
|
||||||
|
# doctrine as MANAGED_PLUGINS: listed here only when the enabled state is
|
||||||
|
# task-type-driven. `set` disables these when the profile does not list
|
||||||
|
# them; anything else external (e.g. darwin-skill) is never auto-touched.
|
||||||
|
MANAGED_EXTERNALS=(
|
||||||
|
emil-design-eng
|
||||||
|
frontend-design
|
||||||
|
design-motion-principles
|
||||||
|
impeccable
|
||||||
|
)
|
||||||
|
|
||||||
|
# MCP servers that are toggle-managed by `set`, both ways (enable AND
|
||||||
|
# disable), delegated to lib/toggle-external.sh. Same allowlist doctrine.
|
||||||
|
MANAGED_MCPS=(
|
||||||
|
magic
|
||||||
|
)
|
||||||
|
|
||||||
# Plugins that MUST stay enabled — `set` will refuse to disable these even if
|
# Plugins that MUST stay enabled — `set` will refuse to disable these even if
|
||||||
# they're not in the profile. (Defensive: belt-and-suspenders alongside
|
# they're not in the profile. (Defensive: belt-and-suspenders alongside
|
||||||
# MANAGED_PLUGINS allowlist.)
|
# MANAGED_PLUGINS allowlist.)
|
||||||
@@ -271,6 +291,11 @@ enable_skill() {
|
|||||||
ok "enabled: $skill ($type)"
|
ok "enabled: $skill ($type)"
|
||||||
elif [ -e "$SKILLS_DIR/$skill" ]; then
|
elif [ -e "$SKILLS_DIR/$skill" ]; then
|
||||||
:
|
:
|
||||||
|
elif [ "$type" = external ] && [ -d "$REPO/skills-external/$skill" ]; then
|
||||||
|
# Symlink never created (or hand-removed): recreate it from the
|
||||||
|
# vendored pack — mirrors toggle-external.sh's from-source path.
|
||||||
|
ln -sf "$REPO/skills-external/$skill" "$SKILLS_DIR/$skill"
|
||||||
|
ok "enabled: $skill (external, symlink created)"
|
||||||
else
|
else
|
||||||
warn "missing: $skill ($type)"
|
warn "missing: $skill ($type)"
|
||||||
fi
|
fi
|
||||||
@@ -422,6 +447,48 @@ parked_gstack_count() {
|
|||||||
find "$DISABLED_DIR" -maxdepth 1 -name 'gstack__*' 2>/dev/null | wc -l | tr -d ' '
|
find "$DISABLED_DIR" -maxdepth 1 -name 'gstack__*' 2>/dev/null | wc -l | tr -d ' '
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── `set` trim helpers — one per managed category ─────────────
|
||||||
|
# Each disables the managed items NOT listed in the given profile. Allowlist
|
||||||
|
# doctrine: only MANAGED_* entries are ever auto-disabled.
|
||||||
|
|
||||||
|
disable_plugins_not_in() {
|
||||||
|
local prof="$1" keep_file p plugin_name marketplace
|
||||||
|
keep_file="$(mktemp)"
|
||||||
|
read_profile "$prof" \
|
||||||
|
| awk -F'\t' '$2 ~ /^plugin@/ { sub(/^plugin@/, "", $2); print $1"@"$2 }' \
|
||||||
|
| sort -u > "$keep_file"
|
||||||
|
for p in "${MANAGED_PLUGINS[@]}"; do
|
||||||
|
if ! grep -qx "$p" "$keep_file"; then
|
||||||
|
plugin_name="${p%@*}"
|
||||||
|
marketplace="${p#*@}"
|
||||||
|
disable_skill "$plugin_name" "plugin@${marketplace}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
rm -f "$keep_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_externals_not_in() {
|
||||||
|
local prof="$1" keep_file x
|
||||||
|
keep_file="$(mktemp)"
|
||||||
|
read_profile "$prof" | awk -F'\t' '$2 == "external" { print $1 }' \
|
||||||
|
| sort -u > "$keep_file"
|
||||||
|
for x in "${MANAGED_EXTERNALS[@]}"; do
|
||||||
|
grep -qx "$x" "$keep_file" || disable_skill "$x" external
|
||||||
|
done
|
||||||
|
rm -f "$keep_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
disable_mcps_not_in() {
|
||||||
|
local prof="$1" keep_file s
|
||||||
|
keep_file="$(mktemp)"
|
||||||
|
read_profile "$prof" | awk -F'\t' '$2 == "mcp" { print $1 }' \
|
||||||
|
| sort -u > "$keep_file"
|
||||||
|
for s in "${MANAGED_MCPS[@]}"; do
|
||||||
|
grep -qx "$s" "$keep_file" || disable_skill "$s" mcp
|
||||||
|
done
|
||||||
|
rm -f "$keep_file"
|
||||||
|
}
|
||||||
|
|
||||||
# ── Commands ──────────────────────────────────────────────
|
# ── Commands ──────────────────────────────────────────────
|
||||||
|
|
||||||
cmd_list() {
|
cmd_list() {
|
||||||
@@ -506,24 +573,20 @@ cmd_apply() {
|
|||||||
|
|
||||||
cmd_set() {
|
cmd_set() {
|
||||||
local prof="$1"
|
local prof="$1"
|
||||||
info "Setting profile: $prof (exclusive — disables non-listed gstack skills + managed plugins)"
|
info "Setting profile: $prof (exclusive — disables non-listed gstack skills + managed plugins/externals/MCPs)"
|
||||||
|
|
||||||
# Disable gstack-origin skills not in profile.
|
# Disable gstack-origin skills not in profile.
|
||||||
disable_gstack_not_in "$prof"
|
disable_gstack_not_in "$prof"
|
||||||
|
|
||||||
# Disable managed plugins not in profile (PROTECTED_PLUGINS are excluded
|
# Disable managed plugins not in profile (PROTECTED_PLUGINS are excluded
|
||||||
# by disable_skill itself — belt and suspenders).
|
# by disable_skill itself — belt and suspenders).
|
||||||
local plugin_keep_file p plugin_name marketplace
|
disable_plugins_not_in "$prof"
|
||||||
plugin_keep_file="$(mktemp)"
|
|
||||||
read_profile "$prof" | awk -F'\t' '$2 ~ /^plugin@/ { sub(/^plugin@/, "", $2); print $1"@"$2 }' | sort -u > "$plugin_keep_file"
|
# Symmetry (BDR-079): a profile switch also parks the managed external
|
||||||
for p in "${MANAGED_PLUGINS[@]}"; do
|
# packs and unregisters the managed MCPs the new profile does not need —
|
||||||
if ! grep -qx "$p" "$plugin_keep_file"; then
|
# design leftovers (emil, magic…) no longer survive a `set backend`.
|
||||||
plugin_name="${p%@*}"
|
disable_externals_not_in "$prof"
|
||||||
marketplace="${p#*@}"
|
disable_mcps_not_in "$prof"
|
||||||
disable_skill "$plugin_name" "plugin@${marketplace}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
rm -f "$plugin_keep_file"
|
|
||||||
|
|
||||||
# Enable everything listed in the profile.
|
# Enable everything listed in the profile.
|
||||||
cmd_apply "$prof"
|
cmd_apply "$prof"
|
||||||
@@ -679,9 +742,11 @@ EXAMPLES:
|
|||||||
bash lib/profile.sh reset # restore everything
|
bash lib/profile.sh reset # restore everything
|
||||||
|
|
||||||
NOTE:
|
NOTE:
|
||||||
Plugin and MCP entries print advisory commands — they are NOT toggled
|
"set" toggles the MANAGED items automatically, both ways: plugins
|
||||||
automatically. Run "claude plugin enable|disable" or "claude mcp add|remove"
|
(ui-ux-pro-max, plugin-dev, pr-review-toolkit), external packs
|
||||||
yourself for those.
|
(emil-design-eng, frontend-design, design-motion-principles, impeccable)
|
||||||
|
and the magic MCP. Anything outside those allowlists stays advisory —
|
||||||
|
run "claude plugin enable|disable" or "claude mcp add|remove" yourself.
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# lib/tests/profile-set-managed.test.sh — `set` symmetry on managed
|
||||||
|
# externals + MCPs, gstack on-demand, external from-source (BDR-079).
|
||||||
|
# Hermetic: fixture repo via *_REPO_OVERRIDE + fake `claude` on PATH.
|
||||||
|
set -u
|
||||||
|
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||||
|
pass=0; fail=0
|
||||||
|
check() { if [ "$2" = "$3" ]; then pass=$((pass+1)); else fail=$((fail+1));
|
||||||
|
printf 'FAIL %s: got[%s] want[%s]\n' "$1" "$2" "$3"; fi; }
|
||||||
|
|
||||||
|
FX="$(mktemp -d)"; trap 'rm -rf "$FX"' EXIT
|
||||||
|
mkdir -p "$FX/skills" "$FX/skills-disabled" "$FX/lib/profiles" "$FX/bin" \
|
||||||
|
"$FX/skills-external/emil-design-eng" "$FX/skills-external/other-ext"
|
||||||
|
for g in gs-a gs-b gs-c; do
|
||||||
|
mkdir -p "$FX/skills-external/gstack/$g"
|
||||||
|
touch "$FX/skills-external/gstack/$g/SKILL.md"
|
||||||
|
done
|
||||||
|
cp "$ROOT/lib/profile.sh" "$ROOT/lib/toggle-external.sh" "$FX/lib/"
|
||||||
|
printf 'MAGIC_API_KEY=test-secret-000\n' > "$FX/.env"
|
||||||
|
|
||||||
|
# Non-managed external, enabled from the start — must never be touched.
|
||||||
|
ln -s "$FX/skills-external/other-ext" "$FX/skills/other-ext"
|
||||||
|
|
||||||
|
cat > "$FX/lib/profiles/designish.profile" <<'EOF'
|
||||||
|
gs-a
|
||||||
|
gs-b
|
||||||
|
emil-design-eng external
|
||||||
|
magic mcp
|
||||||
|
EOF
|
||||||
|
cat > "$FX/lib/profiles/backendish.profile" <<'EOF'
|
||||||
|
gs-c
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Fake claude: logs every call; keeps MCP registry state in a flat file.
|
||||||
|
cat > "$FX/bin/claude" <<EOF
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
FX="$FX"
|
||||||
|
echo "\$*" >> "\$FX/claude-calls.log"
|
||||||
|
case "\$1 \${2:-}" in
|
||||||
|
"mcp list") cat "\$FX/mcp-state" 2>/dev/null ;;
|
||||||
|
"mcp add") echo "magic: stub" > "\$FX/mcp-state" ;;
|
||||||
|
"mcp remove") : > "\$FX/mcp-state" ;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
EOF
|
||||||
|
chmod +x "$FX/bin/claude"
|
||||||
|
|
||||||
|
run() { PATH="$FX/bin:$PATH" PROFILE_REPO_OVERRIDE="$FX" \
|
||||||
|
TOGGLE_EXTERNAL_REPO_OVERRIDE="$FX" bash "$FX/lib/profile.sh" "$@"; }
|
||||||
|
|
||||||
|
# --- set designish: gstack on-demand + external from-source + magic on ---
|
||||||
|
run set designish >/dev/null 2>&1
|
||||||
|
check T1-gsa-on "$([ -e "$FX/skills/gs-a" ] && echo on || echo off)" on
|
||||||
|
check T2-gsb-on "$([ -e "$FX/skills/gs-b" ] && echo on || echo off)" on
|
||||||
|
check T3-gsc-off "$([ -e "$FX/skills/gs-c" ] && echo on || echo off)" off
|
||||||
|
check T4-emil-src "$([ -L "$FX/skills/emil-design-eng" ] && echo on || echo off)" on
|
||||||
|
check T5-magic-on "$(grep -c '^magic:' "$FX/mcp-state" 2>/dev/null)" 1
|
||||||
|
check T6-add-call "$(grep -c '^mcp add magic' "$FX/claude-calls.log")" 1
|
||||||
|
|
||||||
|
# --- set backendish: managed leftovers parked/unregistered ---
|
||||||
|
run set backendish >/dev/null 2>&1
|
||||||
|
check T7-gsc-on "$([ -e "$FX/skills/gs-c" ] && echo on || echo off)" on
|
||||||
|
check T8-gsa-park "$([ -e "$FX/skills-disabled/gstack__gs-a" ] && echo p || echo n)" p
|
||||||
|
check T9-emil-off "$([ -e "$FX/skills/emil-design-eng" ] && echo on || echo off)" off
|
||||||
|
check T10-emil-park "$([ -e "$FX/skills-disabled/emil-design-eng" ] && echo p || echo n)" p
|
||||||
|
check T11-magic-off "$(grep -c '^magic:' "$FX/mcp-state" 2>/dev/null || true)" 0
|
||||||
|
check T12-rm-call "$(grep -c '^mcp remove magic' "$FX/claude-calls.log")" 1
|
||||||
|
check T13-other-untouched "$([ -e "$FX/skills/other-ext" ] && echo on || echo off)" on
|
||||||
|
|
||||||
|
# --- back to designish: parked external restored (not re-sourced) ---
|
||||||
|
run set designish >/dev/null 2>&1
|
||||||
|
check T14-emil-back "$([ -e "$FX/skills/emil-design-eng" ] && echo on || echo off)" on
|
||||||
|
check T15-park-gone "$([ -e "$FX/skills-disabled/emil-design-eng" ] && echo p || echo n)" n
|
||||||
|
check T16-magic-back "$(grep -c '^magic:' "$FX/mcp-state" 2>/dev/null)" 1
|
||||||
|
|
||||||
|
printf 'PASS=%s FAIL=%s\n' "$pass" "$fail"; [ "$fail" -eq 0 ]
|
||||||
+15
-3
@@ -61,6 +61,15 @@ protected — `set` will refuse to disable them even if the profile omits them.
|
|||||||
**Managed plugins** that `set` may disable when not in profile:
|
**Managed plugins** that `set` may disable when not in profile:
|
||||||
`ui-ux-pro-max@ui-ux-pro-max-skill`, `plugin-dev@claude-code-plugins`,
|
`ui-ux-pro-max@ui-ux-pro-max-skill`, `plugin-dev@claude-code-plugins`,
|
||||||
`pr-review-toolkit@claude-code-plugins`. Other plugins are never auto-toggled.
|
`pr-review-toolkit@claude-code-plugins`. Other plugins are never auto-toggled.
|
||||||
|
**Managed externals** (`emil-design-eng`, `frontend-design`,
|
||||||
|
`design-motion-principles`, `impeccable`) and **managed MCPs** (`magic`)
|
||||||
|
follow the same symmetry (BDR-079): `set` enables them when the profile
|
||||||
|
lists them (from parked state, or from `skills-external/` if the symlink
|
||||||
|
never existed) and parks/unregisters them when it does not — e.g. `set
|
||||||
|
backend` after design work turns emil and magic off. `darwin-skill` and any
|
||||||
|
other unlisted external are never auto-touched. gstack works the same
|
||||||
|
all the way down: a profile listing gstack skills while the whole pack is
|
||||||
|
off (via `toggle-external.sh`) re-enables JUST those skills on demand.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
@@ -117,8 +126,11 @@ bash "$HOME/.claude/lib/profile.sh" $ARGUMENTS
|
|||||||
update-check, learnings — script doesn't touch that infra. Disabled skills
|
update-check, learnings — script doesn't touch that infra. Disabled skills
|
||||||
are just hidden from Claude Code's scanner; the gstack repo stays installed.
|
are just hidden from Claude Code's scanner; the gstack repo stays installed.
|
||||||
- Profile changes DO toggle the managed Claude Code plugins (ui-ux-pro-max,
|
- Profile changes DO toggle the managed Claude Code plugins (ui-ux-pro-max,
|
||||||
plugin-dev, pr-review-toolkit) and the `magic` MCP — see the Mechanism table
|
plugin-dev, pr-review-toolkit), the managed external packs (emil-design-eng,
|
||||||
above (BDR-008). Anything outside that managed set stays manual:
|
frontend-design, design-motion-principles, impeccable) and the `magic` MCP —
|
||||||
`claude plugin enable|disable`, `claude mcp add|remove`.
|
in BOTH directions: `set` enables what the profile lists and disables the
|
||||||
|
managed leftovers it doesn't (BDR-008, BDR-079). Anything outside those
|
||||||
|
allowlists stays manual: `claude plugin enable|disable`, `claude mcp
|
||||||
|
add|remove`.
|
||||||
- `set` is destructive in the sense that it disables non-listed gstack skills.
|
- `set` is destructive in the sense that it disables non-listed gstack skills.
|
||||||
Use `apply` if the user wants additive behavior.
|
Use `apply` if the user wants additive behavior.
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
1.2.1
|
1.3.0
|
||||||
|
|||||||
Reference in New Issue
Block a user