📝 Add design: Spire project override + spire:init

Design covering findings 1/2/3 — shell injection for project.md
override, shared-vs-personal gitignore handling, spire:init skill
modelled on tome:init, and adept scaffolding. Ready for plan.
This commit is contained in:
2026-09-17 14:06:45 +02:00
parent bf0274788c
commit c9d0d183ff
@@ -0,0 +1,99 @@
# Design: Spire project override + spire:init
**Date**: 2026-09-17
**Covers**: Findings 1 (no spire:init), 2 (project.md cannot be overridden), 3 (adept scaffolding not shipped)
**Source research**: `research/2026-09-17-project-md-override-mechanics.md`
**Goal**: `goals/igor-project-adoption.md`
## The problem
Igor and Athanor hard-bind `references/project.md` via `@` directives that resolve into the plugin cache. Every project gets the blank template. There is no init, no override, and no adept scaffolding — three gaps that compound into "every session rediscovers the project from scratch."
## Solution: three new pieces
### 1. `spire:project` skill (the injection vehicle)
A tiny skill whose only job is loading the right `project.md` at skill-load time via shell injection:
```markdown
---
name: project
description: Loads per-project Spire configuration (override or bundled template).
allowed-tools: Bash(cat:*)
---
!`cat "${CLAUDE_PROJECT_DIR}/.claude/spire/project.md" 2>/dev/null || cat "${CLAUDE_SKILL_DIR}/../igor/references/project.md"`
<!-- If the above block is the blank template, run /spire:init to configure this project. -->
```
**Igor** (`agents/igor.md`) and **Athanor** (`agents/athanor.md`) add `spire:project` to their `skills:` list and drop their `@` imports. The skill loads at agent startup — same timing as today's `@`, but with fallback.
Runtime belt: Igor's skill text keeps a one-line instruction — "if the project block is the blank template, try reading `.claude/spire/project.md` before orienting from scratch" — so it degrades gracefully if `disableSkillShellExecution` is on.
### 2. Shared vs. personal installs
`.claude/` is often committed to git. But Spire may be personal — installed via `settings.local.json` or user-level settings — in which case `.claude/spire/` should not leak into the project's history.
**The rule**: `spire:init` detects where Spire is enabled:
| Spire enabled in | `.claude/spire/` should be | Init action |
|---|---|---|
| `settings.json` (project-level, committed) | Committed with the project | Write files, commit |
| `settings.local.json` or user settings | Gitignored | Write files, add `/spire/` to `.claude/.gitignore`, do not commit |
| Not detected / ambiguous | Ask the user | Prompt: "Should the Spire config be shared with the project or personal?" |
Detection: check whether `"spire@reliquary"` (or any spire plugin ref) appears in `.claude/settings.json` vs. `.claude/settings.local.json`. If in `settings.json` → shared. If in `settings.local.json` or absent from both → personal/ask.
The `.claude/.gitignore` approach (a gitignore inside `.claude/`) keeps the ignore scoped — it only affects `.claude/spire/`, not the rest of `.claude/`.
A `project.local.md` sibling can follow later for personal overrides on top of shared config — but that's out of scope for the first pass.
### 3. `spire:init` skill
Human-triggered (`disable-model-invocation: true`). Modelled on `tome:init` Phase 2.1.
**Phases**:
1. **Detect** — read the project to auto-fill what we can:
- Workpad location via the `spire:workpad` resolution chain
- Pinned agents from `.claude/agents/*.md`
- Verification commands from `Justfile` (recipe names like `check`, `test`, `lint`, `build`), `Makefile`, `package.json` scripts, `Cargo.toml`
- Conventions from root `CLAUDE.md`
- Tome skill presence (check if a `*-tome:navigate` skill exists or `tome@reliquary` is enabled)
2. **Scaffold** — create the following:
- `.claude/spire/project.md` — filled template with `status: generated` front matter (re-init safe: only overwrite while status is `generated`, never clobber `authored`)
- `<workpad>/adepts/_template/CLAUDE.md` — the adept CLAUDE.md template that `agents/adept.md:23,26` references but doesn't ship
- `<workpad>/adepts/` directory (if missing)
3. **Gitignore** (if personal install) — add `spire/` to `.claude/.gitignore`
4. **Alias** (optional) — offer to create `.claude/skills/assistant/SKILL.md` that invokes `spire:igor` as `/assistant`, so the project can use a generic entry point
5. **Commit** (if shared install) — commit the scaffolded files with gitmoji
### 4. Adept template
The adept agent (`agents/adept.md`) references `<workpad>/adepts/_template/CLAUDE.md` and `<workpad>/adepts/CLAUDE.md` — neither exists. `spire:init` creates both:
- `adepts/CLAUDE.md` — root-level orientation for the adepts directory (what it is, how adepts use it)
- `adepts/_template/CLAUDE.md` — the default starting CLAUDE.md for a newly spawned adept's domain folder
These are `status: generated` and re-init safe.
### 5. Changes to existing files
| File | Change |
|---|---|
| `skills/igor/SKILL.md:32` | Remove `@references/project.md`. Add a one-line runtime fallback instruction. |
| `agents/igor.md` | Add `spire:project` to `skills:` list |
| `agents/athanor.md:25` | Remove `@skills/igor/references/project.md`. Add `spire:project` to `skills:` list |
| `references/project.md` | Add "run `/spire:init`" hint at top. Keep as fallback template. |
### 6. Backward compatibility
- No `.claude/spire/project.md` → shell injection falls back to bundled template → same as today
- Template gains a hint to run init, turning the dead-end into a prompt
- `disableSkillShellExecution` → runtime fallback instruction kicks in
- `status: generated` front matter protects hand-edits from re-init