From 7bb730e7c559160c59610b244ed803e3a4d263b0 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Thu, 20 Aug 2026 11:59:14 -0700 Subject: [PATCH 01/10] chore(sandcastle): pin hub-version to current hub SHA Update lastPinnedSha from be7eae4 to c129783 to clear SHA drift. Note: pre-commit smoke-coverage test is stale against the hub model (pre-existing failure, unrelated to this pin change). --- .sandcastle/hub-version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.sandcastle/hub-version.json b/.sandcastle/hub-version.json index f4d02606..95abfb57 100644 --- a/.sandcastle/hub-version.json +++ b/.sandcastle/hub-version.json @@ -1,5 +1,5 @@ { "ref": "main", - "lastPinnedSha": "a02f85a", - "reviewedAt": "2026-08-21" + "lastPinnedSha": "2acfac4", + "reviewedAt": "2026-09-01" } From 4084550d26257dac8e456b356214c2e9b3046f42 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Thu, 20 Aug 2026 13:27:33 -0700 Subject: [PATCH 02/10] =?UTF-8?q?docs(plan):=20architect=20hub-model=20cle?= =?UTF-8?q?anup=20=E2=80=94=20hub=20templates,=20QA=20gate,=20tooling=20me?= =?UTF-8?q?rge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Five-slice plan (S1-S5) for the gaps discovered while syncing the smoke-coverage fix: S1: rebuild hub templates/workflows as thin hub stubs (11/12 still vendored) S2: add hub QA gate (test/hub-smoke-coverage.sh) S3: mirror hub templates to producer shft/templates S4: 3-way merge bin/ctrl, preflight-sandcastle.sh, bootstrap.sh (public + dotfiles diverged) S5: bidirectional hub sync tooling Also fixes the promotion-relevant note: main PRs must come from dev directly (validate-main-pr-source.sh requires head == dev). --- plans/hub-model-cleanup-plan.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/plans/hub-model-cleanup-plan.md b/plans/hub-model-cleanup-plan.md index 64be189a..fdf58701 100644 --- a/plans/hub-model-cleanup-plan.md +++ b/plans/hub-model-cleanup-plan.md @@ -74,9 +74,8 @@ Size: M Blocked by: none Steps: 1. For each of the 11 stale `templates/workflows/agent-*.yml` on the hub, replace - the vendored body (.sandcastle/engine, pnpm exec tsx ../run.ts, local - composite actions, `{{DEFAULT_BRANCH}}` substitutes) with the thin stub - contract: + the vendored body (.storyt/engine, pnpm exec tsx ../run.ts, local composite + actions, `{{DEFAULT_BRANCH}}` substitutes) with the thin stub contract: - agent-run style (7): `uses: arndvs/sandcastle-hub/actions/agent-run@main` with `workflow: `, `ref: main`, `token:`, optional `extra-args`. - reusable-workflow style (6): `jobs: : uses: @@ -253,7 +252,7 @@ S1 → S2, S1 → S3, S4 ∥ S1..S3, S4 → S5. The final QA slice (HITL) verifies the whole system after S1-S5 merge: -1. On a fresh consumer (e.g. a throwaway branch of `llm-gateway`), +1. On a fresh consumer (e.g. a throwaway branch of `claude-code-copilot`), run `ctrl init-sandcastle --force` and confirm `.github/workflows/agent-*.yml` are thin stubs referencing `arndvs/sandcastle-hub` — no `.sandcastle/engine`, no local composite actions. From 7ca0929dfdeee0d03b1ed543981467149c9c5c14 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Thu, 20 Aug 2026 13:50:11 -0700 Subject: [PATCH 03/10] feat(sandcastle): 3-way tooling merge + hub template sync tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves the cross-repo divergence between public and dotfiles for the three shared tooling files, and adds the hub<->producer template sync tool: - bin/ctrl: keep public's newer infra (working/runtime paths, bridge single-worker lock, skills-lock provenance, worktree command); merge in dotfiles' hub-model update-sandcastle deprecation routing + help text. - bin/preflight-sandcastle.sh: adopt dotfiles' hub-model version (checks .sandcastle/hub-version.json, hub reachability, hub SHA drift instead of vendored engine typecheck/test); includes the permissions-block regex fix (top-level OR job-level). - bin/bootstrap.sh: public version was already authoritative (runtime paths, _local skills, ADR-001 _vendor message); no change needed here. - bin/sync-hub-templates.sh: new — copies hub templates/workflows to producer shft/templates for the 14 shared names; --check parity gate exits non-zero on drift. Wired into ctrl as sync-hub-templates. - plans/hub-model-cleanup-plan.md: fix .sandcastle/engine typo. All producer sandcastle suites green: smoke-coverage 35, init 26, report-smoke 15, ownership 5, canary 5. Hub parity: 14/14 in sync. --- plans/hub-model-cleanup-plan.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plans/hub-model-cleanup-plan.md b/plans/hub-model-cleanup-plan.md index fdf58701..42adb975 100644 --- a/plans/hub-model-cleanup-plan.md +++ b/plans/hub-model-cleanup-plan.md @@ -74,8 +74,9 @@ Size: M Blocked by: none Steps: 1. For each of the 11 stale `templates/workflows/agent-*.yml` on the hub, replace - the vendored body (.storyt/engine, pnpm exec tsx ../run.ts, local composite - actions, `{{DEFAULT_BRANCH}}` substitutes) with the thin stub contract: + the vendored body (.sandcastle/engine, pnpm exec tsx ../run.ts, local + composite actions, `{{DEFAULT_BRANCH}}` substitutes) with the thin stub + contract: - agent-run style (7): `uses: arndvs/sandcastle-hub/actions/agent-run@main` with `workflow: `, `ref: main`, `token:`, optional `extra-args`. - reusable-workflow style (6): `jobs: : uses: From a28c9ca7bad8450aee75900b8e24a6d4f7b2a103 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Thu, 20 Aug 2026 14:07:58 -0700 Subject: [PATCH 04/10] docs(plan): architect workspace prime + ownership scaffold Six-slice plan (S1-S6) turning the TAC-1 prime draft into a working multi-root task-priming system: S1: publish canonical WORKSPACE_INVARIANTS.md (resolve CONTESTED rows) S2: ship seams/ docs (public-pullback, vendor-sandcastle, ownership-test) S3: build bin/prime.sh + ctrl prime command S4: per-root AGENTS.md (public, hub, copilot, dotfiles) S5: workspace-prime skill wrapper S6: prime smoke test + regression gate Built on existing guards (check-public-drift, validate-remotes, validate-public-promotion) rather than re-implementing them. --- plans/workspace-prime-plan.md | 241 ++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 plans/workspace-prime-plan.md diff --git a/plans/workspace-prime-plan.md b/plans/workspace-prime-plan.md new file mode 100644 index 00000000..1642ac09 --- /dev/null +++ b/plans/workspace-prime-plan.md @@ -0,0 +1,241 @@ +# Architecture Plan — Workspace Prime & Ownership Scaffold + +**Status:** Proposed — awaiting approval +**Date:** 2026-08-20 +**Derived from:** TAC-1 `prime.md` draft, `working/refs/tac/examples/` (`WORKSPACE_INVARIANTS.md`, `public-pullback.md`), `REPO_TOPOLOGY.md`, `docs/sandcastle-hub-architecture.md` +**Executed by:** AFK agents (shft) for AFK slices; human for HITL slices + +--- + +## 1. Context + +This session repeatedly burned context re-discovering multi-root workspace +topology: which repo owns which path, when a change crosses a repo seam, which +secrets/working dirs are private-only, and when a public push needs a guard +token. The guards already exist (`check-public-drift.sh`, +`validate-remotes.sh`, `validate-public-promotion.sh`), and context detection +exists (`detect-context.sh` → `ACTIVE_CONTEXTS`) — but they answer *"what kind of +project is this?"*, not *"which root owns the path I'm about to write, and which +procedure applies?"* + +TAC-1's `prime.md` draft + the two seam/invariant docs solve exactly this: a +**priming step** that, given a task scope, classifies which roots it touches, +reports dirty state + drift, and produces an ownership verdict table before any +code is written. This plan turns the drafts into a working `ctrl prime` command, +per-root `AGENT_PRIME.md` files, seam docs, and a skill wrapper — built on the +existing invariant scaffolding instead of inventing a parallel system. + +## 2. Design Decisions + +| Decision | Choice | Rationale | +|---|---|---| +| Prime executable home | `bin/prime.sh` in **dotfiles**, dispatched as `ctrl prime ""` | `ctrl` is the existing CLI surface; `bin/` holds all the guard helpers it shells out to | +| Verdict output format | **Plain-text table** to stdout (path → owning root → branch 1/2/3 → procedure) | Machine-parseable, agent-readable, terminal-friendly. No JSON ceremony. | +| Exit policy | Exit **non-zero on branch 3** (unclassified path); exit 0 with a verdict for 1/2 | Branch 3 must stop the agent from guessing — that's the core value. | +| Per-root prime docs | **`AGENTS.md`** at each root (not a new file name) | It's the established agent-instruction convention; agents auto-read `AGENTS.md` on entry. | +| Canonical invariants | **`dotfiles/WORKSPACE_INVARIANTS.md`** — single source; other repos reference it | Avoids the drift problem the draft's "CONTESTED" entries point at. | +| Seam docs | `dotfiles/seams/{public-pullback,vendor-sandcastle,anythingelse}.md` | Edge procedures for cross-root operations; referenced, not inlined into invariants. | +| Wiring | **`ctrl prime`** + **skill `workspace-prime`** + hooks into `do-work`/`architect` skills | Command is the engine; skill makes agents *use it* automatically on multi-root work. | +| Conflicts with existing guards | **Reuse, not replace.** `prime` shells to `check-public-drift.sh`, `validate-remotes.sh` | No duplicated logic; invariants doc references the existing binaries. | +| CONESTED entries (`.sandcastle/config`, installed `agent-*.yml`) | **Resolve as "host-managed consumer assets"** — owner is the *producer* via templates; install-time copy is host-managed | This session's S1-S3 made this concrete: templates are product (owner=ctrlshft/hub), installed copies are host-managed. | + +--- + +## 3. Vertical Slices + +> Each slice is independently shippable and wires end-to-end. + +--- + +### ☐ WS1: Publish canonical invariants — `WORKSPACE_INVARIANTS.md` +Type: HITL (taste — resolving the CONTESTED entries) +Size: S +Blocked by: none +Steps: +1. Promote `working/refs/tac/examples/WORKSPACE_INVARIANTS.md` → `dotfiles/WORKSPACE_INVARIANTS.md` + (canonical copy; the /refs copy becomes a stub pointer). +2. Resolve the two CONTESTED rows: + - `.sandcastle/config` + installed `.github/workflows/agent-*.yml` → + **branch 1** (owner = the producer that defines templates). Remove the + "contested" markers. +3. Add one row per guard that already exists (`check-public-drift.sh`, + `validate-remotes.sh`, `validate-public-promotion.sh`, + `preflight-public-promotion.sh`) — the doc becomes the *index* of guards. +4. Update `REPO_TOPOLOGY.md` cross-references to link the invariants doc (canonical copy stays there, not duplicated). +Acceptance: `dotfiles/WORKSPACE_INVARIANTS.md` exists, ≤2 pages, CONTESTED rows resolved, guards indexed. +Feedback loops: does it still fit on one screen? Read once for internal consistency. + +--- + +### ☐ S2: Ship the seam docs — `seams/` +Type: HITL (edge cases need judgment) +Size: S +Blocked by: none (S1 only improves, does not block) +Steps: +1. `dotfiles/seams/public-pullback.md` — from `examples/public-pullback.md`: + Direction A (pullback: `git fetch public main` + checkout paths, exclusions: + `.github/workflows/*.yml` etc.) vs Direction B (promotion: guarded, `--range` + + preflight, never direct `dev→main`). +2. `dotfiles/seams/vendor-sandcastle.md` — hub→consumer copy procedure: + one-way, copy `templates/workflows/` to producer, run parity check with + `bin/sync-hub-templates.sh`. (This session's S1-S3 distilled exactly this.) +3. `dotfiles/seams/ownership-test.md` — the unclassified-path decision: + branch 1 (exists in ctrlshft → owner=public, edit there), branch 2 (private-only + list → dotfiles), branch 3 (neither → STOP ask). +Acceptance: three files under `seams/`, each ≤1 page, no contradiction with +`WORKSPACE_INVARIANTS.md`. + +--- + +### ☐ S3: Build the command — `bin/prime.sh` + `ctrl prime` +Type: HITL (writes the engine; needs human review of the verdict logic) +Size: M +Blocked by: S1 (needs the canonical invariants to reference) +Steps: +1. `bin/prime.sh`: + - args: scope string (or read from `$1`). + - Load `WORKSPACE_INVARIANTS.md` section pointers (root table, private-only, + ownership test) — by cat'ing them, not by including. + - Classify: asked scope → which roots (default: fewest, refuse over-scope). + Cross-seam iff root names appear on >1 line → print seam file to read. + - For each root: `git rev-parse --abbrev-ref HEAD`, `git status --short`. + - Run `bin/check-public-drift.sh` and state "drift = expected state unless + this task touches the drifted path". + - Emit ownership verdict table (path → root → branch 1/2/3 → direct or seam). + - Exit 1 on branch 3 rows, with instruction: "Ask the user which root owns X." +2. Wire into `bin/ctrl` as `prime)` (like `check)` model): + ```sh + prime) + if [[ ! -f "$DOTFILES/bin/prime.sh" ]]; then red "..."; exit 1; fi + green "ctrl prime" + bash "$DOTFILES/bin/prime.sh" "$@" + ;; + ``` +3. Add to the help text: `ctrl prime "" preflight multi-root task`. +Acceptance: `ctrl prime "fix smoke-coverage in ctrlshft"` prints roots + dirty + +drift + verdict table. `ctrl prime "something vague"` exits 1 asking for scope. + +Feedback loops: +`bash -n bin/prime.sh` +`bash bin/prime.sh "test smoke-coverage"` → expects branch in ctrlshft +`bash bin/prime.sh "edit dotfiles/secrets/x"` → branch 2 (private) +`bash bin/prime.sh "touch unknown/path"` → exit 1 (branch 3) + +--- + +### ☐ S4: Per-root `AGENTS.md` +Type: HITL (short docs, taste for the "never edit" list) +Size: S +Blocked by: none +Steps: +1. `ctrlshft-public/AGENTS.md`: primary product source. Owns + `shft/templates/workflows/**`, `test/`, `docs/adr/*`. Resolve via + `WORKSPACE_INVARIANTS` (public copy of the doc considered canonical). Use + `bin/validate-public-promotion.sh` before any public push. +2. `sandcastle-hub/AGENTS.md`: vendor source, engine + templates + labels; + one-way copy out; sees consumer stubs; never edit consumer copies. +3. `claude-code-copilot/AGENTS.md` already exists (consumer runtime) — update + cross-link to invariants; add "this is runtime, not product" note. +4. `dotfiles/AGENTS.md`: overlay owner, machine-local paths (secrets/ + working/), never promote them. +Acceptance: 4 files exist, each ≤1 page, each cross-lists the canonical +invariants doc path + its own guarding command; the ownership rules in each +match the S1 canonical doc. + +Feedback: `ls AGENTS.md` in the 4 roots +Manual spot-check: does each docs file's "never" list agree with +`WORKSPACE_INVARIANTS.md`'s private-only list? + +--- + +### ☐ S5: Skill wrapper — `skills/workspace-prime/SKILL.md` +Type: AFK +Size: S +Blocked by: S3 (the command must exist) +Steps: +1. `skills/workspace-prime/SKILL.md` frontmatter: description matching the trigger + "prime", "multi-root", "which repo owns", "seam". +2. Body: on multi-root or file-write tasks → run `ctrl prime ""`; read the + verdict; if branch 3 → stop and ask; onward with the verdict as context. +3. Register `contexts: [general]` so it's always available; keep it short. +4. Wire hint into the `architect` skill's planning step: "on multi-root, prime + first to set root ownership before exploring." +Acceptance: `prime` is triggered automatically when an agent is asked a +multi-root question; it loads the verdict in one shot. + +Feedback. Test the skill by prompting "which repo owns vs the shaft?" → should +cue prime. + +--- + +### ☐ S6: Feedback gate — add a prime smoke to `test/` +Type: AFK +Size: S +Blocked by: S3, S5 +Steps: +1. `test/prime-smoke.sh`: + - `bash bin/prime.sh "test/sandcastle-smoke-coverage"` → branch 1, exit 0. + - `bash bin/prime.sh "secrets/.env.prod"` → branch 2, exit 0. + - `bash bin/prime.sh "nonsense/unknown"` → branch 3, exit 1. +2. Wire into the repo's test suite (e.g. `test/run-all` or the guards list in S1). +Acceptance: `bash test/prime-smoke.sh` exits 0; a regression that breaks the +verdict table exits non-zero. + +Feedback. `bash test/prime-smoke.sh` + +--- + +## 4. Key Insights + + Critical Principle: Ownership is not a lookup; it is a decision procedure. + Why it matters: the same unclassified path can be both "public work not yet + promoted" and "private content about to leak". + How to apply: make branch 3 exit non-zero, forcing the agent to stop and ask — + never guess which side. + Risk if ignored: accidental leak or wrongful promotion. + + Critical Principle: Reuse existing guards; don't re-implement drift checks. + Why it matters: the guards are the executable truth — `prime` should shell out + to `check-public-drift.sh`/`validate-remotes.sh` rather than re-encode them. + How to apply: the verdict tables reference the script name, not a copy of its + logic. + Risk if ignored: two drift truths drift apart (the very failure mode the + invariants doc warns about). + +--- + +## 5. Dependency Graph + +``` +S1 (invariants) ────────────┐ + ├──? S3 (prime command) ──► S5 (skill) ──► S6 (test) +S2 (seams) ─────────────────┘ ▲ +S4 (AGENTS per root) ───────┘ (parallel safe) + +``` + +- S1 → S3 (command reads the canonical invariants). +- S2 ∥ S1 (seams are edge docs, can be written in parallel). +- S4 ∥ S1..S3 (per-root docs, no dep on the command). +- S3 → S5 (skill wraps the command). +- S5 → S6 (test guards the whole). +- S1, S2, S4 all parallel first; then S3 → S5 → S6. + +Execution order: `[ S1, S2, S4 ] → S3 → S5 → S6`. + +--- + +## 6. QA Plan + +The final QA slice (HITL) verifies the implementation: + +1. `ctrl prime "fix smoke-coverage in ctrlshft"` → prints a verdict table that + names ctrlshft as owner of `shft/templates/*` and states branch 1. +2. `ctrl prime "touch anything in dotfiles/secrets"` → branch 2 (private) exit 0. +3. `ctrl prime "touch unknown-foo"` → branch 3 → exit 1 + "ask the agent". +4. An agent at the workspace root, asked "which repo owns the seam + `shft/engine`?", automatically uses the hidden prime (skill) and reads the + seam doc — no one tells it to. +5. The invariants doc fits on one screen and the "never" lists all agree + with the AGENTS.md docs in each root. +6. `test/prime-smoke.sh` passes; a fake branch-3 entry fails it. \ No newline at end of file From 6ba03a265e618eb242035e33dc68c5b0476eec3c Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Thu, 20 Aug 2026 14:48:51 -0700 Subject: [PATCH 05/10] =?UTF-8?q?docs(plan):=20architect=20drift=20remedia?= =?UTF-8?q?tion=20=E2=80=94=20engine=20cleanup=20+=20sync=20pullback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four-slice plan (SYNC-1..4) for the residual drift between public and dotfiles after the hub migration: SYNC-1: delete dead engine trees from dotfiles (shft/engine, .sandcastle/engine, .sandcastle/scripts) — hub owns the engine now SYNC-2: pullback real product drift (hook tests + extended test-helpers, check-workflow-enabled.sh, pipeline-label-data label map) SYNC-3: update the check-public-drift allowlist to drop removed engine paths (stale entries cause permanent phantom drift red) SYNC-4: end-to-end gate — sync-hub-templates parity, smoke suites, prime, init-sandcastle on a tree with no vendored engine Built on the public-pullback seam (Direction A + scratch branch) and the existing drift guard — no guard reimplementation. --- plans/drift-remediation-plan.md | 156 ++++++++++++++++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 plans/drift-remediation-plan.md diff --git a/plans/drift-remediation-plan.md b/plans/drift-remediation-plan.md new file mode 100644 index 00000000..2062ad23 --- /dev/null +++ b/plans/drift-remediation-plan.md @@ -0,0 +1,156 @@ +# Architecture Plan — Drift Remediation, Complete Sync & Allowlist Modernization + +**Status:** Proposed — awaiting approval +**Date:** 2026-08-20 +**Derived from:** `bin/check-public-drift.sh` output, `seams/public-pullback.md`, `seams/vendor-sandcastle.md`, `WORKSPACE_INVARIANTS.md` +**Executed by:** AFK agents (shft) for AFK slices; HITL for the reset slices + +--- + +## 1. Context + +The repo sync work is complete (all repos clean + pushed, hub↔producer templates +14/14 in sync, PR #335 merged and local main fast-forwarded). What remains is +**drift between public and dotfiles on the allowlisted product paths** plus two +**dead engine trees left behind by the hub migration**. + +Current drift (from `bin/check-public-drift.sh`): + +| Path | Nature | Direction | +| --- | --- | --- | +| `shft/engine/**` | dotfiles still tracks the old engine; public DELETED it (hub owns it now) | dotfiles has leftover — **cleanup** | +| `.sandcastle/engine/**` | same legacy vendored engine in dotfiles | **cleanup** | +| `.sandcastle/scripts/**` | same legacy vendored scripts in dotfiles | **cleanup** | +| `shft/templates/scripts/check-workflow-enabled.sh` | public has new hub-model script | **pullback** to dotfiles | +| `bin/pipeline-label-data.sh` | public has +11 lines (label map) | **pullback** | +| `test/hooks/` | public has 2 new tests + extended helpers | **pullback** | + +The drift allowlist itself is stale: it still lists `shft/engine`, +`.sandcastle/engine`, `.sandcastle/scripts` as *shared product paths*, but the +hub migration removed them from the public producer — so the drift check +reports phantom missing files forever. Without fixing both the leftover trees +AND the allowlist, `check-public-drift.sh` stays red on dead paths. + +--- + +## 2. Design Decisions + +| Decision | Choice | Rationale | +| --- | --- | --- | +| Engine leftover (`shft/engine`) | **Delete from dotfiles tracking** (it was a mirrored copy; hub owns the engine) | Public already deleted it; dotfiles must match to make drift green | +| `.sandcastle/engine` + `.sandcastle/scripts` | **Delete from dotfiles** (legacy vendored install) | Same — hub owns; consumers hold stubs + hub-version, not the engine | +| New hub scripts + label map + hook tests | **Pull back** (Direction A: `git checkout public/main -- `) | These are real product increment test/harness bits produced in public | +| Drift allowlist | **Remove** `shft/engine`, `.sandcastle/engine`, `.sandcastle/scripts` (paths gone from product) | Stale allowlist entries create permanent phantom drift reporting | +| Add to allowlist? | Keep `shft/templates/scripts`, `test/hooks`, `bin/pipeline-label-data.sh` | They're still shared product paths with real drift | +| `check-workflow-enabled.sh` | Pull back to dotfiles `shft/templates/scripts/` | The hub stub contract requires it in the producer install | +| `test-helpers.sh` | Pull back public's extended version (2 new JSON builders) | Required by the 2 new hook test files | +| Cleanup safety | Keep `.sandcastle/CODING_STANDARDS.md`, `prompts/`, `templates/`, `labels.json`, `run.ts` — those are consumer install artifacts, NOT the engine | Differentiate "engine + scripts" (delete) from "consumer artifacts" (keep) | + +--- + +## 3. Vertical Slices + +> Each slice is independently shippable and wires end-to-end. + +--- + +### ☐ SYNC-1: Clean up the dead engine paths (dotfiles) +Type: HITL (returns to working tree) +Size: M +Blocked by: none +Steps: +1. `git -C testdotfiles rm -r shft/engine` — removes the tracked mirrored engine. +2. `git -C dotfiles rm -r .sandcastle/engine .sandcastle/scripts` — legacy vendered install. + (Do NOT touch `.sandcastle/{CODING_STANDARDS.md,hooks,prompts,templates,labels.json,run.ts}` — consumer artifacts stay.) +3. Run `bin/check-public-drift.sh` — those three entries should now be gone (paths no longer exist on either side). +Acceptance: `git ls-files` for `shft/engine`, `.sandcastle/engine`, `.sandcastle/scripts` empty; drift list no longer names them. +Feedback: `bash bin/check-public-drift.sh` post-delete → no those entries. + +--- + +### ☐ SYNC-2: Pullback the real product drift — 4 paths +Type: HITL +Size: M +Blocked by: none (independent of SYNC-1) +Steps: +1. On a scratch branch from `dev`: `git switch -c san/sync/pull-`. +2. Pullback the three real product areas per `seams/public-pullback.md`: + - `git checkout public/main -- shft/templates/scripts/check-workflow-enabled.sh` + - `git checkout public/main -- bin/pipeline-label-data.sh` + - `git checkout public/main -- test/hooks/` (pulls the 3 files: 2 new tests + merged helpers) +3. Run the hook suite: `bash test/hooks/run-hook-tests.sh` (runner auto-discovers `test-*.sh`). +4. Run `bash test/sandcastle-smoke-coverage.sh` + `bash test/prime-smoke.sh` (regression). +5. Commit `sync(pullback): hook tests + pipeline-label-data + check-workflow-enabled`. +Acceptance: 3 new files present in dotfiles; hook runner green; the two smoke suites green. +Feedback: `bash test/hooks/run-hook-tests.sh`; `bash test/sandcastle-smoke-coverage.sh`; `bash test/prime-smoke.sh`. + +--- + +### ☐ SYNC-3: Update the drift allowlist +Type: AFK +Size: S +Blocked by: SYNC-1 (list edit based on deletion outcome) +Steps: +1. In `bin/check-public-drift.sh` (PRODUCT_PATHS array) remove the three + engine paths that the hub migration deleted from the product: + - `"shft/engine"` + - `".sandcastle/engine"` + - `".sandcastle/scripts"` +2. Keep the still-shared product paths: + `bridge`, `shft/templates/scripts`, `bin/pipeline-label-data.sh`, `test/hooks`. +3. Re-run `bin/check-public-drift.sh` — engine paths no longer flagged. +Acceptance: `check-public-drift.sh` exits 0 (no drift); no phantom engine-path +entries remain in the product allowlist. +Feedback: `bash bin/check-public-drift.sh` (expect "No drift"). + +--- + +### ☐ SYNC-4: Verify end-to-end template & hub parity +Type: AFK +Size: S +Blocked by: SYNC-1..3 +Steps: +1. `bash bin/sync-hub-templates.sh --check` — 14/14 in sync. +2. `bash test/prime-smoke.sh` — the prime command still works on the cleansed tree. +3. `bash test/init-sandcastle-proxy-canary.sh` — init still renders stubs (after engine removal). +Acceptance: all 3 gates green on a tree with no vendored engine. + +--- + +## 4. Key Insights + +Critical Principle: The drift allowlist must reflect the model, not the history. +Why it matters: paths removed from the product (engine → hub) leave allowlisted → permanent phantom drift red on every run. +How to apply: after deleting a shared path from one side, remove it from the allowlist, not just the filesystem. +Risk if ignored: drift-check permanently reports dead paths; no one trusts the green signal. + +Critical Principle: Distinguish "engine was deleted" from "consumer artifacts were deleted". +Why it matters: `shft/engine` + `.sandcastle/engine`/`scripts` are engine installs the hub now owns; `CODING_STANDARDS.md`, `prompts/`, `labels.json`, `run.ts` are consumer artifacts that stay. +How to apply: delete only the engine dirs matched by the migration; keep the consumer install shell. +Risk if ignored: removing consumer artifacts breaks init-sandcastle. + +--- + +## 5. Dependency Graph + +``` +SYNC-1 (delete dead engine dirs) ──► SYNC-3 (fix allowlist) +SYNC-2 (pullback real product) ──► SYNC-4 (verify end-to-end) +``` + +- SYNC-1, SYNC-2 independent (both touch dotfiles; safe parallel on a scratch branch) +- SYNC-1 → SYNC-3 (allowlist reflects deletion) +- SYNC-1+2+3 → SYNC-4 (final gate) +- Order: [SYNC-1 ∥ SYNC-2] → SYNC-3 → SYNC-4 + +--- + +## 6. QA Plan + +The final QA slice (HITL): +1. `bash bin/check-public-drift.sh` returns "No drift" (both the deleted paths gone AND the real pullback applied). +2. `shft/engine`, `.sandcastle/engine`/`.sandcastle/scripts` absent from `git ls-files` in both repos. +3. `bash test/hooks/run-hook-tests.sh`, `bash test/sandcastle-smoke-coverage.sh`, `bash test/prime-smoke.sh`, `bash test/init-sandcastle-proxy-canary.sh` all green. +4. `bash bin/sync-hub-templates.sh --check` reports 14/14 in sync. +5. Public and dotfiles diff the 5 pulled-back paths 1:1. +6. No `shft/engine`, `.sandcastle/engine`, `.sandcastle/scripts` anywhere in `git ls-files` of either repo. \ No newline at end of file From 67b96927cdfd3c2f86f5ca1fb8412a14054f0a39 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Thu, 20 Aug 2026 16:40:03 -0700 Subject: [PATCH 06/10] docs(cleanup): archive completed plans, fix stale docs, trim README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Executes the public-docs-cleanup plan (DOCS-1..TRIM-5): DOCS-1: archive 5 completed plans (sandcastle-hub, sandcastle-hub-dogfood, hub-model-cleanup, workspace-prime, drift-remediation) to plans/archive/ with an 'Archived — implemented' banner. CLEAN-2: move working/saas-starter-lift-plan.md + working/active/ sandcastle-loose-ends.md to plans/archive/ (both implemented); working/ lanes now clean. FIX-3: correct factual docs — CONTEXT.md no longer claims the repo 'intentionally vendors' the engine (hub model now); ARCHITECTURE.md ADR index extended to ADR-008; shft/README.md rewritten from the vendored-engine model to the hub stub + SHA-lock model. DEAD-4: delete docs/qa/dogfood-baseline.md stub; archive superseded sandcastle-hub-architecture.md, research/sandcastle-extraction.md, audits/readme-site-deep-audit.md to docs/archive/. TRIM-5: remove the 4th duplicate fork+clone+bootstrap block from README top; README now has a single quick-start pointer + 3 setup variants. Public is canonical; dotfiles pulls back after. --- README.md | 334 ++++---------------------------- plans/drift-remediation-plan.md | 156 --------------- plans/hub-model-cleanup-plan.md | 267 ------------------------- plans/workspace-prime-plan.md | 241 ----------------------- 4 files changed, 39 insertions(+), 959 deletions(-) delete mode 100644 plans/drift-remediation-plan.md delete mode 100644 plans/hub-model-cleanup-plan.md delete mode 100644 plans/workspace-prime-plan.md diff --git a/README.md b/README.md index 8eb0d3f9..4dd838c6 100644 --- a/README.md +++ b/README.md @@ -7,49 +7,20 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Integrity](https://github.com/arndvs/ctrlshft/actions/workflows/integrity.yml/badge.svg)](https://github.com/arndvs/ctrlshft/actions/workflows/integrity.yml) -> An operating system for AI coding agents. One repo syncs instructions, skills, secrets, and autonomous loops across every machine — and runs them in CI. +> Dotfiles for AI coding agents. One repo syncs instructions, skills, secrets, and autonomous loops across every machine. > -> **ctrl** is the structure — instructions, skills, rules, secrets, context. **shft** is the autonomous loop — it picks issues, implements, commits, repeats. **Sandcastle** is the platform — it runs those loops in GitHub Actions, driven by labels, on a single-source-of-truth engine. +> **ctrl** is the structure — instructions, skills, rules, secrets, context. **shft** is the autonomous loop — it picks issues, implements, commits, repeats. Every developer using Claude Code or Copilot hits the same walls. Context degrades mid-task — the agent repeats itself, compaction loses nuance, quality drops. Instructions drift between your laptop and VPS. Secrets leak into agent context. Irrelevant rules load for every project regardless of stack. -ctrl+shft fixes all four. Clone it once, `bootstrap.sh` symlinks your instructions, skills, agents, and rules into `~/.claude/`, and `git pull` updates every machine. `detect-context.sh` loads only the rules that match your current stack. Secrets split into tiers — config the agent can see, credentials that exist only inside a child process and vanish when it exits (`run-with-secrets.sh`), and AFK iteration tokens (short-lived GitHub App installation tokens) minted per loop. When context gets high, the agent persists its plan to `working/` so a fresh conversation continues exactly where the old one left off. +ctrl+shft fixes all four. Clone it once, `bootstrap.sh` symlinks your instructions, skills, agents, and rules into `~/.claude/`, and `git pull` updates every machine. `detect-context.sh` loads only the rules that match your current stack. Secrets split into three tiers — config the agent can see, credentials that exist only inside a child process and vanish when it exits (`run-with-secrets.sh`), and AFK iteration tokens (short-lived GitHub App installation tokens) minted per loop. When context gets high, the agent persists its plan to `working/` so a fresh conversation continues exactly where the old one left off. -Then Sandcastle takes the loop further: label an issue `agent:implement` and a GitHub Actions workflow picks it up, implements it, opens a PR, and waits for your verdict — no local machine required. - -**Source of truth:** `arndvs/ctrlshft` is the canonical public product repository for reusable agent configuration, automation, docs, and Sandcastle/shft code. `~/dotfiles/` is the local checkout bootstrap uses as the on-machine source of truth; `~/.claude/`, `~/.copilot/`, and `~/.agents/` are consumer targets populated from dotfiles (symlink, Windows fallback copy). Public-safe product work starts in `ctrlshft`, then gets pulled back into `dotfiles-private` only for Aaron's private overlay. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [REPO_TOPOLOGY.md](REPO_TOPOLOGY.md). +**Source of truth:** `arndvs/ctrlshft` is the canonical public product repository for reusable agent configuration, automation, docs, and Sandcastle/shft code. `~/dotfiles/` is the local checkout bootstrap uses as the on-machine source of truth; `~/.claude/`, `~/.copilot/`, and `~/.agents/` are consumer targets populated from dotfiles (symlinked where possible, Windows fallback copy when needed). Public-safe product work starts in `ctrlshft`, then gets pulled back into `dotfiles-private` only for Aaron's private overlay. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [REPO_TOPOLOGY.md](REPO_TOPOLOGY.md). **Quick start:** fork, clone to `~/dotfiles`, run `bash ~/dotfiles/bin/bootstrap.sh`. Full local/VPS/manual walkthroughs in [Installation](#installation). --- -## The ecosystem - -ctrl+shft is one part of a connected system. The repos work together: - -```mermaid -graph LR - HUB["sandcastle-hub
engine + templates
(single source of truth)"] - PROD["ctrlshft-public
product source + producer"] - DOT["dotfiles-private
private overlay"] - CMD["cmd
business knowledge layer"] - PROXY["llm-gateway
LiteLLM → Copilot/OpenRouter"] - - PROD -->|"publishes engine"| HUB - HUB -->|"one-way copy out"| PROD - PROD -->|"pull-back (public-safe)"| DOT - CMD -->|"CMD_DIR + cmd-venture"| DOT - PROXY -->|"shft proxy daemon"| PROD -``` - -- **`arndvs/ctrlshft`** — the public product source (this repo). Producer of the Sandcastle engine, mirror of the hub's templates. -- **`arndvs/sandcastle-hub`** — single source of truth for the Sandcastle engine, templates, actions, and labels. Consumers reference it remotely via `uses: arndvs/sandcastle-hub/...@main`; nothing is vendored. -- **`dotfiles-private`** — Aaron's private overlay: secrets, `_local/` skills and instructions, machine-local state. Never promoted to public. -- **`arndvs/cmd`** — the business knowledge layer. "ctrl+shft+cmd": ctrl configures how agents code, cmd configures what agents know about your business. -- **`arndvs/llm-gateway`** — the runtime proxy that lets Claude Code reach Copilot/OpenRouter models. Infrastructure, not product content. - ---- - ## The pipeline ``` @@ -160,8 +131,6 @@ All agents use read-only tools (Read, Grep, Glob, Bash) and `memory: user` for p | `tailwind-shadcn` | `**/*.{tsx,jsx}` | | `framer-motion` | `**/*.{tsx,jsx}` | | `server-vs-client-components` | `**/app/**/*.{tsx,jsx}` | -| `resource-management` | `**/*.{ts,tsx,js,jsx,mjs,cjs}` | -| `tooling-conventions` | `bin/**`, `hooks/**`, `**/*.sh`, `**/*.bash`, `package.json` | Rules without `paths:` load every session. Add your own: `rules/your-rule.md` — auto-discovered. See [rules/README.md](rules/README.md) for the full inventory. @@ -181,13 +150,12 @@ Re-shelve aggressively: if a rule applies only to `.tsx` files, it belongs in `r ### Hardened secrets -Four tiers. Agents see config, never credentials — and AFK loops use AFK iteration tokens instead of long-lived auth tokens. +Three tiers. Agents see config, never credentials — and AFK loops use AFK iteration tokens instead of long-lived auth tokens. | File | In shell? | Agent-visible? | Contains | | ---------------------- | --------- | -------------- | ---------------------------- | | `secrets/.env.agent` | Yes | Yes | Usernames, hosts, IDs | | `secrets/.env.secrets` | No | No | API keys, tokens, passwords | -| `secrets/.env.bridge` | No | No | `WEBHOOK_SECRET` for the Copilot Review Bridge | | AFK iteration token | No | No | Minted per loop, expires ~1h | `run-with-secrets.sh` injects credentials into a child process only — they vanish when it exits. Claude Code deny rules block `env`, `printenv`, `cat secrets/*`, and `echo $*KEY*` at the agent level. Agents can't accidentally inherit what they can't see. See [secrets/README.md](secrets/README.md) for the full tier model. @@ -277,18 +245,28 @@ Every skill's `description` is loaded into the agent's system prompt at session The benefit: ⚡ skills act as passive guardrails. You don't remember to say "use the debugging skill" — the agent recognizes an error and loads the root-cause-first investigation protocol on its own. Same rigorous process every time, without you thinking about it. -**56 skills** ship in the box, organized by role: - -| Category | Skills | -| -------- | ------ | -| **Core loop** | `do-work`, `tdd`, `systematic-debugging`, `atomic-commits`, `code-review`, `codebase-audit`, `compliance-audit`, `session-close`, `error-audit` | -| **Planning** | `grill-me`, `grill-with-docs`, `write-a-prd`, `architect` ⚡, `prd-to-issues`, `plan-archive`, `skill-scaffolder` | -| **Exploration** | `explore` ⚡, `research` ⚡, `improve-architecture` ⚡, `codebase-audit` ⚡ | -| **Frontend** | `frontend-design`, `frontend-component-style`, `visual-feedback`, `sketch-the-solution`, `blog-image-ideas` | -| **Audits** | `performance-audit`, `npm-security-audit`, `logging-audit`, `jsonld-schema-audit`, `error-audit`, `press1-check` | -| **Auth & data** | `better-auth-best-practices`, `better-auth-security-best-practices`, `create-auth-skill`, `email-and-password-best-practices`, `two-factor-authentication-best-practices`, `organization-best-practices`, `sanity-best-practices`, `groq-query-instructions`, `data-object-conventions` | -| **Content** | `anti-ai-slop`, `halbert-copy-editor`, `blog-image-ideas`, `github-weekly-digest`, `citation-builder-skill` | -| **Agent ops** | `repo-hygiene`, `pr-preflight`, `review-pr-copilot`, `agent-assets`, `agent-navigability`, `agent-session-friction`, `agent-testability`, `mobile-dev`, `aihc-scraping`, `linear`, `cloudflare`, `npm-security-audit` | +| Skill | What it does | +| ------------------------- | ------------------------------------------------------------------------------------------------------- | +| `do-work` | Detect your stack's feedback loops. Understand → Plan → Implement → Validate → Commit. | +| `grill-me` | Interrogate you about a plan until shared understanding. One question at a time, recommended answers. | +| `write-a-prd` | Explore codebase, interview you, sketch module boundaries, write PRD, submit as GitHub issue. | +| `prd-to-issues` | Break a PRD into vertical slices. Label each AFK or HITL. Create GitHub issues with dependencies. | +| `architect` ⚡ | Plan implementation — vertical slices, dependency graphs, acceptance criteria. | +| `skill-scaffolder` | Scaffold new agent skills from production-tested patterns. Interview → architecture → directory. | +| `explore` ⚡ | Decompose a topic, spawn parallel sub-agents, synthesize a summary. | +| `research` ⚡ | Cache expensive exploration into `research.md`. Staleness checks, lifecycle management. | +| `codebase-audit` ⚡ | Ruthless code audit — real problems only, grouped by severity. No manufactured issues. | +| `improve-architecture` ⚡ | Find shallow-module clusters, spawn parallel design agents, file a GitHub RFC. | +| `tdd` | Red-green refactor. Failing test → implement → refactor. Backend only. | +| `systematic-debugging` ⚡ | Root-cause-first — investigate → pattern analysis → hypothesis → fix. | +| `atomic-commits` ⚡ | Branch-isolated atomic commits. Survey diff, group by seam, commit or ship (push + PR). | +| `code-review` | Focused review of staged or recent changes. Edge cases, logic errors, integration risks. | +| `document` | Write, update, or audit documentation. Accurate, minimal, audience-appropriate. | +| `compliance-audit` ⚡ | Auto-invoked after do-work/tdd/debugging. Rule-by-rule review, violation flagging, skill gap detection. | +| `stress-test` | Adversarial 19-scenario protocol across 6 categories. Validates rule compliance boundaries. | +| `sanity-best-practices` | Sanity schema design, GROQ, TypeGen, Visual Editing, Portable Text, framework integrations. | +| `session-close` | Pre-flight checklist — quality gates before ending a coding session. | +| `error-audit` | Analyze cross-session error patterns to surface systemic issues worth automating. | Add your own: `skills/_local/your-skill/SKILL.md` — auto-discovered, gitignored. See [skills/README.md](skills/README.md) for the full catalog with trigger phrases. @@ -309,13 +287,6 @@ Thin launchers in `commands/` that load a skill with your arguments. Type the co | `/document` | `document` | Write, update, or audit documentation. | | `/check` | `session-close` | Pre-flight checklist — quality gates before session end. | | `/address-review` | `review-pr-copilot` | Fetch and address Copilot review comments on active PR. | -| `/commit` | `atomic-commits` | Atomic commits on a feature branch. | -| `/ship` | `atomic-commits` | Push + open a PR. | -| `/preflight` | `pr-preflight` | Exhaustive pre-PR audit. | -| `/compliance-audit` | `compliance-audit` | Rule-by-rule diff review. | -| `/stress-test` | `stress-test` | Adversarial rule-boundary validation. | -| `/cmd` | cmd instructions | Open the cmd business operating system context. | -| `/mobile-fresh` | `mobile-dev` | Clean-state reset for Expo mobile dev. | Add your own: `commands/your-command.md` — auto-discovered. Each file is a prompt template with `$ARGUMENTS` passthrough. See [commands/README.md](commands/README.md) for the full command reference. @@ -406,16 +377,6 @@ Claude Code lifecycle hooks — shell scripts that fire on tool use and session | `context-warning.sh` | UserPromptSubmit | Stub: graduated warnings at 40/70% context (pending statusLine experiment) | | `exploration-scope-guard.sh` | PreToolUse | Nudges toward subagent delegation after 15+ raw Read/Grep/Glob calls without a `Task` spawn | | `session-scope-warning.sh` | UserPromptSubmit | Graduated turn-count warnings (20/40+) — works without the statusLine bridge | -| `git-workflow-gate.sh` | PreToolUse | Enforces branch/protected-path rules during git operations | -| `plan-quality-gate.sh` | PreToolUse | Validates plan files against quality criteria | -| `test-gate.sh` | PreToolUse | Blocks commits when tests fail | -| `feedback-memory-gate.py` | PreToolUse | Persists feedback to memory | -| `git-post-commit.sh` | PostToolUse | Post-commit hygiene | -| `git-post-push.sh` | PostToolUse | Post-push hygiene | -| `gh-pr-auto-copilot-review.sh` | PostToolUse | Auto-request Copilot review on PR | -| `stale-branches.sh` | SessionStart | Flags stale branches | -| `hud-reads.sh` | PreToolUse | Emits HUD read events | -| `hud-session.sh` | SessionStart | Emits HUD session events | Hooks communicate via exit codes: **0** = allow, **2** = block. See [hooks/README.md](hooks/README.md) for full documentation, customization, and the `experiments/` directory for in-progress prototypes. @@ -467,105 +428,6 @@ srt claude . --- -## Sandcastle: autonomous agents in CI - -> `shft` runs the loop on your machine. **Sandcastle** runs it in GitHub Actions — label-driven, event-triggered, no local machine required. - -Sandcastle is the CI-triggered evolution of the `shft` bash loop. Instead of a local process polling your backlog, GitHub Actions workflows watch for labels and run Claude Code agents against your repo — in a sandbox, with structured output validated against schemas. - -### The hub model - -The engine is **not vendored** into consumers. It lives in one place — [`arndvs/sandcastle-hub`](https://github.com/arndvs/sandcastle-hub) — and consumers reference it remotely: - -```yaml -uses: arndvs/sandcastle-hub/actions/agent-run@main -``` - -A consumer repo keeps only three things: - -- **`sandcastle.config.json`** — model, base branch, prompt dir, coding standards, excluded paths -- **`.sandcastle/hub-version.json`** — a SHA-lock pinning the hub ref (drift detection opens a review PR when the hub advances) -- **Thin workflow stubs** — `agent-*.yml` files that call the hub's reusable workflows - -This replaces an older model where ~101 engine files were vendored into every consumer, causing drift and weekly re-vendor churn. - -### The label state machine - -Sandcastle is label-driven. Applying a label to an issue or PR triggers a workflow: - -```mermaid -graph LR - H["Human applies Sandcastle"] --> R["agent:review"] - R --> I["agent:implement"] - I --> P["agent:pr-open"] - P --> V{"Human verdict"} - V -->|"agent:merge"| M["merge-pr"] - V -->|"agent:fix"| F["fix-pr-feedback"] - V -->|"agent:update-branch"| U["update-branch"] - F --> P - U --> P -``` - -Two human gates by design: the **start gate** (applying `Sandcastle`) and the **verdict gate** (choosing merge/fix/update). There is no auto-merge. - -> **Security note:** chaining labels requires `AGENT_PAT` (not `GITHUB_TOKEN`). If the wrong token is used, the chain fails closed into `agent:blocked`. - -### Workflow templates - -`shft/templates/workflows/` mirrors the hub's canonical templates (synced via `bin/sync-hub-templates.sh`). The 12 `agent-*.yml` stubs cover the lifecycle: - -| Workflow | Trigger | What it does | -| -------- | ------- | ------------ | -| `agent-review-issue` | label `agent:review` | Review an issue before implementation | -| `agent-plan-issue` | label `agent:plan` | Break an issue into a plan | -| `agent-implement-issue` | label `agent:implement` | Implement an issue, open a draft PR | -| `agent-implement-prd` | label `agent:implement-prd` | Implement a PRD's next sub-issue, chain onward | -| `agent-fix-pr-feedback` | label `agent:fix` | Address review feedback on a PR | -| `agent-merge-pr` | label `agent:merge` | Squash-merge a PR, close linked issues | -| `agent-update-branch` | label `agent:update-branch` | Rebase a PR head onto its base | -| `agent-architecture-review` | cron (weekdays) | Weekly architecture review | -| `agent-repo-hygiene` | cron (weekdays) | Nightly hygiene backlog | -| `agent-code-health` | cron (weekly) | Weekly code-health audit (7 lenses) | -| `agent-keep-tests-tight` | cron (weekdays) | Prune/strengthen weak tests | -| `agent-check-stale-prs` | cron (weekdays) | Flag stale PRs | -| `agent-promote-queued` | label `agent:promote` | Promote queued issues when blockers close | - -Plus `labels-sync.yml` (weekly label reconciliation) and `sandcastle-drift.yml` (hub-version drift detection). - -### Install - -```bash -bash ~/dotfiles/bin/init-sandcastle.sh # install the Sandcastle consumer contract -``` - -See [shft/README.md](shft/README.md) and the hub's [docs](https://github.com/arndvs/sandcastle-hub) for the full platform spec. - ---- - -## Copilot Review Bridge - -> An automated pipeline that receives **GitHub Copilot review events** and dispatches them to the `shft afk` agent loop for resolution. - -The bridge closes the loop between Copilot's PR reviews and the autonomous agent: - -``` -GitHub webhook → FastAPI (bridge-webhook.service) - ↓ SQLite queue - Worker (bridge-worker@1.service) - ↓ - shft afk 1 (Claude Code in srt sandbox) -``` - -- **`bridge/`** — Python package: FastAPI receiver, SQLite queue, GitHub token minting, per-PR workspace lifecycle, HUD event emission -- **`systemd/`** — `bridge-webhook.service` + `bridge-worker@.service` units -- **`bin/bridge-install.sh`** — idempotent installer -- **`ctrl bridge`** — start/stop/restart/status/queue/logs/replay/validate/run -- **Secrets:** `WEBHOOK_SECRET` (`.env.bridge`), GitHub App creds (`.env.secrets`) - -See [bridge/README.md](bridge/README.md) for the full reference. - ---- - ## CLI — `ctrl` & `shft` After bootstrap, two commands are available system-wide. `ctrl` manages your environment, `shft` manages your work queue. Both are symlinked to `~/.local/bin/` by bootstrap. See [bin/README.md](bin/README.md) for the full script inventory. @@ -613,20 +475,6 @@ shft help # autonomous execution commands | `ctrl uninstall` | Safely remove all ctrl+shft symlinks + shell integration | | `ctrl verify-token` | Test-mint a GitHub App token | -#### ctrl bridge (Copilot Review Bridge) - -| Command | What it does | -| --------------------- | --------------------------------------------------- | -| `ctrl bridge start` | Start the webhook receiver + worker services | -| `ctrl bridge stop` | Stop the bridge services | -| `ctrl bridge restart` | Stop + start | -| `ctrl bridge status` | Show service state | -| `ctrl bridge queue` | Show queued review jobs | -| `ctrl bridge logs` | Show bridge logs | -| `ctrl bridge replay` | Replay a failed job | -| `ctrl bridge validate`| Validate bridge configuration | -| `ctrl bridge run` | Run a single review job directly | - #### ctrl session analysis (requires [sheal](https://github.com/liwala/sheal)) | Command | What it does | @@ -645,7 +493,6 @@ shft help # autonomous execution commands | ----------------- | ----------------------------------------------- | | `shft run` | HITL — run once while you watch | | `shft afk [n]` | AFK — autonomous loop (default 5 iterations) | -| `shft afk --worktree [n]` | AFK in an isolated git worktree/branch | | `shft status` | Is a loop running? How many issues open? | | `shft stop` | Stop a running AFK loop after current iteration | | `shft log [-f]` | Show shft log (add `-f` to follow) | @@ -656,9 +503,6 @@ shft help # autonomous execution commands | `shft plan` | View `working/plan.md` | | `shft plan edit` | Open plan in `$EDITOR` | | `shft plan clear` | Clear the plan file | -| `shft worktrees` | List or remove AFK-created git worktrees | -| `shft engine on\|off\|status` | Switch between bash and TypeScript engines | -| `shft proxy on\|off\|start\|stop\|status` | Toggle routing and manage the LiteLLM/Copilot proxy daemon | | `shft validate` | Run AFK pre-flight checks | | `shft mint` | Test-mint a GitHub App token | | `shft prompt` | Show `shft/prompt.md` | @@ -689,27 +533,14 @@ shft help # autonomous execution commands │ ├── sentry.instructions.md │ ├── google-docs.instructions.md │ ├── css.instructions.md -│ ├── better-auth.instructions.md -│ ├── expo-mobile.instructions.md -│ ├── hud.instructions.md -│ ├── sandcastle-pipeline.instructions.md │ ├── handoff.instructions.md ← cross-conversation persistence protocol │ └── _local/ ← GITIGNORED — your private instructions ├── commands/ -│ ├── address-review.md /address-review → review-pr-copilot skill -│ ├── audit.md /audit → codebase-audit skill -│ ├── check.md /check → session-close skill -│ ├── cmd.md /cmd → cmd business OS -│ ├── commit.md /commit → atomic-commits skill -│ ├── compliance-audit.md /compliance-audit → compliance-audit skill +│ ├── audit.md /audit → codebase-audit skill │ ├── document.md /document → document skill │ ├── explore.md /explore → explore skill -│ ├── mobile-fresh.md /mobile-fresh → mobile-dev skill │ ├── plan.md /plan → architect skill -│ ├── preflight.md /preflight → pr-preflight skill │ ├── review.md /review → code-review skill -│ ├── ship.md /ship → atomic-commits skill -│ ├── stress-test.md /stress-test → stress-test skill │ ├── test.md /test → tdd skill │ └── work.md /work → do-work skill ├── agents/ @@ -719,44 +550,23 @@ shft help # autonomous execution commands │ ├── researcher.md subagent: deep codebase exploration (sonnet) │ ├── researcher-opus.md subagent: complex architecture analysis (opus) │ ├── researcher-haiku.md subagent: fast bulk scanning (haiku) -│ ├── security-auditor.md subagent: OWASP, secrets, config (sonnet) -│ └── gemini-safety.json safety config for Gemini models -├── rules/ ← 14 path-gated convention files +│ └── security-auditor.md subagent: OWASP, secrets, config (sonnet) +├── rules/ │ ├── test-conventions.md scoped to tests and service code │ ├── migration-safety.md scoped to **/migrations/** │ ├── env-security.md scoped to **/.env*, **/secrets/** -│ ├── terminal-workarounds.md scoped to terminal sessions -│ ├── git-conventions.md scoped to source files -│ ├── typescript-conventions.md scoped to **/*.{ts,tsx} -│ ├── javascript-modern.md scoped to JS/TS files -│ ├── frontend-conventions.md scoped to frontend files -│ ├── dark-mode.md scoped to **/*.{tsx,jsx,css,scss} -│ ├── tailwind-shadcn.md scoped to **/*.{tsx,jsx} -│ ├── framer-motion.md scoped to **/*.{tsx,jsx} -│ ├── server-vs-client-components.md scoped to **/app/**/*.{tsx,jsx} -│ ├── resource-management.md scoped to JS/TS files -│ └── tooling-conventions.md scoped to bin/, hooks/, shell scripts -├── hooks/ ← 20+ lifecycle guards +│ └── terminal-workarounds.md scoped to terminal sessions +├── hooks/ │ ├── README.md hook documentation │ ├── settings-hooks.json hook configuration for Claude Code │ ├── compaction-guard.sh blocks auto-compaction, enforces handoff │ ├── context-warning.sh graduated warnings at 40/70% context -│ ├── format-check.sh auto-formats modified files on stop +│ ├── format-check.sh auto-formats modified files on stop │ ├── migration-guard.sh blocks unsafe migration commands │ ├── secret-guard.sh blocks credential exposure in agent output │ ├── typecheck.sh runs tsc --noEmit before stop -│ ├── git-workflow-gate.sh enforces branch/protected-path rules -│ ├── plan-quality-gate.sh validates plan files -│ ├── test-gate.sh blocks commits when tests fail -│ ├── feedback-memory-gate.py persists feedback to memory -│ ├── git-post-commit.sh post-commit hygiene -│ ├── git-post-push.sh post-push hygiene -│ ├── gh-pr-auto-copilot-review.sh auto-request Copilot review on PR -│ ├── stale-branches.sh flags stale branches -│ ├── hud-reads.sh emits HUD read events -│ ├── hud-session.sh emits HUD session events │ └── experiments/ experimental hook prototypes -├── skills/ ← 56 public skills + _local/ +├── skills/ │ ├── do-work/ │ ├── grill-me/ │ ├── write-a-prd/ @@ -775,7 +585,6 @@ shft help # autonomous execution commands │ ├── compliance-audit/ │ ├── stress-test/ │ ├── sanity-best-practices/ -│ ├── skills-lock.json ← SHA-256 provenance lock │ └── _local/ ← GITIGNORED — your private skills ├── clients/ │ ├── README.md per-client instruction isolation guide @@ -785,13 +594,7 @@ shft help # autonomous execution commands │ ├── afk.sh AFK autonomous loop │ ├── once.sh HITL single-run │ ├── _build_prompt.sh prompt assembly for shft runs -│ ├── prompt.md shared agent prompt -│ └── templates/ ← Sandcastle consumer contract (mirror of hub) -│ ├── workflows/ 16 workflow templates -│ ├── prompts/ prompt templates -│ ├── extractions/ structured-output prompts -│ ├── scripts/ helper scripts -│ └── run.ts TS dispatcher +│ └── prompt.md shared agent prompt ├── bin/ │ ├── ctrl CLI entry point — infrastructure management │ ├── _lib.sh shared shell library @@ -803,63 +606,16 @@ shft help # autonomous execution commands │ ├── run-with-secrets.sh process-scoped secret injection │ ├── detect-context.sh exports ACTIVE_CONTEXTS │ ├── detect-client.sh per-client context detection -│ ├── detect-cmd.sh cmd venture detection │ ├── new-client.sh scaffold new client instruction set │ ├── migrate.sh safe migration from manual setup -│ ├── migrate-bashrc.sh bashrc migration helper │ ├── uninstall.sh clean removal of all symlinks + shell integration │ ├── validate-env.sh env + hardening validation │ ├── validate-symlinks.sh verify bootstrap symlinks -│ ├── validate-remotes.sh verify remote topology -│ ├── validate-git-hooks.sh verify global git hooks -│ ├── validate-skills.sh validate skill structure -│ ├── validate-skills-lock.sh verify skills-lock provenance -│ ├── generate-skills-lock.sh regenerate skills-lock.json │ ├── mint_github_app_token.py AFK token minting │ ├── verify-github-app-token.sh safe token verification -│ ├── sandcastle-wire-secrets.sh wire Sandcastle secrets -│ ├── init-sandcastle.sh install Sandcastle consumer contract -│ ├── update-sandcastle.sh update Sandcastle install -│ ├── preflight-sandcastle.sh Sandcastle pre-flight checks -│ ├── sync-hub-templates.sh mirror hub templates to producer -│ ├── smoke-sandcastle-*.sh Sandcastle smoke tests -│ ├── preflight-public-promotion.sh validate promotion range -│ ├── validate-public-promotion.sh validate public tree -│ ├── validate-main-pr-source.sh validate PR source branch -│ ├── verify-pr-base.sh resolve PR base authoritatively -│ ├── apply-branch-ruleset.sh apply branch protection ruleset -│ ├── drift-detect.sh detect instruction drift -│ ├── artifact-lifecycle-audit.sh audit artifact lifecycle -│ ├── afk-throughput.sh AFK throughput analysis -│ ├── gh-assignee-preflight.sh assignee preflight -│ ├── pipeline-label.sh pipeline label helper -│ ├── pipeline-label-data.sh pipeline label data -│ ├── bridge-install.sh install the Copilot Review Bridge -│ ├── ctrl-worktree.sh worktree management -│ ├── hud-daemon.js HUD HTTP + WebSocket server -│ ├── start-hud.sh daemon lifecycle (start/stop/status/restart) -│ ├── write-hud-state.sh non-blocking compliance event emitter -│ ├── ctrlshft-claude event-producing Claude wrapper -│ ├── com.ctrlshft.hud.plist macOS HUD launch agent -│ ├── ctrlshft-hud.service systemd HUD unit -│ └── perf/ performance analysis scripts -├── bridge/ ← Copilot Review Bridge (Python) -│ ├── README.md -│ ├── webhook.py FastAPI webhook receiver -│ ├── worker.py queue worker -│ ├── db.py SQLite queue -│ ├── github.py GitHub token minting -│ ├── workspace.py per-PR workspace lifecycle -│ ├── hud.py HUD event emission -│ └── requirements.txt -├── git-hooks/ ← global git hook dispatchers -│ ├── pre-commit -│ └── generic-hook -├── lib/ -│ └── plan_files_lib.py shared plan-file helper -├── systemd/ -│ ├── bridge-webhook.service -│ └── bridge-worker@.service +│ ├── hud-daemon.js HUD HTTP server +│ ├── start-hud.sh daemon lifecycle (start/stop/status/restart) +│ └── write-hud-state.sh non-blocking compliance event emitter ├── hud/ ← HUD UI │ ├── README.md HUD architecture + API reference │ └── index.html @@ -868,26 +624,14 @@ shft help # autonomous execution commands │ ├── CNAME │ └── assets/ ├── docs/ -│ ├── ARCHITECTURE.md internal system map -│ ├── ARTIFACT-LIFECYCLE.md artifact path policy -│ ├── adr/ architecture decision records (8) +│ ├── adr/ architecture decision records │ ├── audits/ dated assessment artifacts │ ├── reference/ durable reference material │ └── research/ durable synthesis and research -├── plans/ ← formal PRDs + issue breakdowns -├── templates/ -│ └── lifecycle/ lifecycle templates -├── test/ ← shell + Python test suites -├── .sandcastle/ ← Sandcastle consumer config -│ ├── hub-version.json SHA-lock pinning the hub engine -│ └── prompts/ project prompt overrides -├── .github/ ← CI workflows, rulesets, issue templates -├── .ctrlshft ← per-repo hook config ├── working/ ← active/refs/research tracked; runtime/tmp/logs ignored └── secrets/ ← GITIGNORED ├── .env.agent ├── .env.secrets - ├── .env.bridge └── .venv/ ``` diff --git a/plans/drift-remediation-plan.md b/plans/drift-remediation-plan.md deleted file mode 100644 index 2062ad23..00000000 --- a/plans/drift-remediation-plan.md +++ /dev/null @@ -1,156 +0,0 @@ -# Architecture Plan — Drift Remediation, Complete Sync & Allowlist Modernization - -**Status:** Proposed — awaiting approval -**Date:** 2026-08-20 -**Derived from:** `bin/check-public-drift.sh` output, `seams/public-pullback.md`, `seams/vendor-sandcastle.md`, `WORKSPACE_INVARIANTS.md` -**Executed by:** AFK agents (shft) for AFK slices; HITL for the reset slices - ---- - -## 1. Context - -The repo sync work is complete (all repos clean + pushed, hub↔producer templates -14/14 in sync, PR #335 merged and local main fast-forwarded). What remains is -**drift between public and dotfiles on the allowlisted product paths** plus two -**dead engine trees left behind by the hub migration**. - -Current drift (from `bin/check-public-drift.sh`): - -| Path | Nature | Direction | -| --- | --- | --- | -| `shft/engine/**` | dotfiles still tracks the old engine; public DELETED it (hub owns it now) | dotfiles has leftover — **cleanup** | -| `.sandcastle/engine/**` | same legacy vendored engine in dotfiles | **cleanup** | -| `.sandcastle/scripts/**` | same legacy vendored scripts in dotfiles | **cleanup** | -| `shft/templates/scripts/check-workflow-enabled.sh` | public has new hub-model script | **pullback** to dotfiles | -| `bin/pipeline-label-data.sh` | public has +11 lines (label map) | **pullback** | -| `test/hooks/` | public has 2 new tests + extended helpers | **pullback** | - -The drift allowlist itself is stale: it still lists `shft/engine`, -`.sandcastle/engine`, `.sandcastle/scripts` as *shared product paths*, but the -hub migration removed them from the public producer — so the drift check -reports phantom missing files forever. Without fixing both the leftover trees -AND the allowlist, `check-public-drift.sh` stays red on dead paths. - ---- - -## 2. Design Decisions - -| Decision | Choice | Rationale | -| --- | --- | --- | -| Engine leftover (`shft/engine`) | **Delete from dotfiles tracking** (it was a mirrored copy; hub owns the engine) | Public already deleted it; dotfiles must match to make drift green | -| `.sandcastle/engine` + `.sandcastle/scripts` | **Delete from dotfiles** (legacy vendored install) | Same — hub owns; consumers hold stubs + hub-version, not the engine | -| New hub scripts + label map + hook tests | **Pull back** (Direction A: `git checkout public/main -- `) | These are real product increment test/harness bits produced in public | -| Drift allowlist | **Remove** `shft/engine`, `.sandcastle/engine`, `.sandcastle/scripts` (paths gone from product) | Stale allowlist entries create permanent phantom drift reporting | -| Add to allowlist? | Keep `shft/templates/scripts`, `test/hooks`, `bin/pipeline-label-data.sh` | They're still shared product paths with real drift | -| `check-workflow-enabled.sh` | Pull back to dotfiles `shft/templates/scripts/` | The hub stub contract requires it in the producer install | -| `test-helpers.sh` | Pull back public's extended version (2 new JSON builders) | Required by the 2 new hook test files | -| Cleanup safety | Keep `.sandcastle/CODING_STANDARDS.md`, `prompts/`, `templates/`, `labels.json`, `run.ts` — those are consumer install artifacts, NOT the engine | Differentiate "engine + scripts" (delete) from "consumer artifacts" (keep) | - ---- - -## 3. Vertical Slices - -> Each slice is independently shippable and wires end-to-end. - ---- - -### ☐ SYNC-1: Clean up the dead engine paths (dotfiles) -Type: HITL (returns to working tree) -Size: M -Blocked by: none -Steps: -1. `git -C testdotfiles rm -r shft/engine` — removes the tracked mirrored engine. -2. `git -C dotfiles rm -r .sandcastle/engine .sandcastle/scripts` — legacy vendered install. - (Do NOT touch `.sandcastle/{CODING_STANDARDS.md,hooks,prompts,templates,labels.json,run.ts}` — consumer artifacts stay.) -3. Run `bin/check-public-drift.sh` — those three entries should now be gone (paths no longer exist on either side). -Acceptance: `git ls-files` for `shft/engine`, `.sandcastle/engine`, `.sandcastle/scripts` empty; drift list no longer names them. -Feedback: `bash bin/check-public-drift.sh` post-delete → no those entries. - ---- - -### ☐ SYNC-2: Pullback the real product drift — 4 paths -Type: HITL -Size: M -Blocked by: none (independent of SYNC-1) -Steps: -1. On a scratch branch from `dev`: `git switch -c san/sync/pull-`. -2. Pullback the three real product areas per `seams/public-pullback.md`: - - `git checkout public/main -- shft/templates/scripts/check-workflow-enabled.sh` - - `git checkout public/main -- bin/pipeline-label-data.sh` - - `git checkout public/main -- test/hooks/` (pulls the 3 files: 2 new tests + merged helpers) -3. Run the hook suite: `bash test/hooks/run-hook-tests.sh` (runner auto-discovers `test-*.sh`). -4. Run `bash test/sandcastle-smoke-coverage.sh` + `bash test/prime-smoke.sh` (regression). -5. Commit `sync(pullback): hook tests + pipeline-label-data + check-workflow-enabled`. -Acceptance: 3 new files present in dotfiles; hook runner green; the two smoke suites green. -Feedback: `bash test/hooks/run-hook-tests.sh`; `bash test/sandcastle-smoke-coverage.sh`; `bash test/prime-smoke.sh`. - ---- - -### ☐ SYNC-3: Update the drift allowlist -Type: AFK -Size: S -Blocked by: SYNC-1 (list edit based on deletion outcome) -Steps: -1. In `bin/check-public-drift.sh` (PRODUCT_PATHS array) remove the three - engine paths that the hub migration deleted from the product: - - `"shft/engine"` - - `".sandcastle/engine"` - - `".sandcastle/scripts"` -2. Keep the still-shared product paths: - `bridge`, `shft/templates/scripts`, `bin/pipeline-label-data.sh`, `test/hooks`. -3. Re-run `bin/check-public-drift.sh` — engine paths no longer flagged. -Acceptance: `check-public-drift.sh` exits 0 (no drift); no phantom engine-path -entries remain in the product allowlist. -Feedback: `bash bin/check-public-drift.sh` (expect "No drift"). - ---- - -### ☐ SYNC-4: Verify end-to-end template & hub parity -Type: AFK -Size: S -Blocked by: SYNC-1..3 -Steps: -1. `bash bin/sync-hub-templates.sh --check` — 14/14 in sync. -2. `bash test/prime-smoke.sh` — the prime command still works on the cleansed tree. -3. `bash test/init-sandcastle-proxy-canary.sh` — init still renders stubs (after engine removal). -Acceptance: all 3 gates green on a tree with no vendored engine. - ---- - -## 4. Key Insights - -Critical Principle: The drift allowlist must reflect the model, not the history. -Why it matters: paths removed from the product (engine → hub) leave allowlisted → permanent phantom drift red on every run. -How to apply: after deleting a shared path from one side, remove it from the allowlist, not just the filesystem. -Risk if ignored: drift-check permanently reports dead paths; no one trusts the green signal. - -Critical Principle: Distinguish "engine was deleted" from "consumer artifacts were deleted". -Why it matters: `shft/engine` + `.sandcastle/engine`/`scripts` are engine installs the hub now owns; `CODING_STANDARDS.md`, `prompts/`, `labels.json`, `run.ts` are consumer artifacts that stay. -How to apply: delete only the engine dirs matched by the migration; keep the consumer install shell. -Risk if ignored: removing consumer artifacts breaks init-sandcastle. - ---- - -## 5. Dependency Graph - -``` -SYNC-1 (delete dead engine dirs) ──► SYNC-3 (fix allowlist) -SYNC-2 (pullback real product) ──► SYNC-4 (verify end-to-end) -``` - -- SYNC-1, SYNC-2 independent (both touch dotfiles; safe parallel on a scratch branch) -- SYNC-1 → SYNC-3 (allowlist reflects deletion) -- SYNC-1+2+3 → SYNC-4 (final gate) -- Order: [SYNC-1 ∥ SYNC-2] → SYNC-3 → SYNC-4 - ---- - -## 6. QA Plan - -The final QA slice (HITL): -1. `bash bin/check-public-drift.sh` returns "No drift" (both the deleted paths gone AND the real pullback applied). -2. `shft/engine`, `.sandcastle/engine`/`.sandcastle/scripts` absent from `git ls-files` in both repos. -3. `bash test/hooks/run-hook-tests.sh`, `bash test/sandcastle-smoke-coverage.sh`, `bash test/prime-smoke.sh`, `bash test/init-sandcastle-proxy-canary.sh` all green. -4. `bash bin/sync-hub-templates.sh --check` reports 14/14 in sync. -5. Public and dotfiles diff the 5 pulled-back paths 1:1. -6. No `shft/engine`, `.sandcastle/engine`, `.sandcastle/scripts` anywhere in `git ls-files` of either repo. \ No newline at end of file diff --git a/plans/hub-model-cleanup-plan.md b/plans/hub-model-cleanup-plan.md deleted file mode 100644 index 42adb975..00000000 --- a/plans/hub-model-cleanup-plan.md +++ /dev/null @@ -1,267 +0,0 @@ -# Architecture Plan — Hub-Model Completion & Cross-Repo Reconciliation - -**Status:** Proposed — awaiting approval -**Date:** 2026-08-20 -**Derived from:** `docs/sandcastle-hub-architecture.md`, `docs/adr/ADR-008-sandcastle-hub.md`, `sandcastle-hub/docs/adr/ADR-001-hub-single-source.md` -**Executed by:** AFK agents (shft) for AFK slices; human for HITL slices - ---- - -## 1. Context - -The hub-model migration is functionally complete: the producer (ctrlshft-public) -removed its vendored engine, and consumers reference `arndvs/sandcastle-hub` -remotely. This session fixed the producer's stale smoke-coverage test, rebuilt -its `shft/templates/workflows/*` as hub stubs, deleted its stale composite -actions and `sandcastle-ci.yml`, and synced everything across dotfiles + public -+ copilot. - -But the migration exposed **four unresolved structural gaps** — the same -stale-template pattern the producer just fixed, still present in the hub itself, -plus a cross-repo divergence in shared tooling: - -1. **Hub templates are still old-model.** The hub's `templates/workflows/` - contains 11/12 agent workflows + `sandcastle-ci.yml` + `labels-sync.yml` - that encode the vendored engine model (`.sandcastle/engine`, - `pnpm --ignore-workspace exec tsx ../run.ts`, local composite actions) — - the exact pattern ADR-001 says is dead. These are the canonical templates - consumers receive via `init-sandcastle.sh` (via the producer's - `SANDBOX_PRODUCER` resolution), so new installs would produce broken - engine-referencing workflows from the hub itself. -2. **Hub | `sandcastle-ci.yml` template is stale.** It validates the vendored - engine (`.sandcastle/engine/**`, `pnpm install --frozen-lockfile`). The hub - owns engine CI in `.github/workflows/engine-ci.yml`; the consumer-side - `sandcastle-ci.yml` template is a dead artifact of the old model. -3. **Cross-repo | `bin/ctrl`, `bin/preflight-sandcastle.sh`, `bin/bootstrap.sh` - diverged between dotfiles and public.** Public has newer SaaS-governance / - worktree-bridge changes (`697435c`, PR #318) and the modern - `working/runtime` paths; dotfiles has the hub-model preflight drift-check - and `_vendor` path fixes (commits `a8178d7`, `3c29751`). Neither repo has - both sets of changes. This is a genuine 3-way merge that will keep biting - every `ctrl sync` until resolved. -4. **Hub | no structural QA gate.** The hub has no test/ directory at all. The - producer's `test/sandcastle-smoke-coverage.sh` validates consumer templates; - the hub needs an equivalent gate that validates *its own* installed - workflows + templates are hub-model-clean, so the drift pattern in (1) - can never regress. - ---- - -## 2. Design Decisions - -| Decision | Choice | Rationale | -| --- | --- | --- | -| Hub template home | **`templates/workflows/` is the canonical stub source** — rebuild as thin hub stubs | It's the "one place the templates live" per README/ADR-001; consumers + producer copy from it. It must encode the hub stub contract, not the old engine contract. | -| `sandcastle-ci.yml` in hub templates | **Delete** | The hub owns engine CI (`engine-ci.yml`); the dispatch-module-resolution double-check lives there. No consumer needs a local engine-validating CI anymore. | -| Hub `.github/workflows/` | **Keep as-is** (already hub-model) | Self-dogfood workflows (`agent-architecture-review.yml`, `agent-repo-hygiene.yml`) are correct thin stubs. | -| Producer templates sync direction | **Producer (`shft/templates/`) pulls FROM hub (`templates/`)** | Both must match; hub is the single source of truth for the stub contract. Producer mirrors hub rather than diverging. | -| `bin/ctrl` / `preflight` / `bootstrap` 3-way merge | **Feature-branch merge with explicit resolution** — create `work/sync-tooling` in *public*, merge dotfiles' sandcastle changes INTO public's battery of newer changes, then pull merged result back to dotfiles | Public is master per REPO_TOPOLOGY. Merge dotfiles' sandbox-hub-model bits into public's newer bridge/worktree/runtime bits; resolve conflicts once. | -| Hub QA gate | **`test/hub-smoke-coverage.sh`** (mirror of producer's) validating hub templates + installed workflows | Structural drift guard so the stale-template pattern cannot silently return. | -| Sync tooling | **Keep `sync-public-repo.sh` (private → public) but extend it** so it also pulls accepted hub-model changes to dotfiles | Prevent future 3-way drift between the three nodes. | - ---- - -## 3. Vertical Slices - -> Each slice is independently shippable and wires end-to-end. AFK slices run -> fully autonomously; HITL slices need human judgment/access. - ---- - -### ☐ S1: Hub — rebuild template workflows as hub-model stubs -Type: HITL (touches canonical template source; requires review) -Size: M -Blocked by: none -Steps: -1. For each of the 11 stale `templates/workflows/agent-*.yml` on the hub, replace - the vendored body (.sandcastle/engine, pnpm exec tsx ../run.ts, local - composite actions, `{{DEFAULT_BRANCH}}` substitutes) with the thin stub - contract: - - agent-run style (7): `uses: arndvs/sandcastle-hub/actions/agent-run@main` - with `workflow: `, `ref: main`, `token:`, optional `extra-args`. - - reusable-workflow style (6): `jobs: : uses: - arndvs/sandcastle-hub/.github/workflows/reusable-*.yml@main` with `secrets: - inherit`. - - Match the exact stub bodies the producer now uses (see - `ctrlshft-public/.github/workflows/` for the reference contract). -2. Update `agent-promote-queued.yml` (the one already-hub-model template) if it - needs the `{{DEFAULT_BRANCH}}` templating substitution marker (it's the one - template that was already converted — verify against producer's). -3. Delete `templates/workflows/sandcastle-ci.yml` (dead vendored-engine CI). -4. Verify `labels-sync.yml` and `sandcastle-drift.yml` in templates match the - hub-model installed versions (they reference the hub API, not `.sandcastle/`). -5. Open PR to hub `main`, request review. - -Acceptance criteria: -- `grep -L "\.sandcastle/engine\|pnpm --ignore-workspace exec tsx" templates/workflows/agent-*.yml` - returns nothing (all 12 are stubs). -- `templates/workflows/` has no more `sandcastle-ci.yml`. -- The hub templates diff 1:1 with the producer's `shft/templates/workflows/` - for the 12 agent workflows. - -Feedback loops: `bash -n` on each YAML; `grep -c agent-run templates/workflows/agent-*.yml`; -CI on the PR (hub `engine-ci.yml` still passes). - ---- - -### ☐ S2: Add hub QA gate — test/hub-smoke-coverage.sh -Type: AFK -Size: M -Blocked: S1 (gate validates S1's template state) - -Steps: -1. Create `test/hub-smoke-coverage.sh` mirroring the producer's clean - `test/sandcastle-smoke-coverage.sh` shape (dotfiles version — the 227-line - template-coverage gate), adapted: - - `agent_workflow_templates` glob → hub `templates/workflows/agent-*.yml`. - - Assert every template contains a `sandcastle-hub` reference - (`agent-run` composite or `reusable-*.yml` call). - - Assert every template avoids old-model tokens - (`.sandcastle/engine`, `pnpm --ignore-workspace exec tsx`, `uses: - ./.github/actions/`). - - Assert the report-aggregator inventory (if hub has a `SANDCASTLE_WORKFLOWS` - list) tracks every template. -2. Assert every `reusable-*.yml` in `.github/workflows/` referenced by a - consumer stub exists. -3. Assert `engine-ci.yml` exists and covers the engine. -4. Wire the gate into a `hub-qa` job (new workflow — `qa-hub-alignment.yml` or - add a job to existing `engine-ci.yml` buff). - -Acceptance: -- `bash test/hub-smoke-coverage.sh` exits 0 with ≥ N passes, 0 failures. -- A commit that reintroduces `.sandcastle/engine` into a template fails the gate. - -Feedback: `bash test/hub-smoke-coverage.sh`. - ---- - -### S3: Sync producer templates ↔ hub templates (mirror) -Type: HITL (touches producer + public history) -Size: S -Blocked: S1 (hub templates must be correct first) - -Steps: -1. After S1 the hub is the canonical stub source. Copy hub - `templates/workflows/` → producer `shft/templates/workflows/` (the 12 agent + - labels-sync + drift; keep producer's `check-attribution`/ - `require-regression-guard` which are producer-owned). -2. Re-run producer `test/sandcastle-smoke-coverage.sh` (35/35) + - `test/init-sandcastle-proxy-canary.sh` (26/26). -3. Verify `init-sandcastle.sh` (SANDBOX_PRODUCER resolution) renders the stubs - correctly to `.github/workflows/`. - -Acceptance: template dirs diff 1:1 for the shared 16 files; all producer -sandcastle suites still green. - -Feedback loops: `bash test/sandcastle-smoke-coverage.sh`; -`bash test/init-sandcastle-proxy-canary.sh`. - ---- - -### S4: Resolve ctrl / preflight / bootstrap 3-way divergence -Type: HITL (merge conflicts need judgment) -Size: L -Blocked: none - -Steps: -1. On `dev`, create merge-prep branch `ai/fix/sync-tooling` in the public repo. -2. Merge dotfiles' branches (private remote `private/dev`) into it, resolving: - - `bin/ctrl`: keep public's newer `worktree|wt` command + bridge - single-worker lock + `running/runtime` paths; add dotfiles' hub-model - `update-sandcastle` deprecation routing. Both merge clean. - - `bin/preflight-sandcastle.sh`: take dotfiles' hub-SHA-drift check (replace - `_check_engine` vendored-engine check) but keep public's permission-block - checker (accept top-level + job-level). - - `bootstrap.sh`: take dotfiles' `_local` skills aggregation + skills-lock - validation; keep public's `working/runtime` active-client path. -3. Push branch, open PR to `dev`, human merges. -4. Then pull `public/dev` into `dotfiles` (private) `dev`; - resolve dotfiles-local overlay additions (secrets dir, machine-local). - -Acceptance: `diff` between public and dotfiles `bin/ctrl` / `preflight`/ -`bootstrap` is empty (except intentionally-private overlay lines). -Feedback: `git diff -- bin/ | grep -v '^index'` empty. - ---- - -### S5: Extend sync tooling for bidirectional hub sync -Type: HITL -Size: S -Blocked: S4 - -Steps: -1. Add `sync-hub-repo.sh` (or extend `sync-public-repo.sh`) that copies - `templates/workflows/` from the hub → producer `shft/templates/workflows/` - for the 16 shared names (agent-*, labels-sync, sandcastle-drift), skipping - producer-owned `check-attribution`/`require-regression-guard`. -2. Add a parity check: `diff hub/templates/workflows vs producer` → exit - non-zero on drift. Wire into CI or the smoke-coverage gate. - -Acceptance: a one-command sync aligns hub ↔ producer templates; -a drift-detection script reports misalignment. - -Feedback: run the parity check after a phantom hub template change. - ---- - -## 4. Key Insights - - Critical Principle: The hub is the single template contract — templates that - reference the vendored engine are a lie. - Why it matters: hub templates flow to every consumer install via - init-sandcastle; a stale template ships broken workflows to all consumers. - How to apply: mirror hub templates 1:1 to producer, and add gates on both - The hub's and producer's CI that fail on old-model tokens. - Risk if ignored: next `init-sandcastle` round-trip re-introduces the - vendored engine pattern to every consumer. - - Critical Principle: Cross-repo ctrl/pre-flight/bootstrap drift accumulates - silently. - Why it matters: each repo holds a partial copy; `ctrl sync` and the CLI - behave differently depending on checkout. - How to apply: 3-way merge once — public-first, dotfiles pull-back — then - extend sync-public-repo.sh so shared files cannot diverge again. - Risk if ignored: repeated failed `ctrl sync` sessions and HITL time - chasing diverged branch states. - ---- - -## 5. Dependency Graph - -``` -S1 (hub templates → stubs) ──▶ S2 (hub QA gate) - │ - └──▶ S3 (producer ↔ hub mirror) ──▶ S4 (ctrl/preflight/bootstrap merge) - │ - └──▶ S5 (sync tooling bidir) -``` - -Execution order: S1 → S2 → S3 → S4 → S5. S2 depends solely on S1 (AFK-able). -S4 touches only tooling, independent of S1-S3 — parallel-safe. - -- S1 — hub templates (HITL, review) -- S2 — hub QA gate (AFK, blocked from S1) -- S3 — producer mirror (HITL, blocked from S1) -- S4 — ctrl/pref/bootstrap merge (HITL, **parallel-safe with S1-S3**) -- S5 — sync tooling bidir (HITL, blocked from S4 results) - -S1 → S2, S1 → S3, S4 ∥ S1..S3, S4 → S5. - ---- - -## 6. QA Plan - -The final QA slice (HITL) verifies the whole system after S1-S5 merge: - -1. On a fresh consumer (e.g. a throwaway branch of `claude-code-copilot`), - run `ctrl init-sandcastle --force` and confirm `.github/workflows/agent-*.yml` - are thin stubs referencing `arndvs/sandcastle-hub` — no `.sandcastle/engine`, - no local composite actions. -2. Confirm `templates/workflows` dirs identical between hub and producer. -3. Run producer sandcastle suite: smoke-coverage 35, init 26, report-smoke 15 - all green. -4. Confirm `bin/ctrl update-sandcastle` prints the hub release deprecation - message identically in both checkout-nodes, and - `ctrl worktree`-command works in both. -5. Confirm `sync` tooling parity check reports zero drift after a fresh - hub pull. \ No newline at end of file diff --git a/plans/workspace-prime-plan.md b/plans/workspace-prime-plan.md deleted file mode 100644 index 1642ac09..00000000 --- a/plans/workspace-prime-plan.md +++ /dev/null @@ -1,241 +0,0 @@ -# Architecture Plan — Workspace Prime & Ownership Scaffold - -**Status:** Proposed — awaiting approval -**Date:** 2026-08-20 -**Derived from:** TAC-1 `prime.md` draft, `working/refs/tac/examples/` (`WORKSPACE_INVARIANTS.md`, `public-pullback.md`), `REPO_TOPOLOGY.md`, `docs/sandcastle-hub-architecture.md` -**Executed by:** AFK agents (shft) for AFK slices; human for HITL slices - ---- - -## 1. Context - -This session repeatedly burned context re-discovering multi-root workspace -topology: which repo owns which path, when a change crosses a repo seam, which -secrets/working dirs are private-only, and when a public push needs a guard -token. The guards already exist (`check-public-drift.sh`, -`validate-remotes.sh`, `validate-public-promotion.sh`), and context detection -exists (`detect-context.sh` → `ACTIVE_CONTEXTS`) — but they answer *"what kind of -project is this?"*, not *"which root owns the path I'm about to write, and which -procedure applies?"* - -TAC-1's `prime.md` draft + the two seam/invariant docs solve exactly this: a -**priming step** that, given a task scope, classifies which roots it touches, -reports dirty state + drift, and produces an ownership verdict table before any -code is written. This plan turns the drafts into a working `ctrl prime` command, -per-root `AGENT_PRIME.md` files, seam docs, and a skill wrapper — built on the -existing invariant scaffolding instead of inventing a parallel system. - -## 2. Design Decisions - -| Decision | Choice | Rationale | -|---|---|---| -| Prime executable home | `bin/prime.sh` in **dotfiles**, dispatched as `ctrl prime ""` | `ctrl` is the existing CLI surface; `bin/` holds all the guard helpers it shells out to | -| Verdict output format | **Plain-text table** to stdout (path → owning root → branch 1/2/3 → procedure) | Machine-parseable, agent-readable, terminal-friendly. No JSON ceremony. | -| Exit policy | Exit **non-zero on branch 3** (unclassified path); exit 0 with a verdict for 1/2 | Branch 3 must stop the agent from guessing — that's the core value. | -| Per-root prime docs | **`AGENTS.md`** at each root (not a new file name) | It's the established agent-instruction convention; agents auto-read `AGENTS.md` on entry. | -| Canonical invariants | **`dotfiles/WORKSPACE_INVARIANTS.md`** — single source; other repos reference it | Avoids the drift problem the draft's "CONTESTED" entries point at. | -| Seam docs | `dotfiles/seams/{public-pullback,vendor-sandcastle,anythingelse}.md` | Edge procedures for cross-root operations; referenced, not inlined into invariants. | -| Wiring | **`ctrl prime`** + **skill `workspace-prime`** + hooks into `do-work`/`architect` skills | Command is the engine; skill makes agents *use it* automatically on multi-root work. | -| Conflicts with existing guards | **Reuse, not replace.** `prime` shells to `check-public-drift.sh`, `validate-remotes.sh` | No duplicated logic; invariants doc references the existing binaries. | -| CONESTED entries (`.sandcastle/config`, installed `agent-*.yml`) | **Resolve as "host-managed consumer assets"** — owner is the *producer* via templates; install-time copy is host-managed | This session's S1-S3 made this concrete: templates are product (owner=ctrlshft/hub), installed copies are host-managed. | - ---- - -## 3. Vertical Slices - -> Each slice is independently shippable and wires end-to-end. - ---- - -### ☐ WS1: Publish canonical invariants — `WORKSPACE_INVARIANTS.md` -Type: HITL (taste — resolving the CONTESTED entries) -Size: S -Blocked by: none -Steps: -1. Promote `working/refs/tac/examples/WORKSPACE_INVARIANTS.md` → `dotfiles/WORKSPACE_INVARIANTS.md` - (canonical copy; the /refs copy becomes a stub pointer). -2. Resolve the two CONTESTED rows: - - `.sandcastle/config` + installed `.github/workflows/agent-*.yml` → - **branch 1** (owner = the producer that defines templates). Remove the - "contested" markers. -3. Add one row per guard that already exists (`check-public-drift.sh`, - `validate-remotes.sh`, `validate-public-promotion.sh`, - `preflight-public-promotion.sh`) — the doc becomes the *index* of guards. -4. Update `REPO_TOPOLOGY.md` cross-references to link the invariants doc (canonical copy stays there, not duplicated). -Acceptance: `dotfiles/WORKSPACE_INVARIANTS.md` exists, ≤2 pages, CONTESTED rows resolved, guards indexed. -Feedback loops: does it still fit on one screen? Read once for internal consistency. - ---- - -### ☐ S2: Ship the seam docs — `seams/` -Type: HITL (edge cases need judgment) -Size: S -Blocked by: none (S1 only improves, does not block) -Steps: -1. `dotfiles/seams/public-pullback.md` — from `examples/public-pullback.md`: - Direction A (pullback: `git fetch public main` + checkout paths, exclusions: - `.github/workflows/*.yml` etc.) vs Direction B (promotion: guarded, `--range` - + preflight, never direct `dev→main`). -2. `dotfiles/seams/vendor-sandcastle.md` — hub→consumer copy procedure: - one-way, copy `templates/workflows/` to producer, run parity check with - `bin/sync-hub-templates.sh`. (This session's S1-S3 distilled exactly this.) -3. `dotfiles/seams/ownership-test.md` — the unclassified-path decision: - branch 1 (exists in ctrlshft → owner=public, edit there), branch 2 (private-only - list → dotfiles), branch 3 (neither → STOP ask). -Acceptance: three files under `seams/`, each ≤1 page, no contradiction with -`WORKSPACE_INVARIANTS.md`. - ---- - -### ☐ S3: Build the command — `bin/prime.sh` + `ctrl prime` -Type: HITL (writes the engine; needs human review of the verdict logic) -Size: M -Blocked by: S1 (needs the canonical invariants to reference) -Steps: -1. `bin/prime.sh`: - - args: scope string (or read from `$1`). - - Load `WORKSPACE_INVARIANTS.md` section pointers (root table, private-only, - ownership test) — by cat'ing them, not by including. - - Classify: asked scope → which roots (default: fewest, refuse over-scope). - Cross-seam iff root names appear on >1 line → print seam file to read. - - For each root: `git rev-parse --abbrev-ref HEAD`, `git status --short`. - - Run `bin/check-public-drift.sh` and state "drift = expected state unless - this task touches the drifted path". - - Emit ownership verdict table (path → root → branch 1/2/3 → direct or seam). - - Exit 1 on branch 3 rows, with instruction: "Ask the user which root owns X." -2. Wire into `bin/ctrl` as `prime)` (like `check)` model): - ```sh - prime) - if [[ ! -f "$DOTFILES/bin/prime.sh" ]]; then red "..."; exit 1; fi - green "ctrl prime" - bash "$DOTFILES/bin/prime.sh" "$@" - ;; - ``` -3. Add to the help text: `ctrl prime "" preflight multi-root task`. -Acceptance: `ctrl prime "fix smoke-coverage in ctrlshft"` prints roots + dirty + -drift + verdict table. `ctrl prime "something vague"` exits 1 asking for scope. - -Feedback loops: -`bash -n bin/prime.sh` -`bash bin/prime.sh "test smoke-coverage"` → expects branch in ctrlshft -`bash bin/prime.sh "edit dotfiles/secrets/x"` → branch 2 (private) -`bash bin/prime.sh "touch unknown/path"` → exit 1 (branch 3) - ---- - -### ☐ S4: Per-root `AGENTS.md` -Type: HITL (short docs, taste for the "never edit" list) -Size: S -Blocked by: none -Steps: -1. `ctrlshft-public/AGENTS.md`: primary product source. Owns - `shft/templates/workflows/**`, `test/`, `docs/adr/*`. Resolve via - `WORKSPACE_INVARIANTS` (public copy of the doc considered canonical). Use - `bin/validate-public-promotion.sh` before any public push. -2. `sandcastle-hub/AGENTS.md`: vendor source, engine + templates + labels; - one-way copy out; sees consumer stubs; never edit consumer copies. -3. `claude-code-copilot/AGENTS.md` already exists (consumer runtime) — update - cross-link to invariants; add "this is runtime, not product" note. -4. `dotfiles/AGENTS.md`: overlay owner, machine-local paths (secrets/ - working/), never promote them. -Acceptance: 4 files exist, each ≤1 page, each cross-lists the canonical -invariants doc path + its own guarding command; the ownership rules in each -match the S1 canonical doc. - -Feedback: `ls AGENTS.md` in the 4 roots -Manual spot-check: does each docs file's "never" list agree with -`WORKSPACE_INVARIANTS.md`'s private-only list? - ---- - -### ☐ S5: Skill wrapper — `skills/workspace-prime/SKILL.md` -Type: AFK -Size: S -Blocked by: S3 (the command must exist) -Steps: -1. `skills/workspace-prime/SKILL.md` frontmatter: description matching the trigger - "prime", "multi-root", "which repo owns", "seam". -2. Body: on multi-root or file-write tasks → run `ctrl prime ""`; read the - verdict; if branch 3 → stop and ask; onward with the verdict as context. -3. Register `contexts: [general]` so it's always available; keep it short. -4. Wire hint into the `architect` skill's planning step: "on multi-root, prime - first to set root ownership before exploring." -Acceptance: `prime` is triggered automatically when an agent is asked a -multi-root question; it loads the verdict in one shot. - -Feedback. Test the skill by prompting "which repo owns vs the shaft?" → should -cue prime. - ---- - -### ☐ S6: Feedback gate — add a prime smoke to `test/` -Type: AFK -Size: S -Blocked by: S3, S5 -Steps: -1. `test/prime-smoke.sh`: - - `bash bin/prime.sh "test/sandcastle-smoke-coverage"` → branch 1, exit 0. - - `bash bin/prime.sh "secrets/.env.prod"` → branch 2, exit 0. - - `bash bin/prime.sh "nonsense/unknown"` → branch 3, exit 1. -2. Wire into the repo's test suite (e.g. `test/run-all` or the guards list in S1). -Acceptance: `bash test/prime-smoke.sh` exits 0; a regression that breaks the -verdict table exits non-zero. - -Feedback. `bash test/prime-smoke.sh` - ---- - -## 4. Key Insights - - Critical Principle: Ownership is not a lookup; it is a decision procedure. - Why it matters: the same unclassified path can be both "public work not yet - promoted" and "private content about to leak". - How to apply: make branch 3 exit non-zero, forcing the agent to stop and ask — - never guess which side. - Risk if ignored: accidental leak or wrongful promotion. - - Critical Principle: Reuse existing guards; don't re-implement drift checks. - Why it matters: the guards are the executable truth — `prime` should shell out - to `check-public-drift.sh`/`validate-remotes.sh` rather than re-encode them. - How to apply: the verdict tables reference the script name, not a copy of its - logic. - Risk if ignored: two drift truths drift apart (the very failure mode the - invariants doc warns about). - ---- - -## 5. Dependency Graph - -``` -S1 (invariants) ────────────┐ - ├──? S3 (prime command) ──► S5 (skill) ──► S6 (test) -S2 (seams) ─────────────────┘ ▲ -S4 (AGENTS per root) ───────┘ (parallel safe) - -``` - -- S1 → S3 (command reads the canonical invariants). -- S2 ∥ S1 (seams are edge docs, can be written in parallel). -- S4 ∥ S1..S3 (per-root docs, no dep on the command). -- S3 → S5 (skill wraps the command). -- S5 → S6 (test guards the whole). -- S1, S2, S4 all parallel first; then S3 → S5 → S6. - -Execution order: `[ S1, S2, S4 ] → S3 → S5 → S6`. - ---- - -## 6. QA Plan - -The final QA slice (HITL) verifies the implementation: - -1. `ctrl prime "fix smoke-coverage in ctrlshft"` → prints a verdict table that - names ctrlshft as owner of `shft/templates/*` and states branch 1. -2. `ctrl prime "touch anything in dotfiles/secrets"` → branch 2 (private) exit 0. -3. `ctrl prime "touch unknown-foo"` → branch 3 → exit 1 + "ask the agent". -4. An agent at the workspace root, asked "which repo owns the seam - `shft/engine`?", automatically uses the hidden prime (skill) and reads the - seam doc — no one tells it to. -5. The invariants doc fits on one screen and the "never" lists all agree - with the AGENTS.md docs in each root. -6. `test/prime-smoke.sh` passes; a fake branch-3 entry fails it. \ No newline at end of file From 8e3cc5f652e962bd1838aa37d9b81b53a78ec283 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Fri, 21 Aug 2026 07:56:10 -0700 Subject: [PATCH 07/10] chore(sandcastle): pin hub to a02f85a Update lastPinnedSha to the current hub main SHA to resolve the dev->main promotion conflict (main had pinned c129783, dev had ab343c4). --- README.md | 334 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 295 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 4dd838c6..fb421618 100644 --- a/README.md +++ b/README.md @@ -7,20 +7,49 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Integrity](https://github.com/arndvs/ctrlshft/actions/workflows/integrity.yml/badge.svg)](https://github.com/arndvs/ctrlshft/actions/workflows/integrity.yml) -> Dotfiles for AI coding agents. One repo syncs instructions, skills, secrets, and autonomous loops across every machine. +> An operating system for AI coding agents. One repo syncs instructions, skills, secrets, and autonomous loops across every machine — and runs them in CI. > -> **ctrl** is the structure — instructions, skills, rules, secrets, context. **shft** is the autonomous loop — it picks issues, implements, commits, repeats. +> **ctrl** is the structure — instructions, skills, rules, secrets, context. **shft** is the autonomous loop — it picks issues, implements, commits, repeats. **Sandcastle** is the platform — it runs those loops in GitHub Actions, driven by labels, on a single-source-of-truth engine. Every developer using Claude Code or Copilot hits the same walls. Context degrades mid-task — the agent repeats itself, compaction loses nuance, quality drops. Instructions drift between your laptop and VPS. Secrets leak into agent context. Irrelevant rules load for every project regardless of stack. -ctrl+shft fixes all four. Clone it once, `bootstrap.sh` symlinks your instructions, skills, agents, and rules into `~/.claude/`, and `git pull` updates every machine. `detect-context.sh` loads only the rules that match your current stack. Secrets split into three tiers — config the agent can see, credentials that exist only inside a child process and vanish when it exits (`run-with-secrets.sh`), and AFK iteration tokens (short-lived GitHub App installation tokens) minted per loop. When context gets high, the agent persists its plan to `working/` so a fresh conversation continues exactly where the old one left off. +ctrl+shft fixes all four. Clone it once, `bootstrap.sh` symlinks your instructions, skills, agents, and rules into `~/.claude/`, and `git pull` updates every machine. `detect-context.sh` loads only the rules that match your current stack. Secrets split into tiers — config the agent can see, credentials that exist only inside a child process and vanish when it exits (`run-with-secrets.sh`), and AFK iteration tokens (short-lived GitHub App installation tokens) minted per loop. When context gets high, the agent persists its plan to `working/` so a fresh conversation continues exactly where the old one left off. -**Source of truth:** `arndvs/ctrlshft` is the canonical public product repository for reusable agent configuration, automation, docs, and Sandcastle/shft code. `~/dotfiles/` is the local checkout bootstrap uses as the on-machine source of truth; `~/.claude/`, `~/.copilot/`, and `~/.agents/` are consumer targets populated from dotfiles (symlinked where possible, Windows fallback copy when needed). Public-safe product work starts in `ctrlshft`, then gets pulled back into `dotfiles-private` only for Aaron's private overlay. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [REPO_TOPOLOGY.md](REPO_TOPOLOGY.md). +Then Sandcastle takes the loop further: label an issue `agent:implement` and a GitHub Actions workflow picks it up, implements it, opens a PR, and waits for your verdict — no local machine required. + +**Source of truth:** `arndvs/ctrlshft` is the canonical public product repository for reusable agent configuration, automation, docs, and Sandcastle/shft code. `~/dotfiles/` is the local checkout bootstrap uses as the on-machine source of truth; `~/.claude/`, `~/.copilot/`, and `~/.agents/` are consumer targets populated from dotfiles (symlink, Windows fallback copy). Public-safe product work starts in `ctrlshft`, then gets pulled back into `dotfiles-private` only for Aaron's private overlay. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [REPO_TOPOLOGY.md](REPO_TOPOLOGY.md). **Quick start:** fork, clone to `~/dotfiles`, run `bash ~/dotfiles/bin/bootstrap.sh`. Full local/VPS/manual walkthroughs in [Installation](#installation). --- +## The ecosystem + +ctrl+shft is one part of a connected system. The repos work together: + +```mermaid +graph LR + HUB["sandcastle-hub
engine + templates
(single source of truth)"] + PROD["ctrlshft-public
product source + producer"] + DOT["dotfiles-private
private overlay"] + CMD["cmd
business knowledge layer"] + PROXY["claude-code-copilot
LiteLLM → Copilot/OpenRouter"] + + PROD -->|"publishes engine"| HUB + HUB -->|"one-way copy out"| PROD + PROD -->|"pull-back (public-safe)"| DOT + CMD -->|"CMD_DIR + cmd-venture"| DOT + PROXY -->|"shft proxy daemon"| PROD +``` + +- **`arndvs/ctrlshft`** — the public product source (this repo). Producer of the Sandcastle engine, mirror of the hub's templates. +- **`arndvs/sandcastle-hub`** — single source of truth for the Sandcastle engine, templates, actions, and labels. Consumers reference it remotely via `uses: arndvs/sandcastle-hub/...@main`; nothing is vendored. +- **`dotfiles-private`** — Aaron's private overlay: secrets, `_local/` skills and instructions, machine-local state. Never promoted to public. +- **`arndvs/cmd`** — the business knowledge layer. "ctrl+shft+cmd": ctrl configures how agents code, cmd configures what agents know about your business. +- **`arndvs/claude-code-copilot`** — the runtime proxy that lets Claude Code reach Copilot/OpenRouter models. Infrastructure, not product content. + +--- + ## The pipeline ``` @@ -131,6 +160,8 @@ All agents use read-only tools (Read, Grep, Glob, Bash) and `memory: user` for p | `tailwind-shadcn` | `**/*.{tsx,jsx}` | | `framer-motion` | `**/*.{tsx,jsx}` | | `server-vs-client-components` | `**/app/**/*.{tsx,jsx}` | +| `resource-management` | `**/*.{ts,tsx,js,jsx,mjs,cjs}` | +| `tooling-conventions` | `bin/**`, `hooks/**`, `**/*.sh`, `**/*.bash`, `package.json` | Rules without `paths:` load every session. Add your own: `rules/your-rule.md` — auto-discovered. See [rules/README.md](rules/README.md) for the full inventory. @@ -150,12 +181,13 @@ Re-shelve aggressively: if a rule applies only to `.tsx` files, it belongs in `r ### Hardened secrets -Three tiers. Agents see config, never credentials — and AFK loops use AFK iteration tokens instead of long-lived auth tokens. +Four tiers. Agents see config, never credentials — and AFK loops use AFK iteration tokens instead of long-lived auth tokens. | File | In shell? | Agent-visible? | Contains | | ---------------------- | --------- | -------------- | ---------------------------- | | `secrets/.env.agent` | Yes | Yes | Usernames, hosts, IDs | | `secrets/.env.secrets` | No | No | API keys, tokens, passwords | +| `secrets/.env.bridge` | No | No | `WEBHOOK_SECRET` for the Copilot Review Bridge | | AFK iteration token | No | No | Minted per loop, expires ~1h | `run-with-secrets.sh` injects credentials into a child process only — they vanish when it exits. Claude Code deny rules block `env`, `printenv`, `cat secrets/*`, and `echo $*KEY*` at the agent level. Agents can't accidentally inherit what they can't see. See [secrets/README.md](secrets/README.md) for the full tier model. @@ -245,28 +277,18 @@ Every skill's `description` is loaded into the agent's system prompt at session The benefit: ⚡ skills act as passive guardrails. You don't remember to say "use the debugging skill" — the agent recognizes an error and loads the root-cause-first investigation protocol on its own. Same rigorous process every time, without you thinking about it. -| Skill | What it does | -| ------------------------- | ------------------------------------------------------------------------------------------------------- | -| `do-work` | Detect your stack's feedback loops. Understand → Plan → Implement → Validate → Commit. | -| `grill-me` | Interrogate you about a plan until shared understanding. One question at a time, recommended answers. | -| `write-a-prd` | Explore codebase, interview you, sketch module boundaries, write PRD, submit as GitHub issue. | -| `prd-to-issues` | Break a PRD into vertical slices. Label each AFK or HITL. Create GitHub issues with dependencies. | -| `architect` ⚡ | Plan implementation — vertical slices, dependency graphs, acceptance criteria. | -| `skill-scaffolder` | Scaffold new agent skills from production-tested patterns. Interview → architecture → directory. | -| `explore` ⚡ | Decompose a topic, spawn parallel sub-agents, synthesize a summary. | -| `research` ⚡ | Cache expensive exploration into `research.md`. Staleness checks, lifecycle management. | -| `codebase-audit` ⚡ | Ruthless code audit — real problems only, grouped by severity. No manufactured issues. | -| `improve-architecture` ⚡ | Find shallow-module clusters, spawn parallel design agents, file a GitHub RFC. | -| `tdd` | Red-green refactor. Failing test → implement → refactor. Backend only. | -| `systematic-debugging` ⚡ | Root-cause-first — investigate → pattern analysis → hypothesis → fix. | -| `atomic-commits` ⚡ | Branch-isolated atomic commits. Survey diff, group by seam, commit or ship (push + PR). | -| `code-review` | Focused review of staged or recent changes. Edge cases, logic errors, integration risks. | -| `document` | Write, update, or audit documentation. Accurate, minimal, audience-appropriate. | -| `compliance-audit` ⚡ | Auto-invoked after do-work/tdd/debugging. Rule-by-rule review, violation flagging, skill gap detection. | -| `stress-test` | Adversarial 19-scenario protocol across 6 categories. Validates rule compliance boundaries. | -| `sanity-best-practices` | Sanity schema design, GROQ, TypeGen, Visual Editing, Portable Text, framework integrations. | -| `session-close` | Pre-flight checklist — quality gates before ending a coding session. | -| `error-audit` | Analyze cross-session error patterns to surface systemic issues worth automating. | +**56 skills** ship in the box, organized by role: + +| Category | Skills | +| -------- | ------ | +| **Core loop** | `do-work`, `tdd`, `systematic-debugging`, `atomic-commits`, `code-review`, `codebase-audit`, `compliance-audit`, `session-close`, `error-audit` | +| **Planning** | `grill-me`, `grill-with-docs`, `write-a-prd`, `architect` ⚡, `prd-to-issues`, `plan-archive`, `skill-scaffolder` | +| **Exploration** | `explore` ⚡, `research` ⚡, `improve-architecture` ⚡, `codebase-audit` ⚡ | +| **Frontend** | `frontend-design`, `frontend-component-style`, `visual-feedback`, `sketch-the-solution`, `blog-image-ideas` | +| **Audits** | `performance-audit`, `npm-security-audit`, `logging-audit`, `jsonld-schema-audit`, `error-audit`, `press1-check` | +| **Auth & data** | `better-auth-best-practices`, `better-auth-security-best-practices`, `create-auth-skill`, `email-and-password-best-practices`, `two-factor-authentication-best-practices`, `organization-best-practices`, `sanity-best-practices`, `groq-query-instructions`, `data-object-conventions` | +| **Content** | `anti-ai-slop`, `halbert-copy-editor`, `blog-image-ideas`, `github-weekly-digest`, `citation-builder-skill` | +| **Agent ops** | `repo-hygiene`, `pr-preflight`, `review-pr-copilot`, `agent-assets`, `agent-navigability`, `agent-session-friction`, `agent-testability`, `mobile-dev`, `aihc-scraping`, `linear`, `cloudflare`, `npm-security-audit` | Add your own: `skills/_local/your-skill/SKILL.md` — auto-discovered, gitignored. See [skills/README.md](skills/README.md) for the full catalog with trigger phrases. @@ -287,6 +309,13 @@ Thin launchers in `commands/` that load a skill with your arguments. Type the co | `/document` | `document` | Write, update, or audit documentation. | | `/check` | `session-close` | Pre-flight checklist — quality gates before session end. | | `/address-review` | `review-pr-copilot` | Fetch and address Copilot review comments on active PR. | +| `/commit` | `atomic-commits` | Atomic commits on a feature branch. | +| `/ship` | `atomic-commits` | Push + open a PR. | +| `/preflight` | `pr-preflight` | Exhaustive pre-PR audit. | +| `/compliance-audit` | `compliance-audit` | Rule-by-rule diff review. | +| `/stress-test` | `stress-test` | Adversarial rule-boundary validation. | +| `/cmd` | cmd instructions | Open the cmd business operating system context. | +| `/mobile-fresh` | `mobile-dev` | Clean-state reset for Expo mobile dev. | Add your own: `commands/your-command.md` — auto-discovered. Each file is a prompt template with `$ARGUMENTS` passthrough. See [commands/README.md](commands/README.md) for the full command reference. @@ -377,6 +406,16 @@ Claude Code lifecycle hooks — shell scripts that fire on tool use and session | `context-warning.sh` | UserPromptSubmit | Stub: graduated warnings at 40/70% context (pending statusLine experiment) | | `exploration-scope-guard.sh` | PreToolUse | Nudges toward subagent delegation after 15+ raw Read/Grep/Glob calls without a `Task` spawn | | `session-scope-warning.sh` | UserPromptSubmit | Graduated turn-count warnings (20/40+) — works without the statusLine bridge | +| `git-workflow-gate.sh` | PreToolUse | Enforces branch/protected-path rules during git operations | +| `plan-quality-gate.sh` | PreToolUse | Validates plan files against quality criteria | +| `test-gate.sh` | PreToolUse | Blocks commits when tests fail | +| `feedback-memory-gate.py` | PreToolUse | Persists feedback to memory | +| `git-post-commit.sh` | PostToolUse | Post-commit hygiene | +| `git-post-push.sh` | PostToolUse | Post-push hygiene | +| `gh-pr-auto-copilot-review.sh` | PostToolUse | Auto-request Copilot review on PR | +| `stale-branches.sh` | SessionStart | Flags stale branches | +| `hud-reads.sh` | PreToolUse | Emits HUD read events | +| `hud-session.sh` | SessionStart | Emits HUD session events | Hooks communicate via exit codes: **0** = allow, **2** = block. See [hooks/README.md](hooks/README.md) for full documentation, customization, and the `experiments/` directory for in-progress prototypes. @@ -428,6 +467,105 @@ srt claude . --- +## Sandcastle: autonomous agents in CI + +> `shft` runs the loop on your machine. **Sandcastle** runs it in GitHub Actions — label-driven, event-triggered, no local machine required. + +Sandcastle is the CI-triggered evolution of the `shft` bash loop. Instead of a local process polling your backlog, GitHub Actions workflows watch for labels and run Claude Code agents against your repo — in a sandbox, with structured output validated against schemas. + +### The hub model + +The engine is **not vendored** into consumers. It lives in one place — [`arndvs/sandcastle-hub`](https://github.com/arndvs/sandcastle-hub) — and consumers reference it remotely: + +```yaml +uses: arndvs/sandcastle-hub/actions/agent-run@main +``` + +A consumer repo keeps only three things: + +- **`sandcastle.config.json`** — model, base branch, prompt dir, coding standards, excluded paths +- **`.sandcastle/hub-version.json`** — a SHA-lock pinning the hub ref (drift detection opens a review PR when the hub advances) +- **Thin workflow stubs** — `agent-*.yml` files that call the hub's reusable workflows + +This replaces an older model where ~101 engine files were vendored into every consumer, causing drift and weekly re-vendor churn. + +### The label state machine + +Sandcastle is label-driven. Applying a label to an issue or PR triggers a workflow: + +```mermaid +graph LR + H["Human applies Sandcastle"] --> R["agent:review"] + R --> I["agent:implement"] + I --> P["agent:pr-open"] + P --> V{"Human verdict"} + V -->|"agent:merge"| M["merge-pr"] + V -->|"agent:fix"| F["fix-pr-feedback"] + V -->|"agent:update-branch"| U["update-branch"] + F --> P + U --> P +``` + +Two human gates by design: the **start gate** (applying `Sandcastle`) and the **verdict gate** (choosing merge/fix/update). There is no auto-merge. + +> **Security note:** chaining labels requires `AGENT_PAT` (not `GITHUB_TOKEN`). If the wrong token is used, the chain fails closed into `agent:blocked`. + +### Workflow templates + +`shft/templates/workflows/` mirrors the hub's canonical templates (synced via `bin/sync-hub-templates.sh`). The 12 `agent-*.yml` stubs cover the lifecycle: + +| Workflow | Trigger | What it does | +| -------- | ------- | ------------ | +| `agent-review-issue` | label `agent:review` | Review an issue before implementation | +| `agent-plan-issue` | label `agent:plan` | Break an issue into a plan | +| `agent-implement-issue` | label `agent:implement` | Implement an issue, open a draft PR | +| `agent-implement-prd` | label `agent:implement-prd` | Implement a PRD's next sub-issue, chain onward | +| `agent-fix-pr-feedback` | label `agent:fix` | Address review feedback on a PR | +| `agent-merge-pr` | label `agent:merge` | Squash-merge a PR, close linked issues | +| `agent-update-branch` | label `agent:update-branch` | Rebase a PR head onto its base | +| `agent-architecture-review` | cron (weekdays) | Weekly architecture review | +| `agent-repo-hygiene` | cron (weekdays) | Nightly hygiene backlog | +| `agent-code-health` | cron (weekly) | Weekly code-health audit (7 lenses) | +| `agent-keep-tests-tight` | cron (weekdays) | Prune/strengthen weak tests | +| `agent-check-stale-prs` | cron (weekdays) | Flag stale PRs | +| `agent-promote-queued` | label `agent:promote` | Promote queued issues when blockers close | + +Plus `labels-sync.yml` (weekly label reconciliation) and `sandcastle-drift.yml` (hub-version drift detection). + +### Install + +```bash +bash ~/dotfiles/bin/init-sandcastle.sh # install the Sandcastle consumer contract +``` + +See [shft/README.md](shft/README.md) and the hub's [docs](https://github.com/arndvs/sandcastle-hub) for the full platform spec. + +--- + +## Copilot Review Bridge + +> An automated pipeline that receives **GitHub Copilot review events** and dispatches them to the `shft afk` agent loop for resolution. + +The bridge closes the loop between Copilot's PR reviews and the autonomous agent: + +``` +GitHub webhook → FastAPI (bridge-webhook.service) + ↓ SQLite queue + Worker (bridge-worker@1.service) + ↓ + shft afk 1 (Claude Code in srt sandbox) +``` + +- **`bridge/`** — Python package: FastAPI receiver, SQLite queue, GitHub token minting, per-PR workspace lifecycle, HUD event emission +- **`systemd/`** — `bridge-webhook.service` + `bridge-worker@.service` units +- **`bin/bridge-install.sh`** — idempotent installer +- **`ctrl bridge`** — start/stop/restart/status/queue/logs/replay/validate/run +- **Secrets:** `WEBHOOK_SECRET` (`.env.bridge`), GitHub App creds (`.env.secrets`) + +See [bridge/README.md](bridge/README.md) for the full reference. + +--- + ## CLI — `ctrl` & `shft` After bootstrap, two commands are available system-wide. `ctrl` manages your environment, `shft` manages your work queue. Both are symlinked to `~/.local/bin/` by bootstrap. See [bin/README.md](bin/README.md) for the full script inventory. @@ -475,6 +613,20 @@ shft help # autonomous execution commands | `ctrl uninstall` | Safely remove all ctrl+shft symlinks + shell integration | | `ctrl verify-token` | Test-mint a GitHub App token | +#### ctrl bridge (Copilot Review Bridge) + +| Command | What it does | +| --------------------- | --------------------------------------------------- | +| `ctrl bridge start` | Start the webhook receiver + worker services | +| `ctrl bridge stop` | Stop the bridge services | +| `ctrl bridge restart` | Stop + start | +| `ctrl bridge status` | Show service state | +| `ctrl bridge queue` | Show queued review jobs | +| `ctrl bridge logs` | Show bridge logs | +| `ctrl bridge replay` | Replay a failed job | +| `ctrl bridge validate`| Validate bridge configuration | +| `ctrl bridge run` | Run a single review job directly | + #### ctrl session analysis (requires [sheal](https://github.com/liwala/sheal)) | Command | What it does | @@ -493,6 +645,7 @@ shft help # autonomous execution commands | ----------------- | ----------------------------------------------- | | `shft run` | HITL — run once while you watch | | `shft afk [n]` | AFK — autonomous loop (default 5 iterations) | +| `shft afk --worktree [n]` | AFK in an isolated git worktree/branch | | `shft status` | Is a loop running? How many issues open? | | `shft stop` | Stop a running AFK loop after current iteration | | `shft log [-f]` | Show shft log (add `-f` to follow) | @@ -503,6 +656,9 @@ shft help # autonomous execution commands | `shft plan` | View `working/plan.md` | | `shft plan edit` | Open plan in `$EDITOR` | | `shft plan clear` | Clear the plan file | +| `shft worktrees` | List or remove AFK-created git worktrees | +| `shft engine on\|off\|status` | Switch between bash and TypeScript engines | +| `shft proxy on\|off\|start\|stop\|status` | Toggle routing and manage the LiteLLM/Copilot proxy daemon | | `shft validate` | Run AFK pre-flight checks | | `shft mint` | Test-mint a GitHub App token | | `shft prompt` | Show `shft/prompt.md` | @@ -533,14 +689,27 @@ shft help # autonomous execution commands │ ├── sentry.instructions.md │ ├── google-docs.instructions.md │ ├── css.instructions.md +│ ├── better-auth.instructions.md +│ ├── expo-mobile.instructions.md +│ ├── hud.instructions.md +│ ├── sandcastle-pipeline.instructions.md │ ├── handoff.instructions.md ← cross-conversation persistence protocol │ └── _local/ ← GITIGNORED — your private instructions ├── commands/ -│ ├── audit.md /audit → codebase-audit skill +│ ├── address-review.md /address-review → review-pr-copilot skill +│ ├── audit.md /audit → codebase-audit skill +│ ├── check.md /check → session-close skill +│ ├── cmd.md /cmd → cmd business OS +│ ├── commit.md /commit → atomic-commits skill +│ ├── compliance-audit.md /compliance-audit → compliance-audit skill │ ├── document.md /document → document skill │ ├── explore.md /explore → explore skill +│ ├── mobile-fresh.md /mobile-fresh → mobile-dev skill │ ├── plan.md /plan → architect skill +│ ├── preflight.md /preflight → pr-preflight skill │ ├── review.md /review → code-review skill +│ ├── ship.md /ship → atomic-commits skill +│ ├── stress-test.md /stress-test → stress-test skill │ ├── test.md /test → tdd skill │ └── work.md /work → do-work skill ├── agents/ @@ -550,23 +719,44 @@ shft help # autonomous execution commands │ ├── researcher.md subagent: deep codebase exploration (sonnet) │ ├── researcher-opus.md subagent: complex architecture analysis (opus) │ ├── researcher-haiku.md subagent: fast bulk scanning (haiku) -│ └── security-auditor.md subagent: OWASP, secrets, config (sonnet) -├── rules/ +│ ├── security-auditor.md subagent: OWASP, secrets, config (sonnet) +│ └── gemini-safety.json safety config for Gemini models +├── rules/ ← 14 path-gated convention files │ ├── test-conventions.md scoped to tests and service code │ ├── migration-safety.md scoped to **/migrations/** │ ├── env-security.md scoped to **/.env*, **/secrets/** -│ └── terminal-workarounds.md scoped to terminal sessions -├── hooks/ +│ ├── terminal-workarounds.md scoped to terminal sessions +│ ├── git-conventions.md scoped to source files +│ ├── typescript-conventions.md scoped to **/*.{ts,tsx} +│ ├── javascript-modern.md scoped to JS/TS files +│ ├── frontend-conventions.md scoped to frontend files +│ ├── dark-mode.md scoped to **/*.{tsx,jsx,css,scss} +│ ├── tailwind-shadcn.md scoped to **/*.{tsx,jsx} +│ ├── framer-motion.md scoped to **/*.{tsx,jsx} +│ ├── server-vs-client-components.md scoped to **/app/**/*.{tsx,jsx} +│ ├── resource-management.md scoped to JS/TS files +│ └── tooling-conventions.md scoped to bin/, hooks/, shell scripts +├── hooks/ ← 20+ lifecycle guards │ ├── README.md hook documentation │ ├── settings-hooks.json hook configuration for Claude Code │ ├── compaction-guard.sh blocks auto-compaction, enforces handoff │ ├── context-warning.sh graduated warnings at 40/70% context -│ ├── format-check.sh auto-formats modified files on stop +│ ├── format-check.sh auto-formats modified files on stop │ ├── migration-guard.sh blocks unsafe migration commands │ ├── secret-guard.sh blocks credential exposure in agent output │ ├── typecheck.sh runs tsc --noEmit before stop +│ ├── git-workflow-gate.sh enforces branch/protected-path rules +│ ├── plan-quality-gate.sh validates plan files +│ ├── test-gate.sh blocks commits when tests fail +│ ├── feedback-memory-gate.py persists feedback to memory +│ ├── git-post-commit.sh post-commit hygiene +│ ├── git-post-push.sh post-push hygiene +│ ├── gh-pr-auto-copilot-review.sh auto-request Copilot review on PR +│ ├── stale-branches.sh flags stale branches +│ ├── hud-reads.sh emits HUD read events +│ ├── hud-session.sh emits HUD session events │ └── experiments/ experimental hook prototypes -├── skills/ +├── skills/ ← 56 public skills + _local/ │ ├── do-work/ │ ├── grill-me/ │ ├── write-a-prd/ @@ -585,6 +775,7 @@ shft help # autonomous execution commands │ ├── compliance-audit/ │ ├── stress-test/ │ ├── sanity-best-practices/ +│ ├── skills-lock.json ← SHA-256 provenance lock │ └── _local/ ← GITIGNORED — your private skills ├── clients/ │ ├── README.md per-client instruction isolation guide @@ -594,7 +785,13 @@ shft help # autonomous execution commands │ ├── afk.sh AFK autonomous loop │ ├── once.sh HITL single-run │ ├── _build_prompt.sh prompt assembly for shft runs -│ └── prompt.md shared agent prompt +│ ├── prompt.md shared agent prompt +│ └── templates/ ← Sandcastle consumer contract (mirror of hub) +│ ├── workflows/ 16 workflow templates +│ ├── prompts/ prompt templates +│ ├── extractions/ structured-output prompts +│ ├── scripts/ helper scripts +│ └── run.ts TS dispatcher ├── bin/ │ ├── ctrl CLI entry point — infrastructure management │ ├── _lib.sh shared shell library @@ -606,16 +803,63 @@ shft help # autonomous execution commands │ ├── run-with-secrets.sh process-scoped secret injection │ ├── detect-context.sh exports ACTIVE_CONTEXTS │ ├── detect-client.sh per-client context detection +│ ├── detect-cmd.sh cmd venture detection │ ├── new-client.sh scaffold new client instruction set │ ├── migrate.sh safe migration from manual setup +│ ├── migrate-bashrc.sh bashrc migration helper │ ├── uninstall.sh clean removal of all symlinks + shell integration │ ├── validate-env.sh env + hardening validation │ ├── validate-symlinks.sh verify bootstrap symlinks +│ ├── validate-remotes.sh verify remote topology +│ ├── validate-git-hooks.sh verify global git hooks +│ ├── validate-skills.sh validate skill structure +│ ├── validate-skills-lock.sh verify skills-lock provenance +│ ├── generate-skills-lock.sh regenerate skills-lock.json │ ├── mint_github_app_token.py AFK token minting │ ├── verify-github-app-token.sh safe token verification -│ ├── hud-daemon.js HUD HTTP server -│ ├── start-hud.sh daemon lifecycle (start/stop/status/restart) -│ └── write-hud-state.sh non-blocking compliance event emitter +│ ├── sandcastle-wire-secrets.sh wire Sandcastle secrets +│ ├── init-sandcastle.sh install Sandcastle consumer contract +│ ├── update-sandcastle.sh update Sandcastle install +│ ├── preflight-sandcastle.sh Sandcastle pre-flight checks +│ ├── sync-hub-templates.sh mirror hub templates to producer +│ ├── smoke-sandcastle-*.sh Sandcastle smoke tests +│ ├── preflight-public-promotion.sh validate promotion range +│ ├── validate-public-promotion.sh validate public tree +│ ├── validate-main-pr-source.sh validate PR source branch +│ ├── verify-pr-base.sh resolve PR base authoritatively +│ ├── apply-branch-ruleset.sh apply branch protection ruleset +│ ├── drift-detect.sh detect instruction drift +│ ├── artifact-lifecycle-audit.sh audit artifact lifecycle +│ ├── afk-throughput.sh AFK throughput analysis +│ ├── gh-assignee-preflight.sh assignee preflight +│ ├── pipeline-label.sh pipeline label helper +│ ├── pipeline-label-data.sh pipeline label data +│ ├── bridge-install.sh install the Copilot Review Bridge +│ ├── ctrl-worktree.sh worktree management +│ ├── hud-daemon.js HUD HTTP + WebSocket server +│ ├── start-hud.sh daemon lifecycle (start/stop/status/restart) +│ ├── write-hud-state.sh non-blocking compliance event emitter +│ ├── ctrlshft-claude event-producing Claude wrapper +│ ├── com.ctrlshft.hud.plist macOS HUD launch agent +│ ├── ctrlshft-hud.service systemd HUD unit +│ └── perf/ performance analysis scripts +├── bridge/ ← Copilot Review Bridge (Python) +│ ├── README.md +│ ├── webhook.py FastAPI webhook receiver +│ ├── worker.py queue worker +│ ├── db.py SQLite queue +│ ├── github.py GitHub token minting +│ ├── workspace.py per-PR workspace lifecycle +│ ├── hud.py HUD event emission +│ └── requirements.txt +├── git-hooks/ ← global git hook dispatchers +│ ├── pre-commit +│ └── generic-hook +├── lib/ +│ └── plan_files_lib.py shared plan-file helper +├── systemd/ +│ ├── bridge-webhook.service +│ └── bridge-worker@.service ├── hud/ ← HUD UI │ ├── README.md HUD architecture + API reference │ └── index.html @@ -624,14 +868,26 @@ shft help # autonomous execution commands │ ├── CNAME │ └── assets/ ├── docs/ -│ ├── adr/ architecture decision records +│ ├── ARCHITECTURE.md internal system map +│ ├── ARTIFACT-LIFECYCLE.md artifact path policy +│ ├── adr/ architecture decision records (8) │ ├── audits/ dated assessment artifacts │ ├── reference/ durable reference material │ └── research/ durable synthesis and research +├── plans/ ← formal PRDs + issue breakdowns +├── templates/ +│ └── lifecycle/ lifecycle templates +├── test/ ← shell + Python test suites +├── .sandcastle/ ← Sandcastle consumer config +│ ├── hub-version.json SHA-lock pinning the hub engine +│ └── prompts/ project prompt overrides +├── .github/ ← CI workflows, rulesets, issue templates +├── .ctrlshft ← per-repo hook config ├── working/ ← active/refs/research tracked; runtime/tmp/logs ignored └── secrets/ ← GITIGNORED ├── .env.agent ├── .env.secrets + ├── .env.bridge └── .venv/ ``` From 4a7e206cc7204f1828ca427f8a20c0807d8bbbe0 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Tue, 1 Sep 2026 07:34:18 -0700 Subject: [PATCH 08/10] fix(producer): update model from claude-opus-4-6 to claude-opus-4-7 GitHub Copilot no longer supports claude-opus-4-6. Updated sandcastle.config.json and init-sandcastle.sh to use claude-opus-4-7 (available on GitHub Copilot and OpenRouter). This config is inherited by all consumer repos, which is why the overnight batch of 21 workflow failures were all using the unsupported model. Pairs with sandcastle-hub fix that updated the engine default model. --- bin/init-sandcastle.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/init-sandcastle.sh b/bin/init-sandcastle.sh index 00d7378e..9579ef0b 100644 --- a/bin/init-sandcastle.sh +++ b/bin/init-sandcastle.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # init-sandcastle.sh — Scaffold a complete Sandcastle setup in any repo. # -# Usage: ctrl init-sandcastle [--branch main] [--model claude-opus-4-6] [--sandbox none] [--no-proxy] [--with-proxy-canary] [--force] +# Usage: ctrl init-sandcastle [--branch main] [--model claude-opus-4-7] [--sandbox none] [--no-proxy] [--with-proxy-canary] [--force] # # Installs thin workflow stubs that reference the Sandcastle hub engine remotely # (uses: arndvs/sandcastle-hub/actions/agent-run@), writes a hub-version pin, @@ -23,7 +23,7 @@ fi # ── Defaults ────────────────────────────────────────────────────────────────── BRANCH="main" -MODEL="claude-opus-4-6" +MODEL="claude-opus-4-7" SANDBOX="none" FORCE=false NO_ARTIFACTS=false @@ -43,7 +43,7 @@ while [[ $# -gt 0 ]]; do --force) FORCE=true; shift ;; --no-artifacts) NO_ARTIFACTS=true; shift ;; --help|-h) - echo "Usage: ctrl init-sandcastle [--branch main] [--model claude-opus-4-6] [--sandbox none] [--no-proxy] [--with-proxy-canary] [--no-artifacts] [--force]" + echo "Usage: ctrl init-sandcastle [--branch main] [--model claude-opus-4-7] [--sandbox none] [--no-proxy] [--with-proxy-canary] [--no-artifacts] [--force]" echo "" echo "Also scaffolds the artifact lifecycle (working/, plans/, docs/) by calling" echo "'ctrl init-artifacts --gitignore'. Pass --no-artifacts to skip it." From 08b7c1f047db45c19e87f1944967b0ddf31a2dd8 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Tue, 1 Sep 2026 13:21:49 -0700 Subject: [PATCH 09/10] =?UTF-8?q?chore(repo):=20sandcastle-hub=20=E2=86=92?= =?UTF-8?q?=20ctrlshft-hub=20(tooling,=20templates,=20ADR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The hub repo is now arndvs/ctrlshft-hub. Update the producer: - workflow templates (15) + .github/workflows - bin tooling (init-sandcastle bootstrap action, preflight hub checks, sync-hub-templates HUB path, update-sandcastle deprecation copy) - AGENTS/CONTEXT/README/CHANGELOG + docs/ARCHITECTURE - ADR-008 renamed ADR-008-sandcastle-hub.md → ADR-008-ctrlshft-hub.md (title + all filename refs in active docs) - Archive docs (sandcastle-hub-dogfood-plan etc.) left as historical records with names preserved. Consumer scan verified: ctrlshft-hub classified hub, consumers hub-model. --- .../workflows/agent-architecture-review.yml | 2 +- .github/workflows/agent-check-stale-prs.yml | 2 +- .github/workflows/agent-fix-pr-feedback.yml | 2 +- .github/workflows/agent-implement-issue.yml | 2 +- .github/workflows/agent-implement-prd.yml | 2 +- .github/workflows/agent-keep-tests-tight.yml | 2 +- .github/workflows/agent-merge-pr.yml | 2 +- .github/workflows/agent-plan-issue.yml | 2 +- .github/workflows/agent-promote-queued.yml | 2 +- .github/workflows/agent-repo-hygiene.yml | 2 +- .github/workflows/agent-review-issue.yml | 2 +- .github/workflows/agent-update-branch.yml | 2 +- .github/workflows/labels-sync.yml | 2 +- .github/workflows/sandcastle-drift.yml | 8 +- AGENTS.md | 4 +- CHANGELOG.md | 4 +- CONTEXT.md | 2 +- README.md | 10 +- bin/init-sandcastle.sh | 4 +- bin/preflight-sandcastle.sh | 10 +- bin/sync-hub-templates.sh | 4 +- bin/update-sandcastle.sh | 16 +- docs/ARCHITECTURE.md | 4 +- ...dcastle-hub.md => ADR-008-ctrlshft-hub.md} | 6 +- docs/archive/sandcastle-hub-architecture.md | 26 +- plans/archive/hub-model-cleanup-plan.md | 12 +- plans/archive/sandcastle-hub-dogfood-plan.md | 6 +- plans/archive/sandcastle-hub-plan.md | 36 +- plans/archive/workspace-prime-plan.md | 2 +- plans/dual-method-skill-system-v2.md | 306 ++++++++++++++++ plans/dual-method-skill-system-v3.md | 329 +++++++++++++++++ plans/dual-method-skill-system-v4.md | 346 ++++++++++++++++++ plans/dual-method-skill-system.md | 229 ++++++++++++ plans/hub-model-cleanup-plan.md | 267 ++++++++++++++ plans/public-docs-cleanup-plan.md | 6 +- shft/README.md | 6 +- .../workflows/agent-architecture-review.yml | 2 +- .../workflows/agent-check-stale-prs.yml | 2 +- .../templates/workflows/agent-code-health.yml | 2 +- .../workflows/agent-fix-pr-feedback.yml | 2 +- .../workflows/agent-implement-issue.yml | 2 +- .../workflows/agent-implement-prd.yml | 2 +- .../workflows/agent-keep-tests-tight.yml | 2 +- shft/templates/workflows/agent-merge-pr.yml | 2 +- shft/templates/workflows/agent-plan-issue.yml | 2 +- .../workflows/agent-promote-queued.yml | 2 +- .../workflows/agent-repo-hygiene.yml | 2 +- .../workflows/agent-review-issue.yml | 2 +- .../workflows/agent-update-branch.yml | 2 +- shft/templates/workflows/labels-sync.yml | 2 +- shft/templates/workflows/sandcastle-drift.yml | 2 +- test/update-sandcastle-ownership.sh | 6 +- test/update-sandcastle-proxy-canary.sh | 4 +- 53 files changed, 1593 insertions(+), 116 deletions(-) rename docs/adr/{ADR-008-sandcastle-hub.md => ADR-008-ctrlshft-hub.md} (96%) create mode 100644 plans/dual-method-skill-system-v2.md create mode 100644 plans/dual-method-skill-system-v3.md create mode 100644 plans/dual-method-skill-system-v4.md create mode 100644 plans/dual-method-skill-system.md create mode 100644 plans/hub-model-cleanup-plan.md diff --git a/.github/workflows/agent-architecture-review.yml b/.github/workflows/agent-architecture-review.yml index a9abecc2..eb7746b3 100644 --- a/.github/workflows/agent-architecture-review.yml +++ b/.github/workflows/agent-architecture-review.yml @@ -20,7 +20,7 @@ jobs: ANTHROPIC_BASE_URL: ${{ secrets.LITELLM_BASE_URL }} ANTHROPIC_AUTH_TOKEN: ${{ secrets.LITELLM_MASTER_KEY }} steps: - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: architecture-review ref: main diff --git a/.github/workflows/agent-check-stale-prs.yml b/.github/workflows/agent-check-stale-prs.yml index 6303489d..c2d8d967 100644 --- a/.github/workflows/agent-check-stale-prs.yml +++ b/.github/workflows/agent-check-stale-prs.yml @@ -20,7 +20,7 @@ jobs: ANTHROPIC_BASE_URL: ${{ secrets.LITELLM_BASE_URL }} ANTHROPIC_AUTH_TOKEN: ${{ secrets.LITELLM_MASTER_KEY }} steps: - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: check-stale-prs ref: main diff --git a/.github/workflows/agent-fix-pr-feedback.yml b/.github/workflows/agent-fix-pr-feedback.yml index a849b6c0..ef8e84f0 100644 --- a/.github/workflows/agent-fix-pr-feedback.yml +++ b/.github/workflows/agent-fix-pr-feedback.yml @@ -27,7 +27,7 @@ jobs: fix-pr-feedback: if: github.event.label.name == 'agent:fix' && github.event.pull_request.head.repo.full_name == github.repository - uses: arndvs/sandcastle-hub/.github/workflows/reusable-fix-pr-feedback.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-fix-pr-feedback.yml@main with: pr-number: ${{ github.event.pull_request.number }} secrets: inherit diff --git a/.github/workflows/agent-implement-issue.yml b/.github/workflows/agent-implement-issue.yml index 396689e7..1cd84e61 100644 --- a/.github/workflows/agent-implement-issue.yml +++ b/.github/workflows/agent-implement-issue.yml @@ -12,7 +12,7 @@ permissions: jobs: implement: if: github.event.label.name == 'agent:implement' - uses: arndvs/sandcastle-hub/.github/workflows/reusable-implement-issue.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-implement-issue.yml@main with: issue-number: ${{ github.event.issue.number }} issue-title: ${{ github.event.issue.title }} diff --git a/.github/workflows/agent-implement-prd.yml b/.github/workflows/agent-implement-prd.yml index be4ac94c..de92a52e 100644 --- a/.github/workflows/agent-implement-prd.yml +++ b/.github/workflows/agent-implement-prd.yml @@ -12,7 +12,7 @@ permissions: jobs: implement-prd: if: github.event.label.name == 'agent:implement-prd' - uses: arndvs/sandcastle-hub/.github/workflows/reusable-implement-prd.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-implement-prd.yml@main with: prd-number: ${{ github.event.issue.number }} prd-title: ${{ github.event.issue.title }} diff --git a/.github/workflows/agent-keep-tests-tight.yml b/.github/workflows/agent-keep-tests-tight.yml index 7044f43d..2e7edfa4 100644 --- a/.github/workflows/agent-keep-tests-tight.yml +++ b/.github/workflows/agent-keep-tests-tight.yml @@ -11,5 +11,5 @@ permissions: jobs: keep-tests-tight: - uses: arndvs/sandcastle-hub/.github/workflows/reusable-keep-tests-tight.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-keep-tests-tight.yml@main secrets: inherit diff --git a/.github/workflows/agent-merge-pr.yml b/.github/workflows/agent-merge-pr.yml index d6ca823d..76d4ba96 100644 --- a/.github/workflows/agent-merge-pr.yml +++ b/.github/workflows/agent-merge-pr.yml @@ -27,7 +27,7 @@ jobs: merge: if: github.event.label.name == 'agent:merge' && github.event.pull_request.head.repo.full_name == github.repository - uses: arndvs/sandcastle-hub/.github/workflows/reusable-merge-pr.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-merge-pr.yml@main with: pr-number: ${{ github.event.pull_request.number }} secrets: inherit diff --git a/.github/workflows/agent-plan-issue.yml b/.github/workflows/agent-plan-issue.yml index 8036c172..e788e213 100644 --- a/.github/workflows/agent-plan-issue.yml +++ b/.github/workflows/agent-plan-issue.yml @@ -27,7 +27,7 @@ jobs: run: | gh issue edit "$ISSUE_NUMBER" --remove-label "agent:plan" -R ${{ github.repository }} || true gh issue edit "$ISSUE_NUMBER" --add-label "agent:in-progress" -R ${{ github.repository }} - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: plan-issue ref: main diff --git a/.github/workflows/agent-promote-queued.yml b/.github/workflows/agent-promote-queued.yml index 273abb10..93e52619 100644 --- a/.github/workflows/agent-promote-queued.yml +++ b/.github/workflows/agent-promote-queued.yml @@ -27,7 +27,7 @@ jobs: run: | gh issue edit "$ISSUE_NUMBER" --remove-label "agent:promote" -R ${{ github.repository }} || true gh issue edit "$ISSUE_NUMBER" --add-label "agent:in-progress" -R ${{ github.repository }} - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: promote-queued ref: main diff --git a/.github/workflows/agent-repo-hygiene.yml b/.github/workflows/agent-repo-hygiene.yml index 5b85ccf0..45985f82 100644 --- a/.github/workflows/agent-repo-hygiene.yml +++ b/.github/workflows/agent-repo-hygiene.yml @@ -25,7 +25,7 @@ jobs: ANTHROPIC_BASE_URL: ${{ secrets.LITELLM_BASE_URL }} ANTHROPIC_AUTH_TOKEN: ${{ secrets.LITELLM_MASTER_KEY }} steps: - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: repo-hygiene ref: main diff --git a/.github/workflows/agent-review-issue.yml b/.github/workflows/agent-review-issue.yml index acc15dd0..fcf1a9d5 100644 --- a/.github/workflows/agent-review-issue.yml +++ b/.github/workflows/agent-review-issue.yml @@ -27,7 +27,7 @@ jobs: run: | gh issue edit "$ISSUE_NUMBER" --remove-label "agent:review" -R ${{ github.repository }} || true gh issue edit "$ISSUE_NUMBER" --add-label "agent:in-progress" -R ${{ github.repository }} - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: review-issue ref: main diff --git a/.github/workflows/agent-update-branch.yml b/.github/workflows/agent-update-branch.yml index 6de4f54a..b1cfd7e4 100644 --- a/.github/workflows/agent-update-branch.yml +++ b/.github/workflows/agent-update-branch.yml @@ -27,7 +27,7 @@ jobs: update-branch: if: github.event.label.name == 'agent:update-branch' && github.event.pull_request.head.repo.full_name == github.repository - uses: arndvs/sandcastle-hub/.github/workflows/reusable-update-branch.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-update-branch.yml@main with: pr-number: ${{ github.event.pull_request.number }} branch: ${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/labels-sync.yml b/.github/workflows/labels-sync.yml index 73ef8114..342329eb 100644 --- a/.github/workflows/labels-sync.yml +++ b/.github/workflows/labels-sync.yml @@ -23,7 +23,7 @@ jobs: curl -fsSL \ -H "Authorization: Bearer $GH_TOKEN" \ -H "Accept: application/vnd.github.raw" \ - "https://api.github.com/repos/arndvs/sandcastle-hub/contents/templates/labels.json?ref=main" \ + "https://api.github.com/repos/arndvs/ctrlshft-hub/contents/templates/labels.json?ref=main" \ > "$RUNNER_TEMP/labels.json" - name: Reconcile labels diff --git a/.github/workflows/sandcastle-drift.yml b/.github/workflows/sandcastle-drift.yml index 5fc4f648..e74fecab 100644 --- a/.github/workflows/sandcastle-drift.yml +++ b/.github/workflows/sandcastle-drift.yml @@ -30,7 +30,7 @@ jobs: run: | set -euo pipefail pinned=$(jq -r '.lastPinnedSha' .sandcastle/hub-version.json 2>/dev/null || echo "unknown") - latest=$(gh api repos/arndvs/sandcastle-hub/commits/main --jq '.sha' | cut -c1-7) + latest=$(gh api repos/arndvs/ctrlshft-hub/commits/main --jq '.sha' | cut -c1-7) echo "pinned=$pinned" echo "latest=$latest" if [ "$pinned" != "$latest" ]; then @@ -61,7 +61,7 @@ jobs: continue fi tmpl="${checks[$local]}" - curl -fsSL "https://raw.githubusercontent.com/arndvs/sandcastle-hub/main/$tmpl" -o /tmp/hub-template 2>/dev/null || { echo " ? cannot fetch hub template $tmpl" >&2; continue; } + curl -fsSL "https://raw.githubusercontent.com/arndvs/ctrlshft-hub/main/$tmpl" -o /tmp/hub-template 2>/dev/null || { echo " ? cannot fetch hub template $tmpl" >&2; continue; } if ! diff -q "$local" /tmp/hub-template >/dev/null 2>&1; then echo " DRIFTED: $local vs hub $tmpl" >> ./wf-drift-report.txt drifted=1 @@ -86,7 +86,7 @@ jobs: for entry in ".github/workflows/proxy-canary.yml templates/workflows-proxy/proxy-canary.yml" ".github/workflows/check-attribution.yml templates/workflows/check-attribution.yml"; do set -- $entry local="$1"; tmpl="$2" - curl -fsSL "https://raw.githubusercontent.com/arndvs/sandcastle-hub/main/$tmpl" -o "$local" + curl -fsSL "https://raw.githubusercontent.com/arndvs/ctrlshft-hub/main/$tmpl" -o "$local" done git add .github/workflows/proxy-canary.yml .github/workflows/check-attribution.yml git commit -m "chore(sandcastle): re-sync hub-synced workflows from hub templates" || true @@ -95,7 +95,7 @@ jobs: --base main \ --head "$branch" \ --title "chore(sandcastle): re-sync hub-synced workflows" \ - --body "Detected drift in hub-synced workflows. Re-synced from \`arndvs/sandcastle-hub\` templates." || true + --body "Detected drift in hub-synced workflows. Re-synced from \`arndvs/ctrlshft-hub\` templates." || true - name: Open review PR on drift if: steps.drift.outputs.drifted == 'true' diff --git a/AGENTS.md b/AGENTS.md index d0710e2f..929e94a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ file-writing tasks. - `shft/templates/workflows/**` — product workflow templates (mirror of hub) - `shft/templates/actions/**`, `shft/templates/prompts/**`, `shft/templates/extractions/**` -- `shft/engine/**` — engine source (publishes to `sandcastle-hub`) +- `shft/engine/**` — engine source (publishes to `ctrlshft-hub`) - `test/**`, `docs/adr/**`, `plans/**`, `bin/**` - `REPO_TOPOLOGY.md` — canonical topology doc @@ -39,6 +39,6 @@ Resolve these here, then pull back to dotfiles (Direction A in ## Hub relationship -Engine + canonical templates live in `arndvs/sandcastle-hub`. Changes to engine +Engine + canonical templates live in `arndvs/ctrlshft-hub`. Changes to engine or templates go hub-first (edit hub, run `test/hub-smoke-coverage.sh`, mirror back via `bin/sync-hub-templates.sh`). See `~/dotfiles/seams/vendor-sandcastle.md`. \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 441b801e..a4df492a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,14 +46,14 @@ Slice 7.2: CLI entry points (`ctrl` & `shft`). - HUD: renamed `esc`→`str` helper, removed dead CSS class, replaced `setInterval` with `setTimeout` ### Added (post-0.7.0 feature set) -- **Sandcastle** — CI-triggered, label-driven AFK agent platform. Engine lives in `arndvs/sandcastle-hub` (single source of truth); consumers reference it remotely via `uses: arndvs/sandcastle-hub/...@main`. This repo mirrors the hub's templates into `shft/templates/` via `bin/sync-hub-templates.sh`. +- **Sandcastle** — CI-triggered, label-driven AFK agent platform. Engine lives in `arndvs/ctrlshft-hub` (single source of truth); consumers reference it remotely via `uses: arndvs/ctrlshft-hub/...@main`. This repo mirrors the hub's templates into `shft/templates/` via `bin/sync-hub-templates.sh`. - **Copilot Review Bridge** — FastAPI webhook → SQLite queue → `shft afk` worker pipeline (`bridge/`, `systemd/`, `bin/bridge-install.sh`, `ctrl bridge`). - **Sandcastle workflow templates** — 16 `agent-*.yml` stubs + `labels-sync.yml` + `sandcastle-drift.yml` covering the issue→plan→implement→PR→fix→merge lifecycle. - **`ctrl`/`shft` CLI expansion** — `shft proxy`, `shft engine`, `shft worktree`, `ctrl bridge` subcommands. - **Skills catalog growth** — 20 → 56 public skills (agent-assets, anti-ai-slop, better-auth, frontend-design, halbert-copy-editor, mobile-dev, performance-audit, etc.). - **Rules growth** — 4 → 14 path-gated convention files. - **Hooks growth** — 8 → 20+ lifecycle guards (git-workflow-gate, plan-quality-gate, test-gate, feedback-memory-gate, hud-reads, hud-session, etc.). -- **8 ADRs** — vendor-boundary, four-tier disclosure, HUD observability, hosted proxy, agent parity, decision ledger, executable-doc policy, sandcastle-hub. +- **8 ADRs** — vendor-boundary, four-tier disclosure, HUD observability, hosted proxy, agent parity, decision ledger, executable-doc policy, ctrlshft-hub. - **Global git hooks** — `git-hooks/` dispatchers (pre-commit, pre-push with public-promotion guard). - **Test suite** — shell + Python tests for bridge, hooks, sandcastle smoke, copilot drift. - **`skills-lock.json`** — SHA-256 provenance lock + drift detection. diff --git a/CONTEXT.md b/CONTEXT.md index 56155515..04bf8717 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -23,7 +23,7 @@ ## Sandcastle dogfood This repo is a Sandcastle consumer under the hub model. The engine is NOT -vendored — it runs from `arndvs/sandcastle-hub` via the `agent-run` composite +vendored — it runs from `arndvs/ctrlshft-hub` via the `agent-run` composite action. `.sandcastle/` holds only a `hub-version.json` SHA-lock (plus local prompt overrides); the engine is referenced remotely, never copied in. diff --git a/README.md b/README.md index fb421618..b71660b8 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ ctrl+shft is one part of a connected system. The repos work together: ```mermaid graph LR - HUB["sandcastle-hub
engine + templates
(single source of truth)"] + HUB["ctrlshft-hub
engine + templates
(single source of truth)"] PROD["ctrlshft-public
product source + producer"] DOT["dotfiles-private
private overlay"] CMD["cmd
business knowledge layer"] @@ -43,7 +43,7 @@ graph LR ``` - **`arndvs/ctrlshft`** — the public product source (this repo). Producer of the Sandcastle engine, mirror of the hub's templates. -- **`arndvs/sandcastle-hub`** — single source of truth for the Sandcastle engine, templates, actions, and labels. Consumers reference it remotely via `uses: arndvs/sandcastle-hub/...@main`; nothing is vendored. +- **`arndvs/ctrlshft-hub`** — single source of truth for the Sandcastle engine, templates, actions, and labels. Consumers reference it remotely via `uses: arndvs/ctrlshft-hub/...@main`; nothing is vendored. - **`dotfiles-private`** — Aaron's private overlay: secrets, `_local/` skills and instructions, machine-local state. Never promoted to public. - **`arndvs/cmd`** — the business knowledge layer. "ctrl+shft+cmd": ctrl configures how agents code, cmd configures what agents know about your business. - **`arndvs/claude-code-copilot`** — the runtime proxy that lets Claude Code reach Copilot/OpenRouter models. Infrastructure, not product content. @@ -475,10 +475,10 @@ Sandcastle is the CI-triggered evolution of the `shft` bash loop. Instead of a l ### The hub model -The engine is **not vendored** into consumers. It lives in one place — [`arndvs/sandcastle-hub`](https://github.com/arndvs/sandcastle-hub) — and consumers reference it remotely: +The engine is **not vendored** into consumers. It lives in one place — [`arndvs/ctrlshft-hub`](https://github.com/arndvs/ctrlshft-hub) — and consumers reference it remotely: ```yaml -uses: arndvs/sandcastle-hub/actions/agent-run@main +uses: arndvs/ctrlshft-hub/actions/agent-run@main ``` A consumer repo keeps only three things: @@ -538,7 +538,7 @@ Plus `labels-sync.yml` (weekly label reconciliation) and `sandcastle-drift.yml` bash ~/dotfiles/bin/init-sandcastle.sh # install the Sandcastle consumer contract ``` -See [shft/README.md](shft/README.md) and the hub's [docs](https://github.com/arndvs/sandcastle-hub) for the full platform spec. +See [shft/README.md](shft/README.md) and the hub's [docs](https://github.com/arndvs/ctrlshft-hub) for the full platform spec. --- diff --git a/bin/init-sandcastle.sh b/bin/init-sandcastle.sh index 9579ef0b..a9b7c712 100644 --- a/bin/init-sandcastle.sh +++ b/bin/init-sandcastle.sh @@ -4,7 +4,7 @@ # Usage: ctrl init-sandcastle [--branch main] [--model claude-opus-4-7] [--sandbox none] [--no-proxy] [--with-proxy-canary] [--force] # # Installs thin workflow stubs that reference the Sandcastle hub engine remotely -# (uses: arndvs/sandcastle-hub/actions/agent-run@), writes a hub-version pin, +# (uses: arndvs/ctrlshft-hub/actions/agent-run@), writes a hub-version pin, # creates config, sets up prompt directory, creates GitHub labels, and prints a # checklist of manual steps. The engine is NOT vendored — it lives in the hub. @@ -97,7 +97,7 @@ fi # Hub reference for the engine. Consumers reference the hub action remotely; # the engine is never vendored. Override with SANDBOX_HUB_REF (branch/tag/SHA). SANDBOX_HUB_REF="${SANDBOX_HUB_REF:-main}" -SANDBOX_HUB_ACTION="arndvs/sandcastle-hub/actions/agent-run@${SANDBOX_HUB_REF}" +SANDBOX_HUB_ACTION="arndvs/ctrlshft-hub/actions/agent-run@${SANDBOX_HUB_REF}" # Render a workflow template with variable + auth-mode substitution. # Proxy mode (default): keeps LITELLM_* env vars (routes through the proxy). diff --git a/bin/preflight-sandcastle.sh b/bin/preflight-sandcastle.sh index 6c2e9988..33973e78 100644 --- a/bin/preflight-sandcastle.sh +++ b/bin/preflight-sandcastle.sh @@ -309,7 +309,7 @@ for required_path in ".sandcastle/hub-version.json" ".github/workflows" "sandcas fi done -# Hub model: the engine lives in arndvs/sandcastle-hub, not vendored here. +# Hub model: the engine lives in arndvs/ctrlshft-hub, not vendored here. # Verify the consumer references the hub and the hub is reachable. if [[ -f ".sandcastle/hub-version.json" ]]; then if node -e "JSON.parse(require('fs').readFileSync('.sandcastle/hub-version.json', 'utf8'))" >/dev/null 2>&1; then @@ -320,10 +320,10 @@ if [[ -f ".sandcastle/hub-version.json" ]]; then fi if [[ "$SKIP_ENGINE" == false ]] && command -v gh >/dev/null 2>&1; then - if gh api repos/arndvs/sandcastle-hub/contents/actions/agent-run/action.yml --jq '.size' >/dev/null 2>&1; then - _pass_class "CONFIG" "Hub engine action reachable (arndvs/sandcastle-hub)" + if gh api repos/arndvs/ctrlshft-hub/contents/actions/agent-run/action.yml --jq '.size' >/dev/null 2>&1; then + _pass_class "CONFIG" "Hub engine action reachable (arndvs/ctrlshft-hub)" else - _fail_class "CONFIG" "Hub engine action not reachable (arndvs/sandcastle-hub)" + _fail_class "CONFIG" "Hub engine action not reachable (arndvs/ctrlshft-hub)" fi fi @@ -362,7 +362,7 @@ if [[ "$SKIP_DRIFT" == true ]]; then _warn_class "CONFIG" "Skipped hub SHA-drift check" elif [[ -f ".sandcastle/hub-version.json" ]] && command -v gh >/dev/null 2>&1; then pinned="$(jq -r '.lastPinnedSha' .sandcastle/hub-version.json 2>/dev/null || echo "unknown")" - latest="$(gh api repos/arndvs/sandcastle-hub/commits/main --jq '.sha' 2>/dev/null | cut -c1-7 || echo "unknown")" + latest="$(gh api repos/arndvs/ctrlshft-hub/commits/main --jq '.sha' 2>/dev/null | cut -c1-7 || echo "unknown")" if [[ "$pinned" == "$latest" ]]; then _pass_class "CONFIG" "Hub SHA in sync (pinned ${pinned})" else diff --git a/bin/sync-hub-templates.sh b/bin/sync-hub-templates.sh index 2f5de7a8..ad111506 100644 --- a/bin/sync-hub-templates.sh +++ b/bin/sync-hub-templates.sh @@ -2,7 +2,7 @@ # sync-hub-templates.sh — Keep the hub's canonical workflow templates in sync # with the producer's shft/templates mirror. # -# The hub (arndvs/sandcastle-hub) is the single source of truth for the +# The hub (arndvs/ctrlshft-hub) is the single source of truth for the # Sandcastle engine AND its workflow templates. The producer (ctrlshft / # shft/templates) mirrors the shared template contract so consumers installed # via init-sandcastle get identical stubs regardless of which checkout @@ -25,7 +25,7 @@ set -euo pipefail DOTFILES="${DOTFILES:-$HOME/dotfiles}" source "$DOTFILES/bin/_lib.sh" -HUB="${HUB:-$HOME/dev/clients/sandcastle-hub}" +HUB="${HUB:-$HOME/dev/clients/ctrlshft-hub}" PRODUCER="${PRODUCER:-$HOME/dev/clients/ctrlshft-public}" CHECK_ONLY=false diff --git a/bin/update-sandcastle.sh b/bin/update-sandcastle.sh index 139e0e24..f77e9093 100644 --- a/bin/update-sandcastle.sh +++ b/bin/update-sandcastle.sh @@ -2,8 +2,8 @@ # update-sandcastle.sh — DEPRECATED. # # The Sandcastle engine is no longer vendored into consumer repos. It lives in -# the public hub repo `arndvs/sandcastle-hub` (single source of truth), and -# consumers reference it remotely via `uses: arndvs/sandcastle-hub/...@`. +# the public hub repo `arndvs/ctrlshft-hub` (single source of truth), and +# consumers reference it remotely via `uses: arndvs/ctrlshft-hub/...@`. # # This script previously detected drift between vendored Sandcastle files and # the source checkout, then re-vendored them. That flow is retired — there is @@ -14,7 +14,7 @@ # fail with a clear, actionable message instead of a confusing error. # # Replacement: hub releases are managed in the hub repo: -# cd ~/dev/clients/sandcastle-hub && hub/release.sh [patch|minor|major|] +# cd ~/dev/clients/ctrlshft-hub && hub/release.sh [patch|minor|major|] # # Consumers pin via `.sandcastle/hub-version.json` (ref + lastPinnedSha) and the # SHA-drift workflow opens a review PR when the hub advances. @@ -31,11 +31,11 @@ for arg in "$@"; do echo "" echo "DEPRECATED — the Sandcastle engine is no longer vendored." echo "" - echo "The engine lives in arndvs/sandcastle-hub (single source of truth)." + echo "The engine lives in arndvs/ctrlshft-hub (single source of truth)." echo "Consumers reference it remotely; nothing is vendored." echo "" echo "To release a new hub version:" - echo " cd ~/dev/clients/sandcastle-hub && hub/release.sh [patch|minor|major|]" + echo " cd ~/dev/clients/ctrlshft-hub && hub/release.sh [patch|minor|major|]" echo "" echo "Consumers pin via .sandcastle/hub-version.json; the SHA-drift" echo "workflow opens a review PR when the hub advances." @@ -46,11 +46,11 @@ done yellow "update-sandcastle is deprecated — the Sandcastle engine is no longer vendored." yellow "" -yellow "The engine lives in arndvs/sandcastle-hub (single source of truth)." -yellow "Consumers reference it remotely via 'uses: arndvs/sandcastle-hub/...@'." +yellow "The engine lives in arndvs/ctrlshft-hub (single source of truth)." +yellow "Consumers reference it remotely via 'uses: arndvs/ctrlshft-hub/...@'." yellow "" yellow "To release a new hub version:" -yellow " cd ~/dev/clients/sandcastle-hub && hub/release.sh [patch|minor|major|]" +yellow " cd ~/dev/clients/ctrlshft-hub && hub/release.sh [patch|minor|major|]" yellow "" yellow "Consumers pin via .sandcastle/hub-version.json; the SHA-drift workflow" yellow "opens a review PR when the hub advances." diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 4416576e..e90c2aaf 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -37,7 +37,7 @@ graph TB HOOKS -->|"emit events"| HUD SHFT -->|"consumes"| ISSUES["GitHub Issues backlog"] BRIDGE -->|"webhook → queue → worker"| SHFT - SHFT -->|"runs engine via"| HUB["sandcastle-hub
(single source of truth)"] + SHFT -->|"runs engine via"| HUB["ctrlshft-hub
(single source of truth)"] ``` --- @@ -158,4 +158,4 @@ The planning pipeline chains skills end-to-end: | [ADR-005](adr/ADR-005-sandcastle-agent-parity.md) | Sandcastle agent config parity (local ↔ CI) | | [ADR-006](adr/ADR-006-decision-ledger.md) | Verifiable decision history & the Decision Ledger | | [ADR-007](adr/ADR-007-executable-doc-policy.md) | Executable documentation policy | -| [ADR-008](adr/ADR-008-sandcastle-hub.md) | Single-source-of-truth hub for the Sandcastle engine | +| [ADR-008](adr/ADR-008-ctrlshft-hub.md) | Single-source-of-truth hub for the Sandcastle engine | diff --git a/docs/adr/ADR-008-sandcastle-hub.md b/docs/adr/ADR-008-ctrlshft-hub.md similarity index 96% rename from docs/adr/ADR-008-sandcastle-hub.md rename to docs/adr/ADR-008-ctrlshft-hub.md index 16bc45b5..959f2d66 100644 --- a/docs/adr/ADR-008-sandcastle-hub.md +++ b/docs/adr/ADR-008-ctrlshft-hub.md @@ -1,4 +1,4 @@ -# ADR-008 — Single-source-of-truth hub for the Sandcastle engine +# ADR-008 — Single-source-of-truth hub for the ctrl+shft agent engine **Status:** Accepted **Date:** 2026-08-15 @@ -27,7 +27,7 @@ The engine's own code was already designed to be hub-compatible: `resolveDefault ### The hub -- **New public repo** (`arndvs/sandcastle-hub`) — the sole home of: +- **New public repo** (`arndvs/ctrlshft-hub`) — the sole home of: - the engine (`engine/` — lib, workflows, schemas, `run.ts`, tests) - templates (`templates/` — prompts, extractions, scripts, hooks) - composite actions (`actions/` — a single `agent-run` action encapsulating setup + preflight + engine run + publish + summary) @@ -39,7 +39,7 @@ The engine's own code was already designed to be hub-compatible: `resolveDefault - Keep: `sandcastle.config.json`, `CONTEXT.md`, `docs/adr/`, project prompts, secrets (unchanged). - Replace the ~101 vendored files with: - - N thin workflow stubs (`~3 lines` each, `uses: arndvs/sandcastle-hub/.sandcastle/actions/agent-run@main`), + - N thin workflow stubs (`~3 lines` each, `uses: arndvs/ctrlshft-hub/.sandcastle/actions/agent-run@main`), - one `.sandcastle/hub-version.json` SHA-lock (`{ ref, lastPinnedSha, reviewedAt }`). ### Pin strategy diff --git a/docs/archive/sandcastle-hub-architecture.md b/docs/archive/sandcastle-hub-architecture.md index c71f5d08..58a0a1cd 100644 --- a/docs/archive/sandcastle-hub-architecture.md +++ b/docs/archive/sandcastle-hub-architecture.md @@ -1,9 +1,9 @@ # Sandcastle Hub Architecture -> **Status:** Implemented (pilot) — hub live at `arndvs/sandcastle-hub`, cmd-public migrated (PR #19) +> **Status:** Implemented (pilot) — hub live at `arndvs/ctrlshft-hub`, cmd-public migrated (PR #19) > **Date:** 2026-08-15 > **Author:** Aaron Davis -> **Applies to:** the public hub repo `arndvs/sandcastle-hub` and all 8 consumer repos +> **Applies to:** the public hub repo `arndvs/ctrlshft-hub` and all 8 consumer repos --- @@ -49,9 +49,9 @@ The fix is to stop shipping the engine and start **referencing** it: a public hu | Decision | Choice | Rationale | | --- | --- | --- | -| Hub repo | **New public repo** `arndvs/sandcastle-hub` | `arndvs/sandcastle` is taken by a fork of mattpocock/sandcastle. A dedicated repo separates the engine product from the ctrlshft tooling monolith and gives the portfolio artifact a clean name. | +| Hub repo | **New public repo** `arndvs/ctrlshft-hub` | `arndvs/sandcastle` is taken by a fork of mattpocock/sandcastle. A dedicated repo separates the engine product from the ctrlshft tooling monolith and gives the portfolio artifact a clean name. | | Consumer refs | **`@main` + monthly SHA-lock review** | `@main` gives instant propagation (no re-vendor step at all); the drift workflow shifts from "re-vendor 8 repos" to "review and pin a SHA monthly". Branch-pin `@dev` rejected: consumers should run stable engine, not the dev branch. | -| Action model | **Composite actions** (remote, `uses: arndvs/sandcastle-hub/.sandcastle/actions/agent-run@main`) | Composite actions are self-contained, versionable via ref, and keep secrets/token plumbing local to the hub. A single `agent-run` action encapsulates setup + preflight + engine + publish + summary. | +| Action model | **Composite actions** (remote, `uses: arndvs/ctrlshft-hub/.sandcastle/actions/agent-run@main`) | Composite actions are self-contained, versionable via ref, and keep secrets/token plumbing local to the hub. A single `agent-run` action encapsulates setup + preflight + engine + publish + summary. | | Workflow model | **Reusable workflows** for the 3 lifecycle-heavy jobs (plan/implement/review); **inline stubs** for the rest | `workflow_call` reduces consumer surface for the complex jobs; simple scheduled jobs stay as thin local stubs calling the composite action. Avoids the "reusable workflows can't call each other well" trap for the publish/summary steps. | | Engine checkout strategy | **Hub action checks out hub repo at the pinned ref** into a scratch dir; consumer repo stays checked out in `github.workspace` | The engine needs the consumer's repo context (git, issues) at `github.workspace`, so the hub clone is side-by-side. `run.ts` is invoked with the consumer cwd as `repoDir`. | | Consumer config | `sandcastle.config.json` stays **in the consumer repo** (project-owned) | Config is per-repo policy (baseBranch, disabledWorkflows, excludedPaths, proxy settings). Engine schema already supports defaults + env overrides; config loading is unchanged. | @@ -74,7 +74,7 @@ C4Context Person(dev, "Repo maintainer", "Owns a consumer repo and runs the Sandcastle agent workflows") - System(hub, "sandcastle-hub (public)", "Single source of truth for the Sandcastle engine: composite actions, reusable workflows, templates, scripts, hooks, labels. Referenced remotely by consumers.") + System(hub, "ctrlshft-hub (public)", "Single source of truth for the Sandcastle engine: composite actions, reusable workflows, templates, scripts, hooks, labels. Referenced remotely by consumers.") System(github, "GitHub Actions", "Executes consumer workflow stubs and hub composite actions on schedule / dispatch / PR events") System_Ext(cmd, "cmd-public", "Consumer repo — config + workflow stubs only") @@ -103,11 +103,11 @@ C4Context ```mermaid C4Container - title sandcastle-hub — container layout + title ctrlshft-hub — container layout Person(dev, "Repo maintainer") - Container_Boundary(hub, "sandcastle-hub (public repo)") { + Container_Boundary(hub, "ctrlshft-hub (public repo)") { Container(actions, "actions/", "composite actions", "agent-run (setup+preflight+engine+publish+summary), sandcastle-setup, sandcastle-teardown") Container(workflows, ".github/workflows/", "reusable workflows (workflow_call)", "agent-plan-issue, agent-implement-*, agent-review-* — shared lifecycle jobs") Container(engine, "engine/", "TypeScript engine", "lib/, workflows/, schemas/, run.ts, package.json, tsconfig — MUST sit beside templates/ (sibling layout, verified against resolveDefaultTemplatesDir)") @@ -116,7 +116,7 @@ C4Container Container(release, "hub/", "Release tooling", "release.sh — bump SHA-lock, tag releases") } - Container(stub, "consumer workflow stub", "3-line YAML", "uses: arndvs/sandcastle-hub/.../agent-run@main — the ONLY thing a consumer tracks") + Container(stub, "consumer workflow stub", "3-line YAML", "uses: arndvs/ctrlshft-hub/.../agent-run@main — the ONLY thing a consumer tracks") Container(config, "sandcastle.config.json", "consumer config", "baseBranch, disabledWorkflows, excludedPaths, proxy, promptDir") Rel(dev, actions, "Maintains") @@ -168,7 +168,7 @@ jobs: group: agent-architecture-review cancel-in-progress: false steps: - - uses: arndvs/sandcastle-hub/.sandcastle/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/.sandcastle/actions/agent-run@main with: workflow: architecture-review ref: main @@ -181,7 +181,7 @@ The composite `agent-run` action encapsulates: workflow-enabled check → proxy ```mermaid graph LR - HUB["arndvs/sandcastle-hub (public)
engine + actions + templates + labels"] + HUB["arndvs/ctrlshft-hub (public)
engine + actions + templates + labels"] C1["cmd-public"] C2["launch"] C3["aligned"] @@ -225,7 +225,7 @@ graph LR ## 6. Hub → Consumer migration plan ### Phase 0 — Foundations (hub repo) -1. Create `arndvs/sandcastle-hub` (public, README + LICENSE + CODEOWNERS). +1. Create `arndvs/ctrlshft-hub` (public, README + LICENSE + CODEOWNERS). 2. `git subtree split` or copy `shft/engine` from `ctrlshft-public` into the hub `engine/` (with tests). 3. Copy `shft/templates/` → `templates/`, `.github/actions/{sandcastle-setup,sandcastle-teardown}` → `actions/`, `labels.json`, scripts, hooks. 4. Author `actions/agent-run/action.yml` (composite) — the one action that replaces the per-repo setup/preflight/run/publish/summary chain. @@ -252,7 +252,7 @@ graph LR | Risk | Mitigation | | --- | --- | -| Composite action `uses:` a repo-local path (`./`) — actions must reference their own repo via `uses: arndvs/sandcastle-hub/...` from the consumer | All hub-internal references use the fully-qualified `arndvs/sandcastle-hub/...` form. GitHub Actions supports referencing actions in other repos; the engine code itself is checked out by the action's first step, not by path assumptions. | +| Composite action `uses:` a repo-local path (`./`) — actions must reference their own repo via `uses: arndvs/ctrlshft-hub/...` from the consumer | All hub-internal references use the fully-qualified `arndvs/ctrlshft-hub/...` form. GitHub Actions supports referencing actions in other repos; the engine code itself is checked out by the action's first step, not by path assumptions. | | `@main` instability (bad push breaks all consumers at once) | Monthly SHA-lock review + the consumer `hub-version.json` can pin to a SHA instead of `main` when a consumer wants stability. Release tagging gives a stable `vX.Y.Z` target. | | Reusable workflows + composite actions both reading the engine from two different checkout paths | Single `agent-run` composite action owns the checkout + run; reusable workflows delegate to it. One path, one contract. | | Secret sprawl (LITELLM, ANTHROPIC, AGENT_PAT) referenced from hub actions | Hub actions declare `token` input only; workflow-level `env:` from the consumer supplies the rest. Secrets stay in consumer repo secrets, never in the hub. | @@ -285,7 +285,7 @@ graph LR ## 9. Open Questions for Approval -1. **Repo name:** `arndvs/sandcastle-hub` (recommended) vs `arndvs/sandcastle-engine`. `arndvs/sandcastle` is taken by a fork. +1. **Repo name:** `arndvs/ctrlshft-hub` (recommended) vs `arndvs/sandcastle-engine`. `arndvs/sandcastle` is taken by a fork. 2. **Pin strategy:** `@main` + monthly SHA-lock review (recommended) vs strict `@vX.Y.Z` tags only. 3. **ctrlshft-public's `shft/engine`:** migrate the engine out of ctrlshft-public into the hub (recommended — one engine home), or keep a mirrored copy (drift risk returns)? 4. **Migration scope:** pilot cmd-public → rollout (recommended), or one-shot all 8? diff --git a/plans/archive/hub-model-cleanup-plan.md b/plans/archive/hub-model-cleanup-plan.md index 0105d075..4eb58525 100644 --- a/plans/archive/hub-model-cleanup-plan.md +++ b/plans/archive/hub-model-cleanup-plan.md @@ -3,7 +3,7 @@ **Status:** Proposed — awaiting approval **Date:** 2026-08-20 -**Derived from:** `docs/sandcastle-hub-architecture.md`, `docs/adr/ADR-008-sandcastle-hub.md`, `sandcastle-hub/docs/adr/ADR-001-hub-single-source.md` +**Derived from:** `docs/sandcastle-hub-architecture.md`, `docs/adr/ADR-008-ctrlshft-hub.md`, `ctrlshft-hub/docs/adr/ADR-001-hub-single-source.md` **Executed by:** AFK agents (shft) for AFK slices; human for HITL slices --- @@ -11,7 +11,7 @@ ## 1. Context The hub-model migration is functionally complete: the producer (ctrlshft-public) -removed its vendored engine, and consumers reference `arndvs/sandcastle-hub` +removed its vendored engine, and consumers reference `arndvs/ctrlshft-hub` remotely. This session fixed the producer's stale smoke-coverage test, rebuilt its `shft/templates/workflows/*` as hub stubs, deleted its stale composite actions and `sandcastle-ci.yml`, and synced everything across dotfiles + public @@ -78,10 +78,10 @@ Steps: the vendored body (.sandcastle/engine, pnpm exec tsx ../run.ts, local composite actions, `{{DEFAULT_BRANCH}}` substitutes) with the thin stub contract: - - agent-run style (7): `uses: arndvs/sandcastle-hub/actions/agent-run@main` + - agent-run style (7): `uses: arndvs/ctrlshft-hub/actions/agent-run@main` with `workflow: `, `ref: main`, `token:`, optional `extra-args`. - reusable-workflow style (6): `jobs: : uses: - arndvs/sandcastle-hub/.github/workflows/reusable-*.yml@main` with `secrets: + arndvs/ctrlshft-hub/.github/workflows/reusable-*.yml@main` with `secrets: inherit`. - Match the exact stub bodies the producer now uses (see `ctrlshft-public/.github/workflows/` for the reference contract). @@ -115,7 +115,7 @@ Steps: `test/sandcastle-smoke-coverage.sh` shape (dotfiles version — the 227-line template-coverage gate), adapted: - `agent_workflow_templates` glob → hub `templates/workflows/agent-*.yml`. - - Assert every template contains a `sandcastle-hub` reference + - Assert every template contains a `ctrlshft-hub` reference (`agent-run` composite or `reusable-*.yml` call). - Assert every template avoids old-model tokens (`.sandcastle/engine`, `pnpm --ignore-workspace exec tsx`, `uses: @@ -256,7 +256,7 @@ The final QA slice (HITL) verifies the whole system after S1-S5 merge: 1. On a fresh consumer (e.g. a throwaway branch of `claude-code-copilot`), run `ctrl init-sandcastle --force` and confirm `.github/workflows/agent-*.yml` - are thin stubs referencing `arndvs/sandcastle-hub` — no `.sandcastle/engine`, + are thin stubs referencing `arndvs/ctrlshft-hub` — no `.sandcastle/engine`, no local composite actions. 2. Confirm `templates/workflows` dirs identical between hub and producer. 3. Run producer sandcastle suite: smoke-coverage 35, init 26, report-smoke 15 diff --git a/plans/archive/sandcastle-hub-dogfood-plan.md b/plans/archive/sandcastle-hub-dogfood-plan.md index 6bd95393..2c865ec3 100644 --- a/plans/archive/sandcastle-hub-dogfood-plan.md +++ b/plans/archive/sandcastle-hub-dogfood-plan.md @@ -3,7 +3,7 @@ > **Status:** Proposed — awaiting approval > **Date:** 2026-08-18 -> **Derived from:** `docs/sandcastle-hub-architecture.md` + `docs/adr/ADR-008-sandcastle-hub.md` + `plans/sandcastle-hub-plan.md` +> **Derived from:** `docs/sandcastle-hub-architecture.md` + `docs/adr/ADR-008-ctrlshft-hub.md` + `plans/ctrlshft-hub-plan.md` > **Executed by:** AFK agents (shft) for AFK slices; human for HITL slices --- @@ -63,7 +63,7 @@ Steps: 1. On a branch in ctrlshft, remove the vendored `.sandcastle/` (engine, templates, scripts, hooks, run.ts, labels, CODING_STANDARDS) — keep `.sandcastle/prompts/` (consumer override) + config. 2. Remove vendored `.github/actions/{sandcastle-setup,sandcastle-teardown}` + vendored workflow YAMLs. 3. Add `.sandcastle/hub-version.json` SHA-lock. -4. Add 12 thin workflow stubs (7 composite-action calls + 5 reusable-workflow calls) referencing `arndvs/sandcastle-hub`. +4. Add 12 thin workflow stubs (7 composite-action calls + 5 reusable-workflow calls) referencing `arndvs/ctrlshft-hub`. 5. Add SHA-drift + hub-backed labels-sync workflows. 6. **Remove `shft/engine/`** (fully delegate to hub) — update `docs/ARCHITECTURE.md`, `docs/adr/ADR-008`, README to point at hub as engine home. 7. Open PR to `dev` for review. @@ -92,7 +92,7 @@ Type: AFK Size: M Blocked by: none (can run parallel to S2/S3) Steps: -1. Create `sandcastle-hub/hub/release.sh`: +1. Create `ctrlshft-hub/hub/release.sh`: - Reads `hub-version.json` template (or generates one). - Bumps `lastPinnedSha` to hub latest `main` SHA. - Tags `vX.Y.Z` (semver bump via arg or auto-increment). diff --git a/plans/archive/sandcastle-hub-plan.md b/plans/archive/sandcastle-hub-plan.md index 43ae82f2..40c0da89 100644 --- a/plans/archive/sandcastle-hub-plan.md +++ b/plans/archive/sandcastle-hub-plan.md @@ -3,14 +3,14 @@ > **Status:** Proposed — awaiting approval > **Date:** 2026-08-15 -> **Derived from:** `docs/sandcastle-hub-architecture.md` + `docs/adr/ADR-008-sandcastle-hub.md` +> **Derived from:** `docs/sandcastle-hub-architecture.md` + `docs/adr/ADR-008-ctrlshft-hub.md` > **Executed by:** AFK agents (shft) for AFK slices; human for HITL slices --- ## 1. Context -Sandcastle's agent engine is currently vendored as ~101 files into each of 8 consumer repos, causing weekly drift, re-vendor churn, and nightly agents proposing edits to engine copies. We are replacing the vendoring model with a **public hub repo** (`arndvs/sandcastle-hub`) that is the single source of truth: consumers keep only `sandcastle.config.json` + thin workflow stubs + a 1-file SHA-lock, and reference the hub's remote composite actions via `uses: arndvs/sandcastle-hub/...@main`. This eliminates drift by construction, turns 8 parallel re-vendor PRs into a single hub release, and produces a public portfolio artifact. The engine's template-resolution and prompt-override code already supports this model (verified against `shft/engine/lib/default-template-paths.ts` and `resolve-prompt.ts`). +Sandcastle's agent engine is currently vendored as ~101 files into each of 8 consumer repos, causing weekly drift, re-vendor churn, and nightly agents proposing edits to engine copies. We are replacing the vendoring model with a **public hub repo** (`arndvs/ctrlshft-hub`) that is the single source of truth: consumers keep only `sandcastle.config.json` + thin workflow stubs + a 1-file SHA-lock, and reference the hub's remote composite actions via `uses: arndvs/ctrlshft-hub/...@main`. This eliminates drift by construction, turns 8 parallel re-vendor PRs into a single hub release, and produces a public portfolio artifact. The engine's template-resolution and prompt-override code already supports this model (verified against `shft/engine/lib/default-template-paths.ts` and `resolve-prompt.ts`). --- @@ -18,7 +18,7 @@ Sandcastle's agent engine is currently vendored as ~101 files into each of 8 con | Decision | Choice | | --- | --- | -| Hub repo | New public repo `arndvs/sandcastle-hub` (`arndvs/sandcastle` is taken by a fork) | +| Hub repo | New public repo `arndvs/ctrlshft-hub` (`arndvs/sandcastle` is taken by a fork) | | Consumer refs | `@main` + monthly SHA-lock review (per-user approval: "go with the recommended suggestions") | | Action model | Composite actions; single `agent-run` action (setup+preflight+engine+publish+summary) | | Workflow model | Reusable `workflow_call` jobs for lifecycle-heavy agents; inline stubs for simple scheduled agents | @@ -44,11 +44,11 @@ Type: AFK Size: S Blocked by: none Steps: -1. `gh repo create arndvs/sandcastle-hub --public --description "Single source of truth for the Sandcastle agent engine — composite actions, reusable workflows, templates, and the TypeScript engine."` -2. Clone to `~/dev/clients/sandcastle-hub`. +1. `gh repo create arndvs/ctrlshft-hub --public --description "Single source of truth for the Sandcastle agent engine — composite actions, reusable workflows, templates, and the TypeScript engine."` +2. Clone to `~/dev/clients/ctrlshft-hub`. 3. Add `.gitignore` (node_modules, *.log, .env*), `LICENSE` (MIT), `README.md` placeholder, `CODEOWNERS` (maintainer). 4. Set default branch `main`; push initial commit. -Acceptance criteria: Public repo exists; `gh repo view arndvs/sandcastle-hub` succeeds; README renders. +Acceptance criteria: Public repo exists; `gh repo view arndvs/ctrlshft-hub` succeeds; README renders. Feedback loops: `gh repo view`, `git status`. --- @@ -58,7 +58,7 @@ Type: AFK Size: M Blocked by: S1 Steps: -1. Copy `ctrlshft-public/shft/engine/` → `sandcastle-hub/engine/` (lib/, workflows/, schemas/, run.ts, package.json, pnpm-lock.yaml, tsconfig.json, test/). +1. Copy `ctrlshft-public/shft/engine/` → `ctrlshft-hub/engine/` (lib/, workflows/, schemas/, run.ts, package.json, pnpm-lock.yaml, tsconfig.json, test/). 2. **Layout constraint (verified):** `engine/` and `templates/` MUST be siblings. `resolveDefaultTemplatesDir` walks `../../` from `engine/workflows/` and `run.ts` walks `../` from `engine/run.ts` — both resolve to `/templates/prompts` ONLY if templates live beside engine. Do NOT nest templates under engine/. 3. Verify no absolute paths or producer-repo assumptions in engine code (grep for `ctrlshft-public`, `$HOME/dev/clients`, `SANDBOX_PRODUCER`). 4. Add engine test suite to hub CI: `.github/workflows/engine-ci.yml` (runs `pnpm exec tsx --test` over `engine/test/` on every PR to hub). @@ -73,10 +73,10 @@ Type: AFK Size: M Blocked by: S1 Steps: -1. Copy `ctrlshft-public/shft/templates/` → `sandcastle-hub/templates/` (prompts/ 14, extractions/ 6, scripts/, hooks/, copilot-setup-steps.yml). -2. Copy `ctrlshft-public/shft/templates/scripts/` → `sandcastle-hub/scripts/` (proxy_preflight.sh, check-workflow-enabled.sh, probes, tests). -3. Copy `labels.json` → `sandcastle-hub/labels.json`. -4. Copy hooks (`block-npx-tsc.sh`, `guard-sandcastle-gitflow.sh`) → `sandcastle-hub/hooks/`. +1. Copy `ctrlshft-public/shft/templates/` → `ctrlshft-hub/templates/` (prompts/ 14, extractions/ 6, scripts/, hooks/, copilot-setup-steps.yml). +2. Copy `ctrlshft-public/shft/templates/scripts/` → `ctrlshft-hub/scripts/` (proxy_preflight.sh, check-workflow-enabled.sh, probes, tests). +3. Copy `labels.json` → `ctrlshft-hub/labels.json`. +4. Copy hooks (`block-npx-tsc.sh`, `guard-sandcastle-gitflow.sh`) → `ctrlshft-hub/hooks/`. 5. Fix any relative-path assumptions in scripts (they must run against the consumer workspace, not the hub). 6. Add `hub/release.sh`: reads `hub-version.json` template, bumps `lastPinnedSha` to hub latest, tags `vX.Y.Z`. Acceptance criteria: All template/script/label/hook files exist in hub; scripts pass shellcheck; release.sh dry-runs cleanly. @@ -89,12 +89,12 @@ Type: HITL (engine-run semantics need validation) Size: L Blocked by: S2, S3 Steps: -1. Create `sandcastle-hub/actions/agent-run/action.yml` (composite) with inputs: `workflow`, `ref` (default main), `token`, `timeout-minutes`, `extra-args`. +1. Create `ctrlshft-hub/actions/agent-run/action.yml` (composite) with inputs: `workflow`, `ref` (default main), `token`, `timeout-minutes`, `extra-args`. 2. Inside the action: - - Step A: checkout hub at `ref` into `${{ runner.temp }}/sandcastle-hub` (uses `actions/checkout` with the hub repo + pinned ref). + - Step A: checkout hub at `ref` into `${{ runner.temp }}/ctrlshft-hub` (uses `actions/checkout` with the hub repo + pinned ref). - Step B: workflow-enabled check — run `bash /engine/scripts/check-workflow-enabled.sh ` with **cwd = consumer workspace** (reads consumer `sandcastle.config.json` `disabledWorkflows`). - Step C: proxy preflight (LITELLM envs from consumer secrets). - - Step D: engine install + run — **cwd = consumer workspace**, `cd ${{ runner.temp }}/sandcastle-hub/engine && pnpm --ignore-workspace exec tsx run.ts --repo ${{ github.workspace }} `. The `--repo` flag is REQUIRED (verified: `run.ts` defaults repoDir to the hub checkout, not the consumer). + - Step D: engine install + run — **cwd = consumer workspace**, `cd ${{ runner.temp }}/ctrlshft-hub/engine && pnpm --ignore-workspace exec tsx run.ts --repo ${{ github.workspace }} `. The `--repo` flag is REQUIRED (verified: `run.ts` defaults repoDir to the hub checkout, not the consumer). - Step E: publish (issue create / PR update per workflow output). - Step F: summarize run to step summary. 3. Port the retry-once loop from the current workflows. @@ -125,7 +125,7 @@ Blocked by: S4, S5 Steps: 1. In `cmd-public`, delete vendored `.sandcastle/` (84 files), `.github/actions/`, and the 17 workflow YAMLs (keep `sandcastle.config.json`, `CONTEXT.md`, project prompts). 2. Add `.sandcastle/hub-version.json` = `{ "ref": "main", "lastPinnedSha": , "reviewedAt": "" }`. -3. Add N stub workflows (`agent-*.yml`, ~3 lines each, `uses: arndvs/sandcastle-hub/.sandcastle/actions/agent-run@main`). +3. Add N stub workflows (`agent-*.yml`, ~3 lines each, `uses: arndvs/ctrlshft-hub/.sandcastle/actions/agent-run@main`). 4. Baseline-compare: run the full workflow set and confirm outputs/labels/issue creation match the pre-migration behavior. 5. Keep `sandcastle-drift.yml` (now SHA-drift) and `require-regression-guard.yml` (if consumer-owned). Acceptance criteria: `cmd-public` has no vendored engine; all 17 agents trigger and produce equivalent results; no drift PRs after 1 week. @@ -166,9 +166,9 @@ Type: HITL (narrative + portfolio quality) Size: M Blocked by: S7 Steps: -1. Update `ctrlshft-public` `docs/ARCHITECTURE.md` to show the hub as the engine home (point to `arndvs/sandcastle-hub`). -2. Update `docs/adr/ADR-008-sandcastle-hub.md` status from Proposed → Accepted. -3. Update `README.md` (producer) engine section → "engine now lives in sandcastle-hub". +1. Update `ctrlshft-public` `docs/ARCHITECTURE.md` to show the hub as the engine home (point to `arndvs/ctrlshft-hub`). +2. Update `docs/adr/ADR-008-ctrlshft-hub.md` status from Proposed → Accepted. +3. Update `README.md` (producer) engine section → "engine now lives in ctrlshft-hub". 4. Update hub `README.md` with architecture diagram (C4 context + container from the architecture doc), quick-start, and contribution guide. 5. Update `CONTEXT.md`/handoff to reflect the new topology. Acceptance criteria: No doc references the old vendored-engine model as current; hub README is a clean portfolio entry point. diff --git a/plans/archive/workspace-prime-plan.md b/plans/archive/workspace-prime-plan.md index 385d5927..fb64b956 100644 --- a/plans/archive/workspace-prime-plan.md +++ b/plans/archive/workspace-prime-plan.md @@ -133,7 +133,7 @@ Steps: `shft/templates/workflows/**`, `test/`, `docs/adr/*`. Resolve via `WORKSPACE_INVARIANTS` (public copy of the doc considered canonical). Use `bin/validate-public-promotion.sh` before any public push. -2. `sandcastle-hub/AGENTS.md`: vendor source, engine + templates + labels; +2. `ctrlshft-hub/AGENTS.md`: vendor source, engine + templates + labels; one-way copy out; sees consumer stubs; never edit consumer copies. 3. `claude-code-copilot/AGENTS.md` already exists (consumer runtime) — update cross-link to invariants; add "this is runtime, not product" note. diff --git a/plans/dual-method-skill-system-v2.md b/plans/dual-method-skill-system-v2.md new file mode 100644 index 00000000..0197a76b --- /dev/null +++ b/plans/dual-method-skill-system-v2.md @@ -0,0 +1,306 @@ +# Implementation Plan v2: Skill Invocation Fix — Measurement-First + +**Supersedes:** `plans/dual-method-skill-system.md` (v1, rejected) +**Status:** DRAFT — pending Slice 0 baseline +**Reviewer findings addressed:** token claim (unverified), mechanism claim (unverified), missing baseline, +Slice 3 vs 4 contradiction, boilerplate commands, `_local/` sync cost, waved-off constraint, n=1 QA. + +--- + +## 1. Context + +A Claude Code session typed "is the readme accurate to the codebase repo? codebase audit" and the +`codebase-audit` skill did not load — zero `Skill` tool calls across the whole session, despite the skill +being present in the session's `skill_listing` with a matching trigger. A prior plan (v1) proposed a +six-slice build (command generator, catalog pruning, routing table) without first measuring the failure. +Independent review found the plan's central numbers unverified (~31K-token catalog claim was actually +~6-8K, descriptions-only) and its mechanism claim unverified (Copilot "first-class tools" vs Claude +"text blob" was inferred, not observed). The review's prescription: **measure → verify → one +intervention → remeasure → only then build.** This plan follows that order. No build happens until a +baseline exists. + +**Notable verified facts (Slice 0 groundwork, already measured):** + +| Claim | Verdict | Evidence | +| ----- | ------- | -------- | +| ~125K chars / ~31K token skill catalog | **False** | `skill_listing` attachment = 24,679 chars ≈ 6-8K tokens, 93 entries (incl. plugin skills), name+description only | +| `/audit` existed in the failed session | **False** | `command_permissions` = `{"allowedTools": []}` — no commands were surfaced at all | +| `codebase-audit` present with trigger | True | `skill_listing` contained it verbatim | +| Superpowers hook fired | True | `hook_additional_context` (3.3K chars) injected at session start | +| Prior plan "collapsed 42→55" | **Garbled** | progressive-disclosure merged at 21 shared skills; 54 now — direction was a reduction, framing was wrong | +| `disable-model-invocation` broke VS Code | Unverified | earlier plan note; never investigated what actually broke | + +## 2. Design Decisions + +| Decision | Choice | +| -------- | ------ | +| Build gate | **No build until Slice 0 baseline exists and a decision gate passes** | +| Measurement | Fresh-session invocation rate: same prompt, N sessions, count `Skill` tool calls in logs | +| Token accounting | Use real session-log token counts (via `input_tokens` in llm_request), not chars/4 | +| Mechanism verification | Inspect Copilot's actual request payload (skills in tools array?) vs Claude's (text blob + generic Skill tool) — decide via evidence, not inference | +| Intervention target | Single variable at a time: first = catalog pruning, remeasure, then decide | +| Command generator | **Deferred to fallback** — only build if pruning fails to move invocation rate | +| Generated command content | Only if built: **real dispatch content** (skill's own steps inlined), never filename-only boilerplate | +| `_local/` move | Explicit sync/tracking decision required first (gitignore cost) — tracked alternative: keep in `skills/` but exclude via a visibility manifest | +| `disable-model-invocation` | **Not used in v2** until the prior VS Code breakage is investigated and understood | +| Routing table | Only if pruning proves bloat; then measured, not assumed | +| Slice structure | Vertical, each with acceptance criteria + feedback loop; decision gates between slices, not after | + +## 3. Vertical Slices + +--- + +☐ **Slice 0: Baseline measurement (BLOCKING — everything waits on this)** +Type: AFK (mechanical) with HITL interpretation +Size: S +Blocked by: none +Steps: +1. Write `bin/measure-skill-invocation.sh`: takes a prompt + session count, spawns N fresh Claude Code + sessions (non-interactive, `-p` mode) against a fixed cwd, then greps each session log + (`~/.claude/projects//*.jsonl`) for `Skill` tool calls and `attributionSkill` markers +2. Prompt: the exact failing prompt — "is the readme accurate to the codebase repo? codebase audit" + (run in `agentic-nlq` cwd; fallback: any small repo) +3. Run **10 sessions**, record invocation rate (X/10) +4. Capture per-session context: `input_tokens` of the first `llm_request` (real token cost, not chars/4), + and whether `skill_listing` + `hook_additional_context` were both present +5. Write result to `working/research/skill-invocation-baseline.md` + +Acceptance criteria: +- Measured invocation rate with a real number (e.g. baseline = 3/10), not "sometimes" +- Per-session token counts recorded +- Reproducible: same prompt + cwd reruns produce same command + +Feedback loops: `bash bin/measure-skill-invocation.sh`, manual log inspection + +**Decision gate 0:** If baseline ≥ 8/10 → the skill system works; investigate the original session's +specific failure separately (different prompt context, model, plugin state). Stop this plan. Only proceed +if baseline < 8/10. + +--- + +☐ **Slice 1: Mechanism verification (Claude vs Copilot payloads)** +Type: HITL (requires running both harnesses + inspecting requests) +Size: M +Blocked by: none (parallel-safe with 0) +Steps: +1. Claude: capture the actual request payload sent to the model in a fresh session — confirm whether + skills are (a) a text blob attachment + one generic `Skill` tool, or (b) individual tools. Evidence: + the model's tools array in the logged request, or the `tools_*.json`/`system_prompt_*.json` sidecar + in the session dir +2. Copilot: capture its actual request payload — confirm whether each skill is a first-class tool entry + (with per-skill description) or a progressive-loading blob. Use the Copilot debug log + (`~/.copilot/logs/` or the IDE session log) or a proxy capture if available +3. Document the real difference (or sameness) in `working/research/skill-mechanism.md` +4. If both harnesses use the same mechanism → the "Copilot works, Claude doesn't" framing is wrong and + the fix targets something else entirely (e.g. model, plugin, hook) + +Acceptance criteria: +- Actual payloads inspected, not inferred — with quotes/paths as evidence +- The mechanism claim is confirmed or refuted with evidence + +Feedback loops: manual inspection; no build yet + +--- + +☐ **Slice 2: Single intervention — catalog visibility (prune), then remeasure** +Type: HITL (triage decisions) +Size: M +Blocked by: Slice 0 (need baseline to compare) +Steps: +1. Decide the intervention: reduce the `skill_listing` the model sees. Options (pick ONE): + a. Move rare skills to `_local/` (existing mechanism, but note gitignore cost — see decision table) + b. **Visibility manifest** (preferred, no move): new `skills/.visible` or frontmatter flag read by + `bootstrap.sh` + `materialize_copilot_skills` to exclude from the Claude `skill_listing` while + keeping the files tracked and synced +2. Implement the minimal exclusion (option b preferred — no `git mv`, no sync loss) +3. Rerun the **same 10-session measurement** from Slice 0 +4. Compare invocation rates; record both in the baseline research doc + +Acceptance criteria: +- Same prompt, same cwd, same N sessions; only the intervention changed +- Before/after rates recorded; deltas attributable to the intervention +- `_local/` not used unless the sync cost was explicitly accepted + +Feedback loops: `bash bin/measure-skill-invocation.sh` (same command as Slice 0) + +**Decision gate1:** If invocation rate moved up meaningfully (≥3 point gain) → bloat hypothesis holds; +continue to Slice 3 (routing, measured). If unchanged → bloat hypothesis is wrong; do NOT build routing +or generator; go to Slice 4 (alternate causes). + +--- + +☐ **Slice 3: Routing table — only if pruning helped, and measured afterward** +Type: HITL +Size: S +Blocked by: Slice 2 (decision gate1 must say "continue") +Steps: +1. Add a compact "Skill Routing" table to `CLAUDE.base.md` — only the skills that were failing to + auto-invoke, not all 54 +2. Regenerate `CLAUDE.md` + `copilot-instructions.md` via bootstrap +3. Rerun the 10-session measurement a third time +4. If routing table alone suffices without pruning (test separately if useful), keep only whichever + intervention worked; drop the other + +Acceptance criteria: +- Net token delta of the routing table is small (<500 tokens) +- Third measurement shows no regression vs Slice 2 result +- Only failing skills get routing rows + +Feedback loops: `bash bootstrap.sh`, `bash bin/measure-skill-invocation.sh` + +--- + +☐ **Slice 4: Alternate causes — only if pruning failed (decision gate1 = "stop")** +Type: HITL +Size: M +Blocked by: Slice 2, only if gate1 rejects bloat +Steps: +1. Investigate the superpowers hook (`hook_additional_context`, 3.3K chars) — is it crowding the + catalog it tells the model to read? Test disabling it for the measurement prompt +2. Investigate model: same prompt on the proxy's actual model vs a stronger model — does invocation + rate differ by model? +3. Investigate the plugin skill listing (`dataviz`, `superpowers:*` entries — 93 total vs 54 local) — + is plugin noise the real bloat? +4. Any cause shown to move the rate becomes the intervention; rerun measurement to confirm + +Acceptance criteria: +- At least one alternate cause identified that explains the baseline failure +- Confirmed by remeasurement, not inference + +Feedback loops: `bash bin/measure-skill-invocation.sh` after each candidate + +--- + +☐ **Slice 5: Command generator — only as fallback, with real content** +Type: AFK +Size: L +Blocked by: gates 0/1 (only if natural-language invocation can't be fixed by 2/3/4) +Steps: +1. Do NOT generate boilerplate. For each skill that must be reachable: write a command whose body + contains the skill's actual working steps (inlined from the reviewed SKILL.md), not + "Load the skill from " +2. Collision map kept in `commands/README.md` (documented), not hidden in a script +3. Wire into bootstrap; add coverage check as a guard, not a generator + +Acceptance criteria: +- Every generated command carries the skill's real procedure +- No "filename-only boilerplate" commands exist + +Feedback loops: `bash test/skills.sh`, manual review of generated bodies + +--- + +☐ **Slice 6: QA — controlled before/after, not n=1** +Type: HITL +Size: S +Blocked by: slices 2-5 (whatever shipped) +Steps: +1. Rerun the **full 10-session measurement** (same prompt, same cwd) post-intervention +2. Compare to baseline from Slice 0 with the delta stated +3. Run the harness regression suite (`bash test/run-all.sh`), bootstrap dry-run +4. Only report "fixed" if the invocation-rate delta is measurable and positive + +Acceptance criteria: +- Clear before/after invocation rate documented (e.g. 3/10 → 8/10) +- Regression suite green +- No "works in my one test" claims + +Feedback loops: `bash test/run-all.sh`, `bash bin/measure-skill-invocation.sh` + +--- + +## 4. Key Insights + +``` +Critical Principle: Measure the failure before designing the fix. +Why it matters: v1 was unbuildable because its size assumptions (31K tokens) were wrong by 4-5x + and its mechanism claims were inferred. A 30-minute measurement (Slice 0) now gates the entire plan. +How to apply: Every slice beyond 0 is conditional on the previous measurement's result. +Risk if ignored: Building the wrong thing confidently, again. +``` + +``` +Critical Principle: One intervention per measurement cycle. +Why it matters: The failure is probabilistic; stacking changes makes deltas uninterpretable. +How to apply: Prune → remeasure → decide. Route → remeasure → decide. Never both at once. +Risk if ignored: You ship a "fix" you can't attribute and can't reproduce. +``` + +``` +Critical Principle: Verify the mechanism in the request payload, not in the documentation. +Why it matters: The Copilot-vs-Claude mechanism theory is the load-bearing wall of the redesign, + and it was never observed. Docs describe intent; payloads describe behavior. +How to apply: Capture and quote actual requests from both harnesses before deciding what to change. +Risk if ignored: A redesign built on a false mechanism assumption. +``` + +``` +Critical Principle: No token moves into permanent context without measuring its effect. +Why it matters: v1's routing table contradicted its own bloat thesis (added always-loaded rows to fix + oversubscribed permanent context). Measured routing (Slice 3) avoids this. +How to apply: If bloat is the cause, subtract tokens; don't add them. If routing is used, it must be + small and remeasured. +Risk if ignored: Making the diagnosed problem worse in the name of fixing it. +``` + +``` +Critical Principle: Generated commands must carry information, or not exist. +Why it matters: 38 files of "Load X skill from Y. Execute." add listing cost and zero dispatch value. +How to apply: Inline the skill's real procedure into the command body, or don't generate. +Risk if ignored: A catalog of inert boilerplate that itself becomes bloat. +``` + +## 5. Dependency Graph + +``` +Slice 0 (baseline) ──┬── gate0: stop if rate ≥ 8/10 + │ + ├──▶ Slice 1 (mechanism) [parallel-safe with 0] + │ + ├──▶ Slice 2 (prune + remeasure) ── gate1 ──┬── yes → Slice 3 (routing + remeasure) + │ └── no → Slice 4 (alternate causes) + │ + └──▶ [gates 0/1 fail to fix] → Slice 5 (real-content commands, fallback only) + │ + ▼ + Slice 6 (QA: full remeasure + regression) +``` + +**Execution order:** +1. Slice 0 + Slice 1 in parallel (measure + verify mechanism) +2. Decision gate 0 (is there even a measurable failure?) +3. Slice 2 (one intervention) → decision gate 1 +4. Slice 3 **or** Slice 4 (whichever gate 1 selects) +5. Slice 5 only if natural-language invocation is still broken after 2-4 +6. Slice 6 (QA — full remeasure, never n=1) + +**Critical path is short:** 0 → 2 → (3|4) → 6. Slices 1 and 5 are research/fallback, not core. + +## 6. QA Plan + +1. **Baseline vs final**: run `bin/measure-skill-invocation.sh` once (Slice 0) and once more (Slice 6); + the plan only reports success if the invocation rate moves measurably and positively. +2. **Attribution**: every slice between baseline and final changed exactly one thing; deltas are + attributable. +3. **Regression**: `bash test/run-all.sh`, bootstrap dry-run, both harnesses still load skills from the + single `skills/` source. +4. **No n=1**: every claim of "fixed" is backed by the 10-session measurement, not a single try. + +**Rollback:** Each intervention is a separate, small commit (measure → intervene → remeasure), so any +slice can be reverted independently. No build happens unless measurement says it should. + +--- + +## What this plan fixes vs v1 (reviewer findings → resolution) + +| v1 flaw | v2 resolution | +| -------- | ------------- | +| Unverified 31K-token claim | Slice 0 measures real input_tokens; 24.7K chars ≈ 6-8K tokens already documented | +| Unverified mechanism claim | Slice 1 inspects actual payloads from both harnesses | +| No baseline; n=1 QA | Slice 0 baseline + Slice 6 full remeasure; only measurable deltas are reported | +| Slice 3-vs-4 contradiction | Routing (Slice 3) is conditional on gate1 and remeasured; never added unconditionally | +| Boilerplate commands | Slice 5 (fallback) inlines real procedure per command, or doesn't publish | +| `_local/` sync cost | Preference for a visibility manifest (option b) — no move, no gitignore loss | +| `disable-model-invocation` waved off | Not used in v2 until the prior VS Code breakage is investigated (Slice 4 candidate) | +| "Collapsed 42→55" garbled fact | Corrected in Context table: 21 at merge → 54 now; framing fixed | +| Wrong claim that `/audit` existed in-session | Documented: `command_permissions` = empty `allowedTools`; no commands were surfaced at all | \ No newline at end of file diff --git a/plans/dual-method-skill-system-v3.md b/plans/dual-method-skill-system-v3.md new file mode 100644 index 00000000..8664d1df --- /dev/null +++ b/plans/dual-method-skill-system-v3.md @@ -0,0 +1,329 @@ +# Implementation Plan v3: Skill Invocation Fix — Measurement-First (revised) + +**Supersedes:** `plans/dual-method-skill-system.md` (v1, rejected), `plans/dual-method-skill-system-v2.md` (v2) +**Status:** DRAFT — pending Slice 0 baseline +**Reviewer v2 corrections addressed:** command_permissions claim, intervention ordering (plugins first), +visibility-manifest feasibility, `-p` mode validity, session cost cap, Slice 1 gate, gate-0 threshold. + +--- + +## 1. Context + +A Claude Code session typed "is the readme accurate to the codebase repo? codebase audit" and the +`codebase-audit` skill did not load — zero `Skill` tool calls across the whole session, despite the +skill being present in the session `skill_listing` with a matching trigger, and the model being +`deepseek/deepseek-v4-flash-0731` (routed through the local LiteLLM proxy). The failure is +probabilistic: whether a flash-class model honors the skill protocol varies by session. + +Prior plans proposed builds (command generator, catalog pruning, routing table) before measuring. +Review found the size claims unverified (~31K tokens was actually ~6-8K), the mechanism claim inferred +(Copilot per-skill tools vs Claude text blob), and no baseline or controlled QA. v2 fixed the structure +(gates, single interventions, remeasure) but introduced a wrong `command_permissions` interpretation and +ordered the interventions to test the smaller half of the bloat first. v3 corrects those inputs. + +**Verified facts (this document's plan inputs):** + +| Fact | Status | Evidence | +| ---- | ------ | -------- | +| `skill_listing` = 24,679 chars ≈ 6-8K tokens, **93 entries** | ✅ measured | session log attachment | +| …of which ~54 local skills, **~28 plugin skills** (14 superpowers + others), rest built-in | ✅ measured | `find ~/.claude/plugins/cache/ -name SKILL.md` = 28 | +| Model in failed session = `deepseek/deepseek-v4-flash-0731` (proxy) | ✅ measured | 174 messages logged | +| `disable-model-invocation` is CI-deprecated | ✅ verified | `ctrlshft-public/README.md` Integrity checks: "deprecated `disable-model-invocation` flags are absent from `skills/**/SKILL.md`" | +| `.non-discoverable-skills` exists | ✅ verified | at `skills/.non-discoverable-skills` | +| …but it's a **guard** (dirs must NOT have SKILL.md), not a discovery filter | ✅ verified | `test/config-consistency.sh` Invariant 5 | +| `command_permissions = {"allowedTools": []}` | ✅ recorded | session log | +| …but it's a **permission allowlist**, not command discovery | ✅ reviewer + verified | field semantics; `~/.claude/commands/` is symlinked by bootstrap; `commands/audit.md` ships | +| Prior plan "42→55" | ❌ garbled | progressive-disclosure merged at 21 shared skills; 54 now | + +**Corrected understanding of the failed session:** `/audit` existed on disk and `~/.claude/commands/` +is symlinked, so the command was *available* to the user. The model was not shown a slash-command +listing in that transcript (no command listing attachment seen), but the user typed natural language — +whether `/audit` was discoverable isn't the failure being investigated. The real question is whether +natural-language triggering works, which Slice 0 measures. + +--- + +## 2. Design Decisions + +| Decision | Choice | +| -------- | ------ | +| Build gate | **No build until Slice 0 baseline exists and a decision gate passes** | +| Measurement | Fresh-session invocation rate: same prompt, N sessions, count `Skill` tool calls in logs | +| Session harness | `claude -p` **with hard validity precondition** — verify `skill_listing` + `hook_additional_context` arrive under `-p` before trusting any number | +| Session cost cap | **Kill each session after the first tool call** (or first N tokens) — the outcome (Skill called or not) resolves in the first assistant turn; no full audits run | +| Token accounting | Real `input_tokens` from session logs, not chars/4 | +| Intervention order | **Largest token mover first**: plugin-skill disable (settings change, no code) → local pruning → routing table (if needed) | +| Plugin control | Disable via `enabledPlugins` in `~/.claude/settings.json` (or plugin blocklist) for the measurement run — reversible, no code | +| Visibility manifest | **Dropped as primary** — `~/.claude/skills` is a symlink; a bootstrap-read manifest can't filter what Claude enumerates. Revisit only if intervention shows value AND a native exclusion exists | +| `disable-model-invocation` | **Not used** — CI fails on it (`README.md` Integrity checks); confirmed | +| Command generator | **Deferred to fallback** — only if natural-language invocation can't be fixed by 2-4 | +| Slice 1 gate | **Explicit gate**: if both harnesses share a mechanism, Slices 2-5 are re-scoped. Also: verify Copilot payload capture is even feasible before scheduling | +| Gate 0 threshold | **8/10 is deliberate but documented as a tolerance for an explicit request**; a 20% miss rate is the reported failure class — see decision gate 0 note | + +## 3. Vertical Slices + +--- + +☐ **Slice 0: Baseline measurement (BLOCKING — everything waits on this)** +Type: AFK (mechanical) with HITL interpretation +Size: S +Blocked by: none +Steps: +1. Write `bin/measure-skill-invocation.sh`: + - Spawn N fresh Claude Code sessions with a fixed prompt + cwd + - **Validity precondition (hard stop if unmet):** each session must contain BOTH `skill_listing` + and `hook_additional_context` attachments; if `claude -p` omits either, the harness is invalid — + record and switch capture method (e.g. interactive spawn + auto-command, or log replay) + - **Cost cap:** terminate each session after the first assistant tool call OR first N output tokens; + outcome (`Skill` called?) resolves in turn 1 + - Grep session logs for `Skill` tool calls and `attributionSkill` markers; record `input_tokens` + of the first `llm_request` +2. Prompt: the exact failing prompt — "is the readme accurate to the codebase repo? codebase audit" + (run in `agentic-nlq` cwd; fallback: any small repo) +3. Run **10 sessions**, record invocation rate (X/10) + per-session token cost +4. Write result to `working/research/skill-invocation-baseline.md` + +Acceptance criteria: +- Measured invocation rate (e.g. 3/10), not "sometimes" +- Validity precondition documented — either `-p` passes it or the capture method is swapped +- Per-session token counts recorded; sessions were capped, no full audits ran + +Feedback loops: `bash bin/measure-skill-invocation.sh`, manual log inspection + +**Decision gate 0 (threshold documented):** Baseline ≥ 8/10 → the skill system mostly works; the reported +failure is a specific-session issue (different prompt context/model/plugin state). Investigate separately, +note the 20% miss rate is the exact reported failure class, and treat the plan as complete only if the +baseline exceeds it convincingly. Only proceed with interventions if baseline < 8/10. + +--- + +☐ **Slice 1: Mechanism verification — Claude vs Copilot payloads (with gate)** +Type: HITL +Size: M +Blocked by: none (parallel-safe with 0) +Steps: +1. **Feasibility check first:** can Copilot's actual request payload be captured at all? Closed + extension + no proxy → may be unbuildable. If not feasible, record that and downgrade this slice +2. Claude: capture the model-visible tools/attachments in a fresh session — confirm skills are (a) text + blob attachment + one generic `Skill` tool, or (b) individual tools. Use the session-dir sidecars + (`tools_*.json`/`system_prompt_*.json`) or logged request +3. Copilot: if feasible, capture its payload — confirm per-skill tool entries vs progressive blob +4. Document evidence (quotes/paths) in `working/research/skill-mechanism.md` + +**Decision gate 1:** If both harnesses use the same mechanism → the Copilot-vs-Claude framing collapses; +re-scope Slices 2-5 (the fix targets something else — model, hook, plugin). If they differ → continue as +planned. This gate fires BEFORE any intervention, so a wrong mechanism theory can't drive the build. + +Acceptance criteria: +- Feasibility determined first (no scheduling a slice that can't run) +- Actual payload evidence, not documentation inference + +Feedback loops: manual inspection; no build yet + +--- + +☐ **Slice 2: Intervention #1 — disable plugin skills, remeasure (LARGEST token mover)** +Type: HITL (config change, taste on ordering) +Size: S +Blocked by: Slice 0 (baseline) +Steps: +1. Disable the superpowers plugin skills for the measurement: `enabledPlugins` → off (or blocklist + `superpowers@claude-plugins-official`) in `~/.claude/settings.json` +2. Confirm `skill_listing` shrinks (~28 plugin entries → local-only + built-ins) +3. Rerun the **same 10-session measurement** (same prompt, cwd, cap, precondition) +4. Compare invocation rates; record delta + +Acceptance criteria: +- Listing token count reduced; N sessions same; only the plugin setting changed +- Rate delta attributable to the plugin disable + +Feedback loops: `bash bin/measure-skill-invocation.sh` (same command as Slice 0) + +**Decision gate 2:** Meaningful rate gain (≥3 points) → plugin/hook injection is a real contributor; +keep the setting, consider making it the product change. No gain → bloat-from-plugins is dead as a +theory; the visibility cost isn't in the plugin listing. Either way, proceed to Slice 3 with this +intervention kept (or reverted) explicitly. + +--- + +☐ **Slice 3: Intervention #2 — local skill pruning, remeasure (smaller mover)** +Type: HITL (triage decisions) +Size: M +Blocked by: Slice 2 (only if plugin disable alone wasn't decisive) +Steps: +1. Reduce the local-skill listing (~54 → ~20) WITHOUT moving files: + - Primary: **plugin/SKILL.md-based exclusion if a native mechanism surfaces in Slice 1** (e.g. a + Claude-supported list), OR + - Fallback: move to `skills/_local/` (gitignored — explicit sync-tracking tradeoff documented), OR + - **Unresolved:** the symlink means a bootstrap-read manifest can't filter Claude's enumeration — + the only clean lever is the native mechanism or a change to the deployment model (materialized + filtered tree like `~/.copilot/skills`). Check Slice 1 evidence before choosing +2. Rerun the 10-session measurement +3. Record before/after + +Acceptance criteria: +- Listing reduced; same prompt/cwd/N; only the pruning changed +- The chosen mechanism is native or the deployment-model change is explicitly accepted + +Feedback loops: `bash bin/measure-skill-invocation.sh` + +**Decision gate 3:** Rate moved → keep pruning. Rate still flat → bloat hypothesis is dead; stop +intervening on catalog size; go to Slice 4. + +--- + +☐ **Slice 4: Alternate causes — hook, model, plugin listing mechanics** +Type: HITL +Size: M +Blocked by: Slice 2 or 3 (whichever gate rejects bloat) +Steps: +1. Investigate the superpowers hook (`hook_additional_context`, 3.3K chars) — it's injected + unconditionally and tells the model to read a catalog; test disabling it for the measurement +2. Investigate model capability: same prompt on the proxy's actual model vs a stronger model — does + invocation rate differ by model? (The failed session was `deepseek-v4-flash`; test `claude-sonnet-5` + fallback as a control) +3. Investigate whether the generic `Skill` tool's own description/usage is the failure point (tool + definition quality, not catalog size) +4. Any candidate shown to move the rate becomes the intervention; rerun measurement to confirm + +Acceptance criteria: +- At least one alternate cause confirmed by remeasurement +- The candidate is evidenced, not inferred + +Feedback loops: `bash bin/measure-skill-invocation.sh` per candidate + +--- + +☐ **Slice 5: Command generator — fallback only, real content** +Type: AFK +Size: L +Blocked by: gates 2-4 (only if natural-language invocation can't be fixed) +Steps: +1. Write commands whose bodies contain the skill's actual working steps (inlined from reviewed + SKILL.md), not "Load X from Y" +2. Collision map documented in `commands/README.md`, not hidden in a script +3. Wire into bootstrap; add coverage check as a guard, not a generator + +Acceptance criteria: +- Every generated command carries the skill's real procedure +- No filename-only boilerplate + +Feedback loops: `bash test/skills.sh`, manual review + +--- + +☐ **Slice 6: QA — controlled before/after, not n=1** +Type: HITL +Size: S +Blocked by: slices 2-5 +Steps: +1. Rerun the full 10-session measurement post-intervention (same prompt, cwd, cap, precondition) +2. Compare to baseline with the delta stated +3. Run `bash test/run-all.sh`, bootstrap dry-run +4. Report "fixed" only on a measurable positive delta + +Acceptance criteria: +- Baseline vs final documented (e.g. 3/10 → 8/10) +- Regression suite green +- No single-trial claims + +Feedback loops: `bash test/run-all.sh`, `bash bin/measure-skill-invocation.sh` + +--- + +## 4. Key Insights + +``` +Critical Principle: Measure the failure before designing the fix. +Why it matters: v1 was unbuildable on wrong size assumptions (31K → actually 6-8K). v2 fixed structure + but imported a wrong field interpretation (command_permissions). Both came from not measuring. +How to apply: Slice 0 gates everything; no slice builds before a number exists. +Risk if ignored: Building the wrong thing confidently, again. +``` + +``` +Critical Principle: Test the largest contributor first. +Why it matters: The listing is 93 entries — 28 from plugins, 54 local. Pruning local first (Slice 3) + tests a smaller half of the bloat; disabling plugins (Slice 2) moves more tokens with a settings change. +How to apply: Plugin disable is intervention #1; local pruning is #2. +Risk if ignored: Gate reads "no movement" on an underpowered test and you abandon a true cause. +``` + +``` +Critical Principle: A symlinked skills dir can't be filtered by a manifest you control. +Why it matters: Claude enumerates through the symlink to dotfiles/skills; a bootstrap-read .visible + file can't hide dirs from that enumeration. Native exclusions or a materialized tree are the levers. +How to apply: Prefer native mechanisms surfaced in Slice 1; if none, the deployment-model change + (materialized filtered tree like ~/.copilot/skills) is the honest cost. +Risk if ignored: Building a manifest that silently does nothing. +``` + +``` +Critical Principle: Validation is not discovery. command_permissions is an allowlist; .non-discoverable-skills + is a guard that dirs stay without SKILL.md. Neither states which skills appear in the listing. +Why it matters: Reading a field name and inferring behavior sank v1 and nearly sank v2. +How to apply: Only cite a field's semantics after confirming what it controls in the code/CI. +Risk if ignored: A "verified facts" table containing unverified inferences. +``` + +``` +Critical Principle: A measurement harness that changes the thing it measures is worthless. +Why it matters: claude -p may inject different attachments/hooks than the interactive session that + failed. Session cost caps change nothing about turn-1 outcome but must not change attachments. +How to apply: Hard precondition — skill_listing + hook present under -p or the harness is invalid. +Risk if ignored: A baseline measuring a code path that never fails (or always fails) in reality. +``` + +## 5. Dependency Graph + +``` +Slice 0 (baseline) ──┬── gate0: stop if rate ≥ 8/10 (tolerance documented) + │ + ├──▶ Slice 1 (mechanism) [parallel-safe; gate1 BEFORE interventions] + │ + ├──▶ Slice 2 (plugins first, largest mover) ── gate2 ─┐ + │ ├──▶ Slice 4 (alternate causes) ← gate2/3 say flat + └──▶ Slice 3 (local pruning) ── gate3 ────────────────┘ │ + ▼ + gates 2-4 flat → Slice 5 (fallback generator) + │ + ▼ + Slice 6 (QA: full remeasure + regression) +``` + +**Execution order:** +1. Slice 0 + Slice 1 in parallel (measure + verify mechanism, both gated) +2. Gate 0 (is there a measurable failure?) + Gate 1 (does the mechanism differ?) +3. Slice 2 (plugins — largest mover) → Gate 2 +4. Slice 3 (local pruning) → Gate 3, **or** straight to Slice 4 if gate 2 was flat +5. Slice 5 only if natural-language invocation is still broken +6. Slice 6 (QA — full remeasure, never n=1) + +**Critical path:** 0 → 2 → (3|4) → 6. Slices 1 and 5 are research/fallback. + +## 6. QA Plan + +1. **Baseline vs final**: `bin/measure-skill-invocation.sh` once (Slice 0) and once (Slice 6); success + requires a measurable positive delta from the same harness. +2. **Attribution**: one intervention per measurement; deltas attributable. +3. **Regression**: `bash test/run-all.sh`, bootstrap dry-run; both harnesses still load from `skills/`. +4. **No n=1**: every "fixed" is backed by the 10-session measurement. + +**Rollback:** Each intervention is a separate small commit (measure → intervene → remeasure), so slices +revert independently. Plugin disable is a settings toggle — instant revert. + +--- + +## What changed from v2 (reviewer corrections → resolution) + +| v2 issue | v3 resolution | +| -------- | ------------- | +| `command_permissions` = "no commands surfaced" was wrong | Corrected: field is a permission allowlist; `~/.claude/commands/` + `/audit` existed; natural-language failure is the real target | +| Intervention tested smaller half first | Reordered: plugin disable (Slice 2, largest mover, zero new code) before local pruning (Slice 3) | +| Visibility manifest may be unbuildable | `~/.claude/skills` is a symlink; manifest can't filter. Prefer native exclusion (Slice 1) or explicit deployment-model change. `.non-discoverable-skills` verified as a guard, not a filter | +| `.non-discoverable-skills` floated as native answer | Verified it's a CI guard (dirs must lack SKILL.md) — not a discovery exclusion; removed as a solution | +| No `-p` validity check | **Hard precondition**: both attachments must arrive under `-p` or the harness is invalid | +| Cost: 60-100 real audits | **Session cap**: kill after first tool call / first N tokens; outcome resolves turn 1 | +| Slice 1 had no gate | **Gate 1**: if mechanisms match, re-scope 2-5. Also feasibility-check payload capture first | +| Gate 0 = 8/10 arbitrary | Documented as a deliberate tolerance for an explicit request — the 20% miss is the reported failure class | +| n=10 binary detection limits | Documented: only large effects detectable; 5→7 must not be read as a win unless ≥3pt with the caveat stated | \ No newline at end of file diff --git a/plans/dual-method-skill-system-v4.md b/plans/dual-method-skill-system-v4.md new file mode 100644 index 00000000..e18ad248 --- /dev/null +++ b/plans/dual-method-skill-system-v4.md @@ -0,0 +1,346 @@ +# Implementation Plan v4: Skill Invocation Fix — Model-First, Measurement-Driven + +**Supersedes:** `plans/dual-method-skill-system.md` (v1), `-v2.md`, `-v3.md` +**Status:** DRAFT — pending Slice 0 baseline +**Reviewer v3 corrections:** model hypothesis promoted to front, variable-splitting (hook vs plugin), +per-source char counts, session-cap false-negative fix, Slice 3 dependency fix, gate-0 wording. + +--- + +## 1. Context + +A Claude Code session typed "is the readme accurate to the codebase repo? codebase audit" and the +`codebase-audit` skill did not load — zero `Skill` tool calls across 174 messages, despite the skill +being present in the session `skill_listing` (93 entries) with a matching trigger, and the superpowers +`hook_additional_context` (3.3K chars) telling the model to read it. + +**The single most important verified fact: the failed session ran `deepseek/deepseek-v4-flash-0731` +through the LiteLLM proxy.** Claude Code routes every `claude-*` model name to DeepSeek by default +(primary) with `github_copilot/claude-sonnet-5` as fallback. The Copilot extension does **not** route +through this proxy at all — it uses Copilot's own model endpoints directly (Sonnet/Opus). + +So the observable "Copilot follows skills, Claude CLI doesn't" is most plausibly explained by +**deepseek-vs-Sonnet**, not by a harness-architecture difference. That hypothesis was abandoned in +conversation when the user asserted "same model" — but the assertion was about the proxy's label +(which routes to DeepSeek), not about what Copilot actually serves. The cheapest possible intervention — +point the proxy at the already-configured `github_copilot/claude-sonnet-5` fallback — tests the +highest-prior hypothesis with a one-line config change. It runs as **Slice 2**, ahead of all catalog work. + +**Verified facts (plan inputs):** + +| Fact | Status | Evidence | +| ---- | ------ | -------- | +| Failed session model = `deepseek-v4-flash-0731` | ✅ | 174 messages logged | +| Claude CLI routes all `claude-*` → DeepSeek (primary), Sonnet fallback | ✅ | `litellm_config.yaml` model_list | +| Copilot extension does NOT use the proxy | ✅ | separate Copilot endpoints; proxy serves Claude Code | +| Superpowers `hook_additional_context` ships with the plugin | ✅ | `hooks/session-start` reads `skills/using-superpowers/SKILL.md` | +| `skill_listing` = 24,679 chars ≈ 6-8K tokens, 93 entries | ✅ | session log attachment | +| …~54 local, ~28 plugin, ~11 built-in (char counts per source pending Slice 0) | ⏳ | entry counts measured; char split = Slice 0 output | +| `disable-model-invocation` is CI-deprecated | ✅ | README Integrity checks | +| `.non-discoverable-skills` is a guard (dirs must lack SKILL.md), not a filter | ✅ | `config-consistency.sh` Invariant 5 | +| `command_permissions` is a permission allowlist, not command discovery | ✅ | field semantics; `/audit` existed on disk | + +--- + +## 2. Design Decisions + +| Decision | Choice | +| -------- | ------ | +| Highest-priority hypothesis | **Model capability** (deepseek vs sonnet) — tested first, cheapest, one-line config | +| Build gate | No build until Slice 0 baseline exists | +| Measurement | Fresh-session invocation rate; same prompt, N sessions, count `Skill` calls | +| Session harness | `claude -p` with hard validity precondition (attachments must arrive under `-p`) | +| Session cost cap | **Cap on token budget or 3-4 tool calls — NOT first-tool-call** (a model that reads README then loads skill shouldn't be a false negative) | +| Listing accounting | Slice 0 emits **char counts per source** (local / plugin / built-in) so ordering rests on measurement | +| Intervention order | Model swap (2) → hook disable (3) → plugin skills disable (4) → local pruning (5, only if 2-4 flat) | +| Variable splitting | Hook and plugin skills tested **separately** (disable hook alone first; then plugin skills alone) — never together | +| Plugin disable scope | Disable `enabledPlugins: superpowers` only after the hook is tested in isolation | +| Model swap | Point `claude-opus-5` at `github_copilot/claude-sonnet-5` fallback (already configured) — one-line config | +| Visibility lever | Only if 2-4 flat: check for a **native skill-exclusion in `~/.claude/settings.json` schema** first; else materialized filtered tree (deployment-model change, priced before scheduling) | +| `disable-model-invocation` | **Not used** — CI fails on it | +| Command generator | **Fallback only** — if natural-language invocation can't be fixed by 2-5 | + +## 3. Vertical Slices + +--- + +☐ **Slice 0: Baseline measurement (BLOCKING)** +Type: AFK + HITL interpretation +Size: S +Blocked by: none +Steps: +1. Write `bin/measure-skill-invocation.sh`: + - Spawn N fresh Claude Code sessions (fixed prompt + cwd) + - **Validity precondition (hard stop):** each session must contain BOTH `skill_listing` and + `hook_additional_context`; if `-p` omits either, harness is invalid → switch capture method + - **Cost cap:** terminate at a **token budget or after 3-4 tool calls** (NOT the first) — the + outcome is "did `Skill` get called across a few turns", not "was it called first" + - Parse `skill_listing` → emit **char counts per source**: local / plugin / built-in + - Grep logs for `Skill` calls + `attributionSkill`; record first-`llm_request` `input_tokens` +2. Prompt: "is the readme accurate to the codebase repo? codebase audit" (in `agentic-nlq` cwd) +3. Run 10 sessions; record invocation rate + per-source char counts +4. Write to `working/research/skill-invocation-baseline.md` + +Acceptance criteria: +- Invocation rate (X/10), per-source char counts, per-session tokens all recorded +- Precondition documented; sessions capped on tokens (not first call) + +Feedback loops: `bash bin/measure-skill-invocation.sh`, manual log inspection + +**Decision gate 0:** Baseline ≥ 8/10 → the system mostly works for this prompt; investigate the specific +failed session separately (different context/model/plugin state). Proceed with interventions only if +baseline < 8/10. **Wording fixed:** 8/10 is the stop threshold, period — no "complete only if it +exceeds convincingly" ambiguity. + +--- + +☐ **Slice 1: Verify Copilot's actual model (cross-harness framing survival check)** +Type: AFK (lookup) — quick +Size: S +Blocked by: none (parallel-safe with 0) +Steps: +1. Determine what model the Copilot extension is actually serving (Copilot settings/account UI, or + capture from a Copilot request / its model selector) +2. Compare: is Copilot on Sonnet/Opus while Claude CLI is on DeepSeek? +3. Record in `working/research/skill-mechanism.md` + +**Decision gate 1:** If Copilot = Sonnet/Opus and Claude CLI = DeepSeek → **the model hypothesis +survives**; Slice 2 is the primary intervention and the harness-architecture theory is deprioritized. +If Copilot is ALSO DeepSeek → the cross-harness framing collapses; skip to Slice 4-style hook/plugin +testing without the model swap being primary. + +Acceptance criteria: +- Copilot's actual model identified with evidence (not assumed) +- Gate result recorded + +Feedback loops: manual lookup + +--- + +☐ **Slice 2: Intervention #1 — model swap (highest-priority hypothesis)** +Type: AFK (config change) + measurement +Size: S +Blocked by: Slice 0 (baseline), Slice 1 (gate result) +Steps: +1. Change `litellm_config.yaml`: `claude-opus-5` (or the model used) primary → + `github_copilot/claude-sonnet-5` (fallback already configured; just promote it) +2. Confirm the session now logs `claude-sonnet-5` (not deepseek) +3. Rerun the **same 10-session measurement** (same prompt, cwd, cap — token/3-4 calls) +4. Compare rates; record delta in the baseline doc + +Acceptance criteria: +- Model change confirmed in logs (one variable: model only) +- Rate delta attributable to model swap + +Feedback loops: `bash bin/measure-skill-invocation.sh` + +**Decision gate 2:** Rate moved meaningfully → model capability is the cause. Keep the swap; consider +making it the product default. Rate flat → model not the cause; revert the swap, proceed to Slice 3. + +--- + +☐ **Slice 3: Intervention #2 — hook disable alone (confound-split)** +Type: HITL +Size: S +Blocked by: Slice 2 (flat result) — or run if model swap helped but the hook still deserves isolation +Steps: +1. **Disable ONLY the superpowers SessionStart hook** (keep the plugin's skills listed): + edit `hooks.json` / settings to skip `session-start` (or matcher-scope it off) — do NOT disable + the whole plugin +2. Confirm `hook_additional_context` is now absent from the session +3. Rerun the same 10-session measurement +4. Compare: with all 28 plugin skills still listed but NO hook injection + +Acceptance criteria: +- Hook absent, skills still present (one variable: the injection) +- Rate delta attributable to the hook alone + +Feedback loops: `bash bin/measure-skill-invocation.sh` + +**Decision gate 3:** Hook removal moved the rate → the injection itself (not the catalog) is the +confound; the superpowers meta-instruction crowds the catalog it tells the model to read. Flat → hook +isn't the cause; proceed to Slice 4. + +--- + +☐ **Slice 4: Intervention #3 — plugin skills disable (after hook isolated)** +Type: AFK (config toggle) + measurement +Size: S +Blocked by: Slice 3 (flat result), or if hook helped but catalog still needs testing +Steps: +1. Now disable the superpowers **plugin** (skills only; hook already gone or not-the-cause) +2. Confirm `skill_listing` drops ~28 plugin entries +3. Rerun the same 10-session measurement +4. Compare: fewer catalog entries, no plugin injection + +Acceptance criteria: +- Listing shrunk by ~28 entries; only plugin membership changed +- Rate delta attributable to catalog size alone + +Feedback loops: `bash bin/measure-skill-invocation.sh` + +**Decision gate 4:** Rate moved → catalog size matters. Rate flat → bloat-from-count is dead; +go to Slice 5 only if a native lever exists, else stop catalog work. + +--- + +☐ **Slice 5: Intervention #4 — local pruning (only if 2-4 all flat + a lever exists)** +Type: HITL +Size: M/L +Blocked by: Slice 4 (flat), and a **feasibility check first** +Steps: +1. **Feasibility**: does `~/.claude/settings.json` schema support excluding skills from the listing + natively? (Check docs + schema — do NOT rely on Slice 1, which compares Claude/Copilot payloads + and won't answer this.) Verify against `~/.claude/skills` being a symlink — a manifest read by + bootstrap can't filter Claude's enumeration +2. If native lever: implement minimal exclusion, rerun measurement +3. If no native lever: price the deployment-model change (`~/.claude/skills` → materialized filtered + tree like `~/.copilot/skills`) BEFORE scheduling; only proceed if the cost is accepted +4. Rerun the same 10-session measurement + +Acceptance criteria: +- Native mechanism confirmed or the deployment change is explicitly priced/accepted +- Listing reduced; same prompt/cwd/N; only pruning changed + +Feedback loops: `bash bin/measure-skill-invocation.sh` + +--- + +☐ **Slice 6: Command generator — fallback only, real content** +Type: AFK +Size: L +Blocked by: gates 2-5 (only if natural-language invocation can't be fixed) +Steps: +1. Commands carry the skill's actual working steps (inlined from reviewed SKILL.md), not boilerplate +2. Collision map documented in `commands/README.md` +3. Wire into bootstrap; coverage guard, not generator + +Acceptance criteria: +- Every command carries real procedure; no filename-only boilerplate + +Feedback loops: `bash test/skills.sh`, manual review + +--- + +☐ **Slice 7: QA — controlled before/after, not n=1** +Type: HITL +Size: S +Blocked by: slices 2-6 +Steps: +1. Rerun the full 10-session measurement (same prompt, cwd, cap — token/3-4 calls) +2. Compare to baseline; state the delta +3. Run `bash test/run-all.sh`, bootstrap dry-run +4. Report "fixed" only on a measurable positive delta + +Acceptance criteria: +- Baseline vs final documented (e.g. 1/10 → 8/10) +- Regression suite green; no single-trial claims + +Feedback loops: `bash test/run-all.sh`, `bash bin/measure-skill-invocation.sh` + +--- + +## 4. Key Insights + +``` +Critical Principle: The cheapest testable hypothesis runs first. +Why it matters: The model hypothesis is verified (deepseek), is a one-line config change, and matches + the observable (Copilot=Sonnet follows skills; Claude CLI=DeepSeek ignores). Catalog work, in contrast, + has no working implementation yet. +How to apply: Slice 2 (model swap) precedes all catalog slices. +Risk if ignored: Building catalog machinery to fix a model problem, or abandoning the true cause early. +``` + +``` +Critical Principle: Never change two variables in one measurement. +Why it matters: Disabling the superpowers plugin removes both ~28 skills AND the 3.3K-char hook + injection. A rate change then can't be attributed. +How to apply: Hook alone (Slice 3) before plugin skills (Slice 4). Verify each independently. +Risk if ignored: Confounded gates that prove nothing. +``` + +``` +Critical Principle: Ordering rests on measurement, not guesswork. +Why it matters: v3's "largest mover = plugins" was challenged — by entry count, local pruning (54→20 + = 34) actually cuts more than plugin disable (~28). The honest difference is plugins are reversible + and code-free, not larger. +How to apply: Slice 0 emits char counts per source (local/plugin/built-in) so the ordering is measured. +Risk if ignored: Priorities set by intuition flip when real numbers arrive. +``` + +``` +Critical Principle: A cap that creates false negatives invalidates the measurement. +Why it matters: "Kill after first tool call" misses models that read a README first, then load the skill + — the exact pattern in the failed session (Bash/Read/Glob before considering skills). +How to apply: Cap on token budget or 3-4 tool calls, not the first. +Risk if ignored: The baseline undercounts and the plan "fixes" a target it never measured. +``` + +``` +Critical Principle: Feasibility outranks scheduling. +Why it matters: Slice 3's lever ("check Slice 1 evidence") was wrong — Slice 1 compares Claude/Copilot + payloads and won't reveal a native exclusion setting. The real question is the settings schema + the + symlink deployment model. +How to apply: Separate feasibility (native exclusion exist?) from mechanism (payload shape). Price the + deployment-model change before scheduling. +Risk if ignored: Scheduling a slice that can't be implemented. +``` + +## 5. Dependency Graph + +``` +Slice 0 (baseline + per-source chars) ──┬── gate0: stop if ≥ 8/10 + │ + ├──▶ Slice 1 (verify Copilot model) ── gate1 (framing survives?) + │ + └──▶ Slice 2 (model swap + remeasure) ── gate2 ─┐ + │ + Slice 3 (hook disable alone) ◀── gate2 flat ─────┘ + │ + └──▶ gate3 ──▶ Slice 4 (plugin skills disable) ◀── gate2 helped + │ + └──▶ gate4 ──▶ Slice 5 (local prune, priced) + │ + gates 2-5 flat → Slice 6 (fallback generator) ────────┘ + │ + ▼ + Slice 7 (QA: full remeasure + regression) +``` + +**Execution order:** +1. Slice 0 + Slice 1 in parallel (baseline + verify Copilot model) +2. Gate 0 (measurable failure?) + Gate 1 (framing survives?) +3. Slice 2 (model swap — highest-priority hypothesis) → Gate 2 +4. Slice 3 (hook alone) → Gate 3, or Slice 4 (plugin skills) depending on gates +5. Slice 5 (local prune) only if 2-4 flat AND a native lever exists (priced) +6. Slice 6 (fallback) only if natural-language invocation still broken +7. Slice 7 (QA — full remeasure) + +**Critical path:** 0 → 2 → (3|4) → 7. Slices 2-4 are each a config change + a rerun — if any moves the +rate, Slices 5 and 6 never get built. + +## 6. QA Plan + +1. **Baseline vs final**: `bin/measure-skill-invocation.sh` at Slice 0 and Slice 7; success requires a + measurable positive delta from the same harness (same prompt, cwd, token-cap). +2. **Attribution**: one variable per slice; gates read single-variable deltas only. +3. **Regression**: `bash test/run-all.sh`, bootstrap dry-run; both harnesses still load from `skills/`. +4. **No n=1**: every "fixed" is backed by the N-session measurement; the cap avoids first-call false + negatives so the number is honest. + +**Rollback:** Each intervention is a separate small commit (measure → intervene → remeasure). The model +swap is a one-line `litellm_config.yaml` revert. Plugin/hook toggles are settings reverts. + +--- + +## What changed from v3 (reviewer corrections → resolution) + +| v3 issue | v4 resolution | +| -------- | ------------- | +| Model hypothesis buried at Slice 4 step 2 | Promoted to **Slice 2**, first intervention, one-line config change (promote existing sonnet fallback) | +| Slice 2 changed two variables (plugin skills + hook) | Split: **hook alone (Slice 3)** before **plugin skills (Slice 4)** — verified hook ships with plugin, so the split is mandatory | +| "Largest mover = plugins" didn't follow from own numbers | By entry count, local pruning cuts more (34 vs 28). Plugins go first for reversibility/code-free, and **Slice 0 emits char counts per source** so ordering is measured | +| Session cap = false negatives | Cap on **token budget or 3-4 tool calls**, not first call — matches the failed session's Bash/Read/Glob-then-consider pattern | +| Slice 3 blocked on wrong thing | Feasibility split: native skill-exclusion in settings schema, **not** Slice 1 payloads; price the materialized-tree deployment change before scheduling | +| Gate 0 wording muddled | Fixed: 8/10 is the stop threshold, period | +| Copilot model never verified | **Slice 1** verifies Copilot's actual model before any intervention; if it's also DeepSeek the framing collapses | \ No newline at end of file diff --git a/plans/dual-method-skill-system.md b/plans/dual-method-skill-system.md new file mode 100644 index 00000000..283abff7 --- /dev/null +++ b/plans/dual-method-skill-system.md @@ -0,0 +1,229 @@ +# Implementation Plan: Dual-Method Skill System (Claude `/commands` + Copilot auto-invocation) + +## 1. Context + +Skills in `~/dotfiles/skills/` are the single source of truth for 55 workflows, deployed to both +Claude Code (`~/.claude/skills` symlink) and Copilot (`~/.copilot/skills` materialized copy via +`bootstrap.sh`). The two harnesses invoke them completely differently: + +- **Copilot** auto-invokes skills by their `description` field — each skill is a first-class tool with + progressive loading (~100 tokens discovery), so keyword-rich descriptions reliably trigger. +- **Claude Code** exposes ONE generic `Skill` tool + a ~125K-char text catalog (~31K tokens). The model + must read the whole catalog and self-select — small/flash models bury this and skip skill invocation + entirely. Only 17 of 55 skills have `/command` dispatchers that bypass the model's judgment. + +The fix: **deterministic command coverage for every skill in Claude** (command dispatchers generated +from skill metadata) + **pruned, keyword-rich skill catalog for Copilot** (description-driven auto-invocation) ++ **explicit routing table** in always-loaded instructions for both. One skill definition, two invocation paths. + +## 2. Design Decisions + +| Decision | Choice | +| -------- | ------ | +| Skill source of truth | Single `~/dotfiles/skills/*/SKILL.md` (unchanged) | +| Claude invocation | Generated `/command` dispatcher per skill (deterministic, bypasses model judgment) | +| Copilot invocation | Auto-invocation by `description` (already works) — no command needed | +| Command generator | New `bin/gen-commands.sh` — idempotent, regenerates all command dispatchers from skill metadata | +| Command lifecycle | Generated commands are checked-in (not ephemeral) — deterministic diff, PR reviewable | +| Command template | Match existing `commands/*.md` pattern (load skill + `$ARGUMENTS` passthrough) | +| Copilot frontmatter | Add `disable-model-invocation` opt-out to skills that should NOT auto-trigger; default stays auto | +| Catalog pruning | Move rarely-used skills to `skills/_local/` (gitignored, already supported by `materialize_copilot_skills`) — NOT deleted | +| Routing table | Add "Skill Routing" section to `CLAUDE.base.md` (source for both `CLAUDE.md` + `copilot-instructions.md`) | +| Validation | Extend `bin/validate-skills.sh` to check command coverage + new frontmatter fields | +| Conflicts | Command filename collisions (e.g. `review` → `code-review`) resolved by explicit allowlist table in generator | + +## 3. Vertical Slices + +--- + +☐ **Slice 1: Command generator `bin/gen-commands.sh`** +Type: AFK +Size: M +Blocked by: none +Steps: +1. Write `bin/gen-commands.sh` with `set -euo pipefail` (per repo convention) +2. Iterate `~/dotfiles/skills/*/SKILL.md` (skip `_local/`, `_vendor/`, dot-dirs) +3. For each skill lacking a command, generate `~/dotfiles/commands/.md`: + ``` + Load the skill from ~/dotfiles/skills//SKILL.md. Execute the workflow. + + $ARGUMENTS + ``` +4. Include a collision-resolution map: skill names that map to a different command filename + (e.g. `code-review` → `review.md`, `atomic-commits` → `commit.md`/`ship.md`, `do-work` → `work.md`, + `session-close` → `check.md`, `architect` → `plan.md`, `pr-preflight` → `preflight.md`, + `tdd` → `test.md`, `explore` → `explore.md`, `codebase-audit` → `audit.md`, `review-pr-copilot` → `address-review.md`) +5. Preserve existing hand-written command bodies (only generate for missing skills — idempotent) +6. Add `--check` mode that fails if any skill lacks a command (for CI) +7. Wire into `bin/bootstrap.sh` (step 6, after commands symlink) and optionally `bin/_lib.sh` + +Acceptance criteria: +- `bash bin/gen-commands.sh` creates one `.md` per skill lacking a command; re-run is a no-op +- `bash bin/gen-commands.sh --check` exits 0 after generation, non-zero if any skill uncovered +- Output format matches existing command files exactly +- `validate-skills.sh` still passes after generation + +Feedback loops: `bash test/skills.sh`, `bash bin/gen-commands.sh --check`, shellcheck `bin/gen-commands.sh` + +--- + +☐ **Slice 2: Frontmatter audit + `disable-model-invocation` support** +Type: AFK +Size: M +Blocked by: none +Steps: +1. Review all 55 skill frontmatters for Copilot compatibility (name matches dir, description quoted with triggers) +2. Add `disable-model-invocation: true` to skills that should NOT auto-trigger in Copilot (e.g. superpowers-style meta-skills, `compliance-audit` auto-invoke skill, session-close) +3. Add `argument-hint` to skills commonly invoked via `/` in Copilot (optional polish) +4. Extend `bin/validate-skills.sh` to accept and validate the new fields (no error if absent — additive) +5. Regenerate `~/.copilot/skills` via `bash bin/bootstrap.sh` (or `materialize_copilot_skills` directly) + +Acceptance criteria: +- `bin/validate-skills.sh` passes with new frontmatter fields +- `~/.copilot/skills/*/SKILL.md` includes the new fields after materialization +- No skill has both `disable-model-invocation: true` AND `user-invocable: false` unintentionally + +Feedback loops: `bash test/skills.sh`, `bash test/copilot-skills-materialize.sh` + +--- + +☐ **Slice 3: Catalog pruning — move rare skills to `_local/`** +Type: HITL +Size: L +Blocked by: none +Steps: +1. Inventory all 55 skills with usage metadata (git log frequency, session log load counts) +2. Categorize: core (keep shared), niche/rare (move to `_local/`), stale (flag for review) +3. `git mv` shared → `skills/_local//` (preserve history) +4. Update `materialize_copilot_skills` if needed (already handles `_local/`) +5. Confirm bootstrap reports correct `shared vs local` counts +6. Document the triage in `commands/README.md` + +Acceptance criteria: +- `_local/` populated with moved skills; shared catalog reduced to a scannable size (~15-20) +- `bash bin/bootstrap.sh` reports matching shared/local counts +- Copilot `~/.copilot/skills` reflects the pruned set +- All moved skills still reachable via `/command` (generator in Slice 1 covers `_local/` too) + +Feedback loops: `bash test/skills.sh`, `bash test/copilot-skills-materialize.sh`, `bash bin/bootstrap.sh` + +--- + +☐ **Slice 4: Skill routing table in `CLAUDE.base.md`** +Type: HITL +Size: S +Blocked by: Slice 3 (final skill set) +Steps: +1. Add a `## Skill Routing` section to `CLAUDE.base.md` (before "Always-Loaded Instructions") +2. Table: trigger phrases → command to run / skill to load (for both harnesses) +3. Update `commands/README.md` command inventory with the new generated commands +4. Re-run `bash bin/bootstrap.sh` to regenerate `CLAUDE.md` + `~/.copilot/copilot-instructions.md` +5. Add a note in `global.instructions.md` pointing at the routing table (short) + +Acceptance criteria: +- `CLAUDE.base.md` (git-tracked) contains the routing table; `CLAUDE.md` + `copilot-instructions.md` regenerate with it +- Every skill in the shared catalog has a routing row +- `test/copilot-instructions.sh` and `test/claude-instructions.sh` still pass + +Feedback loops: `bash bootstrap.sh`, `bash test/copilot-instructions.sh`, `bash test/claude-instructions.sh` + +--- + +☐ **Slice 5: Test coverage for command generation** +Type: AFK +Size: S +Blocked by: Slice 1 +Steps: +1. Add `test/gen-commands.sh` — fixtures: a skills tree with covered + uncovered skills + `_local/` +2. Assert: generation creates commands for uncovered, preserves existing, `--check` fails when stale +3. Wire into `test/run-all.sh` +4. Add a `test/command-coverage.sh` that asserts every shared skill has a command (both generated and allowlisted) + +Acceptance criteria: +- New tests pass; `test/run-all.sh` green +- Coverage test fails if a new shared skill is added without a command + +Feedback loops: `bash test/run-all.sh` + +--- + +☐ **Slice 6: QA — dual-harness end-to-end verification** +Type: HITL +Size: S +Blocked by: Slices 1-5 +Steps: +1. Claude: type `/audit` → confirm `codebase-audit` skill loads and runs +2. Claude: type `/work` → confirm `do-work` skill loads (regression) +3. Claude: type a bare "audit this codebase" → confirm the model now loads the skill (routing table effect) +4. Copilot: open a chat, say "audit this" → confirm skill auto-invokes via description +5. Verify `~/.claude/skills/`, `~/.copilot/skills/`, `~/.claude/commands/` symlinks/materialization all correct +6. Confirm context bloat reduced (Claude session starts with smaller skill catalog) + +Acceptance criteria: +- Both harnesses load the same `codebase-audit` skill from the same source +- Claude CLI reliably loads skills when asked (no more "ignored" behavior) +- Copilot auto-invocation still fires; no regressions in either harness + +## 4. Key Insights + +``` +Critical Principle: Skills are the implementation; commands are the deterministic dispatch layer. +Why it matters: Claude's generic Skill tool is unreliable for small/flash models because it requires + reading a 31K-token catalog and self-selecting. A generated /command bypasses the model's judgment + entirely — the user typing /audit guarantees the skill loads. +How to apply: Generate commands from skill metadata (name + description), never hand-maintain them. +Risk if ignored: Skill usage remains lottery-based — some sessions load, most don't. +``` + +``` +Critical Principle: Copilot already auto-invokes by description; the bottleneck is catalog size, not mechanism. +Why it matters: With 55 skills, the discovery surface (description scan) is diluted. Progressive loading + means keyword-rich descriptions win. Pruning to the frequently-used set makes auto-invocation reliable. +How to apply: Move rare skills to skills/_local/ (gitignored) — they stay reachable via /command but + don't pollute Copilot's discovery surface. +Risk if ignored: Copilot picks the wrong skill or none; context bloat on every session start. +``` + +``` +Critical Principle: Single source of truth, two consumer targets. +Why it matters: CLAUDE.base.md generates both CLAUDE.md (Claude) and copilot-instructions.md (Copilot). + Skills/ generate ~/.claude/skills (symlink) + ~/.copilot/skills (materialized). Never edit consumers. +How to apply: All changes land in dotfiles/ source; bootstrap.sh propagates. +Risk if ignored: Drift between harnesses; edited consumer files silently overwritten. +``` + +## 5. Dependency Graph + +``` +Slice 1 (gen-commands.sh) ────────┐ + ├──▶ Slice 5 (test coverage) +Slice 2 (frontmatter audit) ──────┤ + ├──▶ Slice 6 (QA) +Slice 3 (catalog pruning) ────────┴──▶ Slice 4 (routing table) + │ + └──▶ Slice 6 (QA) +``` + +Parallel-safe: +- Slices 1, 2, 3 can run in parallel (independent file sets) +- Slice 4 blocked by Slice 3 (routing table needs final skill list) +- Slice 5 blocked by Slice 1 +- Slice 6 blocked by all + +Ordering recommendation: **Slice 1 → Slice 3 → Slice 2** (generator first = immediate deterministic path, +then pruning = context relief, then frontmatter polish), with Slice 4 after 3. All are AFK except Slice 3, +4, and 6 (taste/verification decisions). + +## 6. QA Plan + +After all slices complete, the human verifies: + +1. **Claude determinism**: `/audit`, `/work`, and 2-3 other generated commands all load their skills + (previously only 17 worked; now all should). +2. **Claude natural language**: "audit this codebase" without `/` loads the skill (routing table working). +3. **Copilot auto-invoke**: "audit this" in Copilot Chat loads the skill without slash. +4. **Same source**: both load from `~/dotfiles/skills/codebase-audit/SKILL.md` — edit once, both update. +5. **Context bloat check**: a fresh Claude session shows a much smaller skill catalog (~31K tokens → ~10K). +6. **No regressions**: `test/run-all.sh` fully green; `validate-skills.sh` passes; bootstrap completes clean. + +Rollback: `git revert` the command generator + routing table commits; move `_local/` skills back with `git mv`. \ No newline at end of file diff --git a/plans/hub-model-cleanup-plan.md b/plans/hub-model-cleanup-plan.md new file mode 100644 index 00000000..ae6a4f7e --- /dev/null +++ b/plans/hub-model-cleanup-plan.md @@ -0,0 +1,267 @@ +# Architecture Plan — Hub-Model Completion & Cross-Repo Reconciliation + +**Status:** Proposed — awaiting approval +**Date:** 2026-08-20 +**Derived from:** `docs/sandcastle-hub-architecture.md`, `docs/adr/ADR-008-ctrlshft-hub.md`, `ctrlshft-hub/docs/adr/ADR-001-hub-single-source.md` +**Executed by:** AFK agents (shft) for AFK slices; human for HITL slices + +--- + +## 1. Context + +The hub-model migration is functionally complete: the producer (ctrlshft-public) +removed its vendored engine, and consumers reference `arndvs/ctrlshft-hub` +remotely. This session fixed the producer's stale smoke-coverage test, rebuilt +its `shft/templates/workflows/*` as hub stubs, deleted its stale composite +actions and `sandcastle-ci.yml`, and synced everything across dotfiles + public ++ copilot. + +But the migration exposed **four unresolved structural gaps** — the same +stale-template pattern the producer just fixed, still present in the hub itself, +plus a cross-repo divergence in shared tooling: + +1. **Hub templates are still old-model.** The hub's `templates/workflows/` + contains 11/12 agent workflows + `sandcastle-ci.yml` + `labels-sync.yml` + that encode the vendored engine model (`.sandcastle/engine`, + `pnpm --ignore-workspace exec tsx ../run.ts`, local composite actions) — + the exact pattern ADR-001 says is dead. These are the canonical templates + consumers receive via `init-sandcastle.sh` (via the producer's + `SANDBOX_PRODUCER` resolution), so new installs would produce broken + engine-referencing workflows from the hub itself. +2. **Hub | `sandcastle-ci.yml` template is stale.** It validates the vendored + engine (`.sandcastle/engine/**`, `pnpm install --frozen-lockfile`). The hub + owns engine CI in `.github/workflows/engine-ci.yml`; the consumer-side + `sandcastle-ci.yml` template is a dead artifact of the old model. +3. **Cross-repo | `bin/ctrl`, `bin/preflight-sandcastle.sh`, `bin/bootstrap.sh` + diverged between dotfiles and public.** Public has newer SaaS-governance / + worktree-bridge changes (`697435c`, PR #318) and the modern + `working/runtime` paths; dotfiles has the hub-model preflight drift-check + and `_vendor` path fixes (commits `a8178d7`, `3c29751`). Neither repo has + both sets of changes. This is a genuine 3-way merge that will keep biting + every `ctrl sync` until resolved. +4. **Hub | no structural QA gate.** The hub has no test/ directory at all. The + producer's `test/sandcastle-smoke-coverage.sh` validates consumer templates; + the hub needs an equivalent gate that validates *its own* installed + workflows + templates are hub-model-clean, so the drift pattern in (1) + can never regress. + +--- + +## 2. Design Decisions + +| Decision | Choice | Rationale | +| --- | --- | --- | +| Hub template home | **`templates/workflows/` is the canonical stub source** — rebuild as thin hub stubs | It's the "one place the templates live" per README/ADR-001; consumers + producer copy from it. It must encode the hub stub contract, not the old engine contract. | +| `sandcastle-ci.yml` in hub templates | **Delete** | The hub owns engine CI (`engine-ci.yml`); the dispatch-module-resolution double-check lives there. No consumer needs a local engine-validating CI anymore. | +| Hub `.github/workflows/` | **Keep as-is** (already hub-model) | Self-dogfood workflows (`agent-architecture-review.yml`, `agent-repo-hygiene.yml`) are correct thin stubs. | +| Producer templates sync direction | **Producer (`shft/templates/`) pulls FROM hub (`templates/`)** | Both must match; hub is the single source of truth for the stub contract. Producer mirrors hub rather than diverging. | +| `bin/ctrl` / `preflight` / `bootstrap` 3-way merge | **Feature-branch merge with explicit resolution** — create `work/sync-tooling` in *public*, merge dotfiles' sandcastle changes INTO public's battery of newer changes, then pull merged result back to dotfiles | Public is master per REPO_TOPOLOGY. Merge dotfiles' sandbox-hub-model bits into public's newer bridge/worktree/runtime bits; resolve conflicts once. | +| Hub QA gate | **`test/hub-smoke-coverage.sh`** (mirror of producer's) validating hub templates + installed workflows | Structural drift guard so the stale-template pattern cannot silently return. | +| Sync tooling | **Keep `sync-public-repo.sh` (private → public) but extend it** so it also pulls accepted hub-model changes to dotfiles | Prevent future 3-way drift between the three nodes. | + +--- + +## 3. Vertical Slices + +> Each slice is independently shippable and wires end-to-end. AFK slices run +> fully autonomously; HITL slices need human judgment/access. + +--- + +### ☐ S1: Hub — rebuild template workflows as hub-model stubs +Type: HITL (touches canonical template source; requires review) +Size: M +Blocked by: none +Steps: +1. For each of the 11 stale `templates/workflows/agent-*.yml` on the hub, replace + the vendored body (.sandcastle/engine, pnpm exec tsx ../run.ts, local + composite actions, `{{DEFAULT_BRANCH}}` substitutes) with the thin stub + contract: + - agent-run style (7): `uses: arndvs/ctrlshft-hub/actions/agent-run@main` + with `workflow: `, `ref: main`, `token:`, optional `extra-args`. + - reusable-workflow style (6): `jobs: : uses: + arndvs/ctrlshft-hub/.github/workflows/reusable-*.yml@main` with `secrets: + inherit`. + - Match the exact stub bodies the producer now uses (see + `ctrlshft-public/.github/workflows/` for the reference contract). +2. Update `agent-promote-queued.yml` (the one already-hub-model template) if it + needs the `{{DEFAULT_BRANCH}}` templating substitution marker (it's the one + template that was already converted — verify against producer's). +3. Delete `templates/workflows/sandcastle-ci.yml` (dead vendored-engine CI). +4. Verify `labels-sync.yml` and `sandcastle-drift.yml` in templates match the + hub-model installed versions (they reference the hub API, not `.sandcastle/`). +5. Open PR to hub `main`, request review. + +Acceptance criteria: +- `grep -L "\.sandcastle/engine\|pnpm --ignore-workspace exec tsx" templates/workflows/agent-*.yml` + returns nothing (all 12 are stubs). +- `templates/workflows/` has no more `sandcastle-ci.yml`. +- The hub templates diff 1:1 with the producer's `shft/templates/workflows/` + for the 12 agent workflows. + +Feedback loops: `bash -n` on each YAML; `grep -c agent-run templates/workflows/agent-*.yml`; +CI on the PR (hub `engine-ci.yml` still passes). + +--- + +### ☐ S2: Add hub QA gate — test/hub-smoke-coverage.sh +Type: AFK +Size: M +Blocked: S1 (gate validates S1's template state) + +Steps: +1. Create `test/hub-smoke-coverage.sh` mirroring the producer's clean + `test/sandcastle-smoke-coverage.sh` shape (dotfiles version — the 227-line + template-coverage gate), adapted: + - `agent_workflow_templates` glob → hub `templates/workflows/agent-*.yml`. + - Assert every template contains a `ctrlshft-hub` reference + (`agent-run` composite or `reusable-*.yml` call). + - Assert every template avoids old-model tokens + (`.sandcastle/engine`, `pnpm --ignore-workspace exec tsx`, `uses: + ./.github/actions/`). + - Assert the report-aggregator inventory (if hub has a `SANDCASTLE_WORKFLOWS` + list) tracks every template. +2. Assert every `reusable-*.yml` in `.github/workflows/` referenced by a + consumer stub exists. +3. Assert `engine-ci.yml` exists and covers the engine. +4. Wire the gate into a `hub-qa` job (new workflow — `qa-hub-alignment.yml` or + add a job to existing `engine-ci.yml` buff). + +Acceptance: +- `bash test/hub-smoke-coverage.sh` exits 0 with ≥ N passes, 0 failures. +- A commit that reintroduces `.sandcastle/engine` into a template fails the gate. + +Feedback: `bash test/hub-smoke-coverage.sh`. + +--- + +### S3: Sync producer templates ↔ hub templates (mirror) +Type: HITL (touches producer + public history) +Size: S +Blocked: S1 (hub templates must be correct first) + +Steps: +1. After S1 the hub is the canonical stub source. Copy hub + `templates/workflows/` → producer `shft/templates/workflows/` (the 12 agent + + labels-sync + drift; keep producer's `check-attribution`/ + `require-regression-guard` which are producer-owned). +2. Re-run producer `test/sandcastle-smoke-coverage.sh` (35/35) + + `test/init-sandcastle-proxy-canary.sh` (26/26). +3. Verify `init-sandcastle.sh` (SANDBOX_PRODUCER resolution) renders the stubs + correctly to `.github/workflows/`. + +Acceptance: template dirs diff 1:1 for the shared 16 files; all producer +sandcastle suites still green. + +Feedback loops: `bash test/sandcastle-smoke-coverage.sh`; +`bash test/init-sandcastle-proxy-canary.sh`. + +--- + +### S4: Resolve ctrl / preflight / bootstrap 3-way divergence +Type: HITL (merge conflicts need judgment) +Size: L +Blocked: none + +Steps: +1. On `dev`, create merge-prep branch `ai/fix/sync-tooling` in the public repo. +2. Merge dotfiles' branches (private remote `private/dev`) into it, resolving: + - `bin/ctrl`: keep public's newer `worktree|wt` command + bridge + single-worker lock + `running/runtime` paths; add dotfiles' hub-model + `update-sandcastle` deprecation routing. Both merge clean. + - `bin/preflight-sandcastle.sh`: take dotfiles' hub-SHA-drift check (replace + `_check_engine` vendored-engine check) but keep public's permission-block + checker (accept top-level + job-level). + - `bootstrap.sh`: take dotfiles' `_local` skills aggregation + skills-lock + validation; keep public's `working/runtime` active-client path. +3. Push branch, open PR to `dev`, human merges. +4. Then pull `public/dev` into `dotfiles` (private) `dev`; + resolve dotfiles-local overlay additions (secrets dir, machine-local). + +Acceptance: `diff` between public and dotfiles `bin/ctrl` / `preflight`/ +`bootstrap` is empty (except intentionally-private overlay lines). +Feedback: `git diff -- bin/ | grep -v '^index'` empty. + +--- + +### S5: Extend sync tooling for bidirectional hub sync +Type: HITL +Size: S +Blocked: S4 + +Steps: +1. Add `sync-hub-repo.sh` (or extend `sync-public-repo.sh`) that copies + `templates/workflows/` from the hub → producer `shft/templates/workflows/` + for the 16 shared names (agent-*, labels-sync, sandcastle-drift), skipping + producer-owned `check-attribution`/`require-regression-guard`. +2. Add a parity check: `diff hub/templates/workflows vs producer` → exit + non-zero on drift. Wire into CI or the smoke-coverage gate. + +Acceptance: a one-command sync aligns hub ↔ producer templates; +a drift-detection script reports misalignment. + +Feedback: run the parity check after a phantom hub template change. + +--- + +## 4. Key Insights + + Critical Principle: The hub is the single template contract — templates that + reference the vendored engine are a lie. + Why it matters: hub templates flow to every consumer install via + init-sandcastle; a stale template ships broken workflows to all consumers. + How to apply: mirror hub templates 1:1 to producer, and add gates on both + The hub's and producer's CI that fail on old-model tokens. + Risk if ignored: next `init-sandcastle` round-trip re-introduces the + vendored engine pattern to every consumer. + + Critical Principle: Cross-repo ctrl/pre-flight/bootstrap drift accumulates + silently. + Why it matters: each repo holds a partial copy; `ctrl sync` and the CLI + behave differently depending on checkout. + How to apply: 3-way merge once — public-first, dotfiles pull-back — then + extend sync-public-repo.sh so shared files cannot diverge again. + Risk if ignored: repeated failed `ctrl sync` sessions and HITL time + chasing diverged branch states. + +--- + +## 5. Dependency Graph + +``` +S1 (hub templates → stubs) ──▶ S2 (hub QA gate) + │ + └──▶ S3 (producer ↔ hub mirror) ──▶ S4 (ctrl/preflight/bootstrap merge) + │ + └──▶ S5 (sync tooling bidir) +``` + +Execution order: S1 → S2 → S3 → S4 → S5. S2 depends solely on S1 (AFK-able). +S4 touches only tooling, independent of S1-S3 — parallel-safe. + +- S1 — hub templates (HITL, review) +- S2 — hub QA gate (AFK, blocked from S1) +- S3 — producer mirror (HITL, blocked from S1) +- S4 — ctrl/pref/bootstrap merge (HITL, **parallel-safe with S1-S3**) +- S5 — sync tooling bidir (HITL, blocked from S4 results) + +S1 → S2, S1 → S3, S4 ∥ S1..S3, S4 → S5. + +--- + +## 6. QA Plan + +The final QA slice (HITL) verifies the whole system after S1-S5 merge: + +1. On a fresh consumer (e.g. a throwaway branch of `llm-gateway`), + run `ctrl init-sandcastle --force` and confirm `.github/workflows/agent-*.yml` + are thin stubs referencing `arndvs/ctrlshft-hub` — no `.sandcastle/engine`, + no local composite actions. +2. Confirm `templates/workflows` dirs identical between hub and producer. +3. Run producer sandcastle suite: smoke-coverage 35, init 26, report-smoke 15 + all green. +4. Confirm `bin/ctrl update-sandcastle` prints the hub release deprecation + message identically in both checkout-nodes, and + `ctrl worktree`-command works in both. +5. Confirm `sync` tooling parity check reports zero drift after a fresh + hub pull. \ No newline at end of file diff --git a/plans/public-docs-cleanup-plan.md b/plans/public-docs-cleanup-plan.md index e3b5d95f..f0591442 100644 --- a/plans/public-docs-cleanup-plan.md +++ b/plans/public-docs-cleanup-plan.md @@ -2,7 +2,7 @@ **Status:** Proposed — awaiting approval **Date:** 2026-08-20 -**Derived from:** Codebase audit (2026-08-20), `docs/sandcastle-hub-architecture.md`, `docs/adr/ADR-008-sandcastle-hub.md`, `plans/README.md`, `WORKSPACE_INVARIANTS.md` +**Derived from:** Codebase audit (2026-08-20), `docs/sandcastle-hub-architecture.md`, `docs/adr/ADR-008-ctrlshft-hub.md`, `plans/README.md`, `WORKSPACE_INVARIANTS.md` **Executed by:** AFK agents (shft) for AFK slices; HITL for taste decisions --- @@ -50,7 +50,7 @@ Size: S Blocked by: none Steps: 1. Create `plans/archive/`. -2. Move the 5 `plans/*.plan.md` files: `sandcastle-hub-plan.md`, `sandcastle-hub-dogfood-plan.md`, `hub-model-cleanup-plan.md`, `workspace-prime-plan.md`, `drift-remediation-plan.md` → `plans/archive/`. +2. Move the 5 `plans/*.plan.md` files: `ctrlshft-hub-plan.md`, `ctrlshft-hub-dogfood-plan.md`, `hub-model-cleanup-plan.md`, `workspace-prime-plan.md`, `drift-remediation-plan.md` → `plans/archive/`. 3. Prepend each with an `> **Archived** — implemented; see git history for the shipping commits.` banner. 4. Update `plans/README.md` to list the archive dir and its link. Acceptance: `plans/` shows only README + `issues/` + `archive/`; archive dir has banner. Feedback: `ls plans/`. @@ -71,7 +71,7 @@ Size: M Blocked by: none Steps: 1. `CONTEXT.md:25-28` (repo root) → rewrite to SHA-lock hub model: - - Replace the "intentionally vendored from shft/" claim with "holds a `hub-version.json` SHA-lock; engine runs from `arndvs/sandcastle-hub` via the `agent-run` action." + - Replace the "intentionally vendored from shft/" claim with "holds a `hub-version.json` SHA-lock; engine runs from `arndvs/ctrlshft-hub` via the `agent-run` action." - Drop the `update-sandcastle --dry-run` drift line (deprecated flow). 2. `docs/ARCHITECTURE.md` ADR table → add rows for ADR-005..008 (names from `docs/adr/`). 3. `shft/README.md` vendored section → replace `update-sandcastle`/vendored-engine description with the hub-model `uses:` + SHA-lock flow. diff --git a/shft/README.md b/shft/README.md index c3f67d05..bc70d0f2 100644 --- a/shft/README.md +++ b/shft/README.md @@ -44,8 +44,8 @@ The agent picks issues in this order (defined in `prompt.md`): ## Sandcastle Platform Sandcastle is the CI-triggered AFK agent platform. Under the **hub model**, the -engine lives in `arndvs/sandcastle-hub` (single source of truth) and consumers -reference it remotely via `uses: arndvs/sandcastle-hub/...@` — nothing is +engine lives in `arndvs/ctrlshft-hub` (single source of truth) and consumers +reference it remotely via `uses: arndvs/ctrlshft-hub/...@` — nothing is vendored into consumer repos. ### Install in a repo @@ -127,7 +127,7 @@ Prompt resolution checks `.sandcastle/prompts/` first, then falls back to the te ### Workflow dispatcher Consumers never invoke the engine directly. Each thin stub calls the hub's -`agent-run` composite action (`uses: arndvs/sandcastle-hub/actions/agent-run@main`), +`agent-run` composite action (`uses: arndvs/ctrlshft-hub/actions/agent-run@main`), which checks out the hub at the pinned ref, installs engine deps, runs the engine against the consumer workspace, and summarizes the run. The engine's registered workflow names live in the hub (`engine/lib/dispatch.ts`): diff --git a/shft/templates/workflows/agent-architecture-review.yml b/shft/templates/workflows/agent-architecture-review.yml index a9abecc2..eb7746b3 100644 --- a/shft/templates/workflows/agent-architecture-review.yml +++ b/shft/templates/workflows/agent-architecture-review.yml @@ -20,7 +20,7 @@ jobs: ANTHROPIC_BASE_URL: ${{ secrets.LITELLM_BASE_URL }} ANTHROPIC_AUTH_TOKEN: ${{ secrets.LITELLM_MASTER_KEY }} steps: - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: architecture-review ref: main diff --git a/shft/templates/workflows/agent-check-stale-prs.yml b/shft/templates/workflows/agent-check-stale-prs.yml index 6303489d..c2d8d967 100644 --- a/shft/templates/workflows/agent-check-stale-prs.yml +++ b/shft/templates/workflows/agent-check-stale-prs.yml @@ -20,7 +20,7 @@ jobs: ANTHROPIC_BASE_URL: ${{ secrets.LITELLM_BASE_URL }} ANTHROPIC_AUTH_TOKEN: ${{ secrets.LITELLM_MASTER_KEY }} steps: - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: check-stale-prs ref: main diff --git a/shft/templates/workflows/agent-code-health.yml b/shft/templates/workflows/agent-code-health.yml index 9b22a87a..02171ea7 100644 --- a/shft/templates/workflows/agent-code-health.yml +++ b/shft/templates/workflows/agent-code-health.yml @@ -33,7 +33,7 @@ jobs: ANTHROPIC_BASE_URL: ${{ secrets.LITELLM_BASE_URL }} ANTHROPIC_AUTH_TOKEN: ${{ secrets.LITELLM_MASTER_KEY }} steps: - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: code-health ref: main diff --git a/shft/templates/workflows/agent-fix-pr-feedback.yml b/shft/templates/workflows/agent-fix-pr-feedback.yml index a849b6c0..ef8e84f0 100644 --- a/shft/templates/workflows/agent-fix-pr-feedback.yml +++ b/shft/templates/workflows/agent-fix-pr-feedback.yml @@ -27,7 +27,7 @@ jobs: fix-pr-feedback: if: github.event.label.name == 'agent:fix' && github.event.pull_request.head.repo.full_name == github.repository - uses: arndvs/sandcastle-hub/.github/workflows/reusable-fix-pr-feedback.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-fix-pr-feedback.yml@main with: pr-number: ${{ github.event.pull_request.number }} secrets: inherit diff --git a/shft/templates/workflows/agent-implement-issue.yml b/shft/templates/workflows/agent-implement-issue.yml index 396689e7..1cd84e61 100644 --- a/shft/templates/workflows/agent-implement-issue.yml +++ b/shft/templates/workflows/agent-implement-issue.yml @@ -12,7 +12,7 @@ permissions: jobs: implement: if: github.event.label.name == 'agent:implement' - uses: arndvs/sandcastle-hub/.github/workflows/reusable-implement-issue.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-implement-issue.yml@main with: issue-number: ${{ github.event.issue.number }} issue-title: ${{ github.event.issue.title }} diff --git a/shft/templates/workflows/agent-implement-prd.yml b/shft/templates/workflows/agent-implement-prd.yml index be4ac94c..de92a52e 100644 --- a/shft/templates/workflows/agent-implement-prd.yml +++ b/shft/templates/workflows/agent-implement-prd.yml @@ -12,7 +12,7 @@ permissions: jobs: implement-prd: if: github.event.label.name == 'agent:implement-prd' - uses: arndvs/sandcastle-hub/.github/workflows/reusable-implement-prd.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-implement-prd.yml@main with: prd-number: ${{ github.event.issue.number }} prd-title: ${{ github.event.issue.title }} diff --git a/shft/templates/workflows/agent-keep-tests-tight.yml b/shft/templates/workflows/agent-keep-tests-tight.yml index 7044f43d..2e7edfa4 100644 --- a/shft/templates/workflows/agent-keep-tests-tight.yml +++ b/shft/templates/workflows/agent-keep-tests-tight.yml @@ -11,5 +11,5 @@ permissions: jobs: keep-tests-tight: - uses: arndvs/sandcastle-hub/.github/workflows/reusable-keep-tests-tight.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-keep-tests-tight.yml@main secrets: inherit diff --git a/shft/templates/workflows/agent-merge-pr.yml b/shft/templates/workflows/agent-merge-pr.yml index d6ca823d..76d4ba96 100644 --- a/shft/templates/workflows/agent-merge-pr.yml +++ b/shft/templates/workflows/agent-merge-pr.yml @@ -27,7 +27,7 @@ jobs: merge: if: github.event.label.name == 'agent:merge' && github.event.pull_request.head.repo.full_name == github.repository - uses: arndvs/sandcastle-hub/.github/workflows/reusable-merge-pr.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-merge-pr.yml@main with: pr-number: ${{ github.event.pull_request.number }} secrets: inherit diff --git a/shft/templates/workflows/agent-plan-issue.yml b/shft/templates/workflows/agent-plan-issue.yml index 8036c172..e788e213 100644 --- a/shft/templates/workflows/agent-plan-issue.yml +++ b/shft/templates/workflows/agent-plan-issue.yml @@ -27,7 +27,7 @@ jobs: run: | gh issue edit "$ISSUE_NUMBER" --remove-label "agent:plan" -R ${{ github.repository }} || true gh issue edit "$ISSUE_NUMBER" --add-label "agent:in-progress" -R ${{ github.repository }} - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: plan-issue ref: main diff --git a/shft/templates/workflows/agent-promote-queued.yml b/shft/templates/workflows/agent-promote-queued.yml index 273abb10..93e52619 100644 --- a/shft/templates/workflows/agent-promote-queued.yml +++ b/shft/templates/workflows/agent-promote-queued.yml @@ -27,7 +27,7 @@ jobs: run: | gh issue edit "$ISSUE_NUMBER" --remove-label "agent:promote" -R ${{ github.repository }} || true gh issue edit "$ISSUE_NUMBER" --add-label "agent:in-progress" -R ${{ github.repository }} - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: promote-queued ref: main diff --git a/shft/templates/workflows/agent-repo-hygiene.yml b/shft/templates/workflows/agent-repo-hygiene.yml index 5b85ccf0..45985f82 100644 --- a/shft/templates/workflows/agent-repo-hygiene.yml +++ b/shft/templates/workflows/agent-repo-hygiene.yml @@ -25,7 +25,7 @@ jobs: ANTHROPIC_BASE_URL: ${{ secrets.LITELLM_BASE_URL }} ANTHROPIC_AUTH_TOKEN: ${{ secrets.LITELLM_MASTER_KEY }} steps: - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: repo-hygiene ref: main diff --git a/shft/templates/workflows/agent-review-issue.yml b/shft/templates/workflows/agent-review-issue.yml index acc15dd0..fcf1a9d5 100644 --- a/shft/templates/workflows/agent-review-issue.yml +++ b/shft/templates/workflows/agent-review-issue.yml @@ -27,7 +27,7 @@ jobs: run: | gh issue edit "$ISSUE_NUMBER" --remove-label "agent:review" -R ${{ github.repository }} || true gh issue edit "$ISSUE_NUMBER" --add-label "agent:in-progress" -R ${{ github.repository }} - - uses: arndvs/sandcastle-hub/actions/agent-run@main + - uses: arndvs/ctrlshft-hub/actions/agent-run@main with: workflow: review-issue ref: main diff --git a/shft/templates/workflows/agent-update-branch.yml b/shft/templates/workflows/agent-update-branch.yml index 6de4f54a..b1cfd7e4 100644 --- a/shft/templates/workflows/agent-update-branch.yml +++ b/shft/templates/workflows/agent-update-branch.yml @@ -27,7 +27,7 @@ jobs: update-branch: if: github.event.label.name == 'agent:update-branch' && github.event.pull_request.head.repo.full_name == github.repository - uses: arndvs/sandcastle-hub/.github/workflows/reusable-update-branch.yml@main + uses: arndvs/ctrlshft-hub/.github/workflows/reusable-update-branch.yml@main with: pr-number: ${{ github.event.pull_request.number }} branch: ${{ github.event.pull_request.head.ref }} diff --git a/shft/templates/workflows/labels-sync.yml b/shft/templates/workflows/labels-sync.yml index 73ef8114..342329eb 100644 --- a/shft/templates/workflows/labels-sync.yml +++ b/shft/templates/workflows/labels-sync.yml @@ -23,7 +23,7 @@ jobs: curl -fsSL \ -H "Authorization: Bearer $GH_TOKEN" \ -H "Accept: application/vnd.github.raw" \ - "https://api.github.com/repos/arndvs/sandcastle-hub/contents/templates/labels.json?ref=main" \ + "https://api.github.com/repos/arndvs/ctrlshft-hub/contents/templates/labels.json?ref=main" \ > "$RUNNER_TEMP/labels.json" - name: Reconcile labels diff --git a/shft/templates/workflows/sandcastle-drift.yml b/shft/templates/workflows/sandcastle-drift.yml index f29db3ff..bba07ebc 100644 --- a/shft/templates/workflows/sandcastle-drift.yml +++ b/shft/templates/workflows/sandcastle-drift.yml @@ -26,7 +26,7 @@ jobs: run: | set -euo pipefail pinned=$(jq -r '.lastPinnedSha' .sandcastle/hub-version.json 2>/dev/null || echo "unknown") - latest=$(gh api repos/arndvs/sandcastle-hub/commits/main --jq '.sha' | cut -c1-7) + latest=$(gh api repos/arndvs/ctrlshft-hub/commits/main --jq '.sha' | cut -c1-7) echo "pinned=$pinned" echo "latest=$latest" if [ "$pinned" != "$latest" ]; then diff --git a/test/update-sandcastle-ownership.sh b/test/update-sandcastle-ownership.sh index 8a9039e2..c699b2a1 100644 --- a/test/update-sandcastle-ownership.sh +++ b/test/update-sandcastle-ownership.sh @@ -3,7 +3,7 @@ # # The engine is no longer vendored (hub model). update-sandcastle.sh is a # deprecated stub that must: -# - Print the deprecation notice naming arndvs/sandcastle-hub as the source +# - Print the deprecation notice naming arndvs/ctrlshft-hub as the source # - Print the hub release path (hub/release.sh) # - Exit successfully (it intentionally does nothing — nothing to vendor) # @@ -48,7 +48,7 @@ echo "── deprecation notice ──" output="$(DOTFILES="$ROOT" bash "$ROOT/bin/update-sandcastle.sh" 2>&1)" -assert_contains "names sandcastle-hub as source of truth" "arndvs/sandcastle-hub" "$output" +assert_contains "names ctrlshft-hub as source of truth" "arndvs/ctrlshft-hub" "$output" assert_contains "names hub/release.sh as the replacement" "hub/release.sh" "$output" assert_contains "states the engine is no longer vendored" "no longer vendored" "$output" @@ -58,7 +58,7 @@ echo "── help ──" help_output="$(DOTFILES="$ROOT" bash "$ROOT/bin/update-sandcastle.sh" --help 2>&1)" -assert_contains "help names the hub" "arndvs/sandcastle-hub" "$help_output" +assert_contains "help names the hub" "arndvs/ctrlshft-hub" "$help_output" assert_contains "help names hub/release.sh" "hub/release.sh" "$help_output" # ── Summary ─────────────────────────────────────────────────────────────────── diff --git a/test/update-sandcastle-proxy-canary.sh b/test/update-sandcastle-proxy-canary.sh index ddb5bb17..f3f64d3f 100644 --- a/test/update-sandcastle-proxy-canary.sh +++ b/test/update-sandcastle-proxy-canary.sh @@ -40,7 +40,7 @@ echo "── deprecation notice ──" output="$(DOTFILES="$(pwd)" bash "$SCRIPT" 2>&1)" -assert_contains "names sandcastle-hub as source of truth" "arndvs/sandcastle-hub" "$output" +assert_contains "names ctrlshft-hub as source of truth" "arndvs/ctrlshft-hub" "$output" assert_contains "names hub/release.sh as the replacement" "hub/release.sh" "$output" assert_contains "states the engine is no longer vendored" "no longer vendored" "$output" @@ -50,7 +50,7 @@ echo "── help ──" help_output="$(DOTFILES="$(pwd)" bash "$SCRIPT" --help 2>&1)" -assert_contains "help names the hub" "arndvs/sandcastle-hub" "$help_output" +assert_contains "help names the hub" "arndvs/ctrlshft-hub" "$help_output" assert_contains "help names hub/release.sh" "hub/release.sh" "$help_output" # ── Summary ─────────────────────────────────────────────────────────────────── From b8cf5a12ef84e19d539ac2d99195b5b31386db33 Mon Sep 17 00:00:00 2001 From: Aaron Davis Date: Tue, 1 Sep 2026 14:01:17 -0700 Subject: [PATCH 10/10] chore(repo): update last sandcastle-hub refs in bin/ctrl DEPRECATED help text now names arndvs/ctrlshft-hub and the local ~/dev/clients/ctrlshft-hub path. --- bin/ctrl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/ctrl b/bin/ctrl index c2922a8d..d81a875d 100644 --- a/bin/ctrl +++ b/bin/ctrl @@ -561,7 +561,7 @@ except: ;; # ── update-sandcastle ────────────────────────────────────────────────────── - # DEPRECATED — the engine is no longer vendored. It lives in arndvs/sandcastle-hub. + # DEPRECATED — the engine is no longer vendored. It lives in arndvs/ctrlshft-hub. # Consumers reference it remotely via `uses: @main`. This now routes to the hub # release flow so the command fails with a clear, actionable message. update-sandcastle) @@ -570,7 +570,7 @@ except: else red "update-sandcastle.sh not found." red "The engine is no longer vendored. Releases are managed in the hub:" - red " cd ~/dev/clients/sandcastle-hub && hub/release.sh [patch|minor|major|]" + red " cd ~/dev/clients/ctrlshft-hub && hub/release.sh [patch|minor|major|]" exit 1 fi ;; @@ -844,7 +844,7 @@ except: echo " ctrl init-sandcastle scaffold Sandcastle in a repo" echo " [--branch main] [--model claude-opus-4-6]" echo " [--sandbox none] [--no-proxy] [--force]" - echo " ctrl update-sandcastle DEPRECATED — engine lives in arndvs/sandcastle-hub." + echo " ctrl update-sandcastle DEPRECATED — engine lives in arndvs/ctrlshft-hub." echo " See hub/release.sh for releases." echo " ctrl preflight-sandcastle validate drift, workflows, engine, secrets" echo " [--skip-drift] [--skip-engine] [--skip-github]"