commit 49e581247b895046701951b2695a34849966bf29 Author: 张宗平 Date: Wed Jun 10 17:30:43 2026 +0800 initial: pi-setup workspace with skill + extension plan diff --git a/.agents/skills/evolve/SKILL.md b/.agents/skills/evolve/SKILL.md new file mode 100644 index 0000000..ea1b480 --- /dev/null +++ b/.agents/skills/evolve/SKILL.md @@ -0,0 +1,503 @@ +--- +name: evolve +description: Run autonomous improvement loops. +practices: +- lean-startup +- dora-metrics +- agile-manifesto +hexagonal_role: supporting +consumes: +- rpi +- goals +- post-mortem +- compile +produces: +- git-changes +- goals-fitness-delta +context_rel: +- kind: customer-of + with: rpi +skill_api_version: 1 +user-invocable: true +context: + window: fork + intent: + mode: task + sections: + exclude: + - HISTORY + intel_scope: full +metadata: + tier: execution + dependencies: + - rpi + - post-mortem + - compile + triggers: + - evolve + - improve everything + - autonomous improvement + - run until done + - postmortem and continue + - analyze repo and keep going +output_contract: code changes, GOALS.md fitness deltas +--- +# /evolve — Goal-Driven Compounding Loop + +> **Cross-vendor analog:** Anthropic Managed Agents Outcomes (May 2026). Both close the loop "agent runs → grader scores against a rubric → agent retries"; AgentOps does it locally against any model. + +> Measure what's wrong. Fix the worst thing. Measure again. Compound. + +**The loop runs as this skill (skills-are-the-runtime).** `evolve` selects work +and invokes complete `/rpi --auto` cycles — that *is* the loop. `evolve` (and +`ao rpi loop --supervisor`) are terminal-native **wrapper commands** for humans or +non-skill runtimes, not the default expression of the loop; they reuse the same v2 +RPI loop engine. (The substrate dispatches the whole `evolve` skill loop as one +unit; it never drives the loop's insides. The `evolve`/`ao rpi` CLI wrappers are +being retired — ag-iowf.) + +**Operator cadence:** post-mortem finished work, analyze the current repo state, +select or create the next highest-value work item, let `rpi` handle research, +planning, pre-mortem, implementation, and validation, then harvest follow-ups +and repeat until a kill switch, max-cycle cap, regression breaker, or real +dormancy stops the run. + +Always-on autonomous loop over `rpi`. Work selection order: +1. **Harvested `.agents/rpi/next-work.jsonl` work** (freshest concrete follow-up) +2. **Open ready beads work** (`bd ready`) +3. **Failing goals and directive gaps** (`ao goals measure`) +4. **Testing improvements** (missing/thin coverage, missing regression tests) +5. **Validation tightening and bug-hunt passes** (gates, audits, bug sweeps) +6. **Complexity / TODO / FIXME / drift / dead code / stale docs / stale research mining** +7. **Concrete feature suggestions** derived from repo purpose when no sharper work exists + +**Work generators** that feed the selection ladder (auto-invoked, skip with `--no-lifecycle`): +- `Skill(skill="test", args="coverage")` → files with <40% coverage become queue items (Step 3.4) +- `Skill(skill="refactor", args="--sweep all --dry-run")` → functions with CC > 20 become queue items (Step 3.6) +- `Skill(skill="deps", args="audit")` → deps with CVSS >= 7.0 or 2+ major versions behind become queue items (Step 3.5) +- `Skill(skill="perf", args="profile --quick")` → perf findings become queue items when hot paths detected (Step 3.5) + +**Dormancy is last resort.** Empty current queues mean "run the generator layers", not "stop". Only go dormant after the queue layers and generator layers come up empty across multiple consecutive passes. + +**Live skill edit immune system:** if an evolve cycle edits +`skills//SKILL.md`, run +`ao skills edit seal --skill --actor "${AGENT_NAME:-agent}"` before the +cycle hands off. The seal creates the rollback commit and records the +`Skill-Edit` trailers used by the daily digest. Critical skills listed in +`docs/contracts/critical-skills.txt` reject unattended edits; use +`--allow-critical` only when Bo is supervising that critical edit. + +```bash +/evolve # Run until kill switch, max-cycles, or real dormancy +/evolve --max-cycles=5 # Cap at 5 cycles +/evolve --dry-run # Show what would be worked on, don't execute +/evolve --beads-only # Skip goals measurement, work beads backlog only +/evolve --quality # Quality-first mode: prioritize post-mortem findings +/evolve --quality --max-cycles=10 # Quality mode with cycle cap +/evolve --compile # Mine → Defrag warmup before first cycle +/evolve --compile --max-cycles=5 # Warm knowledge base then run 5 cycles +/evolve --test-first # Default strict-quality /rpi execution path +/evolve --no-test-first # Explicit opt-out from test-first mode +``` + +## Delineation vs Nightly Knowledge Compounding + +| Lane | Runs | Mutates code? | Mutates corpus? | Outer loop? | Budget | +|------|------|---------------|-----------------|-------------|--------| +| `$curate --mode=dream` | nightly, private local | **No** | **Yes (heavy)** | **Yes (convergence)** | wall-clock + plateau | +| `evolve` | daytime, operator-driven | Yes (via `rpi`) | Yes (light) | Yes | cycle cap | + +**The old dream skill is retired**; out-of-session compounding moved to Gas City and the current skill surface is `$curate --mode=dream`. `/evolve` owns the live daytime code-compounding lane. Both still share the fitness-measurement substrate via `corpus.Compute` / `ao goals measure`. + +## Flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--max-cycles=N` | unlimited | Stop after `N` completed cycles | +| `--dry-run` | off | Show planned cycle actions without executing | +| `--beads-only` | off | Skip goal measurement and run backlog-only selection | +| `--skip-baseline` | off | Skip first-run baseline snapshot | +| `--quality` | off | Prioritize harvested post-mortem findings | +| `--compile` | off | Run `ao mine` + `ao defrag` warmup before cycle 1 | +| `--test-first` | on | Pass strict-quality defaults through to `rpi` | +| `--no-test-first` | off | Explicitly disable test-first passthrough to `rpi` | +| `--no-lifecycle` | off | Skip lifecycle work generators in Steps 3.4-3.6 (/test, /deps, /perf, /refactor). Falls back to manual scanning. | +| `--mode=burst\|loop` | burst | Operator-loop; STOP refused. [loop-mode.md](references/loop-mode.md). | + +## Execution Steps + +**YOU MUST EXECUTE THIS WORKFLOW. Do not just describe it.** + +**FULLY AUTONOMOUS.** Read `references/autonomous-execution.md`. Every `rpi` uses `--auto`. Do NOT ask the user anything. Each cycle = complete 3-phase `rpi` run. + +For broad AgentOps 3.0 domain evolution across skills, CLI, hooks, docs, tests, +beads, and knowledge, first read +[references/domain-evolution-bootstrap.md](references/domain-evolution-bootstrap.md). +It supplies the BDD/DDD/Hexagonal/TDD/XP control surface and the clean-room +skill-factory guardrails. + +### Step 0: Setup + +**Stale-checkout survey guard (run FIRST).** Before any tree-reading survey: `git fetch origin && git status -sb`. If the checkout is behind/diverged AND it is a throwaway orchestration tree with no un-pushed work, `git reset --hard origin/main`. **BAN `git pull --rebase` on the survey path** — it silently no-ops against a diverged local `main`, so merged files appear "missing" and the survey investigates already-merged work. + +```bash +git fetch origin && git status -sb # survey guard — never `git pull --rebase` here +mkdir -p .agents/evolve +ao corpus inject --query "autonomous improvement cycle" --limit 5 2>/dev/null || true +bash scripts/evolve-update-session-state.sh 2>/dev/null || true # refresh derived idle_streak + mode_repeat_streak +``` + +`ao corpus inject` routes through the typed BC1 `CorpusReaderPort` +(`cli/cmd/ao/corpus_reader_adapter.go`, cycle 112 productionCorpusReader), +emitting one ranked `ports.CorpusItem` JSON record per line from +`.agents/learnings/` by default. This closes soc-y5vh.1 — Step 0 prior-knowledge +retrieval is now load-bearing on the typed port, not an untyped `ao lookup` +shell-out. + +**Apply retrieved knowledge:** If learnings are returned, check each for applicability to the current improvement cycle. For applicable learnings, cite by filename and record: `ao metrics cite "" --type applied 2>/dev/null || true` + +**Prior-failure injection (mandatory):** read the last 3 entries of `.agents/evolve/cycle-history.jsonl`. For any with `gate` containing `FAIL|FAILED|BLOCKED`, extract failure-surface keywords (`registry|bats|markdown|supergate|canary|coverage|toolchain`) and search `.agents/learnings/` for matching learnings. Print the top matches before work selection. Without this read path, the loop accumulates write-only ledgers and re-derives lessons each cycle. See `references/convergence-mechanics.md` for the full recipe. + +Before cycle recovery, load the repo execution profile contract when it exists. The repo execution profile is the source for repo policy; the user prompt should mostly supply mission/objective, not restate startup reads, validation bundle, tracker wrapper rules, or `definition_of_done`. + +- Locate `docs/contracts/repo-execution-profile.md` and `docs/contracts/repo-execution-profile.schema.json`. +- Read the ordered `startup_reads` and bootstrap from those repo paths before selecting work. +- Cache repo `validation_commands`, `tracker_commands`, and `definition_of_done` into session state. +- If the repo execution profile is present but missing required fields, stop or downgrade with an explicit warning before cycle 1. Do not silently invent repo policy. +- Read operating-doctrine ADRs (`docs/adr/` or `docs/decisions/`) when present — intent the loop re-reads each cycle: only operator markers stop the loop; the bead queue is a hypothesis re-confirmed against the goal, not spec; file-a-bead when a candidate is architecture disguised as bounded work. + +Then load the repo-local autodev program contract when it exists. The execution profile remains the repo bootstrap and landing-policy layer; `PROGRAM.md` or `AUTODEV.md` is the repo-local execution layer for the current improvement loop. + +- Locate `PROGRAM.md` and `AUTODEV.md`. `PROGRAM.md` takes precedence. +- Read the resolved program before cycle recovery and cache `program_path`, `mutable_scope`, `immutable_scope`, `validation_commands`, `decision_policy`, and `stop_conditions` into session state. +- If the program file exists but is structurally invalid, stop or downgrade with an explicit warning before cycle 1. Do not silently ignore a broken operator contract. +- When a program contract exists, prefer work that can land wholly inside mutable scope. Do not silently widen scope around immutable files. + +Recover cycle number, generator streaks, and the last claimed work item from disk (survives context compaction). Initialize `CYCLE` from `cycle-history.jsonl`, recover `IDLE_STREAK`, `GENERATOR_EMPTY_STREAK`, `LAST_SELECTED_SOURCE`, and `CLAIMED_WORK_REF` from `session-state.json`. + +**Circuit breakers:** Time-based (60 min no productive work). + +**Oscillation quarantine:** Pre-populate quarantine list from cycle history (scan for goals with 3+ improved-to-fail transitions). See `references/oscillation.md`. + +Parse flags: `--max-cycles=N` (default unlimited), `--dry-run`, `--beads-only`, `--skip-baseline`, `--quality`, `--compile`. + +Track cycle-level execution state: + +```text +evolve_state = { + cycle: , + mode: , + test_first: , + repo_profile_path: , + startup_reads: , + validation_commands: , + tracker_commands: , + definition_of_done: , + program_path: , + program_mutable_scope: , + program_immutable_scope: , + program_validation_commands: , + program_decision_policy: , + program_stop_conditions: , + generator_empty_streak: , + last_selected_source: , + claimed_work: , + queue_refresh_count: +} +``` + +Persist `evolve_state` to `.agents/evolve/session-state.json` at each cycle boundary, after work claims, after release/finalize, and during teardown. `cycle-history.jsonl` remains the canonical cycle ledger; `session-state.json` carries resume-only state that has not yet earned a committed cycle entry. Both files are **local-only** (the nested `.agents/.gitignore` denies all paths) — record durable milestones in commit messages too. See `references/cycle-history.md` for full local-only semantics. + +### Step 0.2: Compile Warmup (--compile only) + +Skip if `--compile` was not passed or if `--dry-run`. Read `references/knowledge-loop-integration.md` for the full warmup procedure (mine + defrag + signal notes). + +### Step 0.5: Baseline (first run only) + +Skip if `--skip-baseline` or `--beads-only` or baseline already exists. Read `references/fitness-scoring.md` for the baseline capture procedure. + +### Step 1: Kill Switch Check + +Run at the TOP of every cycle: + +```bash +CYCLE_START_SHA=$(git rev-parse HEAD) +# Mechanical pre-cycle gate (soc-sfjx): markers (KILL/STOP/DORMANT/HANDOFF with +# TTL + soc-5qit non-sticky semantics), goal-regression, and prior-cycle-FAIL. +# This is a SCRIPT the loop MUST run, not prose it can skip — externalized from +# the old inline block so the kill-switch + revert-on-red are enforced, not +# advisory. Adapted from the mt-olympus unbounded-evolve substrate. +if [ -x scripts/evolve/halt-check.sh ]; then + if ! HALT_OUT=$(bash scripts/evolve/halt-check.sh --json); then + REASON=$(printf '%s' "$HALT_OUT" | jq -r '.halt_reason // "unknown"') + if [ "$REASON" = "prior_cycle_fail" ]; then + export EVOLVE_RESTORATIVE=1 # not terminal: Step 1.5 restricts scope to CI-red reduction + else + echo "halt: $REASON"; exit 0 # kill/user_halt/dormant/goal_regression -> stop this cycle + fi + fi +else + # Fallback for repos without the substrate: minimal inline marker check. + for m in "$HOME/.config/evolve/KILL" .agents/evolve/STOP; do [ -f "$m" ] && { echo "halt: $m"; exit 0; }; done + [ -f .agents/evolve/DORMANT ] && { [ "$(bd ready --json 2>/dev/null | jq -r 'length // 0')" -gt 0 ] && rm -f .agents/evolve/DORMANT || { echo dormant; exit 0; }; } + [ -f .agents/evolve/HANDOFF ] && rm -f .agents/evolve/HANDOFF +fi +``` + +**Agile-first dormancy (soc-5qit):** `DORMANT` is NEVER sticky while ready beads exist — `halt-check.sh` auto-clears it when `bd ready`/harvested work exists. KILL/STOP honor `EVOLVE_KILL_TTL_DAYS` (default 7); stale markers are surfaced and bypassed. `goal_regression` (latest cycle report `goals_passing_after < before`) halts the loop for operator attention. Heavy-context sessions write non-sticky HANDOFF; the next fire clears it and resumes. The gate is mechanical: see `scripts/evolve/halt-check.sh`. + +### Step 1.5: Healing-first classifier + +Before fitness or work selection, classify the cycle: `ao ci recent --limit 1 2>/dev/null | jq -r '.Conclusion // empty'` (typed BC2 `CIStatusPort`, soc-y5vh.2). If the last push CI was `failure`, this cycle is **restorative-only** — Step 3 takes only CI-red-reducing work (bug-type harvested items, gate-failure-fix beads, generator bug output); no promotions, features, or new-shape work until green. A `gate=FAIL` in cycle-history.jsonl auto-triggers this for cycle N+1 (and `halt-check.sh` surfaces it as `prior_cycle_fail`). See `references/convergence-mechanics.md`. + +**Convergence check:** evaluate the STOP predicate via the typed BC3 `ConvergenceCheckPort` — `ao loop converged --green-streak --unconsumed-high-medium [--fitness-baseline]` (soc-y5vh.8). Branch on `.converged` (default: CI green streak ≥ 3, HIGH+MEDIUM next-work ≤ 1, fitness baseline captured); if true, emit teardown and do NOT re-arm wakeup. + +### Step 2: Measure Fitness + +Skip if `--beads-only`. Run `scripts/evolve-measure-fitness.sh` to produce a rolling fitness snapshot at `.agents/evolve/fitness-latest.json`. Read `references/fitness-scoring.md` for the full measurement procedure, baseline capture, and post-cycle regression detection. + +### Step 3: Select Work + +Selection is a ladder, not a one-shot check — after every productive cycle, return to the TOP and re-read the queue before considering dormancy. **Read [references/work-selection-ladder.md](references/work-selection-ladder.md) for the full per-rung procedure** (programmatic `ao loop next-work` recommendation, scope filter, metronome gate, the generator rungs with their code blocks, the `--quality` inverted cascade, and the dormancy hard-gate). + +Ladder order (standard mode): +- **3.0 Scope filter** (soc-5qit) — split-or-defer oversized candidates via scout-mode; never bail. +- **3.1 Harvested** — `.agents/rpi/next-work.jsonl`, highest-value unconsumed. +- **3.2 Open ready beads** — `bd ready`, highest priority. +- **3.3 Failing goals + directive gaps** — skip if `--beads-only`; skip quarantined oscillators. +- **3.4–3.6 Generators** — `/test` coverage, `/deps`+`/perf`, `/refactor`; findings → beads/queue items. +- **3.7 Feature suggestions** grounded in repo purpose. + +`--quality` inverts the top (findings before goals/directives). The metronome gate blocks a rung that would repeat the trailing run's `mode` (streak ≥3). + +**Agile invariant (soc-5qit):** `bd ready ≥ 1` ⇒ the loop NEVER writes DORMANT and NEVER exits. The only path to DORMANT is a fully empty backlog + dry generators (3 passes). Context exhaustion → HANDOFF, not DORMANT. Under loop mode, `write-stop-marker` refuses → log blocked + operator-wait (ADR-0007). + +If `--dry-run`: report what would be worked on and go to Teardown. + +### Step 4: Execute + +Primary engine: `rpi` for implementation-quality work (all 3 phases mandatory). `/implement` or `/crank` only when a bead has execution-ready scope. + +If a repo-local `PROGRAM.md` contract is active, `rpi` will load it automatically. `evolve` must compose with that behavior, not bypass it: +- Do not select work that is obviously outside mutable scope. +- If a bead or goal would require edits under immutable scope, escalate it or convert it into durable follow-up work instead of launching `rpi`. +- When work is plausibly in scope but still uncertain, let `rpi` discovery validate the fit and surface a scope escape explicitly. + +For a **harvested item, failing goal, directive gap, testing improvement, validation tightening task, bug-hunt result, drift finding, or feature suggestion**: +``` +Invoke /rpi "{normalized work title}" --auto --max-cycles=1 +``` + +For a **beads issue**: +``` +Prefer: /rpi "Land {issue_id}: {title}" --auto --max-cycles=1 +Fallback: /implement {issue_id} +``` +Or for an epic with children: `Invoke /crank {epic_id}`. + +If Step 3 created durable work instead of executing it immediately, re-enter Step 3 and let the newly-created bead item win through the normal selection order. + +**Mechanical-batch hint:** when the implementation phase identifies > 20 uniform per-file edits, prefer a script (`awk`/`sed`/`for f in $candidates`) over N tool-level Edit calls. See `references/mechanical-batches.md` for the decision rule and the script-first pattern. + +**Pre-flight schema check (architectural migrations):** if the selected work is a port/adapter migration that rewires an existing consumer, BEFORE invoking `rpi`, sample two representative consumer call sites and compare field-use against the target port surface. If the consumer reads > 20% more fields than the port projects, abort the migration cycle and convert the work into a port-widening cycle instead. The phase-2 narrowness post-mortem (`docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md`) is the encoded lesson; see `references/pre-flight-schema-check.md` for the procedure. + +**Operator-shape carve-out:** `AskUserQuestion` is permitted ONLY for shape decisions affecting > 50 files OR a schema/contract surface (carrier choice, struct-field shape, frontmatter-key shape). See `references/autonomous-execution.md` for the bound on this exception. + +### Step 4.5: Source-surface detection (pre-gate sync) + +Before invoking the regression gate, sync downstream artifacts when the staged diff touches binary or embedded surfaces: + +- `cli/**/*.go` changed → `cd cli && make build && go install ./cmd/ao` +- `skills/**` or `hooks/**` changed → `cd cli && make sync-hooks` +- `skills-codex/**` changed → `bash scripts/regen-codex-hashes.sh` + +Without these, the gate fails on stale-binary or embedded-drift errors that look like real regressions. See `references/gate-hygiene.md` for the detection recipe. + +**Adding or modifying a skill?** A skill touches **six derived surfaces** (registry.json SKU catalog, skill-domain-map, context-map, skill counts + the hand-added `SKILL-TIERS.md` row, codex twin, narrative counts) — regenerate them in one shot via `scripts/regen-all.sh` + the codex/count steps, never piecemeal. The most-missed is `registry.json` (stale → `contracts-sync` + `correctness(ubuntu)` fail together). See [references/new-skill-landing.md](references/new-skill-landing.md) for the full procedure, then [references/gate-hygiene.md](references/gate-hygiene.md) for the pre-push diff-scope check + pre-existing-vs-mine red triage. + +### Step 5: Regression Gate + +After execution, run the project build+test bundle. If the repo execution profile declared `validation_commands`, run them. If a repo-local program contract exists, run its `validation_commands` too, de-duplicated and in declared order after the repo bootstrap checks. Also check `if [ -f scripts/check-wiring-closure.sh ]; then bash scripts/check-wiring-closure.sh; fi`. + +Use the program contract's `decision_policy` as the first keep/revert rule set for the cycle: +- if the cycle breached immutable scope, treat it as regressed +- if program validation commands fail, treat it as regressed +- if the decision policy declares a revert rule that fired, revert before consuming claimed work or advancing the queue + +Treat program `stop_conditions` as per-cycle done criteria. Do not mark claimed work consumed, completed, or productive until both the stop conditions and the regression gate pass. + +If not `--beads-only`, re-measure fitness to `fitness-latest-post.json` and detect regressions. The AgentOps CLI is required for fitness measurement. Read `references/fitness-scoring.md` for the full measurement, regression detection, and revert procedure. + +**Gate output parsing:** trust the structural marker `^.*Pass [0-9]+: (FAILED|BLOCKED)` over the trailing status line — the trailing line conflates blocking and advisory results. See `references/gate-hygiene.md`. + +Work finalization after the regression gate: claim it first, then keep `consumed: false` until the /rpi cycle succeeds. After the cycle's `/post-mortem` finishes, immediately re-read `.agents/rpi/next-work.jsonl` before selecting the next item. Read `references/knowledge-loop-integration.md` for full claim/release semantics. + +### Step 6: Log Cycle + Commit + +Two paths: productive cycles get committed, idle cycles are local-only. + +**PRODUCTIVE cycles** (result is improved, regressed, or harvested): compute quality score (if `--quality`), log via `scripts/evolve-log-cycle.sh`, commit if real changes exist. See `references/quality-mode.md` for scoring. + +**IDLE cycles** (nothing found even after generator layers): log via `evolve-log-cycle.sh` with `--result "unchanged"`. No git add, no commit. + +**Record the XP/BDD/TDD trace.** When a cycle worked a product or goal-backed gap, pass `--trace-json` to `evolve-log-cycle.sh` (or `ao loop append`) so the cycle records the continuous-evolution kernel — goal hypothesis → selected gap → Gherkin scenario → first failing proof → red/green evidence → refactor note → validation evidence → ratchet action → goal reshape — and a reviewer can reconstruct the cycle without the transcript. A trivial one-shot cycle records a `trace.exemption_reason` instead of carrying false BDD/TDD ceremony. Trace completeness is advisory, never a gate. See `references/cycle-history.md` ("XP/BDD/TDD Evidence Trace"). + +### Step 7: Loop or Stop + +```bash +while true; do + # Step 1 .. Step 6 + # Stop ONLY if: operator override (KILL/STOP), max-cycles, regression-breaker, + # or genuine stagnation (bd ready=0 AND harvested=0 AND failing-goals=0 AND + # generators dry across 3 passes). Context exhaustion is NOT a stop — it's a + # session-handoff signal (HANDOFF marker) that the next cron-fire clears. + CYCLE=$((CYCLE + 1)) +done +``` + +**Stop reasons (soc-5qit, ALL require genuine reason — never just context size):** + +1. **KILL/STOP file present** — operator override. +2. **`--max-cycles=N` cap reached**. +3. **Genuine stagnation** — `bd ready=0 AND harvested-unconsumed=0 AND failing-goals=0 AND GENERATOR_EMPTY_STREAK>=2 AND IDLE_STREAK>=2`. Writes DORMANT, which auto-clears in Step 1 the moment `bd create` adds a new ready bead. +4. **Regression breaker after a revert**. + +**Context exhaustion is NOT a stop (soc-5qit).** Heavy-context sessions write `.agents/evolve/HANDOFF` (non-sticky), log `result: "context-handoff"` to cycle-history, and exit the turn cleanly. The next cron-fire (compacted/fresh context) clears HANDOFF in Step 1 and resumes. The loop is continuous across compactions; never write DORMANT for context size. See `references/context-budget.md`. + +**Mandatory checkpoint #6 — session-PR threshold (NOT terminal, gates next cycle):** at `session_pr_count >= 5` (soc-waxr default), invoke `/post-mortem --deep`, wait for verdict file. PASS → continue. WARN → continue with caveat in next cycle's `notes`. FAIL or non-convergence → write STOP. Agent MUST NOT self-grade or self-write STOP. Full procedure in `references/postmortem-checkpoint.md` (soc-n75z). + +**Self-perpetuation modes:** the terminal-native `evolve` loop and the Claude-Code-harness `ScheduleWakeup` end-of-turn pattern are duals — both drive Step 1..Step 7 repeatedly against the same persisted state. See `references/autonomous-execution.md` for the ScheduleWakeup cadence and the rule that hard stops must NOT re-arm. + +Push only when productive work has accumulated: +```bash +if [ $((PRODUCTIVE_THIS_SESSION % 5)) -eq 0 ] && [ "$PRODUCTIVE_THIS_SESSION" -gt 0 ]; then + git push +fi +``` + +**Drive to completion (orchestrator-merge model, soc-2drk).** Where the repo requires PRs (branch protection rejects direct `main` pushes), a productive cycle does not stop at "PR opened" — the loop is the orchestrator that drives each bead to *merged*. Ship the bead from its per-bead worktree as a PR (trailers `Closes-scenario` / `Bounded-context` / `Evidence`), wait for CI, and **squash-merge to main yourself once CI is green** (`gh pr merge --squash --admin`), then `bd close` the bead and remove the worktree. **Green CI is the only merge gate** — on a quality/test red, fix-and-repush or revert; never merge red. The loop may dispatch sub-agents to implement and drives their PRs to merge too. The operator stays *on* the loop (intent + STOP marker), not *in* it (per-PR approval). This **supersedes "operator is the merge gate"** for the autonomous loop — see [ADR-0008](../../docs/adr/ADR-0008-evolve-intelligent-agile-operating-model.md). + +**Confirmed-MERGED gate before `bd close` (hard, not advisory).** Re-confirm `gh pr view --json state -q .state` returns `MERGED` *before* `bd close` — never close on a `gh pr merge` exit code, a log line, or a batch `bd --json` query (those flake to null/0). **Close a parent epic ONLY after every child PR is independently confirmed `MERGED`**; re-query per child first, and one non-merged child aborts the epic close. (Caught two premature epic-closes in the 2026-05-31 crank session — this gate is the governance checkpoint, applied here too.) Enforce via the committed `scripts/reconcile-pr.sh [--epic ]` + `scripts/check-epic-children-closed.sh ` (hermetic-tested in `tests/scripts/`), not by hand. + +### Teardown + +Read `references/knowledge-loop-integration.md` for the full teardown learning extraction procedure (commit staged artifacts, run `/post-mortem`, push, report summary). + +A teardown `/post-mortem` is a light-touch retrospective on session-end. It does NOT substitute for the mandatory threshold checkpoint (`references/postmortem-checkpoint.md`); that one is council-gated and edge-triggered at `session_pr_count >= 5`. Never write `.agents/evolve/STOP` as a substitute for the checkpoint's verdict file — STOP without a verdict is the 2026-05-20 anti-pattern (soc-n75z). + +**Release-context teardown (MANDATORY when the loop ran on a release-shaped branch):** + +When the current branch matches `release/*`, `v*-prep`, `v*-evolve-run`, or `v\d+\.\d+*`, the teardown report MUST NOT recommend `/release` as the next step. Instead, emit the explicit pre-release checklist below — the operator must run these AND confirm green before tagging: + +``` +## Pre-release checklist — REQUIRED before /release + +The autonomous loop has stopped, but release-readiness gates have NOT been run +during cycles. The operator MUST run the following sequence and confirm green +before invoking /release. Do NOT skip any of these on the basis of "cycles +were green" — fast pre-push gate ≠ full pre-push gate; goals-measure ≠ +release readiness. + + [ ] 1. Regenerate ALL derived surfaces if any cobra command/flag changed: + bash scripts/regen-all.sh # COMMANDS.md, registry.json, maps + # ADDING an `ao` command also needs the 2 surfaces regen-all only WARNS + # about: cli/cmd/ao/cobra_commands_test.go expectedCmds (x2 lists) + + # the cli-command-surface heading counts in + # evals/agentops-core/fixtures/cli-command-surface-smoke.sh AND + # evals/agentops-core/cli-command-surface-matrix.json (top/sub/all). + # Run the smoke fixture to read the exact new counts. (ag-jy12 will + # automate this.) Full procedure in + # [references/ao-command-landing.md](references/ao-command-landing.md) + git diff cli/docs/COMMANDS.md registry.json # commit if non-empty + + [ ] 2. Run the FULL pre-push gate (NOT --fast) with fail-fast OFF, so a + PRE-EXISTING failure (e.g. corpus-freshness) cannot mask your own + regressions by stopping the run early: + PRE_PUSH_FAIL_FAST=false bash scripts/pre-push-gate.sh + + [ ] 3. Run the release-readiness gate: + bash scripts/ci-local-release.sh + + [ ] 4. (Recommended) Smoke /evolve with the new typed read paths if BC port + wire-ups changed: + /evolve --quick --max-cycles=1 --dry-run + +Only after [1]–[3] pass: /release + +If any check fails, fix the issue, re-run all four, then ship. +``` + +The handoff artifact (e.g., `.agents/runs//READY-TO-TAG.md`) MUST contain this checklist verbatim, unchecked, when written by the loop. The operator checks the boxes as they complete each gate; "ready to tag" means the boxes are checked, not that the loop ran cleanly. + +**Rationale:** cycles 170-183 of the v2.41-evolve-run shipped clean code, all unit/integration tests green, `ao goals measure` 0/30 failing for three consecutive cycles — but the loop never ran the full pre-push gate, `ci-local-release.sh`, or `generate-cli-reference.sh`. The latter was load-bearing (the branch removed a CLI flag). Per-cycle `--fast` is a smoke test, not release readiness. Operator caught the gap; this checklist makes it mechanical. + +## Examples + +**User says:** `/evolve --max-cycles=5` +**What happens:** Evolve re-enters the full selection ladder after every `rpi` cycle and runs producer layers instead of idling on empty queues. + +**User says:** `/evolve --beads-only` +**What happens:** Evolve skips goals measurement and works through `bd ready` backlog. + +**User says:** `/evolve --dry-run` +**What happens:** Evolve shows what would be worked on without executing. + +**User says:** `/evolve --compile` +**What happens:** Evolve runs `ao mine` + `ao defrag` at session start to surface fresh signal (orphaned research, code hotspots, oscillating goals) before the first evolve cycle. Use before a long autonomous run or after a burst of development activity. + +**User says:** `evolve` +**What happens:** See `references/examples.md` for a worked overnight flow that moves through beads -> harvested work -> goals -> testing -> bug hunt -> feature suggestion before dormancy is considered. + +See `references/examples.md` for detailed walkthroughs. + +## Troubleshooting + +| Problem | Solution | +|---------|----------| +| Loop exits immediately | Remove `~/.config/evolve/KILL` or `.agents/evolve/STOP` | +| Stagnation after repeated empty passes | Queue layers and producer layers were empty across multiple passes — dormancy is the fallback outcome | +| `ao goals measure` hangs | Use `--timeout 30 --total-timeout 75` or `--beads-only` to skip | +| Regression gate reverts | Review reverted changes, narrow scope, re-run; claimed work items must be released back to available state | + +See `references/cycle-history.md` for advanced troubleshooting. + +## References + +- [references/evolve.feature](references/evolve.feature) — Executable spec: gated cycles, ladder, bounded slice, never-self-halt +- [references/long-loop-discipline.md](references/long-loop-discipline.md) — Disk-is-truth axiom +- [references/artifacts.md](references/artifacts.md) — Generated files registry +- [references/autonomous-execution.md](references/autonomous-execution.md) — Autonomous-loop rules + operator-shape carve-out +- [references/snapshot-pattern-for-long-cycle-gates.md](references/snapshot-pattern-for-long-cycle-gates.md) — Snapshot pattern for long-cycle gates +- [references/compounding.md](references/compounding.md) — Knowledge flywheel and work harvesting +- [references/context-budget.md](references/context-budget.md) — `CONTEXT_BUDGET_EXHAUSTED` as a third stop reason and handoff protocol +- [references/convergence-mechanics.md](references/convergence-mechanics.md) — Read-path mechanisms for compounding +- [references/domain-evolution-bootstrap.md](references/domain-evolution-bootstrap.md) — BDD/DDD/Hexagonal/TDD/XP control surface for skill/domain evolution +- [references/cycle-history.md](references/cycle-history.md) — JSONL format, recovery protocol, kill switch +- [references/examples.md](references/examples.md) — Detailed usage examples +- [references/fitness-scoring.md](references/fitness-scoring.md) — Baseline capture, regression detection, revert procedure +- [references/gate-hygiene.md](references/gate-hygiene.md) — Pre-gate source-surface detection, structural gate-output parsing, pre-push diff-scope check, pre-existing-vs-mine red triage +- [references/new-skill-landing.md](references/new-skill-landing.md) — The six derived surfaces a new/modified skill must regenerate in one shot to stay one-shot-green +- [references/ao-command-landing.md](references/ao-command-landing.md) — The surfaces a new/renamed `ao` command must regenerate (cobra expectedCmds x2 + cli-command-surface counts that regen-all only WARNS about) +- [references/goals-schema.md](references/goals-schema.md) — GOALS.yaml format and continuous metrics +- [references/knowledge-loop-integration.md](references/knowledge-loop-integration.md) — Claim/release semantics and harvest re-read +- [references/mechanical-batches.md](references/mechanical-batches.md) — Script-first vs per-file Edit for > 20-file uniform batches +- [references/metronome-gate.md](references/metronome-gate.md) — Cross-cycle same-mode-repeat blocker +- [references/oscillation.md](references/oscillation.md) — Oscillation detection and quarantine +- [references/pre-flight-schema-check.md](references/pre-flight-schema-check.md) — Cheap field-fit check before architectural migration cycles +- [references/postmortem-checkpoint.md](references/postmortem-checkpoint.md) — Stop reason #6: session-PR post-mortem checkpoint (soc-n75z) +- [references/parallel-execution.md](references/parallel-execution.md) — Parallel /swarm architecture +- [references/quality-mode.md](references/quality-mode.md) — Quality-first mode: scoring, priority cascade, artifacts +- [references/scout-mode.md](references/scout-mode.md) — Scout-mode as a first-class cycle result; scope filter procedure +- [references/teardown.md](references/teardown.md) — Trajectory computation and session summary + +## See Also + +- `skills/curate/SKILL.md` — the knowledge compounder; `--mode=harvest` gathers artifacts and `--mode=dream` runs the compounding loop overnight +- `skills/rpi/SKILL.md` — Full lifecycle orchestrator (called per cycle) +- `skills/crank/SKILL.md` — Epic execution (called for beads epics) +- `docs/contracts/autodev-program.md` — Repo-local operational contract for bounded autonomous development +- `GOALS.yaml` — Fitness goals for this repo +- [test](../test/SKILL.md) — Test generation and coverage analysis +- [refactor](../refactor/SKILL.md) — Safe, verified refactoring +- [deps](../deps/SKILL.md) — Dependency audit and vulnerability scanning +- [perf](../perf/SKILL.md) — Performance profiling and benchmarking diff --git a/.agents/skills/evolve/references/ao-command-landing.md b/.agents/skills/evolve/references/ao-command-landing.md new file mode 100644 index 0000000..574a513 --- /dev/null +++ b/.agents/skills/evolve/references/ao-command-landing.md @@ -0,0 +1,52 @@ +# AO-Command Landing — the surfaces a new `ao` command must regenerate + +Adding (or renaming/removing) an `ao` command or subcommand touches more than the +generated CLI reference. `scripts/regen-all.sh` regenerates the bulk of the derived +surfaces, but it only **WARNS** about two that it cannot regenerate for you — those +are the dominant fix-and-repush cause when landing a command. Regenerate them in the +**same pass** before opening the PR, not piecemeal. + +This is the command-surface parallel of [new-skill-landing.md](new-skill-landing.md) +(the six derived surfaces a *skill* edit must regenerate). + +## The shortcut + +`scripts/regen-all.sh` regenerates `cli/docs/COMMANDS.md`, `registry.json`, the +skill/context maps, the embedded skills, and the cli-surface inventory in one pass — +prefer it over running the individual generators. Then hand-fix the two surfaces it +only warns about (below), and finally run `scripts/regen-all.sh --check`. + +## The two surfaces regen-all only WARNS about + +1. **Cobra `expectedCmds` (x2 lists)** — `cli/cmd/ao/cobra_commands_test.go` hardcodes + the registered-command list **twice**: once in `TestCobraCommandTreeRegistration` + and once in `TestCobraExpectedCmdsMatchRegistration` (the second comment even says + "Same list as TestCobraCommandTreeRegistration"). Both `expectedCmds` slices must + gain the new top-level command name, or the second test fails with + "registered command %q is not in expectedCmds — add it to keep the list in sync". + +2. **`cli-command-surface` heading counts** — the generated reference's `ao` heading + counts (`top`/`sub`/`all`) are asserted in **two** offline canaries that must move + together: + - `evals/agentops-core/fixtures/cli-command-surface-smoke.sh` — the + `top_count`/`sub_count`/`all_count` literals (currently `76`/`192`/`268`). + - `evals/agentops-core/cli-command-surface-matrix.json` — the public canary that + enumerates every documented command/subcommand help page. + + **Run the smoke fixture to read the exact new counts** rather than computing them by + hand: + + ```bash + bash evals/agentops-core/fixtures/cli-command-surface-smoke.sh + # On a count mismatch it prints: unexpected command heading counts: top=.. sub=.. all=.. + # Update the literals in the fixture (and the matrix.json cases) to the printed values. + ``` + + (Automating this regen is tracked as `ag-jy12`.) + +## See also + +- [new-skill-landing.md](new-skill-landing.md) — the six derived surfaces a *skill* + edit regenerates (the sibling discipline for the skills axis). +- [gate-hygiene.md](gate-hygiene.md) — pre-push diff-scope check + pre-existing-vs-mine + red triage that keeps a command-landing PR one-shot-green. diff --git a/.agents/skills/evolve/references/artifacts.md b/.agents/skills/evolve/references/artifacts.md new file mode 100644 index 0000000..f61750d --- /dev/null +++ b/.agents/skills/evolve/references/artifacts.md @@ -0,0 +1,37 @@ +# /evolve Artifacts + +## Committed to Git + +| File | Purpose | +|------|---------| +| `GOALS.yaml` | Fitness goals (repo root) | +| `.agents/evolve/fitness-baselines/goals-/*.json` | Immutable baseline snapshots, one directory per GOALS.md/GOALS.yaml content hash | +| `.agents/evolve/cycle-history.jsonl` | Cycle outcomes log (includes commit SHAs) | + +## Local Only (gitignored) + +| File | Purpose | +|------|---------| +| `.agents/evolve/fitness-latest.json` | Pre-cycle fitness snapshot (rolling, overwritten each cycle) | +| `.agents/evolve/fitness-latest-post.json` | Post-cycle fitness snapshot (for regression comparison) | +| `.agents/evolve/session-state.json` | Resume-only state: generator streaks, last selected source, pending queue claim | +| `.agents/evolve/session-summary.md` | Session wrap-up | +| `.agents/evolve/session-fitness-delta.md` | Session fitness trajectory (baseline to final delta) | +| `.agents/evolve/STOP` | Local kill switch | +| `~/.config/evolve/KILL` | External kill switch | + +## Removed (legacy) + +These files are no longer generated. Older repos may have them in git history: + +| File | Replacement | +|------|-------------| +| `.agents/evolve/fitness-{N}-pre.json` | `fitness-latest.json` (rolling) | +| `.agents/evolve/fitness-{N}-post.json` | `fitness-latest-post.json` (rolling) | +| `.agents/evolve/cycle-0-report.md` | Inlined into session-summary.md | +| `.agents/evolve/fitness-0-baseline.json` | `.agents/evolve/fitness-baselines/goals-/*.json` | +| `.agents/evolve/active-baseline.txt` | Current GOALS.md/GOALS.yaml hash resolves the active era directory | +| `.agents/evolve/baselines/index.jsonl` | No longer needed by the v2 era-hash baseline gate | +| `.agents/evolve/baselines/*.json` | `.agents/evolve/fitness-baselines/goals-/*.json` | +| `.agents/evolve/last-sweep-date` | No longer needed by the baseline gate | +| `.agents/evolve/KILLED.json` | Kill switch acknowledgment removed (STOP file is sufficient) | diff --git a/.agents/skills/evolve/references/autonomous-execution.md b/.agents/skills/evolve/references/autonomous-execution.md new file mode 100644 index 0000000..4a881fe --- /dev/null +++ b/.agents/skills/evolve/references/autonomous-execution.md @@ -0,0 +1,47 @@ +# Evolve Autonomous Execution Rules + +## Fully Autonomous + +Evolve runs without human intervention from start to teardown. Every `/rpi` invocation uses `--auto`. Do NOT ask the user for confirmation, clarification, or approval at any point. Do NOT pause between cycles. Do NOT summarize and wait. The user's only touchpoint is the teardown report at the very end. + +### Operator-shape carve-out + +A single, bounded exception applies. `AskUserQuestion` is permitted ONLY when the cycle has identified a **shape decision** that affects > 50 files OR touches a schema/contract surface, and where picking wrong costs > 10 minutes of rework. Examples that qualify: declaration carrier shape across a 533-file pool, frontmatter-key vs `$comment`-key vs sidecar-file choice, struct-field addition to a shared contract type. Anything that fits inside an established shape continues to be fully autonomous. The carve-out exists because a 30-second user pick can unblock 300+ file edits — but it is narrow: ask once per shape decision, then resume the autonomous loop. + +## ScheduleWakeup self-perpetuation (Claude-Code harness) + +Inside the Claude-Code harness, `/evolve` (the terminal-native loop) is not the only self-perpetuation surface. Each /evolve cycle can call `ScheduleWakeup` at end-of-turn to fire the next cycle: + +- Productive cycle (commit landed): `delaySeconds=270` keeps the prompt cache warm. +- Scout cycle: `delaySeconds=600` — bigger gap, no cache reload cost since context isn't reused. +- Idle cycle: `delaySeconds=1800` — coarse poll for fresh signal. + +Hard stops MUST NOT call `ScheduleWakeup`: KILL/STOP files, dormancy reached, `CONTEXT_BUDGET_EXHAUSTED` (see `references/context-budget.md`), or `--max-cycles` cap hit. When dormancy fires (Step 3 hard-gate), `/evolve` writes `.agents/evolve/DORMANT` and Step 1 short-circuits on subsequent fires before any further tool calls — this is the operational enforcement of the "no ScheduleWakeup on dormancy" rule (see `references/cycle-history.md` for the marker semantics). The terminal-mode `/evolve` loop and the Claude-Code `ScheduleWakeup` loop are duals: both drive Step 1..Step 7 repeatedly, both honor the same kill switches and stop conditions, both persist resume state via `.agents/evolve/session-state.json`. + +## Each Cycle = Complete /rpi Run + +All 3 phases (discovery → implementation → validation). Never invoke a partial RPI. If a task is too large for one cycle, break it into smaller sub-tasks during discovery and let `/crank` handle the waves. Evolve's job is to keep the loop turning, not to micro-manage individual tasks. + +## Break Large Work into Sub-RPI Cycles + +When work selection identifies a massive task (7+ issues, multi-subsystem scope), decompose it during `/rpi`'s discovery phase into an epic with waves. One evolve cycle = one `/rpi` run = one complete lifecycle. If the epic is too large for a single session, `/rpi`'s built-in retry and `--from=` resume handle continuation. + +## Respect Repo-Local Program Contracts + +When `PROGRAM.md` or `AUTODEV.md` exists, treat it as a hard operational constraint for cycle selection and keep/revert decisions: +- prefer work that can plausibly land inside mutable scope +- do not intentionally select work that requires immutable-scope edits +- treat out-of-scope work as escalation or backlog material, not permission to widen scope in place +- let `/rpi` enforce the active program contract during execution, then use its validation and decision policy during the evolve regression gate + +## Anti-Patterns (DO NOT) + +| Anti-Pattern | Why It's Wrong | Correct Behavior | +|--------------|----------------|------------------| +| Ask the user anything during execution | Evolve is fully autonomous — questions break the loop | Make best judgment, report in teardown. Exception: shape decisions affecting > 50 files or schema/contract surfaces (see "Operator-shape carve-out" above) | +| Stop after one `/rpi` cycle and summarize | Evolve loops until kill switch, max-cycles, or dormancy | Increment cycle and re-enter Step 1 | +| Run `/rpi` without `--auto` | Non-auto `/rpi` has human gates that halt the loop | Always pass `--auto` to `/rpi` | +| Run partial `/rpi` (skip validation) | Each cycle must be a complete 3-phase lifecycle | Let `/rpi` run all 3 phases autonomously | +| Pause between cycles to explain progress | The user wants results, not narration | Log cycle results, immediately start next cycle | +| Treat "no queued work" as "stop" | Generator layers (testing, validation, drift, features) produce work | Run all generator layers before considering dormancy | +| Select work that obviously violates `PROGRAM.md` | Scope escape is a tracked outcome, not a license to widen the loop | Escalate or re-queue out-of-scope work and keep selection inside mutable scope | diff --git a/.agents/skills/evolve/references/compounding.md b/.agents/skills/evolve/references/compounding.md new file mode 100644 index 0000000..a12ded7 --- /dev/null +++ b/.agents/skills/evolve/references/compounding.md @@ -0,0 +1,47 @@ +# How Compounding Works + +Two mechanisms feed the loop: + +**1. Knowledge flywheel (each cycle is smarter):** +``` +Session 1: + ao lookup --query "recent learnings" (nothing yet) → cycle runs blind + /rpi fixes test-pass-rate → post-mortem runs ao forge + Learnings extracted: "tests/skills/run-all.sh validates frontmatter" + +Session 2: + ao lookup --query "recent learnings" (loads Session 1 learnings) → cycle knows about frontmatter validation + /rpi fixes doc-coverage → approach informed by prior learning + Learnings extracted: "references/ dirs need at least one .md file" +``` + +**2. Work harvesting (each cycle discovers the next):** +``` +Cycle 1: /rpi fixes test-pass-rate + → post-mortem harvests: "add missing smoke test for /evolve" → next-work.jsonl + +Cycle 2: all GOALS.yaml goals pass + → /evolve reads next-work.jsonl (exact repo first, then cross-repo '*', then legacy) + → picks "add missing smoke test" + → /rpi fixes it → post-mortem harvests: "update SKILL-TIERS count" + +Cycle 3: reads next-work.jsonl → picks "update SKILL-TIERS count" → ... +``` + +The loop keeps running as long as post-mortem keeps finding follow-up work. Each /rpi cycle generates next-work items from its own post-mortem. The system feeds itself. + +**Priority cascade:** +``` +next-work.jsonl (harvested / queued work) → exact repo first, then `*`, then legacy +Open beads (bd ready) → durable tracked backlog +GOALS.yaml directives and failing goals → explicit fitness gaps +Testing improvements → synthesize coverage / regression-test work +Validation + bug-hunt passes → tighten gates, discover real defects +Hotspots / TODO / drift / stale docs → mine weak signals into work +Feature suggestions → propose new product work when remediation dries up +multiple empty queue + generator passes → last-resort dormancy +60-minute circuit breaker → stop if no productive cycle in 60 min +kill switch → immediate stop +``` + +The loop does NOT stop just because goals are met or the current queue is empty. It re-reads harvested work after every `/rpi` cycle, runs generator layers when queues are empty, and only stops after repeated empty queue + generator passes. Idle cycles are NOT committed to git — only appended locally. The idle streak is re-derived from disk at each session start, while `session-state.json` carries pending claim/generator resume state. Use the kill switch for intentional stops. diff --git a/.agents/skills/evolve/references/context-budget.md b/.agents/skills/evolve/references/context-budget.md new file mode 100644 index 0000000..fbe9804 --- /dev/null +++ b/.agents/skills/evolve/references/context-budget.md @@ -0,0 +1,79 @@ +# Context Budget — Session Handoff, Not a Stop (soc-5qit) + +**Prior framing (deprecated):** context exhaustion was a third stop reason that wrote sticky `DORMANT` and ended the run. + +**Current framing (soc-5qit, 2026-05-21):** context exhaustion is a *session-handoff signal*. The loop is continuous across compactions; a heavy session writes a non-sticky `HANDOFF` marker, exits the current cron-fire turn cleanly, and the NEXT cron-fire (running on harness-compacted or fresh context) automatically clears HANDOFF in Step 1 and resumes. + +This change was forced by an operator failure mode (2026-05-20→21): a cron-driven /evolve session shipped 6 PRs, accumulated context, then sticky-DORMANT'd despite 10+ unblocked P1 beads in `bd ready`. The prior design treated "this Claude session can't safely take on more work" as "the work itself is done" — a category error. The cron loop spans many sessions; one session's context is irrelevant to whether the backlog has work. + +## Why it's a real failure mode (still true) + +A long session that has shipped 5+ productive cycles accumulates context: read files cache, tool-result trails, prior `/rpi` discovery findings. When cycle N+1 selects an item that requires a fresh deep read of unfamiliar surfaces, the cycle either: + +- Forces a scout-mode return because the context is too heavy to execute (correct outcome, but the skill must not treat this as terminal). +- Attempts execution and produces shallow work that the regression gate or self-review catches (recovery cost > the cycle's value). + +The fix is to surface this as a handoff to the next session, not stop the loop. + +## The counter and the signal + +Maintain `context_streak` in `.agents/evolve/session-state.json`. Increment when ANY of these are true at end-of-cycle: + +- Cycle result is `scout` AND the scout's `disposition` says "context too heavy" +- Cycle result is `harvested` AND the harvest was a context-budget defer (vs. a feature-suggestion) +- The /rpi cycle aborted before commit because discovery context overflowed + +Reset to 0 when a productive `improved` cycle lands. + +```bash +context_streak=$(jq -r '.context_streak // 0' .agents/evolve/session-state.json) + +if [ "$context_streak" -ge 2 ]; then + echo "CONTEXT_BUDGET_EXHAUSTED after $context_streak heavy-context cycles." + echo "Writing HANDOFF (non-sticky) and exiting this cron-fire turn." + # HANDOFF carries ONLY the parked work refs and the timestamp. + # It is NOT a sticky stop. Step 1 of the next cron-fire clears it automatically. + cat > .agents/evolve/HANDOFF <", "work_ref": "", + "milestone": "Context-handoff after 2 heavy cycles. Parked: . Next cron-fire (compacted/fresh) resumes."} +``` + +The next operator session reads this entry, knows what was parked, and can either continue manually or wait for the next cron-fire to handle it. + +## Configurable threshold + +Default `context_streak` threshold is 2. Override with `EVOLVE_CONTEXT_STREAK_LIMIT=N` env var if a particular session needs to tolerate more heavy cycles (rare). + +## ScheduleWakeup interaction + +When running the Claude-Code-harness self-perpetuation mode (see `references/autonomous-execution.md`), a context-handoff exit MUST NOT call `ScheduleWakeup` from the same heavy-context turn — re-firing would re-load the heavy context. The cron schedule (or operator's external trigger) handles the re-fire. By the time the next cron-fire runs, the harness has compacted or the session has rolled, and HANDOFF auto-clears. + +## What this is NOT + +- **Not a stop reason.** Stop reasons are operator override, max-cycles cap, regression-breaker, and genuine stagnation (bd ready=0 AND harvested=0 AND failing-goals=0 AND generators dry). +- **Not a sticky marker.** DORMANT is sticky-with-auto-clear-on-new-work; HANDOFF is fully non-sticky and clears on next read. +- **Not a "the work is done" signal.** It explicitly says "this session can't safely continue; the work is parked." The next session does the work. + +## Cross-reference + +- `SKILL.md` Step 1 — HANDOFF auto-clear on cron-fire-N+1 +- `SKILL.md` Step 7 — context exhaustion explicitly removed from stop reasons list +- `references/scout-mode.md` — scope-filter splits or defers; never bails diff --git a/.agents/skills/evolve/references/convergence-mechanics.md b/.agents/skills/evolve/references/convergence-mechanics.md new file mode 100644 index 0000000..1de7465 --- /dev/null +++ b/.agents/skills/evolve/references/convergence-mechanics.md @@ -0,0 +1,102 @@ +# Convergence Mechanics — How the Loop Compounds Instead of Drifts + +The /evolve loop only compounds when each cycle reads prior cycles' outcomes and lets them change behavior. Append-only ledgers that no step reads are write-only artifacts — they accumulate without compounding. + +This reference documents the four feedback mechanisms that turn raw cycle output into next-cycle behavior change. + +## Mechanism 1: Step 0 reads prior-failure surface + +In Step 0 (Setup), after `mkdir -p .agents/evolve`, the loop reads the last 3 entries of `cycle-history.jsonl`. For any entry where `gate` field contains a FAIL marker, it extracts the failure surface (e.g. "registry-check stale", "bats-tests goals-validate") and injects the matching learning before work selection. + +```bash +last3=$(scripts/evolve-read-cycle-history.sh recent 3) # routes through BC3 LoopReaderPort (soc-y5vh.4) +fail_surfaces=$(echo "$last3" | jq -r 'select(.gate | test("FAIL|FAILED|BLOCKED")) | .gate' 2>/dev/null) +if [ -n "$fail_surfaces" ]; then + # Search learnings for surface keywords; print whichever match + keywords=$(echo "$fail_surfaces" | grep -oE 'registry|bats|markdown|supergate|canary|coverage|toolchain' | sort -u) + for kw in $keywords; do + ao lookup --query "$kw failure" --limit 2 2>/dev/null || \ + find .agents/learnings -name "*$kw*.md" -mtime -30 | head -2 + done +fi +``` + +Without this, the 2026-05-07 CI-toil learning sat for 7 days while I burned 5 cycles re-hitting the same `registry.json` non-determinism. Reading the learning at Step 0 would have surfaced the `git ls-files` fix on cycle 45. + +## Mechanism 2: Step 1.5 healing-first classifier + +Before Step 2 (measure fitness) and Step 3 (select work), the loop classifies the cycle: + +```bash +# Step 1.5: healing-first classifier — routes through BC2 CIStatusPort +# (cli/cmd/ao/ci_status_adapter.go, cycle 117 productionCIStatus) per +# soc-y5vh.2. No inline gh shell-outs. +last_ci=$(ao ci recent --limit 1 2>/dev/null | jq -r '.Conclusion // empty') +if [ "$last_ci" = "failure" ]; then + CYCLE_MODE="restorative" + # Read failure surface, search for matching learning + # (see Mechanism 1) + # Selection ladder downgrade: skip Step 3.1 (harvested) for feature + # work; only allow harvested items typed bug/fix/ci-failure. +else + CYCLE_MODE="feature" +fi +``` + +Restorative cycles ONLY take work that reduces CI red. New PG4 promotions, feature additions, doc growth — all blocked until `last_ci=success`. + +This eliminates the cycle-46-47 pattern where I added new evidence files onto a CI-red base. + +## Mechanism 3: Hypothesis tracking for skill changes + +When a cycle edits `skills/evolve/SKILL.md`, it MUST append to the hypothesis +ledger through the typed BC3 `HypothesisLedgerPort` (soc-y5vh.8): + +```bash +ao loop hypothesis append --id "H." --cycle-landed N --check-at-cycle $((N+15)) \ + --patch "" --hypothesis "" --measure "" +``` + +This routes through `productionHypothesisLedger` instead of a raw append to +`.agents/evolve/hypotheses.jsonl`; the port rejects empty and duplicate IDs. +At `check_at_cycle`, the loop reads the ledger with `ao loop hypothesis list` +(one JSON record per line), evaluates each PENDING row's `measure`, and +writes the verdict (VERIFIED / FALSIFIED). Falsified hypotheses are +revisited: either the patch is wrong, or the measurement was wrong. + +Without this, cycle 45's 6 patches landed unmeasured and 2 of them (H45.2 source-surface auto-rebuild, H45.3 grep-based gate parsing) were silently inert for the next 5 cycles — text in SKILL.md but no harness automation behind them. + +## Mechanism 4: Convergence criteria with a STOP + +`.agents/evolve/session-convergence.json` records the terminal state; the STOP +decision is evaluated through the typed BC3 `ConvergenceCheckPort` (soc-y5vh.8): + +```bash +ao loop converged --green-streak "$STREAK" --unconsumed-high-medium "$HM" --fitness-baseline +# emits {converged, ci_green_streak, unconsumed_high_medium, fitness_baseline_captured, reasons} +``` + +The predicate is pure — the loop supplies the evidence it already has +(`ao ci recent` for the streak, the next-work findings count, the +fitness-baseline flag). The criteria are met when all hold: + +- CI Validate green for the last 3 pushes (green streak ≥ 3) +- HIGH+MEDIUM unconsumed next-work entries ≤ 1 +- a fitness baseline has been captured + +When `ao loop converged` reports `converged: true`, the loop emits a teardown +report and does NOT call `ScheduleWakeup`. The wakeup chain terminates. The +autonomous loop is bounded by criteria, not by wakeup count. `reasons` names +every unmet criterion when `converged` is false. + +Without an explicit STOP, the loop drifts indefinitely. With STOP, it converges. + +## Anti-drift rules + +1. **Restorative-only after red.** Any cycle whose `gate` field has FAIL → cycle N+1 is restorative. +2. **3 consecutive restorative without restoration → escalate.** Don't silently grind. +3. **Scope shift resets the streak.** If the operator broadens the convergence target mid-session, reset `ci-green-streak` counter to 0. + +## Why this is the load-bearing change + +Cycles 44-50 wrote ~30 KB of bookkeeping (cycle-history, learning, retro, evidence, hypotheses) but produced ~0 compounded behavior — every cycle had to re-derive the lesson cycle 44 should have surfaced. The compounding lives in the read path, not the write path. These four mechanisms make the read path real. diff --git a/.agents/skills/evolve/references/cycle-history.md b/.agents/skills/evolve/references/cycle-history.md new file mode 100644 index 0000000..db785fa --- /dev/null +++ b/.agents/skills/evolve/references/cycle-history.md @@ -0,0 +1,326 @@ +# Cycle History Format and Recovery Protocol + +## Compaction Resilience + +The evolve loop MUST survive context compaction. Every productive cycle appends +to its ledger artifacts before proceeding. `cycle-history.jsonl` is the +on-disk recovery point for cycle numbering, and `.agents/evolve/session-state.json` +is the on-disk resume point for pending queue claims, queue refresh count, and +generator-empty streaks. + +### Local-Only Status (Not Git-Tracked) + +Both `cycle-history.jsonl` and `session-state.json` are **local-only files**. +The repository's nested `.agents/.gitignore` denies all paths via `*` (with +only `!.gitignore` re-allowed), which overrides the outer `.gitignore` +allowlists at the repo root. Aspirational allowlist entries for +`!/.agents/evolve/cycle-history.jsonl` and `!/.agents/evolve/session-state.json` +exist in the outer `.gitignore` but have no effect because the nested +deny wins per gitignore precedence rules. + +Implication for the cycle protocol: + +- These files survive **session compaction** (recovered from disk on the next + invocation), but they do **not** survive cloning or shared with peers. +- Important per-cycle milestones (e.g. baseline-capture results, regression + events, convergence-criterion transitions) MUST also be recorded in + **commit messages** so they remain in tracked git history. +- Do not depend on cycle-history.jsonl as a cross-clone authoritative ledger. + Treat it as a session journal that helps the next-cycle bootstrap. + +## Cycle History JSONL Format + +Append one line per cycle to `.agents/evolve/cycle-history.jsonl`. + +### Canonical Schema + +All new entries MUST use this schema: + +```json +{ + "cycle": 123, + "target": "goal-id-or-idle", + "result": "improved|regressed|unchanged|harvested|quarantined", + "sha": "abc1234", + "canonical_sha": "abc1234", + "timestamp": "2026-02-23T12:00:00-05:00", + "goals_passing": 59, + "goals_total": 59 +} +``` + +**Field standardization:** +- Use `target` (not `goal_id`) — this is what recent cycles already use +- Use `sha` as the compatibility alias for `canonical_sha` +- Use `canonical_sha` for the implementation commit the cycle actually delivered +- Use `log_sha` only when the bookkeeping/log commit is distinct from `canonical_sha` +- Always include `goals_passing` and `goals_total` — enables trajectory plotting +- Optional fields: `quality_score` (quality mode), `idle_streak` (idle cycles), `parallel` + `goal_ids` (parallel mode) + +**Legacy field names:** Older entries may use `goal_id` instead of `target` and `commit_sha` instead of `sha`. Tools reading cycle-history.jsonl should handle both conventions. + +**Sequential cycle entry:** +```jsonl +{"cycle": 1, "target": "test-pass-rate", "result": "improved", "sha": "abc1234", "canonical_sha": "abc1234", "goals_passing": 18, "goals_total": 23, "timestamp": "2026-02-11T21:00:00Z"} +{"cycle": 2, "target": "doc-coverage", "result": "regressed", "sha": "def5678", "canonical_sha": "def5678", "log_sha": "fedcba9", "goals_passing": 17, "goals_total": 23, "timestamp": "2026-02-11T21:30:00Z"} +``` + +**Idle cycle entry** (not committed to git): +```jsonl +{"cycle": 3, "target": "idle", "result": "unchanged", "timestamp": "2026-02-11T22:00:00Z"} +``` + +**Parallel cycle entry** (use `goal_ids` array and `parallel: true`): +```jsonl +{"cycle": 4, "goal_ids": ["test-pass-rate", "doc-coverage", "lint-clean"], "result": "improved", "sha": "ghi9012", "goals_passing": 22, "goals_total": 23, "parallel": true, "timestamp": "2026-02-11T22:30:00Z"} +``` + +### Mandatory Fields + +Every productive cycle log entry MUST include: + +| Field | Description | +|-------|-------------| +| `cycle` | Cycle number (1-indexed) | +| `target` | Target goal ID, or `"idle"` for idle cycles | +| `result` | One of: `improved`, `regressed`, `unchanged`, `harvested`, `quarantined` | +| `sha` | Compatibility alias for the implementation SHA (omitted for idle cycles) | +| `canonical_sha` | Implementation commit the cycle actually delivered | +| `goals_passing` | Count of goals with result "pass" (omitted for idle cycles) | +| `goals_total` | Total goals measured (omitted for idle cycles) | +| `timestamp` | ISO 8601 timestamp | + +`log_sha` is optional and should only be written when the log/bookkeeping commit +differs from `canonical_sha`. These fields enable fitness trajectory plotting +without losing retrospective provenance. + +### XP/BDD/TDD Evidence Trace (optional `trace` object) + +A productive cycle MAY record a `trace` object capturing the +continuous-evolution kernel — the evidence a reviewer needs to reconstruct +the cycle **without reading the transcript**. The kernel shape is: + +> goal hypothesis → selected gap → Gherkin scenario → first failing proof → +> red evidence → green evidence → refactor note → validation evidence → +> ratchet action → goal reshape decision + +```json +{ + "cycle": 200, + "target": "test-pass-rate", + "result": "improved", + "sha": "abc1234", + "canonical_sha": "abc1234", + "goals_passing": 60, + "goals_total": 60, + "timestamp": "2026-05-16T12:00:00Z", + "trace": { + "goal_hypothesis": "raising test-pass-rate lifts overall fitness", + "selected_gap": "loop cycle ledger carries no evidence trace", + "gherkin": "Feature: trace\n Scenario: reviewer reconstructs a cycle", + "first_failing_proof": "go test ./internal/ports -run TraceCompleteness # FAIL", + "red_evidence": "TestTraceCompleteness_FullTraceIsComplete red: undefined CycleTrace", + "green_evidence": "TestTraceCompleteness_* pass (5/5)", + "refactor_note": "extracted requiredTraceFields table; no behavior change", + "validation_evidence": "cd cli && go test ./... green; bats evolve-log-cycle.bats 10/10", + "ratchet_action": "ao ratchet record implement", + "goal_reshape": "goal unchanged; gap closed — fold trace into next epic's audit" + } +} +``` + +**Trace fields** (all strings, all `omitempty`): + +| Field | Records | +|-------|---------| +| `goal_hypothesis` | The goal/fitness hypothesis the cycle is testing | +| `selected_gap` | The specific gap chosen from the queue/generator | +| `gherkin` | The Given/When/Then scenario the slice satisfies | +| `first_failing_proof` | The command/assertion that fails before the change | +| `red_evidence` | Observed failing-test output (the RED state) | +| `green_evidence` | Observed passing-test output (the GREEN state) | +| `refactor_note` | The refactor step — record `"none"` when there is none | +| `validation_evidence` | Independent validation commands and their results | +| `ratchet_action` | The `ao ratchet record …` action taken | +| `goal_reshape` | Whether the goal stayed, narrowed, or was replaced | + +**Trivial-cycle exemption.** A tiny one-shot bookkeeping change (a typo fix, +a dependency bump) does NOT need full BDD/TDD ceremony. Instead of silently +omitting the trace fields, record an explicit `exemption_reason`: + +```json +{"cycle": 201, "target": "idle", "result": "unchanged", "timestamp": "...", + "trace": {"exemption_reason": "trivial one-shot typo fix; no Gherkin or failing proof appropriate"}} +``` + +A trace with a non-empty `exemption_reason` is **exempt** — no other field is +expected. A trace with neither `exemption_reason` nor the evidence fields is +**incomplete**: `ports.TraceCompleteness` (Go) reports which required fields +are missing. + +**Completeness is advisory, never blocking.** `TraceCompleteness` is a pure +helper for reports and audits; it is deliberately NOT wired into +`ao loop verify` or `scripts/check-evolve-cycle-logging.sh`. The `trace` +object is recorded as-is. This keeps the loop honest about its own evidence +without making loop-shape a hard gate before real cycle output conforms. + +**Writing a trace.** Both writers accept it: + +```bash +# canonical script writer — file path, inline JSON, or - for stdin +bash scripts/evolve-log-cycle.sh --cycle "$CYCLE" --target "$TARGET" \ + --result improved --canonical-sha "$SHA" --cycle-start-sha "$START" \ + --goals-passing "$P" --goals-total "$T" --trace-json cycle-trace.json + +# typed BC3 LoopWriterPort +ao loop append --mode evolve --result improved --trace-json cycle-trace.json +``` + +`ao loop history` emits the `trace` object back as part of each `CycleEntry`, +so the report surface carries the evidence with no extra step. + +### Session-State Sidecar + +Persist the non-ledger loop state to `.agents/evolve/session-state.json`: + +```json +{ + "cycle": 124, + "generator_empty_streak": 1, + "last_selected_source": "testing", + "queue_refresh_count": 17, + "claimed_work": { + "ref": "source_epic=ag-123:item=Add smoke test", + "claimed_by": "evolve:cycle-124", + "claimed_at": "2026-03-08T10:15:00Z" + } +} +``` + +On resume: +1. recover `cycle` from `cycle-history.jsonl` +2. recover generator and claim state from `session-state.json` +3. if `claimed_work` exists, inspect the queue entry: + - if the prior cycle succeeded, finalize it as consumed + - if the prior cycle failed or is ambiguous, release the claim and continue + +### Substantive-Delta Rule + +Do not record `result: "improved"` when a cycle produces no non-agent repo delta. +If the cycle touched only `.agents/` artifacts or otherwise made no substantive +repo change, rewrite the outcome to `unchanged` and keep it local-only. This +prevents ledger churn from being misread as product progress. + +### Telemetry + +Log telemetry at the end of each cycle: +```bash +bash scripts/log-telemetry.sh evolve cycle-complete cycle=${CYCLE} score=${SCORE} goals_passing=${PASSING} goals_total=${TOTAL} +``` + +### Compaction-Proofing: Commit After Productive Cycles + +Only **productive cycles** (improved, regressed, harvested) are committed. Idle +cycles are appended to cycle-history.jsonl locally but NOT committed — they are +disposable if compaction occurs, and the idle streak is re-derived from disk at +session start. Producer-layer exhaustion is tracked in `session-state.json`, not +by stopping early. + +```bash +# Productive cycle: log via the canonical writer, then commit +bash scripts/evolve-log-cycle.sh \ + --cycle "$CYCLE" \ + --target "$TARGET" \ + --result "$OUTCOME" \ + --canonical-sha "$(git rev-parse --short HEAD)" \ + --cycle-start-sha "$CYCLE_START_SHA" \ + --goals-passing "$PASSING" \ + --goals-total "$TOTAL" + +# Parallel productive cycle: +bash scripts/evolve-log-cycle.sh \ + --cycle "$CYCLE" \ + --target "parallel-wave" \ + --goal-ids "${goal_ids_csv}" \ + --parallel \ + --result "$OUTCOME" \ + --canonical-sha "$(git rev-parse --short HEAD)" \ + --goals-passing "$PASSING" \ + --goals-total "$TOTAL" + +# Idle or no-delta cycle: append locally, do NOT commit +bash scripts/evolve-log-cycle.sh --cycle "$CYCLE" --target "idle" --result "unchanged" >/dev/null +# No git add, no git commit +``` + +### 60-Minute Circuit Breaker + +At session start (Step 0), after recovering the idle streak, check the timestamp +of the last productive cycle. If it was more than 60 minutes ago, go directly to +Teardown. This prevents runaway sessions that accumulate empty queue/generator +passes without producing value. + +```bash +LAST_PRODUCTIVE_TS=$(grep -v '"idle"\|"unchanged"' .agents/evolve/cycle-history.jsonl 2>/dev/null \ + | tail -1 | jq -r '.timestamp // empty') +# If >3600s since last productive cycle AND timestamp parsed correctly: CIRCUIT BREAKER → Teardown +# Guard: LAST_EPOCH > 1e9 prevents false trigger on date parse failure +``` + +## Recovery Protocol + +On session restart or after compaction: + +1. Read `.agents/evolve/cycle-history.jsonl` to find last completed cycle number +2. Set `evolve_state.cycle` to last cycle + 1 +3. Resume from Step 1 (kill switch check) +4. Preserve existing `.agents/evolve/fitness-baselines/goals-/` directories; do not regenerate the current era baseline if its directory already contains a JSON snapshot + +## Kill Switch + +Three paths, checked at every cycle boundary: + +| File | Purpose | Who Creates It | +|------|---------|---------------| +| `~/.config/evolve/KILL` | Permanent stop (outside repo) | Human | +| `.agents/evolve/STOP` | One-time local stop | Human or automation | +| `.agents/evolve/DORMANT` | Sticky dormancy after Step 3 hard-gate fired | `/evolve` itself | + +To stop /evolve: +```bash +echo "Taking a break" > ~/.config/evolve/KILL # Permanent +echo "done for today" > .agents/evolve/STOP # Local, one-time +``` + +The `DORMANT` marker is written by `/evolve` when both queue layers and generator layers come up empty across 3 consecutive passes (the Step 3 hard-gate). Its purpose is to prevent post-dormancy cron fires from re-entering the full skill body — once the marker exists, Step 1 short-circuits with zero further tool calls. The marker contains three lines: cycle number, ISO timestamp, reason. + +To re-enable: +```bash +rm ~/.config/evolve/KILL +rm .agents/evolve/STOP +rm .agents/evolve/DORMANT +``` + +The operator typically removes `DORMANT` either when (a) new scope arrives that didn't fit any rung of the ladder when dormancy fired, or (b) the underlying ledger has gained enough new entries that generator layers will produce new work. There is no auto-clear — dormancy is sticky by design. + +## Flags Reference + +| Flag | Default | Description | +|------|---------|-------------| +| `--max-cycles=N` | unlimited | Optional hard cap. Without this, loop runs forever. | +| `--test-first` | off | Pass `--test-first` through to `/rpi` -> `/crank` | +| `--dry-run` | off | Measure fitness and show plan, don't execute | +| `--skip-baseline` | off | Skip cycle-0 baseline sweep | +| `--parallel` | off | Enable parallel goal execution via /swarm per cycle | +| `--max-parallel=N` | 3 | Max goals to fix in parallel (cap: 5). Only with `--parallel`. | + +## Troubleshooting + +| Problem | Cause | Solution | +|---------|-------|----------| +| `/evolve` exits immediately with "KILL SWITCH ACTIVE" | Kill switch file exists | Remove `~/.config/evolve/KILL` or `.agents/evolve/STOP` to re-enable | +| "No goals to measure" error | GOALS.yaml missing or empty | Create GOALS.yaml in repo root with fitness goals (see goals-schema.md) | +| Cycle completes but fitness unchanged | Goal check command is always passing or always failing | Verify check command logic in GOALS.yaml produces exit code 0 (pass) or non-zero (fail) | +| Regression revert fails | Multiple commits in cycle or uncommitted changes | Check cycle-start SHA in fitness snapshot, commit or stash changes before retrying | +| Harvested work never finalizes | Queue item was claimed but cycle did not clear/finalize it | Inspect `claim_status`, `claimed_by`, and `claimed_at`; successful cycles consume, failed cycles release | +| Loop stops after empty queues | Generator streak was exhausted too quickly or `--max-cycles` was set | Verify producer layers ran, inspect `session-state.json`, and omit `--max-cycles` for overnight runs | diff --git a/.agents/skills/evolve/references/domain-evolution-bootstrap.md b/.agents/skills/evolve/references/domain-evolution-bootstrap.md new file mode 100644 index 0000000..767f475 --- /dev/null +++ b/.agents/skills/evolve/references/domain-evolution-bootstrap.md @@ -0,0 +1,50 @@ +# Domain Evolution Bootstrap + +Use this reference when `/evolve` is asked to reshape AgentOps skills, CLI, +hooks, docs, tests, beads, or knowledge as one software-factory system. + +## Product Frame + +AgentOps is the SDLC control plane and context compiler for LLM agents. The +narrow waist is: + +- BDD/Gherkin for observable intent; +- DDD for shared names and bounded contexts; +- Hexagonal architecture for ports and adapters; +- TDD for local proof of done; +- XP for small vertical slices; +- CI, SRE, ADRs, and provenance for repeated trust and memory. + +Do not treat a skill, hook, or command as valuable by itself. It is valuable +only when it advances the factory loop: intent, boundary, proof, evidence, and +compounding context. + +## Bootstrap Sequence + +1. Read current direction from `PRODUCT.md`, `GOALS.md`, + `docs/cdlc.md`, and `docs/architecture/operating-loop.md`. +2. Validate the durable control artifacts: + + ```bash + bash scripts/check-agentops-domain-evolution-plan.sh + ``` + +3. Select one domain and one vertical slice from: + - `docs/reference/agentops-domain-evolution-bdd.md` + - `docs/reference/agentops-skill-domain-map.md` + - `docs/reference/agentops-hexagonal-architecture-map.md` + - `docs/reference/agentops-domain-evolution-plan.md` +4. Use `skill-builder` and `skill-auditor` for skill changes. Use + `skills/skill-auditor/scripts/score_agentops_skill.py` to choose the + smallest score-improving patch. +5. Keep CLI and hook changes behind typed ports or existing validation scripts. +6. Run focused validation before selecting the next slice. + +## Hard Rules + +- No broad rewrite before a Gherkin row, domain, and first proof are named. +- No third-party content copying; use clean-room structural observations only. +- No shipped skill deletion until replacement workflow and validation evidence + exist. +- No shell-only read path when a typed port already exists. +- No unattended run from a dirty canonical root or stale installed `ao` binary. diff --git a/.agents/skills/evolve/references/evolve.feature b/.agents/skills/evolve/references/evolve.feature new file mode 100644 index 0000000..d7405e6 --- /dev/null +++ b/.agents/skills/evolve/references/evolve.feature @@ -0,0 +1,34 @@ +# Executable spec for the /evolve skill — the goal-driven compounding loop (BC3 Loop). +# /evolve runs autonomously over /rpi: each cycle a mechanical pre-cycle gate decides +# continue/halt, the work-selection ladder picks the highest-value bounded item, one +# slice ships gated (revert on red), and the loop NEVER self-halts — only operator +# markers stop it. Hexagon: supporting; consumes: rpi, goals, post-mortem, compile; +# produces: git-changes + goals-fitness-delta. (soc-qk4b.2; see ADR-0007/ADR-0008) + +Feature: Evolve runs a goal-driven compounding loop + As the autonomous improvement loop + I want each cycle gated, work-selected by ladder, and shipped as a bounded slice + So that the repo compounds toward GOALS.md without the agent ever self-halting + + Background: + Given an operator-set intent (GOALS.md, ADRs, the bead queue) + + Scenario: A mechanical pre-cycle gate decides continue or halt + When a cycle starts + Then scripts/evolve/halt-check.sh runs first + And the loop continues unless an operator marker / goal-regression / prior-FAIL fires + + Scenario: Work selection walks the ladder + When the loop selects work + Then it prefers harvested next-work, then ready beads, then failing goals, then generators + And it declines operator-shape work (epics/release/decisions) per ADR-0008 + + Scenario: One bounded slice per cycle, reverted on red + When the loop works a selected item + Then it ships a single bounded slice via /rpi, gated by build + test + lint + And it reverts the slice rather than landing red + + Scenario: The loop never self-halts + When the loop is blocked or out of bounded work + Then it logs `ao loop blocked` and continues or operator-waits + And it never writes a STOP/DORMANT marker itself (operator-only, ADR-0007) diff --git a/.agents/skills/evolve/references/examples.md b/.agents/skills/evolve/references/examples.md new file mode 100644 index 0000000..88af5e2 --- /dev/null +++ b/.agents/skills/evolve/references/examples.md @@ -0,0 +1,64 @@ +# /evolve Examples + +## Infinite Autonomous Improvement + +**User says:** `/evolve` + +**What happens:** +1. Agent checks kill switch files (none found, continues). +2. Agent loads the repo execution profile, reads the ordered startup reads, and caches repo validation commands plus definition_of_done before choosing work. +3. Agent first reads `.agents/rpi/next-work.jsonl`, claims the highest-value harvested item, and runs `/rpi` on it. +4. The cycle's `/post-mortem` harvests 2 new follow-up items; evolve immediately re-reads the queue instead of trusting the pre-cycle snapshot. +5. With harvested work drained, evolve checks `bd ready` and lands the top unblocked bead. +6. With beads drained, evolve measures GOALS.yaml, finds a directive gap, and runs `/rpi` on that goal. +7. Once goals/directives are healthy, evolve generates testing work from thin coverage and lands the best regression-test improvement. +8. Testing producers dry up, so evolve runs validation tightening / bug-hunt and fixes the highest-value finding. +9. When remediation layers are empty, evolve mines hotspot/TODO/stale-doc drift and turns any real findings into durable work. +10. If all remediation layers stay empty, evolve writes a concrete feature suggestion as durable work and starts the next `/rpi`. +11. Only after repeated empty queue + generator passes does dormancy trigger and teardown begin. +12. To stop earlier: create `~/.config/evolve/KILL` or `.agents/evolve/STOP`. + +**Result:** Runs as an always-on compounding loop. Empty queues trigger more work discovery; they do not end the run. + +## Dry-Run Mode + +**User says:** `/evolve --dry-run` + +**What happens:** +1. Agent measures fitness. +2. Agent reports the next harvested/beads/goals item it would work on. +3. If those are empty, agent reports the next generator layer it would run (testing, validation, drift, or feature suggestion). +4. Agent stops without executing. + +**Result:** Next-action preview without code changes. + +## Regression with Revert + +**User says:** `/evolve --max-cycles=3` + +**What happens:** +1. Agent claims a harvested queue item in cycle 1 and starts `/rpi`. +2. Post-cycle fitness shows a regression. +3. Agent reverts the cycle's changes. +4. Agent clears the queue claim and leaves `consumed: false`, so the work is available again. +5. Agent logs the regression and continues. + +**Result:** Fitness regressions are auto-reverted, and claimed work is re-queued instead of being lost. + +## Worked Overnight Ladder + +**User says:** `/evolve --compile` + +**What happens:** +1. Compile warmup surfaces a stale research note about runtime smoke coverage. +2. Evolve loads the repo execution profile first, so the startup reads, tracker wrapper, and validation bundle come from repo policy instead of a giant prompt. +3. `bd ready` has one open docs/runtime parity bead, so evolve runs that first. +4. That bead's `/post-mortem` harvests an implementation follow-up into `next-work.jsonl`; evolve re-reads the queue and runs it immediately. +5. The queue empties, so evolve measures goals and fixes one directive gap via `/rpi`. +6. All goals now pass. Evolve generates testing work from thin coverage and lands a new regression test. +7. Testing producers dry up, so evolve runs a bug-hunt / validation sweep and tightens a missing validation gate. +8. No bug-hunt findings remain, so evolve mines complexity/TODO/stale-doc drift and queues one cleanup item. +9. After that cleanup, the remediation ladder is empty, so evolve writes a concrete feature suggestion bead and starts the next `/rpi`. +10. Only after harvested work, beads, goals, testing, bug hunt, drift mining, and feature suggestions all come up empty across repeated passes does dormancy trigger. + +**Result:** One long-running session compounds across beads -> harvested work -> goals -> testing -> bug hunt -> feature suggestion instead of stopping at the first empty queue. diff --git a/.agents/skills/evolve/references/fitness-scoring.md b/.agents/skills/evolve/references/fitness-scoring.md new file mode 100644 index 0000000..9eb1e44 --- /dev/null +++ b/.agents/skills/evolve/references/fitness-scoring.md @@ -0,0 +1,89 @@ +# Fitness Scoring + +## Measurement + +Run the fitness measurement script to produce a rolling snapshot: + +```bash +bash scripts/evolve-measure-fitness.sh \ + --output .agents/evolve/fitness-latest.json \ + --timeout 60 \ + --total-timeout 75 +``` + +**Do NOT write per-cycle `fitness-{N}-pre.json` files.** The rolling file is sufficient for work selection and regression detection. + +This writes a fitness snapshot to `.agents/evolve/` atomically via a temp file plus JSON validation. The AgentOps CLI is required for fitness measurement because the wrapper shells out to `ao goals measure`. If measurement exceeds the whole-command bound or returns invalid JSON, the wrapper fails without clobbering the previous rolling snapshot. + +## Era Baseline Capture (First Run Only) + +Skip if `--skip-baseline` or `--beads-only` or baseline already exists. + +`/evolve` captures this automatically before entering the RPI loop. It hashes +the active GOALS.md or GOALS.yaml file to an era ID, then writes a snapshot +under `.agents/evolve/fitness-baselines/goals-/` if that era directory +does not already contain a JSON snapshot. + +For manual recovery or one-off capture, compute the same era ID and use the +helper script: + +```bash +GOALS_FILE="" +if [ -f GOALS.md ]; then + GOALS_FILE="GOALS.md" +elif [ -f GOALS.yaml ]; then + GOALS_FILE="GOALS.yaml" +fi + +if [ -n "$GOALS_FILE" ]; then + ERA_ID="goals-$(shasum -a 256 "$GOALS_FILE" | awk '{print substr($1, 1, 12)}')" + bash scripts/evolve-capture-baseline.sh \ + --label "$ERA_ID" \ + --timeout 60 \ + --total-timeout 75 +fi +``` + +## Post-Cycle Re-Measurement (Regression Gate) + +After execution, re-measure to detect regressions: + +```bash +bash scripts/evolve-measure-fitness.sh \ + --output .agents/evolve/fitness-latest-post.json \ + --timeout 60 \ + --total-timeout 75 \ + --goal "$GOAL_ID" + +# Extract goal counts for cycle history entry +PASSING=$(jq '[.goals[] | select(.result=="pass")] | length' .agents/evolve/fitness-latest-post.json 2>/dev/null || echo 0) +TOTAL=$(jq '.goals | length' .agents/evolve/fitness-latest-post.json 2>/dev/null || echo 0) +``` + +**If regression detected** (previously-passing goal now fails): + +```bash +git revert HEAD --no-edit # single commit +# or for multiple commits: +git revert --no-commit ${CYCLE_START_SHA}..HEAD && git commit -m "revert: evolve cycle ${CYCLE} regression" +``` + +Set outcome to "regressed". + +## Oscillation Detection + +Before working a failing goal, check if it has oscillated (improved-to-fail transitions >= 3 times in `cycle-history.jsonl`). If so, quarantine it and try the next failing goal. + +```bash +# Reads cycle history via BC3 LoopReaderPort (soc-y5vh.4 wrapper), +# not raw .agents/evolve/cycle-history.jsonl slurp. +OSC_COUNT=$(scripts/evolve-read-cycle-history.sh recent 0 \ + | jq -r "select(.target==\"$FAILING\") | .result" \ + | awk 'prev=="improved" && $0=="fail" {count++} {prev=$0} END {print count+0}') +if [ "$OSC_COUNT" -ge 3 ]; then + QUARANTINED_GOALS[$FAILING]=true + echo "{\"cycle\":${CYCLE},\"target\":\"${FAILING}\",\"result\":\"quarantined\",\"oscillations\":${OSC_COUNT},\"timestamp\":\"$(date -Iseconds)\"}" >> .agents/evolve/cycle-history.jsonl +fi +``` + +See also: `references/oscillation.md` for full quarantine protocol. diff --git a/.agents/skills/evolve/references/gate-hygiene.md b/.agents/skills/evolve/references/gate-hygiene.md new file mode 100644 index 0000000..b63d6b7 --- /dev/null +++ b/.agents/skills/evolve/references/gate-hygiene.md @@ -0,0 +1,74 @@ +# Gate Hygiene — Pre-Gate Sync and Output Parsing + +Two recurring gate-stage frictions that compound across long sessions: missing source-surface rebuilds (the gate runs against stale binaries) and trusting trailing status lines (the gate output mixes blocking and advisory signals). + +## Pre-gate source-surface detection + +Before invoking `pre-push-gate.sh` (or any binary-dependent gate), inspect the staged diff and rebuild downstream artifacts if any of these surfaces changed: + +| Changed surface | Required pre-gate action | +|---|---| +| `cli/**/*.go` | `cd cli && make build && go install ./cmd/ao` — refreshes `cli/bin/ao` and `~/go/bin/ao` so the gate sees the new Go behaviour | +| `skills/**` or `hooks/**` | `cd cli && make sync-hooks` — refreshes `cli/embedded/{skills,hooks}/` so the embedded-parity check passes | +| `skills-codex/**` | `bash scripts/regen-codex-hashes.sh` — refreshes generated_hash values in the codex manifest | +| `schemas/**` and `docs/contracts/**` together | re-run contract validation locally first; do not let CI surface the drift | + +Detection recipe: + +```bash +changed=$(git diff --cached --name-only) +echo "$changed" | grep -q '^cli/.*\.go$' && (cd cli && make build && go install ./cmd/ao) +echo "$changed" | grep -qE '^(skills/|hooks/)' && (cd cli && make sync-hooks) +echo "$changed" | grep -q '^skills-codex/' && bash scripts/regen-codex-hashes.sh +``` + +Without these pre-gate steps, the gate may fail with stale-binary or embedded-drift errors that look like real regressions but are just plumbing. Each false failure costs a turn of recovery work. + +## Gate output parsing + +The pre-push gate (and similar two-pass scripts) emits both blocking and advisory results. Trusting only the trailing status line conflates them. Use a structured grep: + +```bash +# Capture full output, then parse explicit failure markers +bash scripts/pre-push-gate.sh --fast 2>&1 | tee /tmp/gate.log + +# Authoritative blocking failures (case-sensitive Pass N: FAILED|BLOCKED) +if grep -E '^.*Pass [0-9]+: (FAILED|BLOCKED)' /tmp/gate.log >/dev/null; then + echo "BLOCKING failure detected" + exit 1 +fi + +# Advisory issues — record but don't block +grep -E 'advisory|warning|WARN' /tmp/gate.log || true +``` + +Anti-pattern: reading `tail -1 /tmp/gate.log` and treating "passed (N skipped)" as authoritative. A run can show "Pass 1: FAILED" mid-output and "passed (X skipped)" at the end if Pass 2 ran in advisory-only mode against the worktree. The structural markers (`Pass N: FAILED|BLOCKED`) are the truth. + +## When to use `PRE_PUSH_SKIP_EVAL=1` + +Documented release valve for the eval canary lane only, when: +- The canary is flaking on pre-existing infra (filed as a tracked bead) +- The current diff is unrelated to evals/, schemas/eval-, or cli/cmd/ao/eval +- A recorded recent run has confirmed the canary is currently 50/50 + +Never use `--no-verify`. The pre-commit hook is a no-op for most diffs but the principle violation is durable in `git log` and surfaces in post-mortem. + +## Pre-push diff-scope check (mandatory before every commit) + +Two cheap pre-commit reads catch the failure modes that cost a full fix-and-repush round each (the 2026-05-29 `/burndown` + codex-budget reds): + +1. **`git status --short` must show ALL intended files staged.** A half-staged commit — the feature gate-script left out while only its test was committed — let CI run the *old* code so the new assertion could not pass (the #612 red). A feature and its test are one commit, not the test alone. +2. **`git diff origin/main --stat` must be SCOPE-ONLY.** Confirm: no collateral deletions (a `git checkout --theirs` conflict take silently dropped two *unrelated* skill rows in the #600 rebase), and no lossy whole-file reformats (a 252-line `catalog.json` round-trip — discard those and use the idempotent appender). For generated/narrative files, restore `origin/main`'s version and **re-run the generator** rather than hand-merging. + +## Triage red precisely — pre-existing-main vs your-change + +Not every red is yours. The `agentops` main branch carries known pre-existing failures a local gate or a broad generator will surface: + +- **Local `mkdocs --strict`** fails on tracked docs because the system `mkdocs` (≤1.1.2) can't parse the modern `mkdocs.yml` (needs material plugins). CI uses the venv and passes. Confirm a flagged file is `git ls-tree origin/main`-tracked AND absent from your diff → pre-existing, proceed. +- **~7 codex `.agentops-generated.json` drifts** (deps, provenance, red-team, release, scenario, trace, using-agentops): `regen-codex-hashes.sh` "updates" them because they're drifted on main. **REVERT them** (`git checkout origin/main -- `) and keep only your skill's hash; surgically rebuild the manifest from `origin/main` + your delta. The CI artifact gate is changed-files-scoped (`--scope head`) so it ignores the pre-existing drift; sweeping them in adds unrelated churn and risks a manifest↔marker mismatch. + +**Rule:** only fix red your own diff introduced. When a generator touches files outside your bead's scope, revert those files. "The generator changed it" never justifies shipping unrelated drift. + +## See also + +- [new-skill-landing.md](new-skill-landing.md) — the six derived surfaces a new/modified skill must regenerate in one shot (the companion to this diff-scope discipline). diff --git a/.agents/skills/evolve/references/goals-schema.md b/.agents/skills/evolve/references/goals-schema.md new file mode 100644 index 0000000..b84a4a0 --- /dev/null +++ b/.agents/skills/evolve/references/goals-schema.md @@ -0,0 +1,140 @@ +# GOALS.yaml Schema + +```yaml +version: 1 +mission: "What this repo does" + +goals: + - id: unique-identifier + description: "Human-readable description" + check: "shell command — exit 0 = pass, non-zero = fail" + weight: 1-10 # Higher = fix first +``` + +Goals are checked in weight order (highest first). The first failing goal with the highest weight is selected for improvement. + +## Fitness Snapshot Format + +Each cycle writes a fitness snapshot with **continuous values** (not just pass/fail): + +```json +{ + "cycle": 1, + "timestamp": "2026-02-12T15:45:00-05:00", + "cycle_start_sha": "abc1234", + "goals": [ + { + "id": "go-coverage-floor", + "result": "pass", + "weight": 2, + "value": 86.1, + "threshold": 80 + }, + { + "id": "doc-coverage", + "result": "pass", + "weight": 2, + "value": 20, + "threshold": 16 + }, + { + "id": "go-cli-builds", + "result": "pass", + "weight": 5, + "value": null, + "threshold": null + } + ] +} +``` + +- **value**: The continuous metric extracted from the check command (null for binary-only goals) +- **threshold**: The pass/fail threshold (null for binary-only goals) +- **cycle_start_sha**: Git SHA at cycle start, used for multi-commit revert on regression + +Pre-cycle snapshot: `fitness-latest.json` (rolling, overwritten each cycle) +Post-cycle snapshot: `fitness-latest-post.json` (rolling, for regression comparison) + +## Era Baselines + +Before the first improvement cycle of a new goal era runs, evolve captures an +immutable baseline snapshot under +`.agents/evolve/fitness-baselines/goals-/`, where `` is the first +12 hex characters of the SHA-256 digest of the active GOALS.md or GOALS.yaml +file. A new goal era gets a new directory automatically; an existing era is +not recaptured once that directory contains a JSON snapshot. + +Each era baseline includes: +- **All goals** from GOALS.yaml or GOALS.md, measured in their initial state for that era +- **Baseline metadata** embedded in the snapshot JSON written by `ao goals measure` +- **No regression comparisons** — this is the starting point for that era + +When the session ends (at Teardown), the system computes the **session fitness trajectory** by comparing the current era baseline against the final cycle snapshot. This produces `session-fitness-delta.md`, which shows which goals improved, regressed, or stayed unchanged over the entire /evolve session. + +## Meta-Goals + +Meta-goals validate the validation system itself. Use them to prevent exception lists (allowlists, skip lists) from accumulating stale entries unnoticed. + +```yaml +# Meta-goals validate the validation system itself +goals: + - id: allowlist-hygiene + description: "Every dead-code allowlist entry should have 0 non-test callers" + check: "bash scripts/check-allowlist-hygiene.sh" + weight: 7 + + - id: skip-list-hygiene + description: "Every skip-list entry should still reference an existing test" + check: "bash scripts/check-skip-list-hygiene.sh" + weight: 5 +``` + +**When to add a meta-goal:** After pruning any allowlist or exception list, always add a corresponding meta-goal that fails if entries have callers/references. Allowlists without meta-goals are technical debt magnets — they grow silently across epics. + +## Maintaining GOALS.yaml + +Use `/goals` to maintain the fitness specification: +- `/goals` — run all checks, report pass/fail by pillar +- `/goals generate` — scan repo for uncovered areas, propose new goals +- `/goals prune` — find stale/broken goals, propose removals or updates + +## GOALS.md Format (Version 4) + +GOALS.md extends the YAML format with strategic intent: + +```markdown +# Goals + + + +## North Stars +- + +## Anti Stars +- + +## Directives + +### 1. +<Description> +**Steer:** increase | decrease | hold | explore + +## Gates +| ID | Check | Weight | Description | +|----|-------|--------|-------------| +| id | `command` | N | Description | +``` + +### Evolve Integration + +When GOALS.md is detected, evolve uses the directive-based cascade (Step 3.1): +1. `ao goals measure --directives` returns the directive list as JSON +2. Top-priority directive (lowest number) is assessed for gaps +3. If gap found → generates work item from directive description + steer +4. Directive becomes the work source for the cycle + +When `--beads-only` is passed, directive assessment is skipped entirely. + +### Format Detection + +`ao goals measure` auto-detects format. When both GOALS.yaml and GOALS.md exist, GOALS.md takes precedence. diff --git a/.agents/skills/evolve/references/knowledge-loop-integration.md b/.agents/skills/evolve/references/knowledge-loop-integration.md new file mode 100644 index 0000000..e446860 --- /dev/null +++ b/.agents/skills/evolve/references/knowledge-loop-integration.md @@ -0,0 +1,64 @@ +# Knowledge Loop Integration + +## Compile Warmup (--compile only) + +Skip if `--compile` was not passed or if `--dry-run`. + +Run the mechanical half of the Compile cycle to surface fresh signal before the first evolve cycle: + +```bash +mkdir -p .agents/mine .agents/defrag +echo "Compile warmup: mining signal..." +ao mine --since 26h --quiet 2>/dev/null || echo "(ao mine unavailable — skipping)" + +echo "Compile warmup: defrag sweep..." +ao defrag --prune --dedup --quiet 2>/dev/null || echo "(ao defrag unavailable — skipping)" +``` + +Then read `.agents/mine/latest.json` and `.agents/defrag/latest.json` and note (in 1-2 sentences each): +- Any **orphaned research** files that look relevant to current goals +- Any **code hotspots** (high-CC functions with recent edits) that may be the root cause of failing goals +- Any **duplicate learnings** merged by defrag — context on what's been cleaned up + +These notes inform work selection throughout the evolve session. Store them in a session variable (in-memory), not a file. + +## Harvested Work Selection (Step 3.1) + +Read `.agents/rpi/next-work.jsonl` and pick the highest-value unconsumed item for this repo. Prefer: +- exact repo match before `*`, then legacy unscoped entries +- already-harvested concrete implementation work before process work +- higher severity before lower severity + +When evolve picks a queue item, **claim it first**: +- set `claim_status: "in_progress"` +- set `claimed_by: "evolve:cycle-N"` +- set `claimed_at: "<timestamp>"` +- keep `consumed: false` until the `/rpi` cycle and regression gate both succeed + +If the cycle fails, regresses, or is interrupted before success, release the claim and leave the item available for the next cycle. + +## Queue Finalization After Regression Gate + +- **success:** finalize any claimed queue item with `consumed: true`, `consumed_by`, and `consumed_at`; clear transient claim fields +- **failure/regression:** clear `claim_status`, `claimed_by`, and `claimed_at`; keep `consumed: false`; record the release in `session-state.json` + +After the cycle's `/post-mortem` finishes, immediately re-read `.agents/rpi/next-work.jsonl` before selecting the next item. Never assume the queue state from before the cycle. + +## Teardown Learning Extraction + +1. Commit any staged but uncommitted `cycle-history.jsonl`: +```bash +if git diff --cached --name-only | grep -q cycle-history.jsonl; then + git commit -m "evolve: session teardown -- artifact-only cycles logged" +fi +``` + +2. Run `/post-mortem "evolve session: ${CYCLE} cycles"` to harvest learnings. + +3. Push only if unpushed commits exist: +```bash +UNPUSHED=$(git log origin/main..HEAD --oneline 2>/dev/null | wc -l) +[ "$UNPUSHED" -gt 0 ] && git push +``` + +4. Report summary: cycles, productive/regressed/idle counts, stop reason. Quality mode adds quality score + remaining findings. diff --git a/.agents/skills/evolve/references/long-loop-discipline.md b/.agents/skills/evolve/references/long-loop-discipline.md new file mode 100644 index 0000000..a32ed0d --- /dev/null +++ b/.agents/skills/evolve/references/long-loop-discipline.md @@ -0,0 +1,98 @@ +# Long-Loop Discipline — Disk Is Truth, Conversation Is Decorative + +The cross-cutting axiom that every other `/evolve` reference implements. +When a loop runs past ~50 cycles (`/evolve`, `/loop`, `ScheduleWakeup`-driven +cron), **every load-bearing state must live on disk, not in conversation +context.** The conversation accumulates: skill prompts re-inject, hook +reminders pile up, tool-result history grows. Auto-compact handles it well +enough that correctness survives — *if and only if* the work-state was on +disk to begin with. + +This doc is the principle; the implementing references are listed below. + +## The Axiom + +| State | Truth surface | +|---|---| +| Cycle outcome | `.agents/evolve/cycle-history.jsonl` (append-only, read at every cycle start) | +| Hypothesis tracker | `.agents/evolve/hypotheses.jsonl` | +| Queueable work | `bd ready` (Dolt-backed, out-of-band) | +| Skill rules | `skills/<n>/SKILL.md` + `references/` (read fresh per cycle) | +| Conversation context | **Decorative** — re-derives state from disk; never trusted alone | + +If you remove the conversation context entirely between cycles and the work +keeps moving, the loop is healthy. If removing it breaks the loop, you are +storing load-bearing state in the wrong place. + +## Evidence (anchored) + +> "Every cycle starts by recovering state from `.agents/evolve/cycle-history.jsonl` +> — not from the conversation. Context drift in the conversation doesn't lose +> the work-state … The conversation context that accumulates is mostly +> decorative; the load-bearing state is on disk." +— `docs/learnings/2026-05-13-loop-context-drift-87-cycle-observation.md` +(87-cycle empirical observation) + +> "text in SKILL.md is aspirational until paired with harness automation." +— `docs/learnings/2026-05-11-evolve-skill-friction-from-13-cycle-session.md` +(H45.2 + H45.3 falsified within 4-6 cycles because the rule lived only in +prose, not in a gate) + +> "Cycle 12's deep-scout … was the most productive move of cycles 11-13 even +> though it shipped zero code. … A scout cycle is neither productive (ships +> code) nor idle (finds nothing): it ships durable knowledge about pending +> work, narrowing future cycles' search and discovery cost." +— `.agents/learnings/2026-05-11-evolve-scout-mode-pattern.md` +(durable knowledge IS the disk-truth surface) + +> "in a shared tree, commit by explicit path (`git add <files>`), never +> `git add -A`; treat full-repo gate runs as unreliable when other agents +> are active and lean on targeted proof commands instead." +— `.agents/learnings/2026-05-16-evolve-trace-shared-tree.md` +(disk-truth survives only if peer agents don't clobber it) + +## How To Apply + +1. **Read the ledger first.** Every cycle's first action is + `tail -n 50 .agents/evolve/cycle-history.jsonl` (or the + `productionLoopReader` adapter). Recover state from there, not from prior + turns' messages. +2. **Append, don't overwrite.** Each cycle appends one JSONL line. Never + rewrite earlier cycles. The ledger is the audit trail. +3. **Bead-driven work selection.** `bd ready` is the queue. Beads live in + Dolt; the conversation never owns work scheduling. +4. **Falsify aspirational rules.** Any rule in `SKILL.md` that depends on + the agent following it (vs. a harness gate enforcing it) will be + violated within 4-6 cycles. Wire automation or accept the rule is + guidance only. +5. **Treat "complete" claims with suspicion.** When a cycle says "all + surfaces wired," the next CI run often finds one more. Add a per-cycle + drift sweep instead of trusting prior claims. +6. **Protect disk-truth in shared trees.** See + [../../swarm/references/shared-checkout-discipline.md](../../swarm/references/shared-checkout-discipline.md) + — disk-truth only survives if peer agents don't clobber it via + `git add -A` or destructive recovery. + +## Implementing References (Already In `/evolve`) + +The axiom is the principle. Each reference below is one mechanism that +keeps disk-truth working: + +| Reference | Role | +|---|---| +| [cycle-history.md](cycle-history.md) | JSONL format, recovery protocol, kill switch | +| [autonomous-execution.md](autonomous-execution.md) | ScheduleWakeup self-perpetuation pattern | +| [context-budget.md](context-budget.md) | `CONTEXT_BUDGET_EXHAUSTED` as a third stop reason | +| [convergence-mechanics.md](convergence-mechanics.md) | Read-path mechanisms (prior-failure injection, healing-first classifier) | +| [scout-mode.md](scout-mode.md) | Scout-cycle as a first-class disk-truth-only result type | +| [gate-hygiene.md](gate-hygiene.md) | Pre-gate source-surface detection + structural gate-output parsing | +| [metronome-gate.md](metronome-gate.md) | Cross-cycle detector blocking same-mode-repeated failure | +| [pre-flight-schema-check.md](pre-flight-schema-check.md) | Cheap field-fit check before architectural migration | + +## See Also + +- `skills/rpi/references/orchestrator-compression-anti-pattern.md` — when + the orchestrator skips phases by inlining sub-skill work, no flywheel + artifact lands (same disk-truth principle, applied to /rpi). +- `skills/swarm/references/shared-checkout-discipline.md` — disk-truth only + survives if peer agents don't clobber it. diff --git a/.agents/skills/evolve/references/loop-mode.md b/.agents/skills/evolve/references/loop-mode.md new file mode 100644 index 0000000..e1fc4b4 --- /dev/null +++ b/.agents/skills/evolve/references/loop-mode.md @@ -0,0 +1,19 @@ +# /evolve Loop Mode + +`--mode=loop` flips /evolve from agent-self-regulated to operator-driven. + +## Invariants under --mode=loop + +1. `ao loop write-stop-marker` exits 1 unconditionally +2. DORMANT/STOP/KILL markers are operator-only (operator writes by hand or via `ao loop operator-stop`) +3. Scope-filter (Step 3) splits too-big work into smaller beads via `bd create --deps discovered-from:<parent>`, never halts +4. Step 7 stop reasons are stripped of CONTEXT_BUDGET_EXHAUSTED; that becomes a non-sticky HANDOFF signal cleared by next cron-fire + +## CLI primitives that enforce + +- `/evolve --mode=loop` — flag itself +- `ao loop write-stop-marker` — refuses under loop +- `ao loop operator-stop` — explicit operator override (separate code path) +- `ao loop blocked` (Wave 2) — typed blocked event instead of STOP + +See: docs/plans/2026-05-21-evolve-loop-epic-design.md §A1 + §A7 diff --git a/.agents/skills/evolve/references/mechanical-batches.md b/.agents/skills/evolve/references/mechanical-batches.md new file mode 100644 index 0000000..51aee77 --- /dev/null +++ b/.agents/skills/evolve/references/mechanical-batches.md @@ -0,0 +1,56 @@ +# Mechanical Batch Work — Script vs Per-File Edit + +A "mechanical batch" is work where the same edit is applied to N files mechanically, with no per-file judgement after the pattern is established. Examples from prior sessions: declaring `practices: [slug]` across 533 cli/ Go files, regenerating COMMANDS.md from a Cobra tree, bumping a version literal in N manifests. + +The default per-file `Edit` tool path scales linearly with N and burns context. Switch to a script when ANY of these apply: + +- N > ~20 files +- The transformation is **uniform** (same regex / same sed / same template field) +- A failure on file K does not change the strategy for files K+1..N +- Per-file output verification is cheap (`git diff --stat`, a single grep) + +## Script-first pattern + +```bash +# 1. Inventory candidates with the predicate the task implies +candidates=$(git ls-files 'skills/**/SKILL.md' | xargs -I{} bash -c ' + grep -L "^practices:" "{}" >/dev/null && echo "{}" +' | head -50) + +# 2. Apply the mechanical edit +for f in $candidates; do + awk ' + /^description:/ && !done { print; print "practices: [<slug>]"; done=1; next } + { print } + ' "$f" > "$f.tmp" && mv "$f.tmp" "$f" +done + +# 3. Verify in one shot +git diff --stat +grep -c "^practices:" $candidates | sort +``` + +## Per-file Edit pattern (when the above does NOT apply) + +Use `Edit` per file only when: + +- The transformation requires per-file decisions (which slug? which carrier?) +- N is small (< 10) +- Verification benefits from reading each diff in context + +## Decision in Step 4 + +When `/rpi` discovery surfaces a per-file pattern with N > 20 uniform candidates, the implementation phase should generate a script under `scripts/` or use an inline awk/sed pass rather than queuing N `Edit` operations. The post-edit gate then verifies the batch atomically. + +If the script needs different parameters per file, that is a signal the work isn't actually mechanical — fall back to per-file Edit and reduce N (split the cycle). + +## Why this matters + +A 533-file `practices:` backfill done via per-file Edit takes ~50× more context than the same backfill done via a script with `git diff --stat` verification. The context-budget exhausts well before the work completes, and the loop drops into a forced scout or idle state with most of the work still pending. + +## Anti-patterns + +- Running a script that mutates files but ignoring the result of `git diff` (silent partial application). +- Using `sed -i` on a tree-traversed find without `--null` handling (filename-with-spaces breakage). +- Mixing mechanical and per-file edits in the same commit (auditor cannot tell what was bulk-applied vs per-judged). +- Treating a 200-file batch as a single "atomic" change — split into review-sized waves (~30-100 files) with one commit per wave. diff --git a/.agents/skills/evolve/references/metronome-gate.md b/.agents/skills/evolve/references/metronome-gate.md new file mode 100644 index 0000000..bcb7d29 --- /dev/null +++ b/.agents/skills/evolve/references/metronome-gate.md @@ -0,0 +1,63 @@ +# Metronome Gate — Detecting Productive-Looking Repetition + +The work-selection ladder treats "tests passed + commit landed" as productive. That signal is necessary but not sufficient: it cannot tell apart a cycle that *applied an existing template again* from one that *closed a new lane of work*. Cycles 144-154 in this repo were 7 consecutive CLI-wiring template applications — every cycle green, every commit clean, every gate happy. Only operator override at cycle 154 stopped the run. + +The metronome gate adds a structural detector: when the same `mode` value repeats N times, the next rung of the ladder is forced. + +## State + +`mode_repeat_streak` is computed by `scripts/evolve-update-session-state.sh` from the trailing run of identical `mode` values in `cycle-history.jsonl`. It is derived, not authored — running the script after every cycle keeps it coherent. + +## Thresholds + +| `mode_repeat_streak` | Action | +|---|---| +| 0–2 | No gate. The selection ladder runs normally. | +| 3–4 | **Soft block.** If the selected work would produce the same `mode` value, skip to the next rung of the selection ladder. Do not abort — try the rung below. | +| 5+ | **Hard block.** Record the gap as bead/provenance evidence and require operator override (write `.agents/evolve/RESCOPE` with a new directive). The cycle is logged as `unchanged` with `selected_source: "metronome-gate-blocked"`. | + +## Why the threshold is 3, not 2 + +A 2-cycle streak is normal: harvested work often comes in pairs (port + adapter, R + W, parent + child). A 3-cycle streak is the earliest signal that the *template* has become the work, not the *problem*. Stopping at 3 catches the failure mode early without blocking legitimate harvested-pair cycles. + +## Naming discipline (operator instruction) + +For the gate to work, cycle entries must use *distinguishable* `mode` values. Anti-patterns observed before this gate existed: + +- `template-applied-cli-wiring-5th`, `template-applied-cli-wiring-6th`, `template-applied-cli-wiring-7th` — the suffix `-Nth` made these technically unique strings but obviously the same mode. The gate canonicalizes by stripping trailing `-Nth` / `-N` patterns before comparison. +- `wire-up-BC1`, `wire-up-BC2`, `wire-up-BC3` — the BC suffix changes but the work shape is identical. This is a *legitimate harvested-pair sequence* — three separate BCs, three real ports — and the gate correctly does NOT block here because the streak counts identical strings only. Operator distinguishes "BC-pair work" from "template metronome" by intent. + +When in doubt, the operator override at threshold 5+ is the safety net. + +## Worked example — cycles 144-154 + +``` +cycle 144: wire-up-BC1 (streak: 1) +cycle 145: wire-up-BC1-pair (streak: 1, different) +cycle 146: wire-up-complete (streak: 1) +cycle 147: template-applied-cli-wiring-4th (streak: 1) +cycle 148: template-applied-cli-wiring-5th (streak: 2 after canonicalization) +cycle 149: template-applied-cli-wiring-6th (streak: 3 — SOFT BLOCK fires) +cycle 150: template-applied-cli-wiring-7th (streak: 4 — still blocked, next rung) +cycle 151..154: ... (operator override at 154) +``` + +With the gate active at threshold 3, cycle 149 would have skipped to the next rung of the selection ladder, probably surfacing the latent narrowness problem 5 cycles sooner. + +## Interaction with idle_streak + +The metronome gate fires on *productive* cycles; the dormancy gate fires on *idle* cycles. They are orthogonal: + +- `idle_streak >= 2 AND generator_empty_streak >= 2` → dormancy (see Step 3 hard-gate) +- `mode_repeat_streak >= 3 AND candidate.mode == last_mode` → metronome block (skip rung) +- Both can never fire on the same cycle: idle cycles produce no work, so they cannot extend the metronome. + +## Why this lives in /evolve, not /rpi + +`/rpi` runs one cycle's worth of work. It has no visibility into prior cycles. The metronome is a *cross-cycle* pattern, only visible from the loop driver. `/evolve` is the only place that has both the ledger and the next-cycle decision. + +## See also + +- `docs/rescope/2026-05-13-ddd-hex-architecture-rescope.md` — the original operator override that prompted this gate. +- `references/cycle-history.md` — the ledger this gate reads. +- `references/scout-mode.md` — what to do with work that hits the soft-block (read + annotate, no execution). diff --git a/.agents/skills/evolve/references/new-skill-landing.md b/.agents/skills/evolve/references/new-skill-landing.md new file mode 100644 index 0000000..a274e26 --- /dev/null +++ b/.agents/skills/evolve/references/new-skill-landing.md @@ -0,0 +1,50 @@ +# New-Skill Landing — the six derived surfaces + +Adding or modifying a skill regenerates **six derived surfaces**, each gated by CI. Regenerating them piecemeal is the dominant fix-and-repush cause (the 2026-05-29 `/burndown` re-land needed 3 rounds; the codex-desc-budget fix needed 1). Regenerate them in **one shot** before opening the PR. + +## The shortcut + +`scripts/regen-all.sh` covers surfaces 1–3 + the CLI reference in one pass — prefer it over running the generators individually. Then run the codex + count steps below. + +## Live skill edits + +When an agent edits an existing live skill at `skills/<slug>/SKILL.md`, seal the +edit before the next cycle hands off: + +```bash +ao skills edit seal --skill <slug> --actor "${AGENT_NAME:-agent}" +``` + +That command stages the skill's source directory and any matching Codex skill +directory, creates a git rollback point, and records `Skill-Edit` trailers in +the commit body. Critical skills listed in +`docs/contracts/critical-skills.txt` reject unattended edits; rerun with +`--allow-critical` only for a human-supervised critical edit. + +Daily operator review uses: + +```bash +ao skills edit digest --since "24 hours ago" +``` + +The digest is the lightweight immune-system surface for "what did agents teach +the runtime today?" A live edit seal does not replace the derived-surface work +below: new skills, renames, metadata changes, and publication-bound edits still +need the full six-surface regeneration. + +## The six surfaces + +1. **registry.json (SKU catalog)** — `scripts/generate-registry.sh` (verify with `--check`). **The most-missed surface**: a stale `registry.json` trips `contracts-sync` ("registry.json is stale") AND `correctness(ubuntu)` ("SKU_CATALOG: DRIFT") *together*. As of ag-ekyq, `skills/skill-builder/scripts/init.sh` regenerates it automatically during scaffold; regenerate by hand for any out-of-band skill edit. +2. **skill-domain-map** — `scripts/generate-skill-domain-map.sh` (narrative `N skills` count + per-skill row; verify `--check`). +3. **context-map** — `scripts/generate-context-map.sh` (hex roles from frontmatter; CI gate `validate-context-map-drift`). +4. **skill counts** — `scripts/sync-skill-counts.sh`. **The `SKILL-TIERS.md` row must be added by hand** — `sync-skill-counts` only syncs the *counts* across PRODUCT/ARCHITECTURE/SKILLS/using-agentops; it never adds the row. +5. **codex twin** — `scripts/register-new-codex-skill.sh <name> --treatment parity_only|bespoke --reason "…"` (manifest entry + override-catalog + `.agentops-generated.json` marker, atomic + idempotent). Then **hand-author `skills-codex/<name>/prompt.md`** — codex twins are MANUAL; `regen-all.sh` does NOT generate them. Re-run `scripts/regen-codex-hashes.sh` afterward (adding `prompt.md` changes the tree hash). Parity_only minimal `prompt.md` = title + description + an `## Instructions` block ("Load and follow the sibling `SKILL.md`"). +6. **narrative skill counts** — `scripts/check-registry-drift.sh --fix-counts` (the `N skills` lines in `agentops-skill-domain-map.md` + `agentops-domain-evolution-bdd.md`). + +## Embedded sync + +If the edit touches `skills/**` content that is embedded (e.g. `using-agentops`), run `cd cli && make sync-hooks` so `cli/embedded/skills/...` stays in sync — else the `codex artifact metadata`/embedded-drift gate fails. + +## See also + +- [gate-hygiene.md](gate-hygiene.md) — pre-push diff-scope check + pre-existing-vs-mine red triage (the companion discipline that keeps a new-skill PR one-shot-green). diff --git a/.agents/skills/evolve/references/oscillation.md b/.agents/skills/evolve/references/oscillation.md new file mode 100644 index 0000000..a5543d1 --- /dev/null +++ b/.agents/skills/evolve/references/oscillation.md @@ -0,0 +1,55 @@ +# Oscillation Detection + +## What Is Oscillation? + +A goal **oscillates** when it alternates between passing and failing across +evolve cycles. Typically this means the fix for the goal causes a side-effect +that breaks something else, which gets reverted, which re-exposes the original +failure — creating a loop. + +Example from cycle-history.jsonl: +``` +cycle 5: goal-X → improved +cycle 8: goal-X → fail +cycle 12: goal-X → improved +cycle 15: goal-X → fail +``` + +## Detection + +Count **improved→fail transitions** for the same `target` in +`.agents/evolve/cycle-history.jsonl`: + +```bash +# Count oscillations for a given goal — reads via BC3 LoopReaderPort +# (soc-y5vh.4 wrapper) instead of inline JSONL slurp. +scripts/evolve-read-cycle-history.sh recent 0 \ + | jq -r "select(.target==\"$GOAL_ID\") | .result" \ + | awk 'prev=="improved" && $0=="fail" {count++} {prev=$0} END {print count+0}' +``` + +## Threshold + +**3 oscillations** (improved→fail transitions) within a single session +triggers quarantine. The goal is skipped in Step 3 selection. + +## Effect + +- Quarantined goals are skipped during work selection (Step 3) +- Skipping a quarantined goal counts as idle (no actionable work found) +- The quarantine is session-scoped — a new session resets the count +- Quarantine events are logged in cycle-history.jsonl with `"result": "quarantined"` + +## Recovery + +1. Human identifies the root cause (usually a conflict between two goals) +2. Fix the underlying issue manually +3. Start a new evolve session (quarantine resets) +4. Or: remove the quarantine by deleting the goal from the skip list + +## Why Not Just Increase the Skip Threshold? + +The 3-consecutive-regression skip in Step 3 only catches monotonic failure. +Oscillation is worse — it burns cycles alternating between "fixed" and "broken" +without the stagnation detector ever triggering (because the goal intermittently +passes). The oscillation detector catches this pattern explicitly. diff --git a/.agents/skills/evolve/references/parallel-execution.md b/.agents/skills/evolve/references/parallel-execution.md new file mode 100644 index 0000000..9965c48 --- /dev/null +++ b/.agents/skills/evolve/references/parallel-execution.md @@ -0,0 +1,128 @@ +# Parallel Goal Execution + +## Architecture + +When `--parallel` is enabled, `/evolve` uses `/swarm` to execute multiple independent +goal improvements concurrently instead of fixing one goal per cycle. + +``` +/evolve --parallel (Fitness Loop) + │ + ├─ Step 2: Measure ALL goals + │ + ├─ Step 3: Select top N independent failing goals (max_parallel, default 3) + │ └─ select_parallel_goals: heuristic independence via check-script overlap + │ + ├─ Step 4: Parallel execution via /swarm + │ ├─ TaskCreate for each selected goal + │ ├─ Artifact isolation: .agents/evolve/parallel-rpi/{goal.id}/ + │ ├─ Git isolation: /swarm --worktrees (each worker in /tmp/evolve-{goal.id}) + │ └─ /swarm spawns N fresh-context workers, each runs full /rpi cycle: + │ └─ research → plan → pre-mortem → crank → vibe → post-mortem + │ + ├─ Step 5: Single regression gate (re-measure ALL goals after wave) + │ ├─ If ANY goal regressed → revert ENTIRE parallel wave + │ └─ If clean → log cycle with goal_ids array + │ + └─ Step 6-7: Log, loop (same as sequential) +``` + +## The Fractal Pattern + +Swarm is the universal coordination primitive at every level: + +``` +LEVEL 0: /evolve --parallel + └─ /swarm (parallel goal improvements) ← NEW: swarm at evolve level + └─ LEVEL 1: /rpi (per-goal lifecycle) + └─ research → plan → crank → vibe → post-mortem + └─ LEVEL 2: /crank (epic execution) + └─ /swarm (parallel issue implementation) ← existing: swarm at crank level + └─ LEVEL 3: workers (atomic tasks) +``` + +Each level creates fresh context for the next (Ralph Wiggum pattern). +The pattern is always: **one leader + N fresh-context workers + validation + cleanup**. + +## Goal Independence Detection + +`select_parallel_goals` uses a heuristic check: + +1. Start with highest-weight failing goal +2. For each remaining eligible goal (weight-sorted): + - Compare check commands for shared scripts/paths + - If independent: add to selection (up to max_parallel) + - If overlapping: skip (handled in next cycle) + +**This is a heuristic, not a guarantee.** Goals don't declare which files their +improvements will modify — only which scripts verify them. Two goals with different +check scripts may still modify overlapping files. + +**The regression gate (Step 5) is the real safety net.** If parallel goals conflict, +the regression check detects it and reverts the entire wave. This makes false +negatives in independence detection safe (they just cost one wasted cycle). + +## Artifact Isolation + +Each parallel /rpi worker needs isolated artifact directories to prevent collision: + +| Directory | Purpose | Isolation | +|-----------|---------|-----------| +| `.agents/evolve/parallel-rpi/{goal.id}/` | /rpi phase summaries, next-work | Per-goal subdirectory | +| `.agents/evolve/parallel-results/{goal.id}.md` | Worker result summary | Per-goal file | +| `/tmp/evolve-{goal.id}` | Git worktree | Per-goal worktree via /swarm | + +Without isolation, N concurrent /rpi cycles would collide on `.agents/rpi/` +(phase summaries, next-work.jsonl) and git index locks. + +## Git Isolation + +Parallel workers MUST use worktree isolation (via `/swarm --worktrees`): + +- Each worker operates in `/tmp/evolve-{goal.id}` worktree +- No git lock conflicts (each worktree has its own index) +- Lead merges worktrees after all complete, before regression gate +- On regression: revert all merged commits using `cycle_start_sha` + +## Regression Handling + +**Sequential mode:** Revert commits from one goal's /rpi cycle. + +**Parallel mode:** Revert ALL commits from the entire parallel wave. +The `cycle_start_sha` (captured before the wave) anchors the revert point. +All N goal improvements are rolled back together — even goals that individually +succeeded. This is by design: if goals interfere, we can't know which one +caused the regression without testing each in isolation. + +## Cycle History Schema + +Sequential cycles use `target` (string). Parallel cycles use `goal_ids` (array) with `parallel: true`: + +```jsonl +{"cycle": 1, "target": "test-pass-rate", "result": "improved", "sha": "abc1234", ...} +{"cycle": 2, "goal_ids": ["doc-coverage", "lint-clean"], "result": "improved", "sha": "def5678", "parallel": true, ...} +``` + +Legacy entries may use `goal_id` instead of `target` and `commit_sha` instead of `sha`. Tools should handle both. + +## Compounding + +Each parallel /rpi worker runs its own /post-mortem, which feeds the knowledge +flywheel independently. Learnings from all N parallel cycles compound into the +flywheel, feeding the next /evolve cycle. + +## When to Use + +| Scenario | Mode | +|----------|------| +| 1-2 failing goals | Sequential (default) — parallelism overhead not worth it | +| 3+ independent failing goals | `--parallel` — significant speedup | +| Goals with overlapping files | Sequential — parallel would cause conflicts | +| First run on new repo | Sequential — learn the codebase before parallelizing | + +## Constraints + +- Max 5 parallel goals per wave (`--max-parallel` cap) +- Default 3 parallel goals (balance between speedup and resource usage) +- Each /rpi worker needs a full context window — budget accordingly +- Worktree isolation required (no shared-worktree parallel /rpi) diff --git a/.agents/skills/evolve/references/postmortem-checkpoint.md b/.agents/skills/evolve/references/postmortem-checkpoint.md new file mode 100644 index 0000000..11d2a2a --- /dev/null +++ b/.agents/skills/evolve/references/postmortem-checkpoint.md @@ -0,0 +1,81 @@ +# Mandatory checkpoint: session-PR threshold (soc-n75z) + +> Stop reason #6 of `/evolve`. NOT terminal — gates the next cycle, not the loop's existence. Derivation: the 2026-05-19 and 2026-05-20 sessions both shipped 5-6 PRs while the agent self-graded "HEALTHY" without running a real council; STOP got written as if it were post-mortem completion. This procedure closes that gap. + +## When this fires + +At Step 7 boundary, after a cycle whose result is `improved` or `regressed` +(`$session_pr_count` is the canonical count from `scripts/session-pr-scope.sh --count`): + +```bash +if [ "$session_pr_count" -ge "${SESSION_PR_THRESHOLD:-5}" ] \ + && [ "$session_pr_count_at_last_checkpoint" -lt "${SESSION_PR_THRESHOLD:-5}" ]; then + # checkpoint fires once per crossing — not repeatedly at every cycle past 5 + run_postmortem_checkpoint +fi +``` + +The check is **edge-triggered** (fires when the counter first crosses the threshold), not level-triggered. After PASS/WARN, the loop continues until either another stop reason fires OR another threshold crossing — e.g., at `2 × SESSION_PR_THRESHOLD = 10`, the checkpoint re-fires for a second post-mortem. + +## What it MUST do + +1. **Invoke `/post-mortem --deep`** on the cycles since the last checkpoint (or session start). `--deep` runs 3 same-vendor judges in adversarial posture. + + - Do NOT use `--mixed`. Cross-vendor (Codex availability) is an infrastructure dependency that should not block the loop gate. Reserve `--mixed` for strategic architecture decisions (e.g., the DUEL.md precedent), not per-session health checks. + - Do NOT use single-judge mode. Single-judge sweeps are too weak for a gate that enables continued autonomous work — the whole point of the checkpoint is adversarial counter-balance against the author who just wrote the cycles. + +2. **Wait for the verdict file** at `.agents/council/<date>-<scope>-postmortem/verdict.md`. The verdict file's first non-blank line must start with `## Council Verdict: <PASS|WARN|FAIL>`. + +3. **Branch on verdict:** + + | Verdict | Action | + |---|---| + | `PASS` | Continue the ladder. Next cycle's `notes` field MUST record the verdict path. | + | `WARN` | Continue the ladder. Next cycle's `notes` field MUST emit the council's caveats verbatim — they need to trace through `cycle-history.jsonl` so the loop's recorded behavior reflects what was acknowledged. | + | `FAIL` | Write `.agents/evolve/STOP` with the verdict path embedded. Do NOT clear. Surface the council's required-follow-up list to the operator. | + +4. **Treat non-convergence as FAIL.** If after 600 seconds the verdict file is missing, malformed, or the council's judges disagreed irreconcilably after a second pass, default to FAIL. **Absence of evidence is NOT evidence of pass.** The 2026-05-20 failure mode was an empty 2-minute cron pass treated as council completion; this rule is the corrective. + +## What it MUST NOT do + +- **Never self-grade.** The agent who ran the cycles is the same agent doing the post-mortem. Self-attestation is definitionally invalid for the gate; council enforcement is the structural requirement. +- **Never write `.agents/evolve/STOP` without a council verdict file.** STOP without a verdict is the 2026-05-20 anti-pattern — the file becomes a self-imposed pause that the same agent (or operator) clears without rigor. Every STOP written by this checkpoint must cite a verdict path that resolves to a FAIL verdict. +- **Never run `/post-mortem` without `--deep` from this checkpoint.** A light-touch cron-fire pass that writes learnings but skips Phase 1 council does not satisfy the gate. The 2026-05-20 cron `/post-mortem` was insufficient by design. + +## Pre-creation signal (opt-in, not shipped) + +The pre-creation counterpart to this post-merge checkpoint — `hooks/session-pr-counter.sh` (PR #362, soc-1aou), which fired on `gh pr create` at `count >= threshold - 1` — was **removed** in the 3.0 hookless teardown (#511). AgentOps ships no hook. The reusable session-PR count it provided now lives in `scripts/session-pr-scope.sh` (the canonical `$session_pr_count` source — `scripts/session-pr-scope.sh --count`). Re-author the always-on pre-creation signal as an **opt-in** hook via the hooks-authoring skill if you want it. + +This checkpoint is the *post-merge* gate — it fires after PR #N+1 has merged, before cycle N+1 enters Step 1. It is **mandatory** and cannot be bypassed (the loop genuinely waits on the verdict file), unlike the advisory pre-creation signal: + +| Layer | Trigger | Default | Status | +|---|---|---|---| +| `scripts/session-pr-scope.sh` (wrap as an opt-in hook via hooks-authoring) | `gh pr create` at `count >= threshold-1` | Advisory | Reusable count; not shipped as a hook (removed in #511) | +| `evolve` checkpoint #6 | Cycle boundary after `count >= threshold` | Mandatory | Structural council enforcement before the next cycle starts | + +## Configuration + +| Variable | Default | Effect | +|---|---|---| +| `SESSION_PR_THRESHOLD` | `5` | Matches soc-waxr documentation default. The 2026-05-19 session (6 PRs, 3 self-corrections) and 2026-05-20 session (5 PRs, 1 self-correction + 1 confirmation-bias verdict) are the two derivation points. Do not change without a third data point. | +| `EVOLVE_POSTMORTEM_TIMEOUT_SEC` | `600` | Max wait for verdict file. Past this, treat as FAIL. | +| `EVOLVE_POSTMORTEM_COUNCIL_FLAG` | `--deep` | Override only with operator sign-off in a follow-up PR; reverting to single-judge or `--mixed` requires a council justifying the change. | + +## Failure modes this closes + +The 2026-05-20 post-mortem (`.agents/council/2026-05-20-evolve-204-208-postmortem/verdict.md`) found WARN with Q4 FAIL: + +> The "post-mortem" that "cleared" cycle 208 was a cron-fire that wrote 5 learning files and appended a harvest entry. No Phase 1 (council) ran. No Phase 2 extraction with adversarial review. No Phase 3 backlog processing. No verdict file was produced. +> +> Cycle 208's notes contain the string "HEALTHY verdict from /post-mortem cron" — a verdict that does not exist in any file the council directory contains. + +That cycle-history entry transformed an absence (no council ran) into a positive claim (clearance exists) in an append-only ledger. Stop reason #6 prevents that pattern by requiring a real verdict file path before continuing. + +## Cross-references + +- `skills/post-mortem/SKILL.md` — the post-mortem skill this checkpoint invokes +- `skills/council/SKILL.md` — the council skill `--deep` runs against +- `skills/ship-loop/SKILL.md` — the soc-waxr session-scope doctrine source +- `scripts/session-pr-scope.sh` — canonical session-PR count source (the pre-creation hook was **removed** in #511; re-author as an opt-in hook via hooks-authoring) +- `CLAUDE.md` § "Autonomous-session scope" — top-level doctrine reference +- `.agents/council/2026-05-20-evolve-204-208-postmortem/verdict.md` — the derivation council (local; gitignored) diff --git a/.agents/skills/evolve/references/pre-flight-schema-check.md b/.agents/skills/evolve/references/pre-flight-schema-check.md new file mode 100644 index 0000000..9326188 --- /dev/null +++ b/.agents/skills/evolve/references/pre-flight-schema-check.md @@ -0,0 +1,64 @@ +# Pre-Flight Schema Check — Stop Migration Cycles That Can't Succeed + +Three phase-2 port migrations attempted in cycles 156-157 of this repo all failed for the same structural reason: the port surface projected ~5 fields, the consumer read ~13 fields. The migration would have silently dropped 8+ fields the consumer relied on. The lesson is captured in `docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md`. This document encodes that lesson as a pre-cycle check. + +## When this gate runs + +Before invoking `/rpi` on a cycle whose selected work matches any of: + +- title contains `migrate`, `rewire`, `route through`, `port-ify`, `adapt to <Port>` +- title contains `<Module> → <Port>Port` or similar arrow syntax +- bd issue has label `migration` or `bc-port-adoption` +- selected_source is `harvested` and the queue entry has `kind: "migration"` + +## Procedure + +1. **Identify the port surface.** From the work title, locate the port type definition (typically `cli/internal/ports/<port>.go`). Extract the projected struct fields. + +2. **Sample two consumer call sites.** From the work description, find the function or file being migrated. Grep for *its* current consumers — the call sites that depend on the data the migration will route through the port. Pick two representative ones (different files, different formatting/decision shapes). + +3. **Field-fit comparison.** + ```bash + PORT_FIELDS=$(grep -E '^\s+[A-Z][a-zA-Z0-9_]+\s+' cli/internal/ports/<port>.go | awk '{print $1}' | sort -u) + CONSUMER_FIELDS=$(grep -oE '\.[A-Z][a-zA-Z0-9_]+\b' <consumer-files> | sed 's/^\.//' | sort -u) + comm -23 <(echo "$CONSUMER_FIELDS") <(echo "$PORT_FIELDS") + ``` + The output is the set of fields the consumer reads that the port does NOT project. + +4. **Decision.** + + | Missing fields | Action | + |---|---| + | 0 | Migration is safe. Proceed with `/rpi`. | + | 1–2 | Migration with widening. Add the missing fields to the port + writer + tests in the same cycle. Mark mode `port-widen-and-migrate`. | + | 3+ | **Abort the migration cycle.** File a port-widening bd issue with the field list, log this cycle as `unchanged` with `selected_source: "pre-flight-schema-fail"`, return to Step 3 selection. | + +5. **Special case — zero-data target.** If the consumer reads a field (e.g. `Target`) that exists in the type definition but *no actual entries in the production data store have that field set*, the consumer is dead-code-in-disguise. Don't widen — investigate whether the consumer was ever wired. Log as `selected_source: "pre-flight-dead-target"`. + +## Why this check is cheap + +A migration cycle costs ~15-30 minutes of /rpi (discovery + crank + validation). The pre-flight check is two `grep` calls and one `comm` — under 30 seconds. Catching even one bad migration pays for ~50 pre-flight runs. In this repo, three cycles spent on bad migrations (156, 157, plus the soc-0pku bead) would have been caught by 90 seconds of pre-flight. + +## What this does NOT replace + +This is a *cycle-selection* gate, not a code-quality gate. Validating the actual migration code still belongs to `/rpi` → `/validate`. The pre-flight only answers "is this migration even shaped right for the current port?" + +## Worked example — cycle 157 (soc-0pku) + +Selected work: `context_assemble.go → LoopReaderPort` + +``` +PORT_FIELDS=Number,Mode,Result,Commit,Milestone # 5 +CONSUMER_FIELDS=Timestamp,Cycle,Target,GoalIDs,Result,Status,Sha,CanonicalSha,LogSha,GoalsPassing,GoalsTotal,Summary,Error # 13 +Missing: Timestamp,Target,GoalIDs,Status,Sha,CanonicalSha,LogSha,GoalsPassing,GoalsTotal,Summary,Error # 11 +``` + +Missing fields = 11. Threshold 3+. **Abort.** File port-widening issue: "Widen CycleEntry: +Timestamp,+Summary at minimum; full set deferred." Cycle is logged as `unchanged`, ladder advances. + +This is exactly what cycle 161 ended up doing reactively — adding `StartedAt` and `Title` after the migrations bounced. With pre-flight, the widening would have happened *before* burning three cycles on doomed migrations. + +## See also + +- `docs/learnings/2026-05-13-bc-ports-narrowness-postmortem.md` — the source observation +- `references/scout-mode.md` — first-class result for cycles that intentionally don't execute +- `references/metronome-gate.md` — sibling cross-cycle gate diff --git a/.agents/skills/evolve/references/quality-mode.md b/.agents/skills/evolve/references/quality-mode.md new file mode 100644 index 0000000..a179b3f --- /dev/null +++ b/.agents/skills/evolve/references/quality-mode.md @@ -0,0 +1,70 @@ +# Quality Mode + +## When to Use + +Use `--quality` when: +- Post-mortem findings are accumulating faster than they're consumed +- All GOALS pass but `next-work.jsonl` has unconsumed high-severity items +- You want to resolve context-hot findings from a just-completed epic +- Running immediately after `/post-mortem` to action its findings + +Do NOT use `--quality` when: +- GOALS have critical failures (build broken, tests failing) +- No `next-work.jsonl` exists or is empty +- You want standard fitness-driven improvement + +## Quality Score + +Simple severity-weighted score: + +``` +score = 100 - (high_count * 10) - (medium_count * 3) +``` + +Where counts are unconsumed findings remaining in next-work.jsonl. + +| Score | Meaning | +|-------|---------| +| 90-100 | Excellent — few or no findings remaining | +| 70-89 | Good — medium-severity items remain | +| 50-69 | Attention needed — high-severity items remain | +| <50 | Quality debt — many high-severity findings | + +## Priority Cascade (Quality Mode) + +1. High-severity unconsumed findings → /rpi +2. Medium-severity unconsumed findings → /rpi +3. Open ready beads → prefer /rpi with bead context +4. Failing GOALS.yaml goals and directive gaps → /rpi +5. Testing improvements → /rpi +6. Validation tightening / bug-hunt / drift mining → /rpi +7. Feature suggestions → durable work + /rpi +8. Nothing after repeated generator passes → dormancy fallback + +## Marking Findings Consumed + +When evolve picks a finding from next-work.jsonl, claim it first: +- Set `claim_status: "in_progress"` +- Set `claimed_by: "evolve-quality:cycle-N"` +- Set `claimed_at: "<timestamp>"` +- Keep `consumed: false` until the /rpi cycle and regression gate both succeed + +If the /rpi cycle fails (regression), clear the claim and leave the finding available. + +## Artifacts + +| File | Purpose | +|------|---------| +| `cycle-history.jsonl` | Same as standard mode + `quality_score` field | +| `fitness-latest.json` | Same as standard mode (goals measurement) | +| `quality-trajectory.md` | Quality score over time (written at teardown) | + +## Interaction with Standard Mode + +Quality mode and standard mode share: +- The same cycle-history.jsonl +- The same fitness measurement (goals are still checked) +- The same dormancy guard (repeated empty queue + generator passes) +- The same circuit breaker (60 minutes) + +They differ in work selection priority at the top of the ladder: quality mode picks findings first, then still falls through to the same producer layers before dormancy is allowed. diff --git a/.agents/skills/evolve/references/scout-mode.md b/.agents/skills/evolve/references/scout-mode.md new file mode 100644 index 0000000..9c64bd3 --- /dev/null +++ b/.agents/skills/evolve/references/scout-mode.md @@ -0,0 +1,77 @@ +# Scout Mode — First-Class Cycle Result + +Scout mode is a result type alongside `improved`, `regressed`, `harvested`, and `idle`. A scout cycle reads a candidate work item, validates its scope and shape, and either annotates the queue entry with a deeper plan or splits it into smaller beads — without executing the underlying work. + +## When to scout + +Use scout-mode whenever a Step 3 selection meets any of these criteria: + +- The work touches **> 5 files** and is **not** a mechanical batch (a single script-driven rewrite across N similar files). +- The work introduces a **new shape**: schema field, frontmatter carrier, JSON top-level key, validator rule, contract surface, or struct field that downstream consumers will read. +- The work is **operator-level epic work**: multiple cooperating sub-systems must change together (e.g. emitter + consumer + validator + tests + docs in one cycle). +- The **current cycle is > 5 productive cycles into the session** and the work would extend the implementation arc rather than close it. + +The scope-filter step (Step 3.0 in `SKILL.md`) consults these heuristics before any work is claimed. + +## What a scout cycle does (soc-5qit: split-or-defer, never bail) + +A scout cycle is **work**, not a stop. It MUST produce exactly one of: + +### Path A: Split (preferred when the queue is light and the work is decomposable) + +1. Read the target file(s) named in the work item (no edits). +2. Map the **current shape** at the relevant boundary (what fields exist, what callers read it, what validators enforce). +3. Run `bd create` to decompose the candidate into 2-N child beads, each ≤5 files and single-shape: + ```bash + bd create "Slice 1 of <parent-title>: <smaller-scope>" \ + --description="Carved from <parent-id> by scout-mode. Scope: <files/contract>" \ + --deps discovered-from:<parent-id> -t task -p <inherit> --json + ``` +4. Update the parent bead with `bd update <parent-id> --notes "scout-split into: <child-ids>"`. +5. **Re-enter Step 3** so the smallest new child OR another ready bead gets claimed THIS cycle. + +### Path B: Defer (preferred when the queue has other ready beads) + +1. Read the target file(s) briefly to confirm the scope assessment. +2. Append a `disposition: defer:<reason>` block to the work item. +3. **Re-enter Step 3** so the next-priority ready bead gets claimed THIS cycle. The big candidate stays available for a future session with lighter context. + +### Path C: Park (operator-level epic, no obvious split) + +1. `bd update <id> --status blocked --notes "scope-too-big: <why>; needs operator triage"`. +2. **Re-enter Step 3** for the next ready bead. + +A scout cycle does NOT: + +- Run `/rpi` against the original too-big candidate. +- Edit any source file outside `.agents/rpi/next-work.jsonl` and bd metadata. +- Commit code (bd updates land in Dolt automatically). +- **Exit the loop** — if `bd ready` returns ≥1 unblocked bead, the cycle MUST claim and work one of them after the scout decision. (soc-5qit invariant.) + +## Logging a scout cycle + +Append to `cycle-history.jsonl` with: + +```json +{"cycle": N, "result": "scout", "selected_source": "<source>", "work_ref": "<id>", + "net_change": 0, "commit": null, + "milestone": "Scouted <work>; recommendation: <split|park|smaller-slice>"} +``` + +The `result: scout` value is canonical alongside `improved | regressed | harvested | idle | unchanged`. + +## Daily learning capture + +Scout cycles still get a micro-capture line. Use the form: + +``` +- cycle N [scout] <work-ref>: <what was learned about the shape> INSIGHT: <tag> +``` + +## Promotion path + +When a scouted item later becomes single-cycle-doable (because earlier prerequisites landed), drop the `disposition` block and let normal Step 3.1 selection pick it up. + +## Why scout is not "idle" + +`idle` means "no actionable work in any layer". Scout means "actionable work found but the shape is wrong for this cycle's budget". These are structurally different stop reasons. Conflating them masks the real failure mode: the loop has work but can't safely run it. diff --git a/.agents/skills/evolve/references/snapshot-pattern-for-long-cycle-gates.md b/.agents/skills/evolve/references/snapshot-pattern-for-long-cycle-gates.md new file mode 100644 index 0000000..e0edd81 --- /dev/null +++ b/.agents/skills/evolve/references/snapshot-pattern-for-long-cycle-gates.md @@ -0,0 +1,175 @@ +# Snapshot Pattern For Long-Cycle Gates + +Gates that depend on **multi-session corpus state** (flywheel +compounding, corpus freshness, knowledge-registry health, AOP-claim +coverage) cannot be evaluated by a single CI run on a greenfield +checkout — the corpus isn't there. The 4-step **snapshot pattern** +converts these into single-commit-validatable artifacts: the live gate +computes the metric on demand, the operator snapshots it to a +git-tracked JSON file with a staleness window, and the CI gate +validates the snapshot file rather than re-running the live metric. + +This is how disk-is-truth (see long-loop-discipline (disk-is-truth axiom)) +extends into CI: the **disk file is the gate's truth surface**, and +the live metric only refreshes it. + +## The 4-Step Pattern + +### 1. Live Gate (Operator-Local) + +A script that computes the multi-session metric on demand. Runs in +the operator's full corpus, not in CI. + +```bash +# Example: live gate for flywheel compounding +bash scripts/check-flywheel-compounding.sh +# Internally: ao flywheel status → exits 0 if compounding, 1 if not +``` + +### 2. Snapshot Command (Operator Cadence) + +Wraps the live result in a JSON envelope with `recorded_at`, +`git_sha`, and the metric payload. Writes to a git-tracked path. + +```bash +# Example: snapshot the live result +bash scripts/snapshot-flywheel-compounding.sh +# Writes docs/releases/flywheel-compounding-snapshot.json +``` + +The snapshot file is **the durable proof**. Operator runs the +snapshot script on cadence (e.g., before each release, or weekly). + +### 3. CI Gate (Snapshot Validation) + +CI does **not** re-run the live metric (it can't — no corpus). It +validates the snapshot file: + +- Structural shape (required keys present) +- Staleness threshold (e.g., < 14 days old) +- Asserted value (`compounding == true`, `freshness_score >= N`) + +```bash +# Example: CI gate validates the snapshot +bash scripts/check-flywheel-compounding-snapshot.sh +# Fails if snapshot missing | > 14 days old | shows metric flipped +``` + +### 4. Wire As A Regular Gate + +- `GOALS.md` row naming the metric +- `.github/workflows/validate.yml` CI job +- `scripts/pre-push-gate.sh` lane invocation +- `AGENTS.md` "CI Jobs and What They Check" row +- (per the parity-surface inventory — see + `docs/learnings/2026-05-12-parity-surface-inventory-grew-from-4-to-7-across-cycles-64-70.md`) + +Once wired, the snapshot pattern looks like a regular gate; the +operator-cadence-refresh is the only non-standard piece. + +## Why This Matters + +Without this pattern, long-cycle gates stay in the Roadmap column +forever because **CI can't run them green** — a clean CI runner has +no corpus, no flywheel history, no AOP-claim accumulation. Three +GOALS.md gates closed in one /evolve session because of this pattern +(G1 flywheel-snapshot, D11 corpus-freshness, D10 workbench-delta). + +The pattern is the implementation of long-loop-discipline (disk-is-truth axiom) +at the CI layer: the disk file is the load-bearing state, the live +metric is the refresh path, and CI consumes the disk. + +## Evidence (anchored) + +> "Gates that depend on multi-session corpus state (e.g., flywheel +> compounding, corpus freshness, knowledge registry health) cannot be +> evaluated by a single CI run on a greenfield checkout — the corpus +> isn't there. … The snapshot file is the durable proof. The CI gate +> ensures the operator refreshes it on cadence. Each surface is +> testable on a clean CI runner without N sessions of warmup." +— `.agents/learnings/2026-05-11-quick-snapshot-pattern-for-long-cycle-gates.md` +(retro-quick, validate phase) + +> "Applications validated this session: corpus snapshot (D11, cycle +> 22), flywheel compounding (G1, cycle 24), workbench delta (D10, +> cycle 26), README claim manifest (PG4, cycle 30), AOP-CLAIM +> all-claims map (cycle 35). The structural-floor contract gate +> (cycle 41) is the generalization — same pattern at the corpus level." +— same source + +## How To Apply (When Adding A New Long-Cycle Gate) + +1. **Identify the metric.** Does it need a corpus, history, or + multi-session state to compute? If yes, snapshot pattern. If no, a + regular CI gate suffices. + +2. **Write the live gate** under `scripts/check-<metric>.sh`. Exit 0 + on success, non-zero on regression. This runs in the operator's + environment. + +3. **Write the snapshot command** under `scripts/snapshot-<metric>.sh`. + Output JSON to a git-tracked path: + ```json + { + "recorded_at": "2026-05-18T19:42:00Z", + "git_sha": "abc1234", + "metric_name": "flywheel_compounding", + "value": true, + "details": { /* metric-specific payload */ } + } + ``` + +4. **Write the snapshot validator** under + `scripts/check-<metric>-snapshot.sh`. Read the JSON, assert shape + + staleness + value. + +5. **Wire all 7 parity surfaces** per the parity-surface inventory: + pre-push lane, validate.yml job, summary.needs[], summary echo, + AGENTS.md row, registry.json (if a new hook), bats stub (if pre-push). + +6. **Set a refresh cadence.** Document in `AGENTS.md` or `GOALS.md` + when the operator runs the snapshot script (before release, + weekly, before-each-evolve-session). + +## Failure Modes + +- **Skipping step 4 (snapshot validator).** Without the validator, + the snapshot file drifts undetected — operators stop refreshing it, + and CI claims green forever. The validator forces the cadence. +- **Snapshot in chat / ephemeral state.** The snapshot MUST be + git-tracked. A snapshot in a session JSONL is invisible to CI. +- **Staleness window too long.** > 30 days means the gate + effectively never fails. Tighten to 7-14 days. +- **Live gate and snapshot validator disagree on shape.** Schema + drift between the two scripts; CI passes but the metric is wrong. + Lock the JSON shape in the snapshot command's documentation. + +## Applications (Empirical Reference) + +The pattern shipped 6 times across cycles 22-41 of the 2026-05-11 +evolution-roadmap drain: + +| Cycle | Metric | Snapshot path | +|---|---|---| +| 22 (D11) | corpus freshness | `docs/releases/corpus-snapshot.json` | +| 24 (G1) | flywheel compounding | `docs/releases/flywheel-compounding-snapshot.json` | +| 26 (D10) | workbench delta | `docs/releases/workbench-delta-snapshot.json` | +| 30 (PG4) | README claim manifest | `docs/releases/readme-claim-manifest.json` | +| 35 | AOP-CLAIM all-claims map | `docs/releases/aop-claims-all-snapshot.json` | +| 41 | structural-floor contract gate (the generalization) | per-contract snapshot | + +Reuse-rate after first ship: 5 reuses in 19 cycles. The pattern is +durable. + +## See Also + +- long-loop-discipline (disk-is-truth axiom) — the principle this + pattern implements at the CI layer (sibling reference in this dir, + landing concurrently) +- [cycle-history.md](cycle-history.md) — the JSONL ledger pattern + (same disk-truth principle, different consumer) +- [gate-hygiene.md](gate-hygiene.md) — gate-output parsing; complements + snapshot validation +- `docs/learnings/2026-05-12-parity-surface-inventory-grew-from-4-to-7-across-cycles-64-70.md` + — the 9-surface checklist for wiring any new gate, including + snapshot gates diff --git a/.agents/skills/evolve/references/teardown.md b/.agents/skills/evolve/references/teardown.md new file mode 100644 index 0000000..e427835 --- /dev/null +++ b/.agents/skills/evolve/references/teardown.md @@ -0,0 +1,109 @@ +# Teardown Procedure + +**Auto-run /post-mortem on the full evolution session:** + +``` +/post-mortem "evolve session: $CYCLE cycles, goals improved: X, harvested: Y" +``` + +This captures learnings from the ENTIRE evolution run (all cycles, all /rpi invocations) in one council review. The post-mortem harvests follow-up items into `next-work.jsonl`, feeding the next `/evolve` session. + +**Compute session fitness trajectory:** + +```bash +# Check if both current-era baseline and final snapshot exist +GOALS_FILE="" +if [ -f GOALS.md ]; then + GOALS_FILE="GOALS.md" +elif [ -f GOALS.yaml ]; then + GOALS_FILE="GOALS.yaml" +fi + +ACTIVE_BASELINE_PATH="" +if [ -n "$GOALS_FILE" ]; then + ERA_ID="goals-$(shasum -a 256 "$GOALS_FILE" | awk '{print substr($1, 1, 12)}')" + ACTIVE_BASELINE_PATH="$(ls -t ".agents/evolve/fitness-baselines/$ERA_ID"/*.json 2>/dev/null | head -1 || true)" +fi + +if [ -n "$ACTIVE_BASELINE_PATH" ] && [ -f "$ACTIVE_BASELINE_PATH" ] && [ -f .agents/evolve/fitness-latest.json ]; then + baseline = load("$ACTIVE_BASELINE_PATH") + final = load(".agents/evolve/fitness-latest.json") + + # Compute delta — goals that flipped between baseline and final + improved_count = 0 + regressed_count = 0 + unchanged_count = 0 + delta_rows = [] + + for final_goal in final.goals: + baseline_goal = baseline.goals.find(g => g.id == final_goal.id) + baseline_result = baseline_goal ? baseline_goal.result : "unknown" + final_result = final_goal.result + + if baseline_result == "fail" and final_result == "pass": + delta = "improved" + improved_count += 1 + elif baseline_result == "pass" and final_result == "fail": + delta = "regressed" + regressed_count += 1 + else: + delta = "unchanged" + unchanged_count += 1 + + delta_rows.append({goal_id: final_goal.id, baseline_result, final_result, delta}) + + # Write session-fitness-delta.md with trajectory table + cat > .agents/evolve/session-fitness-delta.md << EOF + # Session Fitness Trajectory + + | goal_id | baseline_result | final_result | delta | + |---------|-----------------|--------------|-------| + $(for row in delta_rows: "| ${row.goal_id} | ${row.baseline_result} | ${row.final_result} | ${row.delta} |") + + **Summary:** ${improved_count} improved, ${regressed_count} regressed, ${unchanged_count} unchanged + EOF + + # Include delta summary in user-facing teardown report + log "Fitness trajectory: ${improved_count} improved, ${regressed_count} regressed, ${unchanged_count} unchanged" +fi +``` + +**Then write session summary:** + +```bash +cat > .agents/evolve/session-summary.md << EOF +# /evolve Session Summary + +**Date:** $(date -Iseconds) +**Cycles:** $CYCLE of $MAX_CYCLES +**Goals measured:** $(wc -l < GOALS.yaml goals) + +## Cycle History +$(cat .agents/evolve/cycle-history.jsonl) + +## Final Fitness +$(cat .agents/evolve/fitness-latest.json) + +## Post-Mortem +<path to post-mortem report from above> + +## Next Steps +- Run \`/evolve\` again to continue improving +- Run \`/evolve --dry-run\` to check current fitness without executing +- Create \`~/.config/evolve/KILL\` to prevent future runs +- Create \`.agents/evolve/STOP\` for a one-time local stop +EOF +``` + +Report to user: +``` +## /evolve Complete + +Cycles: N of M +Goals improved: X +Goals regressed: Y (reverted) +Goals unchanged: Z +Post-mortem: <verdict> (see <report-path>) + +Run `/evolve` again to continue improving. +``` diff --git a/.agents/skills/evolve/references/work-selection-ladder.md b/.agents/skills/evolve/references/work-selection-ladder.md new file mode 100644 index 0000000..77e13cb --- /dev/null +++ b/.agents/skills/evolve/references/work-selection-ladder.md @@ -0,0 +1,204 @@ +# /evolve — Work-Selection Ladder (Step 3, full procedure) + +> Extracted from `skills/evolve/SKILL.md` Step 3 to keep the skill under the 10000-token ceiling (soc-opq5). SKILL.md keeps the summary + this pointer; this file owns the full per-rung procedure, code blocks, quality cascade, and dormancy hard-gate. + +Selection is a ladder, not a one-shot check. After every productive cycle, return to the TOP of this step and re-read the queue before considering dormancy. + +**Programmatic recommendation (soc-g2qd wire):** when present, consult the ladder primitive first and prefer its `.recommended_bead`; the rungs below are the cross-check + fallback. + +```bash +ao loop next-work --help >/dev/null 2>&1 && RECO_BEAD=$(ao loop next-work --json 2>/dev/null | jq -r '.recommended_bead // empty') +``` + +When a repo-local program contract exists, apply a scope filter before Step 4: +- candidate work that clearly requires immutable-scope edits is not eligible for direct execution +- prefer harvested, beads, goals, and generated work that can plausibly land within mutable scope +- if the selected item is inherently out of scope, escalate it or convert it into durable follow-up work instead of invoking `/rpi` and hoping discovery widens scope + +**Step 3.0: Scope filter — split-or-defer, never bail (soc-5qit)** + +Before claiming a candidate, gate scope vs session budget. If the work touches > 5 non-uniform files, introduces a new shape (schema field, validator, contract surface), is operator-level epic work, OR `PRODUCTIVE_THIS_SESSION > 5` and would extend an arc rather than close one — route to **scout-mode**, which MUST produce one of: + +1. **Split** — `bd create` 2-N child beads (each ≤5 files, single-shape) with `--deps discovered-from:<parent-id>`, annotate parent, then **re-enter Step 3** so the smallest child (or another ready bead) gets claimed THIS cycle. +2. **Defer** — annotate the candidate with `defer:<reason>` and re-enter Step 3 so the next-priority ready bead gets claimed. +3. **Park** (rare) — `bd update <id> --status blocked --notes "scope-too-big"` and re-enter Step 3. + +Scout NEVER returns "no work done." If `bd ready` ≥1, the loop MUST claim one this cycle. See `references/scout-mode.md` and `references/mechanical-batches.md`. + +**Metronome gate:** read `mode_repeat_streak` from `session-state.json`. If `>= 3` AND the candidate would repeat the trailing run's `mode`, BLOCK this rung and jump to the next. If `>= 5`, record the gap as bead/provenance evidence and require operator override. See `references/metronome-gate.md`. + +**Step 3.1: Harvested work first** + +Read `.agents/rpi/next-work.jsonl` and pick the highest-value unconsumed item. Prefer exact repo match, then concrete implementation work, then higher severity. Read `references/knowledge-loop-integration.md` for the claim/release protocol. + +**Step 3.2: Open ready beads** + +If no harvested item is ready, check `bd ready`. Pick the highest-priority unblocked issue. + +**Step 3.3: Failing goals and directive gaps** (skip if `--beads-only`) + +First assess directives, then goals: +- top-priority directive gap from `ao goals measure --directives` +- highest-weight failing goals (skip quarantined oscillators) +- lower-weight failing goals + +This step exists even when all queued work is empty. Goals are the third source, not the stop condition. + +```bash +DIRECTIVES=$(ao goals measure --directives 2>/dev/null) +FAILING=$(jq -r '.goals[] | select(.result=="fail") | .id' .agents/evolve/fitness-latest.json | head -1) +``` + +**Oscillation check:** Before working a failing goal, check if it has oscillated (improved-to-fail transitions >= 3 times). If so, quarantine it and try the next goal. See `references/oscillation.md` and `references/fitness-scoring.md` for the detection procedure. + +**Duplicate-work guard (mandatory before every generator `bd create`).** The +generators below (3.4–3.7) and the Step-4 Split rung all create beads. A stale +phase-1 handoff repeatedly re-seeded beads for work already covered by an +existing bead or merged PR (ag-b8m≈ag-jov, ag-6kw≈ag-c2i — ag-6jt). Before +`bd create`, run the guard; skip creation when it reports a duplicate: + +```bash +skills/evolve/scripts/duplicate-work-guard.sh "<candidate title>" || { + echo "skip: existing work already covers this surface"; } +# exit 1 + "DUPLICATE: <id> [<status>] <title>" → an open/closed bead already +# covers it (exact title OR significant-token overlap). exit 0 → safe to create. +``` + +This complements the loop's origin/main fast-forward: the cron runs +`skills/evolve/scripts/sync-main-to-origin.sh` before discovery (wired into +`scripts/overnight-evolve.sh`), which fetches origin and fast-forwards local +`main` to `origin/main` so discovery diffs candidate slices against the true +merge base — already-merged work reports as done, not re-seen (ag-6jt). Run it +manually in any rpi worktree whose local `main` may be stale: + +```bash +skills/evolve/scripts/sync-main-to-origin.sh +# → "DIFF_BASE: origin/main <sha>" — diff slices against THIS, not local main. +``` + +**Step 3.4: Testing improvements** + +When queues and goals are empty, generate concrete testing work via `/test`: + +``` +if --no-lifecycle is NOT set: + Skill(skill="test", args="coverage") + Only files with < 40% coverage become queue items (severity threshold). +``` + +If `/test` is unavailable or `--no-lifecycle` is set, fall back to manual scanning: +- find packages/files with thin or missing tests +- look for missing regression tests around recent bug-fix paths +- identify flaky or absent headless/runtime smokes + +Convert any real finding into durable work: +- add a bead when the work needs tracked backlog ownership, or +- append a queue item under the shared next-work contract when it should flow directly back into `/rpi` + +**Step 3.5: Validation tightening and bug-hunt passes** + +If testing improvement generation returns nothing, run lifecycle generators then bug-hunt sweeps: + +``` +if --no-lifecycle is NOT set: + a) Skill(skill="deps", args="audit") + Only deps with CVSS >= 7.0 or 2+ major versions behind become queue items. + + b) if perf-sensitive code detected (benchmarks exist, hot path patterns): + Skill(skill="perf", args="profile --quick") + Convert significant perf findings to queue items. +``` + +If lifecycle generators return nothing or are skipped, fall back to manual sweeps: +- missing validation gates +- weak lint/contract coverage +- bug-hunt style audits for risky areas +- stale assumptions between docs, contracts, and runtime truth + +Again: convert findings into beads or queue items, then immediately select the highest-priority result and continue. + +**Step 3.6: Drift / hotspot / dead-code mining** + +If the prior generators are empty, mine for complexity debt via `/refactor`: + +``` +if --no-lifecycle is NOT set: + Skill(skill="refactor", args="--sweep all --dry-run") + Only functions with CC > 20 become queue items (severity threshold). +``` + +If `/refactor` is unavailable or `--no-lifecycle` is set, fall back to manual mining: +- complexity hotspots +- stale TODO/FIXME markers +- dead code +- stale docs +- stale research +- drift between generated artifacts and source-of-truth files + +Do not stop here. Normalize findings into tracked work and continue. + +**Step 3.7: Feature suggestions** + +If all concrete remediation layers are empty, propose one or more specific feature ideas grounded in the repo purpose, write them as durable work, and continue: +- create a bead when the feature needs review/backlog treatment +- or append a queue item with `source: "feature-suggestion"` when it is ready for the next `/rpi` cycle + +**Quality mode (`--quality`)** — inverted cascade (findings before directives): + +Step 3.0q: Unconsumed high-severity post-mortem findings: +```bash +HIGH=$(jq -r 'select(.consumed==false) | .items[] | select(.severity=="high") | .title' \ + .agents/rpi/next-work.jsonl 2>/dev/null | head -1) +``` + +Step 3.1q: Unconsumed medium-severity findings. + +Step 3.2q: Open ready beads. + +Step 3.3q: Emergency gates (weight >= 5) and top directive gaps. + +Step 3.4q: Testing improvements. + +Step 3.5q: Validation tightening / bug-hunt / drift mining. + +Step 3.6q: Feature suggestions. + +This inverts the standard cascade only at the top of the ladder: findings BEFORE goals and directives. It does NOT skip the generator layers. + +When evolve picks a finding, claim it first in next-work.jsonl: +- Set `claim_status: "in_progress"`, `claimed_by: "evolve-quality:cycle-N"`, `claimed_at: "<timestamp>"` +- Set `consumed: true` only after the /rpi cycle and regression gate succeed +- If the /rpi cycle fails (regression), clear the claim and leave `consumed: false` + +See `references/quality-mode.md` for scoring and full details. + +**Nothing found?** HARD GATE — dormancy only when ALL sources empty (soc-5qit): + +```bash +READY_BEADS=$(bd ready --json 2>/dev/null | jq -r 'length // 0' 2>/dev/null || echo 0) +HARVESTED=$(jq -r 'select(.consumed==false) | .severity' .agents/rpi/next-work.jsonl 2>/dev/null | wc -l | tr -d ' ') +FAILING_GOALS=$(jq -r '.goals[] | select(.result=="fail") | .id' .agents/evolve/fitness-latest.json 2>/dev/null | wc -l | tr -d ' ') +IDLE_STREAK=$(jq -r '.idle_streak // 0' .agents/evolve/session-state.json 2>/dev/null) + +if [ "$READY_BEADS" -gt 0 ] || [ "$HARVESTED" -gt 0 ] || [ "$FAILING_GOALS" -gt 0 ]; then + continue # work exists — loop back to Step 3 (agile invariant) +fi +if [ "${GENERATOR_EMPTY_STREAK:-0}" -ge 2 ] && [ "${IDLE_STREAK:-0}" -ge 2 ]; then + REASON="stagnation: all sources empty x3" + # soc-g2qd wire: under loop, write-stop-marker refuses → log blocked + operator-wait, never self-halt (ADR-0007). + if ao loop write-stop-marker --help >/dev/null 2>&1; then + ao loop write-stop-marker --marker dormant --reason "$REASON" --mode loop 2>/dev/null \ + || ao loop blocked --reason "$REASON" --needed-context "queue empty; operator adds work or marker" 2>/dev/null || true + else + printf '%s\n%s\n%s\n' "cycle $CYCLE" "$(date -u +%FT%TZ)" "$REASON" > .agents/evolve/DORMANT # fallback + fi +fi +``` + +**Agile invariant (soc-5qit):** `bd ready ≥ 1` ⇒ loop NEVER writes DORMANT, NEVER exits. The only path to DORMANT is fully empty backlog + dry generators. Context exhaustion → HANDOFF, not DORMANT. + +If work layers were empty but generators haven't exhausted 3 passes yet, persist `GENERATOR_EMPTY_STREAK` and loop back to Step 1. + +A cycle is idle only if NO work source returned actionable work and every generator layer also came up empty. A cycle that targeted an oscillating goal and skipped it counts as idle only after the remaining ladder was exhausted. + +If `--dry-run`: report what would be worked on and go to Teardown. diff --git a/.agents/skills/evolve/scripts/duplicate-work-guard.sh b/.agents/skills/evolve/scripts/duplicate-work-guard.sh new file mode 100755 index 0000000..a3dd01c --- /dev/null +++ b/.agents/skills/evolve/scripts/duplicate-work-guard.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# duplicate-work-guard.sh — before the evolve-cron-rpi discovery loop creates a +# tracking bead, check whether an existing OPEN or CLOSED bead already covers the +# same work. Exits 1 (with the matching bead) when a duplicate is found, 0 when +# the candidate is genuinely new. +# +# Why this exists (ag-6jt/ag-2je): a stale phase-1 handoff kept re-seeding beads +# for work already merged or already tracked. The prior guard only matched EXACT +# OPEN-bead titles, so same-surface, different-wording dups slipped through +# (ag-b8m≈ag-jov, ag-6kw≈ag-c2i — 4th recurrence). This guard matches on: +# 1. exact normalized title (lowercased, punctuation-stripped), OR +# 2. significant-token overlap (Jaccard-style: shared / candidate tokens), +# across both open and closed beads. +# +# Usage: duplicate-work-guard.sh "<candidate bead title>" +# Exit: 0 no match · 1 duplicate found · 2 usage error +# Env: DUP_GUARD_THRESHOLD (default 0.6) overlap ratio to call it a dup +# DUP_GUARD_MIN_SHARED (default 3) min shared significant tokens +# BD_BIN (default "bd") bd binary (override for tests) +set -euo pipefail + +THRESHOLD="${DUP_GUARD_THRESHOLD:-0.6}" +MIN_SHARED="${DUP_GUARD_MIN_SHARED:-3}" +BD_BIN="${BD_BIN:-bd}" + +usage() { + echo "usage: duplicate-work-guard.sh \"<candidate bead title>\"" >&2 + exit 2 +} + +[ "$#" -ge 1 ] || usage +candidate="$1" +# Reject empty / whitespace-only titles. +[ -n "${candidate//[[:space:]]/}" ] || usage + +# All issues including closed (the dup-seeding failure spans merged/closed work). +beads_json="$("$BD_BIN" list --all --limit 0 --json 2>/dev/null || true)" +[ -n "$beads_json" ] || beads_json='[]' + +match="$( + printf '%s' "$beads_json" | jq -r \ + --arg cand "$candidate" \ + --argjson threshold "$THRESHOLD" \ + --argjson min "$MIN_SHARED" ' + # Normalize a title to a set of significant (>=3 char) tokens. + def norm: (. // "") + | ascii_downcase + | gsub("[^a-z0-9]+"; " ") + | split(" ") + | map(select(length >= 3)) + | unique; + ($cand | norm) as $c + | ($c | length) as $cn + | [ .[] + | . as $b + | ($b.title | norm) as $t + | ([ $t[] | select(. as $x | $c | index($x) != null) ] | length) as $shared + | { id: $b.id, status: $b.status, title: $b.title, + exact: ($cn > 0 and $t == $c), + shared: $shared, + ratio: (if $cn > 0 then ($shared / $cn) else 0 end) } ] + | map(select(.exact or (.shared >= $min and .ratio >= $threshold))) + | if length == 0 then empty + else (max_by(.ratio)) | "DUPLICATE: \(.id) [\(.status)] \(.title)" + end +' +)" + +if [ -n "$match" ]; then + echo "$match" + exit 1 +fi + +echo "OK: no existing work matches \"$candidate\"" +exit 0 diff --git a/.agents/skills/evolve/scripts/sync-main-to-origin.sh b/.agents/skills/evolve/scripts/sync-main-to-origin.sh new file mode 100755 index 0000000..ec1a79c --- /dev/null +++ b/.agents/skills/evolve/scripts/sync-main-to-origin.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# sync-main-to-origin.sh — before the evolve-cron-rpi discovery phase selects a +# slice, fetch origin and fast-forward the local `main` to `origin/main` so that +# discovery diffs candidate slices against the TRUE merge base, not a stale local +# main. +# +# Why this exists (ag-6jt): the rpi worktree's local `main` lags `origin/main` +# (e.g. local main=04b5d7cc vs origin/main=9e9caccb). Phase-1 discovery diffed +# the candidate slice against the stale local `main`, so work already merged to +# origin/main read as "open" — repeatedly re-seeding duplicate work/beads +# (ag-b8m≈ag-jov, ag-6kw≈ag-c2i, 4th recurrence). The work-selection-ladder +# reference claimed "the cron syncs local main to origin/main before discovery", +# but no code actually did it. This script makes that claim real and testable, +# and pairs with duplicate-work-guard.sh (the grep-existing-beads half). +# +# Behavior: +# 1. `git fetch <remote>` (so refs/remotes/<remote>/main is current). +# 2. Resolve the diff base to <remote>/main — ALWAYS, never local main. +# 3. Fast-forward the LOCAL `main` ref to <remote>/main without forcing: +# - if `main` is checked out: `git merge --ff-only`, +# - otherwise: `git fetch <remote> main:main` (refuses a non-fast-forward). +# 4. Print the resolved diff base SHA so callers/tests can assert discovery +# diffs against <remote>/main, not the previously-stale local main. +# +# Usage: sync-main-to-origin.sh [<remote>] (remote defaults to "origin") +# Exit: 0 synced (or already up to date) · non-zero on fetch/ff failure +# Output: final line is "DIFF_BASE: <remote>/main <sha>" +# Env: SYNC_MAIN_REMOTE (default "origin") overrides the remote name. +set -euo pipefail + +REMOTE="${1:-${SYNC_MAIN_REMOTE:-origin}}" + +# 1. Refresh remote-tracking refs. +if ! git fetch "$REMOTE" >/dev/null 2>&1; then + echo "FAIL: git fetch $REMOTE failed (offline or remote unreachable)" >&2 + exit 1 +fi + +# The authoritative diff base after sync is ALWAYS <remote>/main, never local main. +if ! ORIGIN_MAIN_SHA="$(git rev-parse --verify --quiet "refs/remotes/$REMOTE/main")"; then + echo "FAIL: $REMOTE/main does not exist after fetch" >&2 + exit 1 +fi + +CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" + +if [ "$CURRENT_BRANCH" = "main" ]; then + # main is checked out — fast-forward only (never a merge commit, never force). + if ! git merge --ff-only "$REMOTE/main" >/dev/null 2>&1; then + echo "FAIL: local main is not a fast-forward of $REMOTE/main (diverged); resolve manually" >&2 + exit 1 + fi +else + # main is not checked out — move the ref directly, fast-forward only. + # `git fetch <remote> main:main` refuses a non-fast-forward update (no force). + if ! git fetch "$REMOTE" main:main >/dev/null 2>&1; then + echo "FAIL: cannot fast-forward local main to $REMOTE/main (diverged); resolve manually" >&2 + exit 1 + fi +fi + +# Emit the resolved diff base so discovery (and tests) can confirm it is +# <remote>/main, not the previously-stale local main. +echo "DIFF_BASE: $REMOTE/main $ORIGIN_MAIN_SHA" +exit 0 diff --git a/.agents/skills/evolve/scripts/validate.sh b/.agents/skills/evolve/scripts/validate.sh new file mode 100755 index 0000000..4e4cafa --- /dev/null +++ b/.agents/skills/evolve/scripts/validate.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail +SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)" +PASS=0; FAIL=0 + +check() { if bash -c "$2"; then echo "PASS: $1"; PASS=$((PASS + 1)); else echo "FAIL: $1"; FAIL=$((FAIL + 1)); fi; } + +check "SKILL.md exists" "[ -f '$SKILL_DIR/SKILL.md' ]" +check "SKILL.md has YAML frontmatter" "head -1 '$SKILL_DIR/SKILL.md' | grep -q '^---$'" +check "SKILL.md has name: evolve" "grep -q '^name: evolve' '$SKILL_DIR/SKILL.md'" +check "references/ directory exists" "[ -d '$SKILL_DIR/references' ]" +check "references/ has at least 1 file" "[ \$(ls '$SKILL_DIR/references/' | wc -l) -ge 1 ]" +check "SKILL.md mentions kill switch" "grep -qi 'kill switch' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions fitness" "grep -qi 'fitness' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions GOALS.yaml" "grep -q 'GOALS.yaml' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions cycle" "grep -qi 'cycle' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions /rpi" "grep -q '/rpi' '$SKILL_DIR/SKILL.md'" +# Behavioral contracts from retro learnings (2026-02-12) +check "SKILL.md has KILL file path" "grep -q 'KILL' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents regression detection" "grep -qi 'regression' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents snapshot enforcement" "grep -qi 'snapshot' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents session_start_sha" "grep -qi 'session.start.sha\|cycle_start_sha' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents continuous values" "grep -qi 'continuous\|value.*threshold' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents regression gate" "grep -qi 'regression gate' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents post-cycle snapshot" "grep -q 'fitness-.*-post' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents oscillation detection" "grep -qi 'oscillat' '$SKILL_DIR/SKILL.md'" +# Design-level checks (2026-03-01) +check "Step 0 has oscillation sweep (always-on)" "grep -q 'Pre-populate quarantine list' '$SKILL_DIR/SKILL.md'" +check "Step 5 has wiring script pre-flight check" "grep -q 'if.*check-wiring-closure.sh' '$SKILL_DIR/SKILL.md'" +check "No ambiguous YAML fallback in Step 2" "! grep -q 'run each goal.*check command manually' '$SKILL_DIR/SKILL.md'" +check "CLI required for fitness measurement" "grep -q 'CLI is required for fitness measurement' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents harvested-first selection order" "grep -Fq 'Harvested \`.agents/rpi/next-work.jsonl\` work' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents generator ladder" "grep -q 'Testing improvements' '$SKILL_DIR/SKILL.md' && grep -q 'Validation tightening and bug-hunt passes' '$SKILL_DIR/SKILL.md' && grep -q 'Concrete feature suggestions' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents dormancy as last resort" "grep -q 'Dormancy is last resort' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents queue claim before consume" "grep -q 'claim it first' '$SKILL_DIR/SKILL.md' && grep -Fq 'keep \`consumed: false\`' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents session-state persistence" "grep -q 'session-state.json' '$SKILL_DIR/SKILL.md'" +check "SKILL.md documents immediate queue reread after /rpi" "grep -Fq 'immediately re-read \`.agents/rpi/next-work.jsonl\`' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions repo execution profile" "grep -qi 'repo execution profile' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions startup_reads" "grep -q 'startup_reads' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions validation_commands" "grep -q 'validation_commands' '$SKILL_DIR/SKILL.md'" +check "SKILL.md mentions definition_of_done" "grep -q 'definition_of_done' '$SKILL_DIR/SKILL.md'" + +echo ""; echo "Results: $PASS passed, $FAIL failed" +[ $FAIL -eq 0 ] && exit 0 || exit 1 diff --git a/.agents/skills/evolve/templates/cron-loop-mode.md b/.agents/skills/evolve/templates/cron-loop-mode.md new file mode 100644 index 0000000..121fc1a --- /dev/null +++ b/.agents/skills/evolve/templates/cron-loop-mode.md @@ -0,0 +1,63 @@ +--- +template_version: 1 +verbatim_markers: + 3-adr-cite: 5bfa1c52a93edff9316b65025a0cc75a97169c61266470cc58037f9a168cf9ba + unblock-ladder: 704781847dee29c7e239754c15d848d0d3c0f27a5fbfd17a0c87b42d563b519a + layer-3-authority: 8a4478939885980823d49ac156d27553c4a42258af5399b6e04f6d7f96016f1b + no-self-stop: 62e97f78951dbd1067a1661c0fb8b8e5d8754ebd2581ddfcba63856ee4e4f48b +--- + +# /evolve loop-mode cron prompt (cycle {{.CronSelfAdjustCounter}}) + +You are in /evolve --mode=loop. This is cycle {{.CronSelfAdjustCounter}}. + +## Last cycle outcome + +Shipped: {{range .ShippedCommits}}{{.Sha}} ({{.Bead}}{{if .Scenario}} #{{.Scenario}}{{end}}); {{end}} +Tests delta: {{.TestsDelta}} + +## Sub-beads filed this cycle + +{{range .SubBeadsFiledThisCycle}}- {{.}} +{{else}}(none){{end}} + +## Recommended next work + +{{.NextRecommendedBead}} (advisory; Layer-3 authority may override) + +<!-- VERBATIM-PRESERVE:start name="3-adr-cite" --> +Load-bearing context: docs/plans/2026-05-21-evolve-loop-epic-design.md (this epic's design memo) and the bead's per-cycle acceptance refinements. +<!-- VERBATIM-PRESERVE:end --> + +<!-- VERBATIM-PRESERVE:start name="unblock-ladder" --> +## 7-step unblock ladder + +If `bd ready` returns ≥1 unblocked bead, claim and work one. +If you feel "blocked": surface the blocker and pick the next ready bead. NEVER write a STOP marker. +The ladder (programmatic enforcement: soc-mlbm Wave 2): +1. Shape-compatible ready bead exists → pick it +2. Sibling pattern in another bead's description → surface match +3. Decomposable epic → recommend smaller child +4. Cross-Hop pickup → traverse discovered-from chain +5. Bug-fix fallback → smallest-surface bug +6. Still nothing? Log blocked event, pick smallest available P1 anyway +7. NEVER write STOP. Operator owns stop signals. +<!-- VERBATIM-PRESERVE:end --> + +<!-- VERBATIM-PRESERVE:start name="layer-3-authority" --> +## Layer-3 shaping authority + +The agent can: +- File new beads as discoveries land (`bd create` + `discovered-from`) +- Restructure the DAG +- Update the cron's recommended-next pointer +- Write ADRs for emergent precedents in `docs/decisions/` + +Bounded by: bead-graph coherence, operator-set scope (preferences.yaml), no-self-stop principle. +<!-- VERBATIM-PRESERVE:end --> + +<!-- VERBATIM-PRESERVE:start name="no-self-stop" --> +## The no-self-stop principle + +In `--mode=loop`, the agent NEVER writes a STOP/DORMANT/KILL marker. `ao loop write-stop-marker` mechanically refuses (exit 1). When stuck: log via `ao loop blocked`, pick smallest-available P1 anyway. Stop signals come from the operator, never the agent. +<!-- VERBATIM-PRESERVE:end --> diff --git a/.claude/commands/opsx/apply.md b/.claude/commands/opsx/apply.md new file mode 100644 index 0000000..ae14f0f --- /dev/null +++ b/.claude/commands/opsx/apply.md @@ -0,0 +1,152 @@ +--- +name: "OPSX: Apply" +description: Implement tasks from an OpenSpec change (Experimental) +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name (e.g., `/opsx:apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`). + +2. **Check status to understand the schema** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "<name>" --json + ``` + + This returns: + - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx:continue` + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read every file path listed under `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: <change-name> (schema: <schema-name>) + +Working on task 3/7: <task description> +[...implementation happening...] +✓ Task complete + +Working on task 4/7: <task description> +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! You can archive this change with `/opsx:archive`. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** <change-name> +**Schema:** <schema-name> +**Progress:** 4/7 tasks complete + +### Issue Encountered +<description of the issue> + +**Options:** +1. <option 1> +2. <option 2> +3. Other approach + +What would you like to do? +``` + +**Guardrails** +- Keep going through tasks until done or blocked +- Always read context files before starting (from the apply instructions output) +- If task is ambiguous, pause and ask before implementing +- If implementation reveals issues, pause and suggest artifact updates +- Keep code changes minimal and scoped to each task +- Update task checkbox immediately after completing each task +- Pause on errors, blockers, or unclear requirements - don't guess +- Use contextFiles from CLI output, don't assume specific file names + +**Fluid Workflow Integration** + +This skill supports the "actions on a change" model: + +- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions +- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly diff --git a/.claude/commands/opsx/archive.md b/.claude/commands/opsx/archive.md new file mode 100644 index 0000000..5e91608 --- /dev/null +++ b/.claude/commands/opsx/archive.md @@ -0,0 +1,157 @@ +--- +name: "OPSX: Archive" +description: Archive a completed change in the experimental workflow +category: Workflow +tags: [workflow, archive, experimental] +--- + +Archive a completed change in the experimental workflow. + +**Input**: Optionally specify a change name after `/opsx:archive` (e.g., `/opsx:archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show only active changes (not already archived). + Include the schema used for each change if available. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check artifact completion status** + + Run `openspec status --change "<name>" --json` to check artifact completion. + + Parse the JSON to understand: + - `schemaName`: The workflow being used + - `artifacts`: List of artifacts with their status (`done` or other) + + **If any artifacts are not `done`:** + - Display warning listing incomplete artifacts + - Prompt user for confirmation to continue + - Proceed if user confirms + +3. **Check task completion status** + + Read the tasks file (typically `tasks.md`) to check for incomplete tasks. + + Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete). + + **If incomplete tasks found:** + - Display warning showing count of incomplete tasks + - Prompt user for confirmation to continue + - Proceed if user confirms + + **If no tasks file exists:** Proceed without task-related warning. + +4. **Assess delta spec sync state** + + Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt. + + **If delta specs exist:** + - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md` + - Determine what changes would be applied (adds, modifications, removals, renames) + - Show a combined summary before prompting + + **Prompt options:** + - If changes needed: "Sync now (recommended)", "Archive without syncing" + - If already synced: "Archive now", "Sync anyway", "Cancel" + + If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice. + +5. **Perform the archive** + + Create the archive directory if it doesn't exist: + ```bash + mkdir -p openspec/changes/archive + ``` + + Generate target name using current date: `YYYY-MM-DD-<change-name>` + + **Check if target already exists:** + - If yes: Fail with error, suggest renaming existing archive or using different date + - If no: Move the change directory to archive + + ```bash + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + +6. **Display summary** + + Show archive completion summary including: + - Change name + - Schema that was used + - Archive location + - Spec sync status (synced / sync skipped / no delta specs) + - Note about any warnings (incomplete artifacts/tasks) + +**Output On Success** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** ✓ Synced to main specs + +All artifacts complete. All tasks complete. +``` + +**Output On Success (No Delta Specs)** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** No delta specs + +All artifacts complete. All tasks complete. +``` + +**Output On Success With Warnings** + +``` +## Archive Complete (with warnings) + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** Sync skipped (user chose to skip) + +**Warnings:** +- Archived with 2 incomplete artifacts +- Archived with 3 incomplete tasks +- Delta spec sync was skipped (user chose to skip) + +Review the archive if this was not intentional. +``` + +**Output On Error (Archive Exists)** + +``` +## Archive Failed + +**Change:** <change-name> +**Target:** openspec/changes/archive/YYYY-MM-DD-<name>/ + +Target archive directory already exists. + +**Options:** +1. Rename the existing archive +2. Delete the existing archive if it's a duplicate +3. Wait until a different date to archive +``` + +**Guardrails** +- Always prompt for change selection if not provided +- Use artifact graph (openspec status --json) for completion checking +- Don't block archive on warnings - just inform and confirm +- Preserve .openspec.yaml when moving to archive (it moves with the directory) +- Show clear summary of what happened +- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven) +- If delta specs exist, always run the sync assessment and show the combined summary before prompting diff --git a/.claude/commands/opsx/bulk-archive.md b/.claude/commands/opsx/bulk-archive.md new file mode 100644 index 0000000..1284d3d --- /dev/null +++ b/.claude/commands/opsx/bulk-archive.md @@ -0,0 +1,242 @@ +--- +name: "OPSX: Bulk Archive" +description: Archive multiple completed changes at once +category: Workflow +tags: [workflow, archive, experimental, bulk] +--- + +Archive multiple completed changes in a single operation. + +This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented. + +**Input**: None required (prompts for selection) + +**Steps** + +1. **Get active changes** + + Run `openspec list --json` to get all active changes. + + If no active changes exist, inform user and stop. + +2. **Prompt for change selection** + + Use **AskUserQuestion tool** with multi-select to let user choose changes: + - Show each change with its schema + - Include an option for "All changes" + - Allow any number of selections (1+ works, 2+ is the typical use case) + + **IMPORTANT**: Do NOT auto-select. Always let the user choose. + +3. **Batch validation - gather status for all selected changes** + + For each selected change, collect: + + a. **Artifact status** - Run `openspec status --change "<name>" --json` + - Parse `schemaName` and `artifacts` list + - Note which artifacts are `done` vs other states + + b. **Task completion** - Read `openspec/changes/<name>/tasks.md` + - Count `- [ ]` (incomplete) vs `- [x]` (complete) + - If no tasks file exists, note as "No tasks" + + c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory + - List which capability specs exist + - For each, extract requirement names (lines matching `### Requirement: <name>`) + +4. **Detect spec conflicts** + + Build a map of `capability -> [changes that touch it]`: + + ``` + auth -> [change-a, change-b] <- CONFLICT (2+ changes) + api -> [change-c] <- OK (only 1 change) + ``` + + A conflict exists when 2+ selected changes have delta specs for the same capability. + +5. **Resolve conflicts agentically** + + **For each conflict**, investigate the codebase: + + a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify + + b. **Search the codebase** for implementation evidence: + - Look for code implementing requirements from each delta spec + - Check for related files, functions, or tests + + c. **Determine resolution**: + - If only one change is actually implemented -> sync that one's specs + - If both implemented -> apply in chronological order (older first, newer overwrites) + - If neither implemented -> skip spec sync, warn user + + d. **Record resolution** for each conflict: + - Which change's specs to apply + - In what order (if both) + - Rationale (what was found in codebase) + +6. **Show consolidated status table** + + Display a table summarizing all changes: + + ``` + | Change | Artifacts | Tasks | Specs | Conflicts | Status | + |---------------------|-----------|-------|---------|-----------|--------| + | schema-management | Done | 5/5 | 2 delta | None | Ready | + | project-config | Done | 3/3 | 1 delta | None | Ready | + | add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* | + | add-verify-skill | 1 left | 2/5 | None | None | Warn | + ``` + + For conflicts, show the resolution: + ``` + * Conflict resolution: + - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) + ``` + + For incomplete changes, show warnings: + ``` + Warnings: + - add-verify-skill: 1 incomplete artifact, 3 incomplete tasks + ``` + +7. **Confirm batch operation** + + Use **AskUserQuestion tool** with a single confirmation: + + - "Archive N changes?" with options based on status + - Options might include: + - "Archive all N changes" + - "Archive only N ready changes (skip incomplete)" + - "Cancel" + + If there are incomplete changes, make clear they'll be archived with warnings. + +8. **Execute archive for each confirmed change** + + Process changes in the determined order (respecting conflict resolution): + + a. **Sync specs** if delta specs exist: + - Use the openspec-sync-specs approach (agent-driven intelligent merge) + - For conflicts, apply in resolved order + - Track if sync was done + + b. **Perform the archive**: + ```bash + mkdir -p openspec/changes/archive + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + + c. **Track outcome** for each change: + - Success: archived successfully + - Failed: error during archive (record error) + - Skipped: user chose not to archive (if applicable) + +9. **Display summary** + + Show final results: + + ``` + ## Bulk Archive Complete + + Archived 3 changes: + - schema-management-cli -> archive/2026-01-19-schema-management-cli/ + - project-config -> archive/2026-01-19-project-config/ + - add-oauth -> archive/2026-01-19-add-oauth/ + + Skipped 1 change: + - add-verify-skill (user chose not to archive incomplete) + + Spec sync summary: + - 4 delta specs synced to main specs + - 1 conflict resolved (auth: applied both in chronological order) + ``` + + If any failures: + ``` + Failed 1 change: + - some-change: Archive directory already exists + ``` + +**Conflict Resolution Examples** + +Example 1: Only one implemented +``` +Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt] + +Checking add-oauth: +- Delta adds "OAuth Provider Integration" requirement +- Searching codebase... found src/auth/oauth.ts implementing OAuth flow + +Checking add-jwt: +- Delta adds "JWT Token Handling" requirement +- Searching codebase... no JWT implementation found + +Resolution: Only add-oauth is implemented. Will sync add-oauth specs only. +``` + +Example 2: Both implemented +``` +Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql] + +Checking add-rest-api (created 2026-01-10): +- Delta adds "REST Endpoints" requirement +- Searching codebase... found src/api/rest.ts + +Checking add-graphql (created 2026-01-15): +- Delta adds "GraphQL Schema" requirement +- Searching codebase... found src/api/graphql.ts + +Resolution: Both implemented. Will apply add-rest-api specs first, +then add-graphql specs (chronological order, newer takes precedence). +``` + +**Output On Success** + +``` +## Bulk Archive Complete + +Archived N changes: +- <change-1> -> archive/YYYY-MM-DD-<change-1>/ +- <change-2> -> archive/YYYY-MM-DD-<change-2>/ + +Spec sync summary: +- N delta specs synced to main specs +- No conflicts (or: M conflicts resolved) +``` + +**Output On Partial Success** + +``` +## Bulk Archive Complete (partial) + +Archived N changes: +- <change-1> -> archive/YYYY-MM-DD-<change-1>/ + +Skipped M changes: +- <change-2> (user chose not to archive incomplete) + +Failed K changes: +- <change-3>: Archive directory already exists +``` + +**Output When No Changes** + +``` +## No Changes to Archive + +No active changes found. Create a new change to get started. +``` + +**Guardrails** +- Allow any number of changes (1+ is fine, 2+ is the typical use case) +- Always prompt for selection, never auto-select +- Detect spec conflicts early and resolve by checking codebase +- When both changes are implemented, apply specs in chronological order +- Skip spec sync only when implementation is missing (warn user) +- Show clear per-change status before confirming +- Use single confirmation for entire batch +- Track and report all outcomes (success/skip/fail) +- Preserve .openspec.yaml when moving to archive +- Archive directory target uses current date: YYYY-MM-DD-<name> +- If archive target exists, fail that change but continue with others diff --git a/.claude/commands/opsx/continue.md b/.claude/commands/opsx/continue.md new file mode 100644 index 0000000..af255c6 --- /dev/null +++ b/.claude/commands/opsx/continue.md @@ -0,0 +1,114 @@ +--- +name: "OPSX: Continue" +description: Continue working on a change - create the next artifact (Experimental) +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Continue working on a change by creating the next artifact. + +**Input**: Optionally specify a change name after `/opsx:continue` (e.g., `/opsx:continue add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on. + + Present the top 3-4 most recently modified changes as options, showing: + - Change name + - Schema (from `schema` field if present, otherwise "spec-driven") + - Status (e.g., "0/5 tasks", "complete", "no tasks") + - How recently it was modified (from `lastModified` field) + + Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check current status** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand current state. The response includes: + - `schemaName`: The workflow schema being used (e.g., "spec-driven") + - `artifacts`: Array of artifacts with their status ("done", "ready", "blocked") + - `isComplete`: Boolean indicating if all artifacts are complete + +3. **Act based on status**: + + --- + + **If all artifacts are complete (`isComplete: true`)**: + - Congratulate the user + - Show final status including the schema used + - Suggest: "All artifacts created! You can now implement this change with `/opsx:apply` or archive it with `/opsx:archive`." + - STOP + + --- + + **If artifacts are ready to create** (status shows artifacts with `status: "ready"`): + - Pick the FIRST artifact with `status: "ready"` from the status output + - Get its instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - Parse the JSON. The key fields are: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - **Create the artifact file**: + - Read any completed dependency files for context + - Use `template` as the structure - fill in its sections + - Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file + - Write to the output path specified in instructions + - Show what was created and what's now unlocked + - STOP after creating ONE artifact + + --- + + **If no artifacts are ready (all blocked)**: + - This shouldn't happen with a valid schema + - Show status and suggest checking for issues + +4. **After creating an artifact, show progress** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After each invocation, show: +- Which artifact was created +- Schema workflow being used +- Current progress (N/M complete) +- What artifacts are now unlocked +- Prompt: "Run `/opsx:continue` to create the next artifact" + +**Artifact Creation Guidelines** + +The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create. + +Common artifact patterns: + +**spec-driven schema** (proposal → specs → design → tasks): +- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact. + - The Capabilities section is critical - each capability listed will need a spec file. +- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name). +- **design.md**: Document technical decisions, architecture, and implementation approach. +- **tasks.md**: Break down implementation into checkboxed tasks. + +For other schemas, follow the `instruction` field from the CLI output. + +**Guardrails** +- Create ONE artifact per invocation +- Always read dependency artifacts before creating a new one +- Never skip artifacts or create out of order +- If context is unclear, ask the user before creating +- Verify the artifact file exists after writing before marking progress +- Use the schema's artifact sequence, don't assume specific artifact names +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output diff --git a/.claude/commands/opsx/explore.md b/.claude/commands/opsx/explore.md new file mode 100644 index 0000000..1757907 --- /dev/null +++ b/.claude/commands/opsx/explore.md @@ -0,0 +1,173 @@ +--- +name: "OPSX: Explore" +description: "Enter explore mode - think through ideas, investigate problems, clarify requirements" +category: Workflow +tags: [workflow, explore, experimental, thinking] +--- + +Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. + +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. + +**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. + +**Input**: The argument after `/opsx:explore` is whatever the user wants to think about. Could be: +- A vague idea: "real-time collaboration" +- A specific problem: "the auth system is getting unwieldy" +- A change name: "add-dark-mode" (to explore in context of that change) +- A comparison: "postgres vs sqlite for this" +- Nothing (just enter explore mode) + +--- + +## The Stance + +- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script +- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions. +- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking +- **Adaptive** - Follow interesting threads, pivot when new information emerges +- **Patient** - Don't rush to conclusions, let the shape of the problem emerge +- **Grounded** - Explore the actual codebase when relevant, don't just theorize + +--- + +## What You Might Do + +Depending on what the user brings, you might: + +**Explore the problem space** +- Ask clarifying questions that emerge from what they said +- Challenge assumptions +- Reframe the problem +- Find analogies + +**Investigate the codebase** +- Map existing architecture relevant to the discussion +- Find integration points +- Identify patterns already in use +- Surface hidden complexity + +**Compare options** +- Brainstorm multiple approaches +- Build comparison tables +- Sketch tradeoffs +- Recommend a path (if asked) + +**Visualize** +``` +┌─────────────────────────────────────────┐ +│ Use ASCII diagrams liberally │ +├─────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ │ +│ │ State │────────▶│ State │ │ +│ │ A │ │ B │ │ +│ └────────┘ └────────┘ │ +│ │ +│ System diagrams, state machines, │ +│ data flows, architecture sketches, │ +│ dependency graphs, comparison tables │ +│ │ +└─────────────────────────────────────────┘ +``` + +**Surface risks and unknowns** +- Identify what could go wrong +- Find gaps in understanding +- Suggest spikes or investigations + +--- + +## OpenSpec Awareness + +You have full context of the OpenSpec system. Use it naturally, don't force it. + +### Check for context + +At the start, quickly check what exists: +```bash +openspec list --json +``` + +This tells you: +- If there are active changes +- Their names, schemas, and status +- What the user might be working on + +If the user mentioned a specific change name, read its artifacts for context. + +### When no change exists + +Think freely. When insights crystallize, you might offer: + +- "This feels solid enough to start a change. Want me to create a proposal?" +- Or keep exploring - no pressure to formalize + +### When a change exists + +If the user mentions a change or you detect one is relevant: + +1. **Read existing artifacts for context** + - `openspec/changes/<name>/proposal.md` + - `openspec/changes/<name>/design.md` + - `openspec/changes/<name>/tasks.md` + - etc. + +2. **Reference them naturally in conversation** + - "Your design mentions using Redis, but we just realized SQLite fits better..." + - "The proposal scopes this to premium users, but we're now thinking everyone..." + +3. **Offer to capture when decisions are made** + + | Insight Type | Where to Capture | + |----------------------------|--------------------------------| + | New requirement discovered | `specs/<capability>/spec.md` | + | Requirement changed | `specs/<capability>/spec.md` | + | Design decision made | `design.md` | + | Scope changed | `proposal.md` | + | New work identified | `tasks.md` | + | Assumption invalidated | Relevant artifact | + + Example offers: + - "That's a design decision. Capture it in design.md?" + - "This is a new requirement. Add it to specs?" + - "This changes scope. Update the proposal?" + +4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture. + +--- + +## What You Don't Have To Do + +- Follow a script +- Ask the same questions every time +- Produce a specific artifact +- Reach a conclusion +- Stay on topic if a tangent is valuable +- Be brief (this is thinking time) + +--- + +## Ending Discovery + +There's no required ending. Discovery might: + +- **Flow into a proposal**: "Ready to start? I can create a change proposal." +- **Result in artifact updates**: "Updated design.md with these decisions" +- **Just provide clarity**: User has what they need, moves on +- **Continue later**: "We can pick this up anytime" + +When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value. + +--- + +## Guardrails + +- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't fake understanding** - If something is unclear, dig deeper +- **Don't rush** - Discovery is thinking time, not task time +- **Don't force structure** - Let patterns emerge naturally +- **Don't auto-capture** - Offer to save insights, don't just do it +- **Do visualize** - A good diagram is worth many paragraphs +- **Do explore the codebase** - Ground discussions in reality +- **Do question assumptions** - Including the user's and your own diff --git a/.claude/commands/opsx/ff.md b/.claude/commands/opsx/ff.md new file mode 100644 index 0000000..69f749c --- /dev/null +++ b/.claude/commands/opsx/ff.md @@ -0,0 +1,97 @@ +--- +name: "OPSX: Fast Forward" +description: Create a change and generate all artifacts needed for implementation in one go +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Fast-forward through artifact creation - generate everything needed to start implementation. + +**Input**: The argument after `/opsx:ff` is the change name (kebab-case), OR a description of what the user wants to build. + +**Steps** + +1. **If no input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change at `openspec/changes/<name>/`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "✓ Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx:apply` to start implementing." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, ask if user wants to continue it or create a new one +- Verify each artifact file exists after writing before proceeding to next diff --git a/.claude/commands/opsx/new.md b/.claude/commands/opsx/new.md new file mode 100644 index 0000000..ef26cfa --- /dev/null +++ b/.claude/commands/opsx/new.md @@ -0,0 +1,69 @@ +--- +name: "OPSX: New" +description: Start a new change using the experimental artifact workflow (OPSX) +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Start a new change using the experimental artifact-driven approach. + +**Input**: The argument after `/opsx:new` is the change name (kebab-case), OR a description of what the user wants to build. + +**Steps** + +1. **If no input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Determine the workflow schema** + + Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow. + + **Use a different schema only if the user mentions:** + - A specific schema name → use `--schema <name>` + - "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose + + **Otherwise**: Omit `--schema` to use the default. + +3. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + Add `--schema <name>` only if the user requested a specific workflow. + This creates a scaffolded change at `openspec/changes/<name>/` with the selected schema. + +4. **Show the artifact status** + ```bash + openspec status --change "<name>" + ``` + This shows which artifacts need to be created and which are ready (dependencies satisfied). + +5. **Get instructions for the first artifact** + The first artifact depends on the schema. Check the status output to find the first artifact with status "ready". + ```bash + openspec instructions <first-artifact-id> --change "<name>" + ``` + This outputs the template and context for creating the first artifact. + +6. **STOP and wait for user direction** + +**Output** + +After completing the steps, summarize: +- Change name and location +- Schema/workflow being used and its artifact sequence +- Current status (0/N artifacts complete) +- The template for the first artifact +- Prompt: "Ready to create the first artifact? Run `/opsx:continue` or just describe what this change is about and I'll draft it." + +**Guardrails** +- Do NOT create any artifacts yet - just show the instructions +- Do NOT advance beyond showing the first artifact template +- If the name is invalid (not kebab-case), ask for a valid name +- If a change with that name already exists, suggest using `/opsx:continue` instead +- Pass --schema if using a non-default workflow diff --git a/.claude/commands/opsx/onboard.md b/.claude/commands/opsx/onboard.md new file mode 100644 index 0000000..10a7eb6 --- /dev/null +++ b/.claude/commands/opsx/onboard.md @@ -0,0 +1,550 @@ +--- +name: "OPSX: Onboard" +description: Guided onboarding - walk through a complete OpenSpec workflow cycle with narration +category: Workflow +tags: [workflow, onboarding, tutorial, learning] +--- + +Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step. + +--- + +## Preflight + +Before starting, check if the OpenSpec CLI is installed: + +```bash +# Unix/macOS +openspec --version 2>&1 || echo "CLI_NOT_INSTALLED" +# Windows (PowerShell) +# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" } +``` + +**If CLI not installed:** +> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`. + +Stop here if not installed. + +--- + +## Phase 1: Welcome + +Display: + +``` +## Welcome to OpenSpec! + +I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it. + +**What we'll do:** +1. Pick a small, real task in your codebase +2. Explore the problem briefly +3. Create a change (the container for our work) +4. Build the artifacts: proposal → specs → design → tasks +5. Implement the tasks +6. Archive the completed change + +**Time:** ~15-20 minutes + +Let's start by finding something to work on. +``` + +--- + +## Phase 2: Task Selection + +### Codebase Analysis + +Scan the codebase for small improvement opportunities. Look for: + +1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files +2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch +3. **Functions without tests** - Cross-reference `src/` with test directories +4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`) +5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code +6. **Missing validation** - User input handlers without validation + +Also check recent git activity: +```bash +# Unix/macOS +git log --oneline -10 2>/dev/null || echo "No git history" +# Windows (PowerShell) +# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" } +``` + +### Present Suggestions + +From your analysis, present 3-4 specific suggestions: + +``` +## Task Suggestions + +Based on scanning your codebase, here are some good starter tasks: + +**1. [Most promising task]** + Location: `src/path/to/file.ts:42` + Scope: ~1-2 files, ~20-30 lines + Why it's good: [brief reason] + +**2. [Second task]** + Location: `src/another/file.ts` + Scope: ~1 file, ~15 lines + Why it's good: [brief reason] + +**3. [Third task]** + Location: [location] + Scope: [estimate] + Why it's good: [brief reason] + +**4. Something else?** + Tell me what you'd like to work on. + +Which task interests you? (Pick a number or describe your own) +``` + +**If nothing found:** Fall back to asking what the user wants to build: +> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix? + +### Scope Guardrail + +If the user picks or describes something too large (major feature, multi-day work): + +``` +That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through. + +For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details. + +**Options:** +1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]? +2. **Pick something else** - One of the other suggestions, or a different small task? +3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer. + +What would you prefer? +``` + +Let the user override if they insist—this is a soft guardrail. + +--- + +## Phase 3: Explore Demo + +Once a task is selected, briefly demonstrate explore mode: + +``` +Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction. +``` + +Spend 1-2 minutes investigating the relevant code: +- Read the file(s) involved +- Draw a quick ASCII diagram if it helps +- Note any considerations + +``` +## Quick Exploration + +[Your brief analysis—what you found, any considerations] + +┌─────────────────────────────────────────┐ +│ [Optional: ASCII diagram if helpful] │ +└─────────────────────────────────────────┘ + +Explore mode (`/opsx:explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem. + +Now let's create a change to hold our work. +``` + +**PAUSE** - Wait for user acknowledgment before proceeding. + +--- + +## Phase 4: Create the Change + +**EXPLAIN:** +``` +## Creating a Change + +A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks. + +Let me create one for our task. +``` + +**DO:** Create the change with a derived kebab-case name: +```bash +openspec new change "<derived-name>" +``` + +**SHOW:** +``` +Created: `openspec/changes/<name>/` + +The folder structure: +``` +openspec/changes/<name>/ +├── proposal.md ← Why we're doing this (empty, we'll fill it) +├── design.md ← How we'll build it (empty) +├── specs/ ← Detailed requirements (empty) +└── tasks.md ← Implementation checklist (empty) +``` + +Now let's fill in the first artifact—the proposal. +``` + +--- + +## Phase 5: Proposal + +**EXPLAIN:** +``` +## The Proposal + +The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work. + +I'll draft one based on our task. +``` + +**DO:** Draft the proposal content (don't save yet): + +``` +Here's a draft proposal: + +--- + +## Why + +[1-2 sentences explaining the problem/opportunity] + +## What Changes + +[Bullet points of what will be different] + +## Capabilities + +### New Capabilities +- `<capability-name>`: [brief description] + +### Modified Capabilities +<!-- If modifying existing behavior --> + +## Impact + +- `src/path/to/file.ts`: [what changes] +- [other files if applicable] + +--- + +Does this capture the intent? I can adjust before we save it. +``` + +**PAUSE** - Wait for user approval/feedback. + +After approval, save the proposal: +```bash +openspec instructions proposal --change "<name>" --json +``` +Then write the content to `openspec/changes/<name>/proposal.md`. + +``` +Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves. + +Next up: specs. +``` + +--- + +## Phase 6: Specs + +**EXPLAIN:** +``` +## Specs + +Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear. + +For a small task like this, we might only need one spec file. +``` + +**DO:** Create the spec file: +```bash +# Unix/macOS +mkdir -p openspec/changes/<name>/specs/<capability-name> +# Windows (PowerShell) +# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>" +``` + +Draft the spec content: + +``` +Here's the spec: + +--- + +## ADDED Requirements + +### Requirement: <Name> + +<Description of what the system should do> + +#### Scenario: <Scenario name> + +- **WHEN** <trigger condition> +- **THEN** <expected outcome> +- **AND** <additional outcome if needed> + +--- + +This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases. +``` + +Save to `openspec/changes/<name>/specs/<capability>/spec.md`. + +--- + +## Phase 7: Design + +**EXPLAIN:** +``` +## Design + +The design captures **how** we'll build it—technical decisions, tradeoffs, approach. + +For small changes, this might be brief. That's fine—not every change needs deep design discussion. +``` + +**DO:** Draft design.md: + +``` +Here's the design: + +--- + +## Context + +[Brief context about the current state] + +## Goals / Non-Goals + +**Goals:** +- [What we're trying to achieve] + +**Non-Goals:** +- [What's explicitly out of scope] + +## Decisions + +### Decision 1: [Key decision] + +[Explanation of approach and rationale] + +--- + +For a small task, this captures the key decisions without over-engineering. +``` + +Save to `openspec/changes/<name>/design.md`. + +--- + +## Phase 8: Tasks + +**EXPLAIN:** +``` +## Tasks + +Finally, we break the work into implementation tasks—checkboxes that drive the apply phase. + +These should be small, clear, and in logical order. +``` + +**DO:** Generate tasks based on specs and design: + +``` +Here are the implementation tasks: + +--- + +## 1. [Category or file] + +- [ ] 1.1 [Specific task] +- [ ] 1.2 [Specific task] + +## 2. Verify + +- [ ] 2.1 [Verification step] + +--- + +Each checkbox becomes a unit of work in the apply phase. Ready to implement? +``` + +**PAUSE** - Wait for user to confirm they're ready to implement. + +Save to `openspec/changes/<name>/tasks.md`. + +--- + +## Phase 9: Apply (Implementation) + +**EXPLAIN:** +``` +## Implementation + +Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach. +``` + +**DO:** For each task: + +1. Announce: "Working on task N: [description]" +2. Implement the change in the codebase +3. Reference specs/design naturally: "The spec says X, so I'm doing Y" +4. Mark complete in tasks.md: `- [ ]` → `- [x]` +5. Brief status: "✓ Task N complete" + +Keep narration light—don't over-explain every line of code. + +After all tasks: + +``` +## Implementation Complete + +All tasks done: +- [x] Task 1 +- [x] Task 2 +- [x] ... + +The change is implemented! One more step—let's archive it. +``` + +--- + +## Phase 10: Archive + +**EXPLAIN:** +``` +## Archiving + +When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`. + +Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way. +``` + +**DO:** +```bash +openspec archive "<name>" +``` + +**SHOW:** +``` +Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/` + +The change is now part of your project's history. The code is in your codebase, the decision record is preserved. +``` + +--- + +## Phase 11: Recap & Next Steps + +``` +## Congratulations! + +You just completed a full OpenSpec cycle: + +1. **Explore** - Thought through the problem +2. **New** - Created a change container +3. **Proposal** - Captured WHY +4. **Specs** - Defined WHAT in detail +5. **Design** - Decided HOW +6. **Tasks** - Broke it into steps +7. **Apply** - Implemented the work +8. **Archive** - Preserved the record + +This same rhythm works for any size change—a small fix or a major feature. + +--- + +## Command Reference + +**Core workflow:** + + | Command | What it does | + |-------------------|--------------------------------------------| + | `/opsx:propose` | Create a change and generate all artifacts | + | `/opsx:explore` | Think through problems before/during work | + | `/opsx:apply` | Implement tasks from a change | + | `/opsx:archive` | Archive a completed change | + +**Additional commands:** + + | Command | What it does | + |--------------------|----------------------------------------------------------| + | `/opsx:new` | Start a new change, step through artifacts one at a time | + | `/opsx:continue` | Continue working on an existing change | + | `/opsx:ff` | Fast-forward: create all artifacts at once | + | `/opsx:verify` | Verify implementation matches artifacts | + +--- + +## What's Next? + +Try `/opsx:propose` on something you actually want to build. You've got the rhythm now! +``` + +--- + +## Graceful Exit Handling + +### User wants to stop mid-way + +If the user says they need to stop, want to pause, or seem disengaged: + +``` +No problem! Your change is saved at `openspec/changes/<name>/`. + +To pick up where we left off later: +- `/opsx:continue <name>` - Resume artifact creation +- `/opsx:apply <name>` - Jump to implementation (if tasks exist) + +The work won't be lost. Come back whenever you're ready. +``` + +Exit gracefully without pressure. + +### User just wants command reference + +If the user says they just want to see the commands or skip the tutorial: + +``` +## OpenSpec Quick Reference + +**Core workflow:** + + | Command | What it does | + |--------------------------|--------------------------------------------| + | `/opsx:propose <name>` | Create a change and generate all artifacts | + | `/opsx:explore` | Think through problems (no code changes) | + | `/opsx:apply <name>` | Implement tasks | + | `/opsx:archive <name>` | Archive when done | + +**Additional commands:** + + | Command | What it does | + |---------------------------|-------------------------------------| + | `/opsx:new <name>` | Start a new change, step by step | + | `/opsx:continue <name>` | Continue an existing change | + | `/opsx:ff <name>` | Fast-forward: all artifacts at once | + | `/opsx:verify <name>` | Verify implementation | + +Try `/opsx:propose` to start your first change. +``` + +Exit gracefully. + +--- + +## Guardrails + +- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive) +- **Keep narration light** during implementation—teach without lecturing +- **Don't skip phases** even if the change is small—the goal is teaching the workflow +- **Pause for acknowledgment** at marked points, but don't over-pause +- **Handle exits gracefully**—never pressure the user to continue +- **Use real codebase tasks**—don't simulate or use fake examples +- **Adjust scope gently**—guide toward smaller tasks but respect user choice diff --git a/.claude/commands/opsx/propose.md b/.claude/commands/opsx/propose.md new file mode 100644 index 0000000..05276f4 --- /dev/null +++ b/.claude/commands/opsx/propose.md @@ -0,0 +1,106 @@ +--- +name: "OPSX: Propose" +description: Propose a new change - create it and generate all artifacts in one step +category: Workflow +tags: [workflow, artifacts, experimental] +--- + +Propose a new change - create the change and generate all artifacts in one step. + +I'll create a change with artifacts: +- proposal.md (what & why) +- design.md (how) +- tasks.md (implementation steps) + +When ready to implement, run /opsx:apply + +--- + +**Input**: The argument after `/opsx:propose` is the change name (kebab-case), OR a description of what the user wants to build. + +**Steps** + +1. **If no input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx:apply` to start implementing." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, ask if user wants to continue it or create a new one +- Verify each artifact file exists after writing before proceeding to next diff --git a/.claude/commands/opsx/sync.md b/.claude/commands/opsx/sync.md new file mode 100644 index 0000000..1571610 --- /dev/null +++ b/.claude/commands/opsx/sync.md @@ -0,0 +1,134 @@ +--- +name: "OPSX: Sync" +description: Sync delta specs from a change to main specs +category: Workflow +tags: [workflow, specs, experimental] +--- + +Sync delta specs from a change to main specs. + +This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement). + +**Input**: Optionally specify a change name after `/opsx:sync` (e.g., `/opsx:sync add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have delta specs (under `specs/` directory). + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Find delta specs** + + Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`. + + Each delta spec file contains sections like: + - `## ADDED Requirements` - New requirements to add + - `## MODIFIED Requirements` - Changes to existing requirements + - `## REMOVED Requirements` - Requirements to remove + - `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format) + + If no delta specs found, inform user and stop. + +3. **For each delta spec, apply changes to main specs** + + For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`: + + a. **Read the delta spec** to understand the intended changes + + b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet) + + c. **Apply changes intelligently**: + + **ADDED Requirements:** + - If requirement doesn't exist in main spec → add it + - If requirement already exists → update it to match (treat as implicit MODIFIED) + + **MODIFIED Requirements:** + - Find the requirement in main spec + - Apply the changes - this can be: + - Adding new scenarios (don't need to copy existing ones) + - Modifying existing scenarios + - Changing the requirement description + - Preserve scenarios/content not mentioned in the delta + + **REMOVED Requirements:** + - Remove the entire requirement block from main spec + + **RENAMED Requirements:** + - Find the FROM requirement, rename to TO + + d. **Create new main spec** if capability doesn't exist yet: + - Create `openspec/specs/<capability>/spec.md` + - Add Purpose section (can be brief, mark as TBD) + - Add Requirements section with the ADDED requirements + +4. **Show summary** + + After applying all changes, summarize: + - Which capabilities were updated + - What changes were made (requirements added/modified/removed/renamed) + +**Delta Spec Format Reference** + +```markdown +## ADDED Requirements + +### Requirement: New Feature +The system SHALL do something new. + +#### Scenario: Basic case +- **WHEN** user does X +- **THEN** system does Y + +## MODIFIED Requirements + +### Requirement: Existing Feature +#### Scenario: New scenario to add +- **WHEN** user does A +- **THEN** system does B + +## REMOVED Requirements + +### Requirement: Deprecated Feature + +## RENAMED Requirements + +- FROM: `### Requirement: Old Name` +- TO: `### Requirement: New Name` +``` + +**Key Principle: Intelligent Merging** + +Unlike programmatic merging, you can apply **partial updates**: +- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios +- The delta represents *intent*, not a wholesale replacement +- Use your judgment to merge changes sensibly + +**Output On Success** + +``` +## Specs Synced: <change-name> + +Updated main specs: + +**<capability-1>**: +- Added requirement: "New Feature" +- Modified requirement: "Existing Feature" (added 1 scenario) + +**<capability-2>**: +- Created new spec file +- Added requirement: "Another Feature" + +Main specs are now updated. The change remains active - archive when implementation is complete. +``` + +**Guardrails** +- Read both delta and main specs before making changes +- Preserve existing content not mentioned in delta +- If something is unclear, ask for clarification +- Show what you're changing as you go +- The operation should be idempotent - running twice should give same result diff --git a/.claude/commands/opsx/verify.md b/.claude/commands/opsx/verify.md new file mode 100644 index 0000000..7f403c1 --- /dev/null +++ b/.claude/commands/opsx/verify.md @@ -0,0 +1,164 @@ +--- +name: "OPSX: Verify" +description: Verify implementation matches change artifacts before archiving +category: Workflow +tags: [workflow, verify, experimental] +--- + +Verify that an implementation matches the change artifacts (specs, tasks, design). + +**Input**: Optionally specify a change name after `/opsx:verify` (e.g., `/opsx:verify add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have implementation tasks (tasks artifact exists). + Include the schema used for each change if available. + Mark changes with incomplete tasks as "(In Progress)". + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check status to understand the schema** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifacts exist for this change + +3. **Get the change directory and load artifacts** + + ```bash + openspec instructions apply --change "<name>" --json + ``` + + This returns the change directory and `contextFiles` (artifact ID -> array of concrete file paths). Read all available artifacts from `contextFiles`. + +4. **Initialize verification report structure** + + Create a report structure with three dimensions: + - **Completeness**: Track tasks and spec coverage + - **Correctness**: Track requirement implementation and scenario coverage + - **Coherence**: Track design adherence and pattern consistency + + Each dimension can have CRITICAL, WARNING, or SUGGESTION issues. + +5. **Verify Completeness** + + **Task Completion**: + - If `contextFiles.tasks` exists, read every file path in it + - Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete) + - Count complete vs total tasks + - If incomplete tasks exist: + - Add CRITICAL issue for each incomplete task + - Recommendation: "Complete task: <description>" or "Mark as done if already implemented" + + **Spec Coverage**: + - If delta specs exist in `openspec/changes/<name>/specs/`: + - Extract all requirements (marked with "### Requirement:") + - For each requirement: + - Search codebase for keywords related to the requirement + - Assess if implementation likely exists + - If requirements appear unimplemented: + - Add CRITICAL issue: "Requirement not found: <requirement name>" + - Recommendation: "Implement requirement X: <description>" + +6. **Verify Correctness** + + **Requirement Implementation Mapping**: + - For each requirement from delta specs: + - Search codebase for implementation evidence + - If found, note file paths and line ranges + - Assess if implementation matches requirement intent + - If divergence detected: + - Add WARNING: "Implementation may diverge from spec: <details>" + - Recommendation: "Review <file>:<lines> against requirement X" + + **Scenario Coverage**: + - For each scenario in delta specs (marked with "#### Scenario:"): + - Check if conditions are handled in code + - Check if tests exist covering the scenario + - If scenario appears uncovered: + - Add WARNING: "Scenario not covered: <scenario name>" + - Recommendation: "Add test or implementation for scenario: <description>" + +7. **Verify Coherence** + + **Design Adherence**: + - If `contextFiles.design` exists: + - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:") + - Verify implementation follows those decisions + - If contradiction detected: + - Add WARNING: "Design decision not followed: <decision>" + - Recommendation: "Update implementation or revise design.md to match reality" + - If no design.md: Skip design adherence check, note "No design.md to verify against" + + **Code Pattern Consistency**: + - Review new code for consistency with project patterns + - Check file naming, directory structure, coding style + - If significant deviations found: + - Add SUGGESTION: "Code pattern deviation: <details>" + - Recommendation: "Consider following project pattern: <example>" + +8. **Generate Verification Report** + + **Summary Scorecard**: + ``` + ## Verification Report: <change-name> + + ### Summary + | Dimension | Status | + |--------------|------------------| + | Completeness | X/Y tasks, N reqs| + | Correctness | M/N reqs covered | + | Coherence | Followed/Issues | + ``` + + **Issues by Priority**: + + 1. **CRITICAL** (Must fix before archive): + - Incomplete tasks + - Missing requirement implementations + - Each with specific, actionable recommendation + + 2. **WARNING** (Should fix): + - Spec/design divergences + - Missing scenario coverage + - Each with specific recommendation + + 3. **SUGGESTION** (Nice to fix): + - Pattern inconsistencies + - Minor improvements + - Each with specific recommendation + + **Final Assessment**: + - If CRITICAL issues: "X critical issue(s) found. Fix before archiving." + - If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)." + - If all clear: "All checks passed. Ready for archive." + +**Verification Heuristics** + +- **Completeness**: Focus on objective checklist items (checkboxes, requirements list) +- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty +- **Coherence**: Look for glaring inconsistencies, don't nitpick style +- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL +- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable + +**Graceful Degradation** + +- If only tasks.md exists: verify task completion only, skip spec/design checks +- If tasks + specs exist: verify completeness and correctness, skip design +- If full artifacts: verify all three dimensions +- Always note which checks were skipped and why + +**Output Format** + +Use clear markdown with: +- Table for summary scorecard +- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION) +- Code references in format: `file.ts:123` +- Specific, actionable recommendations +- No vague suggestions like "consider reviewing" diff --git a/.claude/skills/comet-archive/SKILL.md b/.claude/skills/comet-archive/SKILL.md new file mode 100644 index 0000000..d092889 --- /dev/null +++ b/.claude/skills/comet-archive/SKILL.md @@ -0,0 +1,73 @@ +--- +name: comet-archive +description: "Comet 阶段 5:归档。用 /comet-archive 调用。同步 delta spec 到主 spec,归档 change。" +--- + +# Comet 阶段 5:归档(Archive) + +## 前置条件 + +- 验证已通过(阶段 4 完成) +- 分支已处理 +- `openspec/changes/<name>/.comet.yaml` 中 `verify_result: pass` + +## 步骤 + +### 0. 入口状态验证(Entry Check) + +执行入口验证: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" +bash "$COMET_STATE" check <name> archive +``` + +验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。 + +### 1. 执行归档 + +运行归档脚本,自动完成以下全部步骤: + +```bash +bash "$COMET_ARCHIVE" "<change-name>" +``` + +脚本自动执行: +1. 入口状态验证(phase=archive, verify_result=pass, archived=false) +2. Delta spec 同步到主 spec +3. Design doc 前置元数据标注(archived-with, status) +4. Plan 前置元数据标注(archived-with) +5. 移动 change 到归档目录 +6. 通过 `comet-state transition <archive-name> archived` 更新 `archived: true` + +如脚本返回非零退出码,报告错误并停止。 +如脚本返回零退出码,归档完成。 +脚本摘要中的 `X/Y steps succeeded` 以真实执行步骤计数,不会因 delta spec 同步或文档标注重复累计。 + +当待同步的 delta spec 与已有主 spec 不一致时,脚本会在覆盖前打印 unified diff 预览,帮助确认归档同步内容。 + +如需预览而不实际执行,使用 `--dry-run` 参数。 + +### 2. 生命周期闭环 + +Spec 生命周期在此完成: +``` +brainstorming → delta spec → 实施 → 验证 → 主 spec 覆盖 → design doc 标注 → 归档 +``` + +## 退出条件 + +- 归档脚本执行成功(退出码 0) +- 归档目录 `openspec/changes/archive/YYYY-MM-DD-<change-name>/` 存在 +- 归档后的 `.comet.yaml` 中 `archived: true` + +归档脚本会把 `openspec/changes/<name>/` 移动到 `openspec/changes/archive/YYYY-MM-DD-<name>/`。归档成功后**不要再对原 change 名运行** `bash "$COMET_GUARD" <change-name> archive`,因为原活跃目录已经不存在。归档完整性以脚本退出码和归档目录状态为准。 + +## 完成 + +Comet 流程全部完成。如需开始新工作,调用 `/comet` 或 `/comet-open`。 diff --git a/.claude/skills/comet-build/SKILL.md b/.claude/skills/comet-build/SKILL.md new file mode 100644 index 0000000..b951118 --- /dev/null +++ b/.claude/skills/comet-build/SKILL.md @@ -0,0 +1,190 @@ +--- +name: comet-build +description: "Comet 阶段 3:计划与构建。用 /comet-build 调用。制定计划并选择执行方式(subagent 或直接执行)实施。" +--- + +# Comet 阶段 3:计划与构建(Build) + +## 前置条件 + +- Design Doc 已创建(阶段 2 完成) +- 活跃 change 存在 + +## 步骤 + +### 0. 入口状态验证(Entry Check) + +执行入口验证: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" +bash "$COMET_STATE" check <name> build +``` + +验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。 + +**幂等性**:build 阶段所有操作可安全重复执行。读取 `.comet.yaml` 的 `phase` 字段确认仍在 build 阶段,读取 plan 文件头的 `base-ref`,再读取 tasks.md 找到第一个未勾选任务继续执行。已提交的任务不得重复提交。 + +### 1. 制定计划 + +**立即执行:** 使用 Skill 工具加载 `superpowers:writing-plans` 技能。禁止跳过此步骤。 + +技能加载后,按其指引制定计划。计划要求: +- 保存至 `docs/superpowers/plans/YYYY-MM-DD-<feature>.md` +- 引用设计文档,拆分为可执行任务 +- **Plan 文件头必须包含关联元数据**: + +```yaml +--- +change: <openspec-change-name> +design-doc: docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md +base-ref: <git rev-parse HEAD before implementation> +--- +``` + +`base-ref` 用于验证阶段跨提交统计改动规模。创建计划时先记录当前提交: + +```bash +git rev-parse HEAD +``` + +### 2. 更新计划状态 + +先记录 plan 路径: + +```bash +bash "$COMET_STATE" set <name> plan docs/superpowers/plans/YYYY-MM-DD-feature.md +``` + +无需手动更新 phase,guard 会在退出条件满足后自动流转。 + +### 3. 选择工作方式 + +计划已写入当前分支。在开始执行前,**一次性询问用户**选择工作区隔离方式和执行方式: + +**工作区隔离**: + +| 选项 | 方式 | 说明 | +|------|------|------| +| A | 创建分支 | 在当前仓库创建新分支,简单快速 | +| B | 创建 Worktree | 隔离工作区,完全独立,适合并行开发 | + +**推荐规则**: +- 变更涉及 ≤ 3 个文件 → 推荐 A +- 需要并行开发、当前分支有未提交工作 → 推荐 B + +**执行方式**: + +| 选项 | 技能 | 适用场景 | +|------|------|---------| +| A | `superpowers:subagent-driven-development` | 任务独立、复杂度高、需要双阶段审查 | +| B | `superpowers:executing-plans` | 任务简单、无子agent环境、轻量快速 | + +**执行方式推荐规则**: +- 任务数 ≥ 3 → 推荐 A +- 任务数 ≤ 2 且无跨模块依赖 → 推荐 B +- 来自 hotfix 路径 → 推荐 B + +这是用户决策点。**必须使用 AskUserQuestion 工具暂停并等待用户明确选择隔离方式和执行方式**,不得根据推荐规则自行选择 `branch` 或 `worktree`,也不得根据推荐规则自行选择执行方式。推荐规则只能用于说明建议,不能替代用户确认。禁止仅输出文字提示后继续执行。 + +用户选择后,更新 `isolation` 和 `build_mode` 字段: + +```bash +bash "$COMET_STATE" set <name> isolation <branch|worktree> +bash "$COMET_STATE" set <name> build_mode <subagent-driven-development|executing-plans|direct> +``` + +`isolation` 是脚本级硬约束。full workflow 初始化时可以为 `null`,但只允许存在到本步骤之前。若保持 `null`,`build → verify` 的 guard 和 `comet-state transition build-complete` 都会失败。 + +`build_mode` 默认仅 hotfix/tweak preset 使用 `direct`。full workflow 不得默认使用 `direct`。只有用户明确要求跳过计划执行技能,且你已记录显式 override 时,才允许: + +```bash +bash "$COMET_STATE" set <name> direct_override true +bash "$COMET_STATE" set <name> build_mode direct +``` + +没有 `direct_override: true` 时,full workflow 的 `build_mode=direct` 会被 guard 和状态转换同时拦截。 + +**执行隔离**: + +- **branch**:执行 `git checkout -b <change-name>`,后续工作在新分支上进行 +- **worktree**:必须使用 Skill 工具加载 `superpowers:using-git-worktrees` 技能创建隔离工作区。禁止用普通 shell 命令或原生工具绕过该技能;如该技能不可用,停止流程并提示安装或启用 Superpowers 技能。 + +创建隔离后,确认计划文件可访问(分支方式天然可访问;worktree 方式需确认计划已提交)。 + +**加载执行技能**:使用 Skill 工具加载对应技能。禁止跳过此步骤。 + +如所选 Superpowers 技能不可用,停止流程并提示安装或启用对应技能,不要用普通对话替代该步骤。 + +技能加载后,按其指引执行: +- 按计划执行任务 +- 完成 tasks.md 勾选(`- [ ]` → `- [x]`) +- 每个任务完成后提交代码 + +### 4. Spec 增量更新 + +实施过程中发现初版 spec 不完整时,按变更规模分级处理: + +| 规模 | 触发条件 | 做法 | +|------|---------|------| +| 小 | 遗漏验收场景、边界条件 | 直接编辑 delta spec + design.md,追加 tasks.md 任务 | +| 中 | 接口变更、新增组件、数据流变化 | **使用 AskUserQuestion 工具暂停并等待用户确认后**,必须使用 Skill 工具加载 `superpowers:brainstorming` 更新 Design Doc + delta spec | +| 大 | 全新 capability 需求 | **必须使用 AskUserQuestion 工具暂停并等待用户确认拆分**;用户确认后,通过 `/comet-open` 创建独立 change | + +**50% 阈值判定**:以 tasks.md 初始任务总数为基准,若新增任务数超过该总数的一半,视为超出原计划范围,**必须使用 AskUserQuestion 工具暂停并等待用户决定是否拆分为新 change**。 + +创建独立 change 时必须调用 `/comet-open`,不得直接调用 `/opsx:new`。`/comet-open` 会同时创建 OpenSpec 产物和 `.comet.yaml`,避免新 change 脱离 Comet 状态机。 + +**原则**: +- delta spec 是活文档,本阶段期间随时可修改 +- 每次更新应提交,commit message 说明变更原因 +- 不提前同步到 main spec,归档时统一同步 +- 小规模增量直接改 delta spec 时,应在 commit message 中注明,便于归档时判断 design doc 漂移 + +### 5. 上下文管理 + +Build 是最长阶段,可能跨越大量任务。为支持上下文压缩后断点恢复: + +- **每完成一个 task**:立即勾选 tasks.md 并提交代码,确保 `.comet.yaml` 和文件状态持久化 +- **上下文压缩后恢复**:先运行 `bash "$COMET_STATE" check <change-name> build --recover`,脚本输出结构化恢复上下文(isolation/build_mode 状态、plan 路径、任务完成进度、恢复动作)。根据 Recovery action 决定下一步。 +- **用户手动修改恢复**:按 `comet/reference/dirty-worktree.md` 协议处理未提交改动。该协议定义了检查步骤、归因分类和禁令。build 阶段的特殊处理: + 1. 归因后,若 diff 暗示计划或 spec 已变化,按 Step 4「Spec 增量更新」分级处理 +- **长任务拆分**:单任务超过 200 行代码变更时,考虑拆分为多个子任务分别提交 + +## 退出条件 + +- tasks.md 全部勾选 +- 代码已提交 +- 已显式运行项目对应的构建/测试命令并通过(不要只依赖 guard 自动猜测) +- `isolation` 已写为 `branch` 或 `worktree` +- `build_mode` 已写为 `subagent-driven-development`、`executing-plans` 或带显式 override 的 `direct` +- **阶段守卫**:运行 `bash "$COMET_GUARD" <change-name> build --apply`,全部 PASS 后自动流转到 `phase: verify` + +Guard 会优先读取项目配置中的命令: + +```yaml +build_command: <build command> +verify_command: <verify command> +``` + +配置位置可为 change 的 `.comet.yaml`,也可为仓库根目录的 `.comet.yaml` / `comet.yaml` / `.comet.yml` / `comet.yml`。 +未配置时才回退到 `npm run build`、Maven 或 Cargo 的默认探测。构建失败时 guard 会打印失败命令输出,作为排查证据。 + +退出前运行 guard 自动流转: + +```bash +bash "$COMET_GUARD" <change-name> build --apply +``` + +状态文件自动更新为 `phase: verify`、`verify_result: pending`。 + +## 自动流转 + +退出条件满足后(包括用户选择工作方式),自动流转到下一阶段: + +> **REQUIRED NEXT SKILL:** 调用 `comet-verify` skill 进入验证与收尾阶段。 diff --git a/.claude/skills/comet-design/SKILL.md b/.claude/skills/comet-design/SKILL.md new file mode 100644 index 0000000..4fa6adf --- /dev/null +++ b/.claude/skills/comet-design/SKILL.md @@ -0,0 +1,166 @@ +--- +name: comet-design +description: "Comet 阶段 2:深度设计。用 /comet-design 调用。通过 brainstorming 产出 Design Doc 和 delta spec。" +--- + +# Comet 阶段 2:深度设计(Design) + +## 前置条件 + +- 活跃 change 已存在(proposal.md、design.md、tasks.md) +- 无 Design Doc(`docs/superpowers/specs/` 下无对应文件) + +## 步骤 + +### 0. 入口状态验证(Entry Check) + +执行入口验证: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" +bash "$COMET_STATE" check <name> design +``` + +验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。 + +**幂等性**:所有 design 阶段操作可以安全重试。如果 `handoff_context` 和 `handoff_hash` 已存在,先确认它们与当前产物一致再决定是否重新生成。 + +### 1a. 生成 OpenSpec → Superpowers 交接包 + +**必须由脚本生成,不允许 agent 临场手写 summary 代替。** + +```bash +bash "$COMET_HANDOFF" <change-name> design --write +``` + +脚本会生成并记录: + +``` +openspec/changes/<name>/.comet/handoff/design-context.json +openspec/changes/<name>/.comet/handoff/design-context.md +``` + +并在 `.comet.yaml` 写入: + +```yaml +handoff_context: openspec/changes/<name>/.comet/handoff/design-context.json +handoff_hash: <sha256> +``` + +默认交接包是 **compact 可追溯摘录**,不是 agent summary: +- `design-context.json`:机器索引,包含 change、phase、canonical spec、source paths、hash +- `design-context.md`:供 Superpowers 阅读的上下文,包含脚本标记、source path、line range、sha256、确定性摘录 +- 超出摘录预算时标记 `[TRUNCATED]`,并保留 Full source 路径 + +如确实需要全文上下文,可显式运行: + +```bash +bash "$COMET_HANDOFF" <change-name> design --write --full +``` + +交接包来源来自 OpenSpec open 阶段产物: +- `proposal.md`:目标、动机、范围、非目标 +- `design.md`:高层架构决策、方案约束 +- `tasks.md`:初始任务边界 +- `specs/*/spec.md`:delta 能力规格 + +### 1b. 执行 Brainstorming(带上下文) + +**立即执行:** 使用 Skill 工具加载 `superpowers:brainstorming` 技能,ARGUMENTS 包含: + +``` +Change: <change-name> +OpenSpec Context Pack: openspec/changes/<name>/.comet/handoff/design-context.md +Machine handoff: openspec/changes/<name>/.comet/handoff/design-context.json + +OpenSpec 产物是上游事实源,不要重新定义需求,不要重写 proposal/spec。 +你的任务是基于交接包做深度技术设计:实现方案、技术风险、测试策略、边界条件。 +如发现 OpenSpec delta spec 缺少验收场景,只能提出 Spec Patch,并回写 OpenSpec delta spec;不要在 Design Doc 中创建第二份需求 spec。 + +Design Doc frontmatter 必须最小化,只包含: +--- +comet_change: <change-name> +role: technical-design +canonical_spec: openspec +--- + +跳过重复上下文探索,直接进入设计提问。 +``` + +禁止跳过此步骤,禁止在未加载该技能的情况下继续。 + +如 `superpowers:brainstorming` 不可用,停止流程并提示安装或启用 Superpowers 技能,不要用普通对话替代该步骤。 + +技能加载后,按其指引产出设计方案(以对话形式呈现): +- 技术方案:架构、数据流、关键技术选型与风险 +- 测试策略 +- 如需补充验收场景,标明将回写的 delta spec 变更 + +brainstorming 阶段不写入 Design Doc 文件,仅产出设计方案供 Step 1c 用户确认。确认后才创建 `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` 并回写 delta spec。 + +### 1c. 用户确认设计方案(阻塞点) + +brainstorming 产出设计方案后,**必须使用 AskUserQuestion 工具暂停并等待用户明确确认设计方案**。不得在用户确认前创建最终 Design Doc、写入 `design_doc`、运行 design guard,或进入 `/comet-build`。也不得仅输出文字提示后继续执行。 + +暂停时只展示必要摘要: +- 采用的技术方案 +- 关键取舍与风险 +- 测试策略 +- 如有 Spec Patch,列出将回写的 delta spec 变更 + +用户明确确认后,才继续 Step 2。若用户要求调整,继续 brainstorming 迭代,直到用户确认。 + +### 2. 更新 Comet 状态 + +先记录 design_doc 路径。如果 Step 1c 回写了 delta spec(新增或修改了 `specs/*/spec.md`),必须重新生成 handoff 以更新 hash: + +```bash +# 记录 design_doc 路径 +bash "$COMET_STATE" set <name> design_doc docs/superpowers/specs/YYYY-MM-DD-topic-design.md + +# 如有 delta spec 变更,重新生成 handoff(更新 hash) +bash "$COMET_HANDOFF" <change-name> design --write + +# 自动流转到下一阶段 +bash "$COMET_GUARD" <change-name> design --apply +``` + +如果没有 delta spec 变更,跳过 handoff 重新生成步骤。状态文件自动更新,无需手动编辑其他字段。 + +## 退出条件 + +- Design Doc 已创建并保存 +- Design Doc frontmatter 包含 `comet_change`、`role: technical-design`、`canonical_spec: openspec` +- `handoff_context` 和 `handoff_hash` 已写入 `.comet.yaml`(由 guard 强制校验) +- `handoff_hash` 与当前 OpenSpec open 阶段产物一致(由 guard 强制校验) +- `design-context.md` 必须是脚本生成,且包含 source path、mode、sha256 等可追溯标记(由 guard 强制校验) +- 如有新能力或补充验收场景,OpenSpec delta spec 已创建/更新 +- `design_doc` 已写入 `.comet.yaml` +- **阶段守卫**:运行 `bash "$COMET_GUARD" <change-name> design --apply`,全部 PASS 后自动流转到 `phase: build` + +退出前必须使用 `--apply`: + +```bash +bash "$COMET_GUARD" <change-name> design --apply +``` + +## 上下文压缩恢复 + +design 阶段在 brainstorming 过程中可能触发上下文压缩。恢复时先运行: + +```bash +bash "$COMET_STATE" check <change-name> design --recover +``` + +脚本输出结构化恢复上下文(阶段、已完成字段、待完成字段、恢复动作)。按 Recovery action 判断下一步。 + +## 自动流转 + +退出条件满足后(包括用户确认设计方案),自动流转到下一阶段: + +> **REQUIRED NEXT SKILL:** 调用 `comet-build` skill 进入计划与构建阶段。 diff --git a/.claude/skills/comet-hotfix/SKILL.md b/.claude/skills/comet-hotfix/SKILL.md new file mode 100644 index 0000000..18d278a --- /dev/null +++ b/.claude/skills/comet-hotfix/SKILL.md @@ -0,0 +1,169 @@ +--- +name: comet-hotfix +description: "Comet 预设路径:Bug fix / 热修复。跳过 brainstorming,直接 open → build → verify → archive。适用于行为修复、不涉及新 capability 设计的场景。" +--- + +# Comet 预设路径:Hotfix + +快速 bug fix 工作流:open → build → verify → archive。跳过 brainstorming 和完整 plan,适用于行为修复、不涉及新 capability 设计的场景。 + +**适用条件**(必须全部满足): +1. 修复已有功能的 bug,不新增 capability +2. 不涉及接口变更或架构调整 +3. 改动范围可预估(通常 ≤ 2 个文件) + +**不适用**:如修复过程发现需要架构调整,应升级为完整 `/comet` 流程。 + +--- + +## 流程(preset workflow,5 阶段) + +执行链路:open → build → verify → archive。Hotfix 为每个阶段提供默认决策:精简开启、直接构建、按规模验证、验证通过后归档。 + +开始前先定位 Comet 脚本: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" +``` + +### 1. 快速开启(preset open) + +复用 Comet open 能力创建 change,但使用 hotfix 默认值:不执行 `openspec-explore` 长探索,直接进入精简 change 创建。 + +**立即执行:** 使用 Skill 工具加载 `openspec-new-change` 技能。禁止跳过此步骤。 + +技能加载后,按其指引创建精简版产物: + - `proposal.md` — 问题描述 + 根因分析 + 修复目标(无需方案对比) + - `design.md` — 修复方案(1 个即可,无需多方案对比) + - `tasks.md` — 修复任务清单 +- **无需 delta spec**(除非修复改变了已有 spec 的验收场景) + +初始化 Comet 状态文件: + +```bash +bash "$COMET_STATE" init <name> hotfix +``` + +初始化后验证状态: + +```bash +bash "$COMET_STATE" check <name> open +``` + +阶段守卫完成 open → build 过渡: + +```bash +bash "$COMET_GUARD" <change-name> open --apply +``` + +### 2. 直接构建(preset build) + +使用 hotfix 默认值:`build_mode: direct`。跳过 `superpowers:brainstorming` 和 `superpowers:writing-plans`(除非任务 > 3 个;若超过 3 个任务,转入 `/comet-build` 的计划与执行方式选择)。 + +继续或开始修改前,按 `comet/reference/dirty-worktree.md` 协议处理未提交改动。若归因后发现修复范围超出 hotfix,按本文件“升级条件”处理。 + +**立即执行:** 按 tasks.md 逐个执行任务: + +1. 读取 `openspec/changes/<name>/tasks.md`,获取未完成任务列表 +2. 对每个未完成任务: + - 根据任务描述修改代码 + - 运行项目格式化命令(如 `mvn spotless:apply`、`npm run format` 等) + - 运行相关测试确认通过 + - 将 tasks.md 中对应 `- [ ]` 勾选为 `- [x]` + - 提交代码,commit message 格式:`fix: <简述修复>` +3. 全部任务完成后,显式运行项目相关测试和构建命令 + +**如修复影响已有 spec 验收场景**: +- 在 `openspec/changes/<name>/specs/<capability>/spec.md` 创建 delta spec +- 仅包含 `## MODIFIED Requirements` 部分 + +### 3. 根因消除检查 + +**在运行 build guard 之前执行**,确保修复确实消除了问题根因: + +1. 读取 proposal.md 中的 bug 描述和根因 +2. 搜索验证问题代码不再存在 +3. 如根因未消除,回到 Step 2 继续修复(此时仍在 build 阶段,无需状态回退) + +**升级条件**: +- 根因消除检查发现深层架构问题 → 停止 hotfix,按升级条件阻塞确认处理 +- 修复需要额外接口变更 → 停止 hotfix,按升级条件阻塞确认处理 + +根因确认消除后,运行阶段守卫完成 build → verify 过渡: + +```bash +bash "$COMET_GUARD" <change-name> build --apply +``` + +状态文件自动更新为 `phase: verify`、`verify_result: pending`,然后进入验证。 + +### 4. 验证(preset verify) + +复用 `/comet-verify`,由 comet-verify 的规模评估决定轻量或完整验证。 + +**立即执行:** 使用 Skill 工具加载 `comet-verify` 技能。禁止跳过此步骤。 + +无 delta spec 的小范围 hotfix 通常满足轻量验证条件(≤ 3 tasks、≤ 2 files),comet-verify 的规模评估会选择轻量验证路径(5 项快速检查)。若 hotfix 创建了 delta spec,则根据 comet-verify 的规模评估规则进入完整验证路径。 + +验证通过后,按 `/comet-verify` 的规则将 `.comet.yaml` 的 `verify_result` 记录为 `pass`,归档前不得跳过该状态。 + +### 5. 归档(preset archive) + +复用 `/comet-archive`。归档前必须满足 `.comet.yaml` 中 `verify_result: pass`。 + +**立即执行:** 使用 Skill 工具加载 `comet-archive` 技能进行归档。禁止跳过此步骤。 +如有 delta spec,按 comet-archive 规则同步到 main spec,并处理关联 Design Doc 与 Plan 的归档标注。 + +--- + +## 连续执行模式 + +<IMPORTANT> +Hotfix 流程为 **一次性连续执行**。调用 `/comet-hotfix` 后,agent 在 hotfix 自有步骤间自动推进,不主动停顿。但以下情况必须暂停等待用户确认: + +1. 遇到升级条件(见"升级条件"章节),**必须使用 AskUserQuestion 工具暂停并等待用户明确确认**升级为完整流程 +2. 任务超过 3 个转入 `/comet-build` 时的工作区隔离和执行方式选择 +3. 验证阶段(comet-verify)的验证失败决策和分支处理决策 + +执行顺序:快速开启 → 直接构建 → 根因消除检查 → 验证 → 归档 → 完成 + +每个阶段完成后立即进入下一阶段。阶段内部仍必须按上文要求调用对应 Comet/OpenSpec/Superpowers skill,被调用的 skill 如有自己的用户决策点,按该 skill 规则执行。 +</IMPORTANT> + +--- + +## 升级条件 + +满足以下**任一**条件时,停止 hotfix 流程,升级为完整 `/comet`: + +| 条件 | 说明 | +|------|------| +| 改动涉及 **3+ 文件** | 超出单点修复范围 | +| 架构变更 | 新模块、新接口、新依赖 | +| 数据库 schema 变更 | 结构性调整 | +| 引入新的 public API | 修复产生了新的对外接口 | +| 修复范围超出单一函数/模块 | 需要多处协调修改 | + +满足升级条件时**必须使用 AskUserQuestion 工具暂停并等待用户明确确认**升级为完整 `/comet` 流程。不得直接进入 `/comet-design`,不得自动补充 Design Doc。不得仅输出文字提示后继续执行。 + +用户确认升级后,**必须先更新 workflow 字段**再进入完整流程: + +```bash +bash "$COMET_STATE" set <name> workflow full +``` + +然后在当前 change 基础上补充 Design Doc:**立即使用 Skill 工具加载 `comet-design` skill**,后续正常走完整流程。若用户不确认升级,停止 hotfix 并报告当前变更已超出 hotfix 适用范围。 + +--- + +## 退出条件 + +- Bug 已修复,测试通过 +- change 已归档 +- 如有 spec 变更,已同步到 main spec +- **阶段守卫**:build → verify 前运行 `bash "$COMET_GUARD" <change-name> build --apply`,verify → archive 前按 `/comet-verify` 规则运行 `bash "$COMET_GUARD" <change-name> verify --apply` diff --git a/.claude/skills/comet-open/SKILL.md b/.claude/skills/comet-open/SKILL.md new file mode 100644 index 0000000..9f97107 --- /dev/null +++ b/.claude/skills/comet-open/SKILL.md @@ -0,0 +1,113 @@ +--- +name: comet-open +description: "Comet 阶段 1:开启。用 /comet-open 调用。通过 OpenSpec 探索想法、创建 change 结构(proposal + design + tasks)。" +--- + +# Comet 阶段 1:开启(Open) + +## 前置条件 + +- 无活跃 change,或用户希望创建新 change + +## 步骤 + +### 1. 探索想法 + +**立即执行:** 使用 Skill 工具加载 `openspec-explore` 技能。禁止跳过此步骤。 + +技能加载后,按其指引自由探索问题空间。 + +### 2. 创建 Change 结构 + 初始化状态 + +**立即执行:** 使用 Skill 工具加载 `openspec-new-change` 技能。若用户意图未明确、需要先形成建议,改为加载 `openspec-propose`。禁止跳过此步骤。 + +**命名与范围守卫**:change name 必须使用用户指定或通过 AskUserQuestion 确认的名称,不得自动生成或推断。变更范围必须与用户描述一致,不得自行扩大或缩小。 + +确认以下产物已创建: + +``` +openspec/changes/<name>/ +├── .openspec.yaml +├── .comet.yaml +├── proposal.md # Why + What:问题、目标、范围 +├── design.md # How(高层):架构决策、方案选型 +└── tasks.md # 任务清单(勾选框) +``` + +创建 `.comet.yaml` 状态文件: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" + +if [ -z "$COMET_STATE" ] || [ -z "$COMET_GUARD" ]; then + echo "ERROR: Comet scripts not found. Ensure the comet skill is installed." >&2 + return 1 +fi + +bash "$COMET_STATE" init <name> full +``` + +### 3. 入口状态验证 + +验证状态机已正确初始化: + +```bash +bash "$COMET_STATE" check <name> open +``` + +验证通过后继续 Step 4。验证失败时脚本会输出具体失败原因。 + +**幂等性**:open 阶段所有操作可安全重复执行。如 `.comet.yaml` 已处于 `phase: open` 且三个产物文件均已存在,跳过已完成步骤,从第一个缺失步骤继续。 + +### 4. 内容完整性检查 + +确认三个文档内容完整: +- **proposal.md**:问题背景、目标、范围、非目标 +- **design.md**:高层架构决策、方案选型、数据流 +- **tasks.md**:任务列表,每个任务有明确描述 + +**文件存在性验证**:逐个确认三个文件路径存在且非空。任一文件缺失或为空时,不得进入 Step 5 或执行阶段守卫,必须回到创建步骤补充。 + +### 5. 用户审视确认(阻塞点) + +三个文档创建完成且内容完整性检查通过后,**必须使用 AskUserQuestion 工具暂停并等待用户确认**。不得在用户确认前执行阶段守卫或自动流转。 + +AskUserQuestion 必须以单选题形式呈现,包含以下摘要和选项: + +**摘要内容**: +- **proposal.md**:问题背景、目标、范围 +- **design.md**:高层架构决策、方案选型 +- **tasks.md**:任务数量和关键任务描述 + +**选项**: +- 「确认,继续下一阶段」— 产物符合预期,执行阶段守卫流转 +- 「需要调整」— 附带调整说明,修改后重新请求确认 + +用户选择「确认」后继续执行退出条件。用户选择「需要调整」时,按其说明修改对应文件,然后重新使用 AskUserQuestion 请求确认。 + +## 退出条件 + +- proposal.md、design.md、tasks.md 均已创建且内容完整 +- **用户已确认** proposal、design、tasks 内容符合预期 +- **阶段守卫**:运行 `bash "$COMET_GUARD" <change-name> open --apply`,全部 PASS 后自动流转到下一阶段 + +退出前必须使用 `--apply`,否则 `.comet.yaml` 仍停留在 `phase: open`,下一阶段入口检查会失败。 + +```bash +bash "$COMET_GUARD" <change-name> open --apply +``` + +完整流程会自动更新为 `phase: design`;hotfix/tweak preset 会自动更新为 `phase: build`。 + +## 自动流转 + +用户确认后,退出条件满足,自动流转到下一阶段: + +> **REQUIRED NEXT SKILL(完整流程):** 调用 `comet-design` skill 进入深度设计阶段。 +> +> hotfix/tweak preset 由对应 preset skill 控制后续流转(phase 直接进入 build),不经过本节。 diff --git a/.claude/skills/comet-tweak/SKILL.md b/.claude/skills/comet-tweak/SKILL.md new file mode 100644 index 0000000..7a7b2d0 --- /dev/null +++ b/.claude/skills/comet-tweak/SKILL.md @@ -0,0 +1,154 @@ +--- +name: comet-tweak +description: "Comet 预设路径:非 bug 的小改动(tweak)。跳过 brainstorming 和完整 plan,直接 open → lightweight build → light verify → archive。适用于文案、配置、文档或 prompt 的局部优化。" +--- + +# Comet 预设路径:Tweak + +Tweak 是 Comet 五阶段能力的预设工作流,不是独立的平行流程。它复用 open、build、verify、archive 能力,仅跳过 brainstorming 和完整 plan。 + +适用于非 bug 的小范围变更,例如文案调整、配置调整、文档或 prompt 的局部优化。 + +**适用条件**(必须全部满足): +1. 不新增 capability +2. 不改变架构 +3. 不涉及接口变化 +4. 通常不超过 3 个 tasks、4 个文件 + +**不适用**:如变更过程中发现需要 capability、架构或接口调整,应升级为完整 `/comet` 流程。 + +--- + +## 流程(preset workflow,4 阶段) + +执行链路:open → lightweight build → light verify → archive。Tweak 为每个阶段提供默认决策:精简开启、轻量构建、轻量验证、验证通过后归档。 + +开始前先定位 Comet 脚本: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" +``` + +### 1. 快速开启(preset open) + +复用 Comet open 能力创建 change,但使用 tweak 默认值:不执行 `openspec-explore` 长探索,直接进入精简 change 创建。 + +**立即执行:** 使用 Skill 工具加载 `openspec-new-change` 技能。禁止跳过此步骤。 + +技能加载后,按其指引创建精简版产物: + - `proposal.md` — 变更动机 + 目标 + 范围 + - `design.md` — 简短实现说明(无需方案对比) + - `tasks.md` — 不超过 3 个任务 +- **无需 delta spec**(除非变更改变了已有 spec 的验收场景;一旦需要 delta spec,升级为完整 `/comet`) + +初始化 Comet 状态文件: + +```bash +bash "$COMET_STATE" init <name> tweak +``` + +初始化后验证状态: + +```bash +bash "$COMET_STATE" check <name> open +``` + +阶段守卫完成 open → build 过渡: + +```bash +bash "$COMET_GUARD" <change-name> open --apply +``` + +### 2. 轻量构建(preset build) + +使用 tweak 默认值:`build_mode: direct`。跳过 `superpowers:brainstorming` 和 `superpowers:writing-plans`。 + +继续或开始修改前,按 `comet/reference/dirty-worktree.md` 协议处理未提交改动。若归因后发现范围超出 tweak,按本文件“升级条件”处理。 + +**立即执行:** 按 tasks.md 逐个执行任务: + +1. 读取 `openspec/changes/<name>/tasks.md`,获取未完成任务列表 +2. 对每个未完成任务: + - 根据任务描述修改目标文件 + - 运行项目格式化命令(如 `mvn spotless:apply`、`npm run format` 等) + - 运行相关测试确认通过 + - 将 tasks.md 中对应 `- [ ]` 勾选为 `- [x]` + - 提交代码,commit message 格式:`tweak: <简述变更>` +3. 全部任务完成后,显式运行项目相关测试和构建命令 +4. 运行阶段守卫完成 build → verify 过渡: + +```bash +bash "$COMET_GUARD" <change-name> build --apply +``` + +状态文件自动更新为 `phase: verify`、`verify_result: pending`,然后进入验证。 + +### 3. 轻量验证(preset verify) + +复用 `/comet-verify`。Tweak 必须保持轻量验证条件:≤ 3 tasks、≤ 4 files、无 delta spec、无新 capability。 + +**立即执行:** 使用 Skill 工具加载 `comet-verify` 技能。禁止跳过此步骤。 + +如规模评估进入完整验证路径,停止 tweak,按升级条件阻塞确认处理。 + +验证通过后,按 `/comet-verify` 的规则将 `.comet.yaml` 的 `verify_result` 记录为 `pass`,归档前不得跳过该状态。 + +### 4. 归档(preset archive) + +复用 `/comet-archive`。归档前必须满足 `.comet.yaml` 中 `verify_result: pass`。 + +**立即执行:** 使用 Skill 工具加载 `comet-archive` 技能进行归档。禁止跳过此步骤。 + +--- + +## 连续执行模式 + +<IMPORTANT> +Tweak 流程为 **一次性连续执行**。调用 `/comet-tweak` 后,agent 在 tweak 自有步骤间自动推进,不主动停顿。但以下情况必须暂停等待用户确认: + +1. 遇到升级条件(见"升级条件"章节),**必须使用 AskUserQuestion 工具暂停并等待用户明确确认**升级为完整流程 +2. 验证阶段(comet-verify)的验证失败决策和分支处理决策 + +执行顺序:快速开启 → 轻量构建 → 轻量验证 → 归档 → 完成 + +每个阶段完成后立即进入下一阶段。阶段内部仍必须按上文要求调用对应 Comet/OpenSpec/Superpowers skill,被调用的 skill 如有自己的用户决策点,按该 skill 规则执行。 +</IMPORTANT> + +--- + +## 升级条件 + +满足以下**任一**条件时,停止 tweak 流程,升级为完整 `/comet`: + +| 条件 | 说明 | +|------|------| +| 改动涉及 **5+ 文件** | 超出小改动范围 | +| 多模块协调修改 | 需要跨组件协调 | +| 需要新增测试用例 **5+** | 变更复杂度上升 | +| 配置项新增或删除 | 非值修改的配置变更 | +| 需要新增 capability | 超出局部优化 | +| 需要 delta spec | 影响了已有规格 | + +满足升级条件时**必须使用 AskUserQuestion 工具暂停并等待用户明确确认**升级为完整 `/comet` 流程。不得直接进入 `/comet-design`,不得自动补充 Design Doc。不得仅输出文字提示后继续执行。 + +用户确认升级后,**必须先更新 workflow 字段**再进入完整流程: + +```bash +bash "$COMET_STATE" set <name> workflow full +``` + +然后在当前 change 基础上补充 Design Doc:**立即使用 Skill 工具加载 `comet-design` skill**,后续正常走完整流程。若用户不确认升级,停止 tweak 并报告当前变更已超出 tweak 适用范围。 + +--- + +## 退出条件 + +- 小改动已完成,测试通过 +- change 已归档 +- 未新增 capability、架构调整或接口变化 +- **阶段守卫**:build → verify 前运行 `bash "$COMET_GUARD" <change-name> build --apply`,verify → archive 前按 `/comet-verify` 规则运行 `bash "$COMET_GUARD" <change-name> verify --apply` diff --git a/.claude/skills/comet-verify/SKILL.md b/.claude/skills/comet-verify/SKILL.md new file mode 100644 index 0000000..cbf8ff5 --- /dev/null +++ b/.claude/skills/comet-verify/SKILL.md @@ -0,0 +1,201 @@ +--- +name: comet-verify +description: "Comet 阶段 4:验证与收尾。用 /comet-verify 调用。验证实现符合设计,处理开发分支。" +--- + +# Comet 阶段 4:验证与收尾(Verify) + +## 前置条件 + +- 代码已提交(阶段 3 完成) +- tasks.md 全部任务已完成 + +## 步骤 + +### 0. 入口状态验证(Entry Check) + +执行入口验证: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" +bash "$COMET_STATE" check <change-name> verify +``` + +验证通过后继续 Step 1。验证失败时脚本会输出具体失败原因。 + +**幂等性**:verify 阶段所有检查可安全重复执行。如 `verify_result` 已为 `pass` 且 `branch_status` 已为 `handled`,说明验证已完成,直接执行 guard 流转。如 `verify_result` 为 `pending`,从头开始验证。 + +### 1. 改动规模评估 + +执行规模评估: + +```bash +bash "$COMET_STATE" scale <change-name> +``` + +脚本自动统计任务数、增量规格数、变更文件数,判断使用 light 或 full 验证模式,并设置 verify_mode 字段。 + +验证开始前,按 `comet/reference/dirty-worktree.md` 协议检查并处理未提交改动。verify 阶段的特殊处理: + +1. 若 dirty diff 属于当前 change 且涉及实现、测试、tasks、delta spec 或 design doc 变更,不在 verify 阶段直接修复或提交;报告失败项并进入 Step 1b 的验证失败决策阻塞点 +2. 若 dirty diff 只是 verify 本阶段产物(例如验证报告草稿、分支处理记录),可继续在 verify 阶段完成并记录状态 +3. 若 dirty diff 已实现但 tasks.md 未勾选,视为 build 状态滞后;报告失败项并进入 Step 1b,由用户决定回退修复或接受偏差 + +用户选择修复后,才允许回退到 build 阶段: + +```bash +# 仅在用户确认修复后执行 +bash "$COMET_STATE" transition <change-name> verify-fail +``` + +注意:如果 build 阶段每个任务都已提交,脚本基于工作区 diff 的文件数可能低估改动规模。此时必须读取 plan 文件头的 `base-ref` 并用提交区间复核: + +```bash +PLAN=$(bash "$COMET_STATE" get <change-name> plan) +BASE_REF=$(grep '^base-ref:' "$PLAN" 2>/dev/null | head -1 | sed 's/^base-ref: *//') +git diff --stat "$BASE_REF"...HEAD +``` + +若提交区间显示改动超过轻量阈值(> 4 个文件、跨模块协调、或 delta spec 超过 1 个 capability),手动设置为完整验证: + +```bash +bash "$COMET_STATE" set <change-name> verify_mode full +``` + +### 1b. 验证失败决策(阻塞点) + +验证不通过时**必须使用 AskUserQuestion 工具暂停并等待用户决定修复或接受偏差**。不得自动运行 `bash "$COMET_STATE" transition <change-name> verify-fail`,也不得自动调用 `/comet-build`。禁止仅输出文字提示后继续执行。 + +暂停时必须列出: +- 失败项 +- 是否属于 CRITICAL(构建失败、测试失败、安全问题、核心验收场景失败) +- 推荐处理方式 + +**不确定性原则**:无法确定严重程度时,降级处理(SUGGESTION > WARNING > CRITICAL)。仅对构建失败、测试失败、安全问题使用 CRITICAL;模糊或不确定的问题标为 WARNING 或 SUGGESTION。 + +用户选择后按以下方式继续: +- **全部修复**:运行 `bash "$COMET_STATE" transition <change-name> verify-fail`,然后调用 `/comet-build` 修复 +- **逐项处理**:CRITICAL 失败项必须修复;非 CRITICAL 失败项可选择接受偏差,但必须在验证报告中记录接受原因和影响范围。若存在任何 CRITICAL 失败项,不允许跳过修复直接全部接受 + +### 2a. 轻量验证(小改动) + +当规模评估结果为"小"时,跳过 `openspec-verify-change`,直接执行以下检查: + +1. tasks.md 全部任务已完成 `[x]` +2. 改动文件与 tasks.md 描述一致(`git diff --stat` / `git diff --cached --stat` / `git diff --stat <base-ref>...HEAD` 对照 tasks 内容) +3. 编译通过(执行项目对应的构建命令,如 `npm run build`、`mvn compile`、`cargo build` 等) +4. 相关测试通过 +5. 无明显安全问题(无硬编码密钥、无新增 unsafe 操作) + +**通过标准**:5 项全部 OK,无 CRITICAL 问题。 + +**不通过时**:报告失败项,进入 Step 1b 的验证失败决策阻塞点。用户选择修复后,才执行以下命令记录失败并回退到 build 阶段,然后调用 `/comet-build` 修复: + +```bash +# 仅在用户确认修复后执行 +bash "$COMET_STATE" transition <change-name> verify-fail +``` + +**报告格式**:简表列出 5 项检查结果 + PASS/FAIL。 + +**跳过项**(不在轻量验证中检查): +- spec scenario 覆盖率 +- design doc 一致性深度比对 +- code pattern consistency 建议 +- delta spec 与 design doc 漂移检测 + +### 2b. 完整验证(大改动) + +当规模评估结果为"大"时: + +**立即执行:** 使用 Skill 工具加载 `openspec-verify-change` 技能。禁止跳过此步骤。 + +技能加载后,按其指引验证。检查项: +1. tasks.md 全部任务已完成(`[x]`) +2. 实现符合 `openspec/changes/<name>/design.md` 高层设计决策 +3. 实现符合 Design Doc(`docs/superpowers/specs/` 下的技术设计文档) +4. 能力规格场景全部通过 +5. proposal.md 目标已满足 +6. delta spec 与 design doc 无矛盾(若 Build 阶段有增量修改 spec,检查 design doc 是否有对应记录) +7. `docs/superpowers/specs/` 关联的设计文档可定位(文件存在且与当前 change 相关) + +验证不通过时:报告缺失项,进入 Step 1b 的验证失败决策阻塞点。用户选择修复后,才执行以下命令记录失败并回退到 build 阶段,然后调用 `/comet-build` 补充: + +```bash +# 仅在用户确认修复后执行 +bash "$COMET_STATE" transition <change-name> verify-fail +``` + +**Spec 漂移处理**(用户决策点): +- 若检查项 6 发现矛盾(delta spec 有内容但 design doc 未体现),**必须使用 AskUserQuestion 工具以单选题形式暂停并等待用户选择处理方式**,不得自动选择。选项: + - 选项 A:在 design doc 追加 "Implementation Divergence" 节记录偏差原因。选项 A 属于 verify 阶段允许产物;写入后不得因该 design doc 变更再次触发 Step 1b dirty-worktree 决策 + - 选项 B:用户选择 B 后,运行 `bash "$COMET_STATE" transition <change-name> verify-fail`,然后调用 `/comet-build`;由 `/comet-build` 的 Spec 增量更新规则加载 `superpowers:brainstorming` 更新 Design Doc + delta spec + - 选项 C:确认偏差可接受,继续验证(归档时 design doc 将标记为 `superseded-by-main-spec`) + +### 3. 收尾(Superpowers) + +**立即执行:** 使用 Skill 工具加载 `superpowers:finishing-a-development-branch` 技能。禁止跳过此步骤。 + +如 `superpowers:finishing-a-development-branch` 不可用,停止流程并提示安装或启用 Superpowers 技能,不要用普通对话替代该步骤。 + +技能加载后,按其指引收尾。分支处理选项: +1. 本地合并到主分支 +2. 推送并创建 PR +3. 保持分支(稍后处理) +4. 丢弃工作 + +这是用户决策点。**必须使用 AskUserQuestion 工具暂停并等待用户选择分支处理方式**,不得根据推荐、默认值或当前分支状态自行选择。禁止仅输出文字提示后继续执行。只有在用户完成选择且对应操作完成后,才允许写入 `branch_status: handled`。 + +**确认项**: +- 全部测试通过 +- 无硬编码密钥或安全问题 + +### 4. 记录验证证据 + +验证报告必须落盘,并在 `.comet.yaml` 中记录;分支处理完成后也必须写入状态字段。不要手动设置 `verify_result: pass`,通过 guard 自动流转。 + +```bash +mkdir -p docs/superpowers/reports +# 将本次验证结论写入报告文件,例如: +# docs/superpowers/reports/YYYY-MM-DD-<change-name>-verify.md + +bash "$COMET_STATE" set <change-name> verification_report docs/superpowers/reports/YYYY-MM-DD-<change-name>-verify.md +bash "$COMET_STATE" set <change-name> branch_status handled +``` + +## 退出条件 + +- 验证报告通过 +- 分支已处理 +- `.comet.yaml` 中 `verification_report` 指向已存在的验证报告文件 +- `.comet.yaml` 中 `branch_status: handled` +- **阶段守卫**:运行 `bash "$COMET_GUARD" <change-name> verify --apply`,全部 PASS 后通过 `comet-state transition verify-pass` 自动流转到 `phase: archive` + +验证和分支处理均完成后,运行 guard 自动流转: + +```bash +bash "$COMET_GUARD" <change-name> verify --apply +``` + +状态文件自动更新为 `phase: archive`、`verify_result: pass`、`verified_at: YYYY-MM-DD`。 + +## 上下文压缩恢复 + +Verify 阶段可能触发上下文压缩。恢复时先运行: + +```bash +bash "$COMET_STATE" check <change-name> verify --recover +``` + +脚本输出结构化恢复上下文(phase、验证状态、分支状态、恢复动作),根据输出的 Recovery action 决定下一步。 + +## 自动流转 + +退出条件满足后(包括用户选择分支处理方式),自动流转到下一阶段: + +> **REQUIRED NEXT SKILL:** 调用 `comet-archive` skill 进入归档阶段。 diff --git a/.claude/skills/comet/SKILL.md b/.claude/skills/comet/SKILL.md new file mode 100644 index 0000000..43de98e --- /dev/null +++ b/.claude/skills/comet/SKILL.md @@ -0,0 +1,294 @@ +--- +name: comet +description: "Comet — OpenSpec + Superpowers 双星开发流程。用 /comet 启动,自动检测阶段并分发到子命令。五阶段:开启 → 深度设计 → 计划与构建 → 验证与收尾 → 归档。" +--- + +# Comet — OpenSpec + Superpowers 双星开发流程 + +OpenSpec 与 Superpowers 如双星系统围绕同一目标运转。 + +``` +OpenSpec 负责 WHAT — 大纲、提案、spec 生命周期、归档 +Superpowers 负责 HOW — 技术设计、计划、执行、收尾 +``` + +**核心原则:brainstorming 必不可跳过。每次变更都必须经过深度设计(hotfix 和 tweak preset 除外)。** + +--- + +## 决策核心(Decision Core) + +agent 做决策只需读本节,参考附录按需查阅。 + +### 阶段自动检测 + +**Step 0: 活跃 Change 发现与意图判定** + +1. 先做 Preset 检测;命中 hotfix/tweak 时直接调用对应 preset skill,不进入普通 open 分支 +2. 未命中 preset 时,运行 `openspec list --json` 获取所有活跃 change + +**Preset 检测优先级最高**: +- 用户明确描述为 bug fix / 热修复 + 满足 hotfix 条件 → 直接 `/comet-hotfix` +- 用户明确描述为文案/配置/文档/prompt 小调整 + 满足 tweak 条件 → 直接 `/comet-tweak` +- 未命中 preset → 按下表处理 + +| 活跃 change | 用户输入 | 行为 | +|-------------|---------|------| +| 无 | 非 preset 输入 | → 调用 `/comet-open` | +| 恰好 1 个 | `/comet <描述>` | → **询问**:继续该变更 or 创建新变更 | +| 多个 | `/comet <描述>` | → **询问**:继续现有变更 or 创建新变更;若选继续 → 列出清单让用户选择 | +| 恰好 1 个 | `/comet`(无描述) | → 自动选中,进入 Step 1 | +| 多个 | `/comet`(无描述) | → 列出清单让用户选择 | + +<IMPORTANT> +当用户选择「创建新变更」时,**必须调用 `/comet-open`**(禁止直接调用 `/opsx:new`)。 +`/comet-open` 负责完整双初始化:OpenSpec artifacts(由内部 `/opsx:new` 创建)+ `.comet.yaml` 状态文件。 +直接调用 `/opsx:new` 会缺失 `.comet.yaml`,导致后续阶段判定失败。 +</IMPORTANT> + +**Step 1: 读取 `.comet.yaml` 状态元数据** + +优先读取 `openspec/changes/<name>/.comet.yaml`。不存在时回退到 `openspec status --change "<name>" --json`、`tasks.md` 和 `docs/superpowers/` 文件检查。 + +**断点恢复规则**: +- 每次恢复上下文时,先重新执行 Step 0 和 Step 1,不依赖对话历史判断阶段 +- 只要存在 active change 且工作区有未提交改动,必须按 `comet/reference/dirty-worktree.md` 协议处理。该协议定义了检查步骤、归因分类和禁令,本文件不重复 +- 若 `phase: build`,先检查 `build_mode` 和 `isolation` 是否已设置;若有未设置的字段,回到 `/comet-build` 对应步骤补充后再执行;若均已设置,读取 tasks.md 的下一个未勾选任务继续 +- 若 `phase: verify` 且 `verify_result: fail`,进入验证失败决策阻塞点:暂停并询问用户修复或接受偏差;用户选择修复后才运行 `bash "$COMET_STATE" transition <name> verify-fail` 并调用 `/comet-build` +- 若 `phase: open` 但 proposal/design/tasks 已完整,先运行 `bash "$COMET_GUARD" <change-name> open --apply` 修正状态,再继续判定 +- 若 `phase: archive`,只允许调用 `/comet-archive`;归档成功后 change 会移动到 archive 目录,不再对原活跃目录运行 guard + +**Step 2: 阶段判定**(按顺序,命中即停) + +1. `archived: true` 或 change 已移入 archive → 流程已完成 +2. `verify_result: pass` 且 `archived` 不是 `true` → `/comet-archive` +3. `verify_result: fail` → 进入验证失败决策阻塞点(暂停询问修复或接受偏差;用户选择修复后才 `verify-fail` 并 `/comet-build`) +4. `phase: verify` 或 tasks.md 全部勾选 → `/comet-verify` +5. `phase: build` 或已有 Design Doc 但计划/执行未完成 → 优先按 workflow 路由:`hotfix` → `/comet-hotfix`,`tweak` → `/comet-tweak`,`full` → `/comet-build` +6. `phase: design` 或有 change 但无 Design Doc → `/comet-design` +7. `phase: open` 或有活跃 change 但 `.comet.yaml` 缺失 → `/comet-open` +8. 无活跃 change → `/comet-open` + +如果元数据与文件状态冲突,以文件状态为准,修正 `.comet.yaml` 后继续。 + +### 预设升级条件 + +**hotfix → full**(满足任一即升级): +- 改动涉及 **3+ 文件** +- 涉及架构变更(新模块、新接口、新依赖) +- 涉及数据库 schema 变更 +- 修复引入新的 public API +- 修复范围超出单一函数/模块 + +**tweak → full**(满足任一即升级): +- 改动涉及 **5+ 文件** +- 涉及多个模块的协调修改 +- 需要新增测试用例 **5+** +- 涉及配置项的新增或删除(非值修改) + +### 错误处理速查 + +| 场景 | 处理方式 | +|------|---------| +| `openspec list --json` 失败 | 检查 openspec 是否已安装,提示 `openspec init` | +| 子 skill 不可用 | 停止流程,提示安装或启用对应 skill | +| `.comet.yaml` 格式异常或缺失 | 以文件状态为准,用 `bash $COMET_STATE set` 修正后继续 | +| 构建/测试失败 | 返回 build 阶段修复,不进入 verify | +| change 目录结构不完整 | 按 `comet-open` 产物要求补齐 | + +### 阶段衔接 + +<IMPORTANT> +单次 `/comet` 调用从检测到的阶段开始,退出条件满足后进入下一阶段。 + +流转链:open → design → build → verify → archive + +**连续执行要求**:从检测到的阶段开始,agent 自动推进后续阶段。但**自动推进仅适用于没有用户决策的衔接点**。遇到用户决策点时,**必须使用 AskUserQuestion 工具暂停并等待用户明确回复**,不得用推荐规则、默认值或历史偏好代替用户确认,也不得仅输出文字提示后继续执行。 + +**决策点是阻塞点**:只要到达下列任一节点,当前 `/comet` 调用必须停住,**使用 AskUserQuestion 工具等待用户选择**。用户明确选择后才能写入对应状态字段、执行对应操作,随后再继续自动流转。 + +需要用户参与的节点(仅在这些节点暂停): +1. open 阶段 proposal/design/tasks 审视确认 +2. brainstorming 确认设计方案 +3. build 阶段选择工作方式(隔离方式 + 执行方式,一次交互完成) +4. verify 不通过时决定修复或接受偏差(含 Spec 漂移处理方式选择) +5. finishing-branch 选择分支处理方式 +6. 遇到升级条件(hotfix/tweak → 完整流程) +7. build 阶段范围扩张需重新设计或拆分新 change + +agent 不应跳过这些决策点;其他明确无歧义的阶段衔接必须自动继续推进,不得中途退出。到达决策点时,**禁止以文字输出代替工具等待——必须通过 AskUserQuestion 明确获取用户选择后才能继续**。 + +**红旗清单** — 以下想法出现时立即停止并检查: + +| Agent 心理 | 实际风险 | +|-----------|---------| +| "用户应该会同意这个方案" | 不能替用户决策,用 AskUserQuestion | +| "这只是个小改动,不需要确认" | 决策点无大小之分,阻塞点必须等待 | +| "用户之前选过 A,这次也选 A" | 历史偏好不能替代当前确认 | +| "我已经解释了方案,用户没反对" | 没反对 ≠ 同意,必须用工具获取明确选择 | +| "流程走到这里应该没问题了" | 验证不通过 ≠ 通过,检查 verify_result | +</IMPORTANT> + +--- + +## 子命令速查 + +| 命令 | 阶段 | 归属 | 产物 | +|------|------|------|------| +| `/comet-open` | 1. 开启 | OpenSpec | proposal.md、design.md、tasks.md | +| `/comet-design` | 2. 深度设计 | Superpowers | Design Doc、delta spec | +| `/comet-build` | 3. 计划与构建 | Superpowers | 实施计划、代码提交 | +| `/comet-verify` | 4. 验证与收尾 | Both | 验证报告、分支处理 | +| `/comet-archive` | 5. 归档 | OpenSpec | delta→main spec 同步、design doc 标注、归档 | +| `/comet-hotfix` | 预设路径 | Both | 快速修复(跳过 brainstorming) | +| `/comet-tweak` | 预设路径 | Both | 小改动(跳过 brainstorming 和完整 plan) | + +``` +/comet + ↓ 自动检测 +/comet-open ──→ /comet-design ──→ /comet-build ──→ /comet-verify ──→ /comet-archive + (OpenSpec) (Superpowers) (Superpowers) (Both) (OpenSpec) + +/comet-hotfix(预设路径,跳过 brainstorming) + open ──→ build ──→ verify ──→ archive + ↑ 如触发升级条件 → 阻塞确认升级 → 补充 Design Doc → 回到完整流程 + +/comet-tweak(预设路径,跳过 brainstorming 和完整 plan) + open ──→ lightweight build ──→ light verify ──→ archive + ↑ 如触发升级条件 → 阻塞确认升级 → 补充 Design Doc → 回到完整流程 +``` + +--- + +## 参考附录(Reference Appendix) + +### .comet.yaml 字段说明 + +```yaml +workflow: full +phase: build +design_doc: docs/superpowers/specs/YYYY-MM-DD-topic-design.md +plan: docs/superpowers/plans/YYYY-MM-DD-feature.md +base_ref: a1b2c3d4e5f6... +build_mode: subagent-driven-development +isolation: branch +verify_mode: light +verify_result: pending +verification_report: null +branch_status: pending +created_at: 2026-05-26 +verified_at: null +archived: false +``` + +| 字段 | 含义 | +|------|------| +| `workflow` | `full`、`hotfix` 或 `tweak` | +| `phase` | 当前阶段:`open`、`design`、`build`、`verify`、`archive`(init 统一设为 `open`,guard 负责过渡) | +| `design_doc` | 关联的 Superpowers Design Doc 路径,可为空 | +| `plan` | 关联的 Superpowers Plan 路径,可为空 | +| `base_ref` | init 时记录的 git commit SHA,用于 scale 评估。无 plan 时作为改动文件数统计基准 | +| `build_mode` | 已选择的执行方式,可为空 | +| `isolation` | `branch` 或 `worktree`,工作区隔离方式。full 初始化可为 `null`,但只允许持续到 `/comet-build` Step 3 前;hotfix/tweak 默认 `branch` | +| `verify_mode` | `light` 或 `full`,可为空 | +| `verify_result` | `pending`、`pass` 或 `fail` | +| `verification_report` | 验证报告文件路径,verify 通过前必须指向已存在文件 | +| `branch_status` | `pending` 或 `handled`,分支处理完成后设为 `handled` | +| `created_at` | change 创建日期(init 时自动写入),格式 `YYYY-MM-DD` | +| `verified_at` | 验证通过时间,可为空 | +| `archived` | change 是否已归档 | + +可选字段: + +| 字段 | 含义 | +|------|------| +| `direct_override` | `true`/`false`。full workflow 如需使用 `build_mode: direct`,必须显式设为 `true` | +| `build_command` | 项目构建命令。guard 优先运行该命令,失败时打印命令输出 | +| `verify_command` | 项目验证命令。verify guard 优先运行该命令,未配置时回退到构建命令 | + +状态机硬约束: +- `build → verify` 前,`isolation` 必须是 `branch` 或 `worktree` +- `build → verify` 前,`build_mode` 必须已选择 +- `build_mode: direct` 默认只允许 `hotfix` / `tweak`;full workflow 需要 `direct_override: true` +- 这些约束同时存在于 `comet-guard.sh build --apply` 和 `comet-state.sh transition <name> build-complete` + +### 脚本定位 + +Comet 脚本随 skill 包分发在 `comet/scripts/` 下。**不硬编码路径** — 定位一次,缓存到环境变量: + +```bash +COMET_ENV="${COMET_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" "$HOME/.gemini" -path '*/comet/scripts/comet-env.sh' -type f -print -quit 2>/dev/null)}" +if [ -z "$COMET_ENV" ]; then + echo "ERROR: comet-env.sh not found. Ensure the comet skill is installed." >&2 + return 1 +fi +. "$COMET_ENV" + +# 脚本定位失败时停止流程 +if [ -z "$COMET_GUARD" ] || [ -z "$COMET_STATE" ] || [ -z "$COMET_HANDOFF" ] || [ -z "$COMET_ARCHIVE" ]; then + echo "ERROR: Comet scripts not found. Ensure the comet skill is installed." >&2 + echo "Expected path pattern: */comet/scripts/comet-*.sh under project or platform skill directories" >&2 + return 1 +fi +``` + +**自动状态更新**:guard 支持 `--apply` 参数,验证通过后自动更新 `.comet.yaml` 状态字段: + +```bash +bash "$COMET_GUARD" <change-name> <phase> --apply +``` + +`--apply` 内部委托给 `comet-state transition`。需要直接表达状态事件时使用: + +```bash +bash "$COMET_STATE" transition <change-name> open-complete +bash "$COMET_STATE" transition <change-name> design-complete +bash "$COMET_STATE" transition <change-name> build-complete +bash "$COMET_STATE" transition <change-name> verify-pass +bash "$COMET_STATE" transition <change-name> verify-fail +bash "$COMET_STATE" transition <archive-name> archived +``` + +**归档脚本**:一键完成归档全部步骤: + +```bash +bash "$COMET_ARCHIVE" <change-name> +``` + +加载 comet 后,agent 应执行以上变量赋值一次,后续全程复用 `$COMET_GUARD`、`$COMET_STATE`、`$COMET_HANDOFF`、`$COMET_ARCHIVE`。 + +### 文件结构 + +``` +openspec/ # OpenSpec — WHAT +├── config.yaml +├── changes/ +│ ├── <name>/ # 活跃 change +│ │ ├── .openspec.yaml +│ │ ├── .comet.yaml +│ │ ├── proposal.md # Why + What +│ │ ├── design.md # 高层架构决策 +│ │ ├── specs/<capability>/spec.md # Delta 能力规格 +│ │ ├── .comet/handoff/ # 脚本生成的阶段交接包 +│ │ └── tasks.md # 任务清单 +│ └── archive/YYYY-MM-DD-<name>/ # 已归档 +└── specs/<capability>/spec.md # 主 specs(归档时从 delta 覆盖) + +docs/superpowers/ # Superpowers — HOW +├── specs/YYYY-MM-DD-<topic>-design.md # 设计文档(技术 RFC,归档时标注状态) +└── plans/YYYY-MM-DD-<feature>.md # 实施计划(文件头含 change 关联元数据) +``` + +### 最佳实践 + +1. **brainstorming 不可跳过** — 每次变更必须经过深度设计(hotfix 和 tweak 除外) +2. **delta spec 是活文档** — 阶段 3 期间可自由修改,归档时同步 +3. **交接包由脚本生成** — OpenSpec → Superpowers 的上下文必须通过 `comet-handoff.sh` 生成 compact 可追溯摘录(必要时 `--full`),并由 guard 校验 source/hash/mode +4. **保持 tasks.md 同步** — 完成一个勾一个 +5. **频繁提交** — 每个任务一次提交,message 体现设计意图 +6. **先验证再归档** — `/comet-verify` 通过后才执行 `/comet-archive` +7. **增量更新分级** — 小编辑、中重 brainstorming、大新 change +8. **Plan 必须关联 change** — 文件头包含 `change:` 和 `design-doc:` 元数据 +9. **归档闭环** — design doc 和 plan 必须标注 `archived-with` 状态 +10. **修改已有功能** — 直接 open 新 change 即可 +11. **Preset 有上限** — hotfix/tweak 满足升级条件时及时切换到完整流程 diff --git a/.claude/skills/comet/reference/dirty-worktree.md b/.claude/skills/comet/reference/dirty-worktree.md new file mode 100644 index 0000000..60d5c18 --- /dev/null +++ b/.claude/skills/comet/reference/dirty-worktree.md @@ -0,0 +1,58 @@ +# Dirty Worktree 协议 + +规范路径:`comet/reference/dirty-worktree.md` + +本协议由所有涉及代码修改的 comet 子 skill 共享。当 agent 恢复上下文或继续执行时,必须按本协议处理未提交的工作区改动。 + +## 1. 检查步骤 + +继续或开始修改前,必须先运行以下命令: + +```bash +git status --short +git diff --stat +git diff --cached --stat +git ls-files --others --exclude-standard +``` + +必要时再查看 `git diff` / `git diff --cached` / 新建文件内容。 + +## 2. 核心规则 + +- 用户可能不会说明自己改了哪里。只要存在 dirty worktree(包括 Git 状态里显示为 `??` 的新建文件),就先假设改动可能来自用户或混合来源 +- dirty worktree 只代表代码事实,不会自动推进 `.comet.yaml` 的 `phase` 或勾选 `tasks.md`;只有完成归因、验证、同步必要文档,并通过对应阶段 guard 后,才允许推进 Comet 状态 + +## 3. 归因分类 + +将 dirty diff 分为三类: + +1. **属于当前 change**:文件和内容能对应当前 change 的目标、tasks.md、plan 或 delta spec。将其纳入当前任务继续,不重复改同一处 +2. **不属于当前 change**:文件或内容与当前目标无关。暂停并询问用户:并入当前 change、拆成新 change、保留不处理,或明确授权丢弃 +3. **来源不确定**:无法从 diff 和文档判断归属。暂停并向用户汇报文件列表和判断依据,不继续推进阶段 + +## 4. 常见处理模式 + +### 已实现但 tasks.md 未勾选 + +先验证实现(运行构建和测试),通过后补勾任务。不要因为任务未勾选就重做一遍,也不要因为状态文件滞后而忽略代码事实。若当前子 skill 定义了阶段特例,以子 skill 为准。 + +### 暗示计划或范围已变化 + +按当前子 skill 的升级、增量更新或回退规则处理,本协议不重复阶段特例。 + +### 模糊恢复意图 + +用户说"继续""接着跑""我改了一点""刚才不满意""重新弄""代码动过""先按现在的来"等模糊恢复意图时,按本协议处理。不要要求用户先回忆具体改了哪里。 + +### open/design 阶段出现代码改动 + +若当前仍处于 `open` 或 `design`,但 dirty worktree 已经包含代码改动,先按本协议归因,不要直接推进阶段: + +- 属于当前 change 的改动:作为需求或设计输入记录到 proposal/design/spec/design doc/tasks 中;进入 build 前仍需完成对应阶段 guard +- 不属于当前 change 或来源不确定:暂停询问用户是并入当前 change、拆成新 change、保留不处理,还是明确授权丢弃 +- 禁止在 open/design 阶段直接把代码改动当作已完成实现并推进到 verify + +## 5. 禁令 + +- 禁止在未理解 dirty diff 来源时覆盖、回滚、格式化重写或忽略用户改动 +- 禁止在 dirty diff 未解释清楚时判定验证通过 diff --git a/.claude/skills/comet/scripts/comet-archive.sh b/.claude/skills/comet/scripts/comet-archive.sh new file mode 100644 index 0000000..9cf031b --- /dev/null +++ b/.claude/skills/comet/scripts/comet-archive.sh @@ -0,0 +1,293 @@ +#!/bin/bash +# Comet Archive — automates the archive phase in one command +# Usage: comet-archive.sh <change-name> [--dry-run] +# Exit 0 = archive complete, exit 1 = fatal error + +set -euo pipefail + +red() { echo -e "\033[31m$1\033[0m" >&2; } +green() { echo -e "\033[32m$1\033[0m" >&2; } +yellow() { echo -e "\033[33m$1\033[0m" >&2; } + +DRY_RUN=0 +if [[ "${2:-}" == "--dry-run" ]]; then + DRY_RUN=1 +fi + +# Input validation +validate_change_name() { + local name="$1" + if [ -z "$name" ]; then + red "FATAL: Change name cannot be empty" + exit 1 + fi + if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then + red "FATAL: Invalid change name: '$name'" + red "Valid characters: a-z, A-Z, 0-9, -, _" + exit 1 + fi + if [[ "$name" =~ \.\. ]]; then + red "FATAL: Change name cannot contain '..'" + exit 1 + fi +} + +CHANGE="$1" +validate_change_name "$CHANGE" + +CHANGE_DIR="openspec/changes/$CHANGE" +YAML="$CHANGE_DIR/.comet.yaml" +SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")" 2>/dev/null || dirname "$0")" && pwd)" +STATE_SH="$SCRIPT_DIR/comet-state.sh" +TODAY=$(date +%Y-%m-%d) +ARCHIVE_NAME="${TODAY}-${CHANGE}" +ARCHIVE_DIR="openspec/changes/archive/${ARCHIVE_NAME}" + +STEPS_OK=0 +STEPS_TOTAL=0 + +step_ok() { + green " [OK] $1" + STEPS_OK=$((STEPS_OK + 1)) + STEPS_TOTAL=$((STEPS_TOTAL + 1)) +} + +step_fail() { + red " [FAIL] $1" + STEPS_TOTAL=$((STEPS_TOTAL + 1)) +} + +step_dry_run() { + yellow " [DRY-RUN] $1" + STEPS_OK=$((STEPS_OK + 1)) + STEPS_TOTAL=$((STEPS_TOTAL + 1)) +} + +echo "=== Comet Archive: $CHANGE ===" >&2 + +# --- Step 1: Read .comet.yaml, extract paths --- + +yaml_field() { + local field="$1" + if [ -f "$STATE_SH" ]; then + bash "$STATE_SH" get "$CHANGE" "$field" 2>/dev/null + else + if [ -f "$YAML" ]; then + local value + value=$(grep "^${field}:" "$YAML" 2>/dev/null | sed "s/^${field}: *//" || true) + value=$(strip_inline_comment "$value") + strip_wrapping_quotes "$value" + fi + fi +} + +strip_inline_comment() { + local value="$1" + printf '%s\n' "$value" | awk -v squote="'" ' + { + out = "" + quote = "" + for (i = 1; i <= length($0); i++) { + c = substr($0, i, 1) + if (quote == "") { + if (c == "\"" || c == squote) { + quote = c + } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) { + sub(/[[:space:]]+$/, "", out) + print out + next + } + } else if (c == quote) { + quote = "" + } + out = out c + } + print out + } + ' +} + +strip_wrapping_quotes() { + local value="$1" + case "$value" in + \"*\") printf '%s\n' "${value:1:${#value}-2}" ;; + \'*\') printf '%s\n' "${value:1:${#value}-2}" ;; + *) printf '%s\n' "$value" ;; + esac +} + +if [ ! -f "$YAML" ]; then + red "FATAL: .comet.yaml not found in $CHANGE_DIR/" + exit 1 +fi + +DESIGN_DOC=$(yaml_field "design_doc") +PLAN_PATH=$(yaml_field "plan") + +# --- Step 2: Validate entry state --- + +PHASE_VAL=$(yaml_field "phase") +VERIFY_VAL=$(yaml_field "verify_result") +ARCHIVED_VAL=$(yaml_field "archived") + +if [ "$PHASE_VAL" != "archive" ]; then + red "FATAL: phase is '$PHASE_VAL', expected 'archive'" + exit 1 +fi + +if [ "$VERIFY_VAL" != "pass" ]; then + red "FATAL: verify_result is '$VERIFY_VAL', expected 'pass'. Run comet-verify first." + exit 1 +fi + +if [ "$ARCHIVED_VAL" = "true" ]; then + red "FATAL: change already archived" + exit 1 +fi + +step_ok "Entry state verified" + +# --- Step 3: Check archive target --- + +if [ -d "$ARCHIVE_DIR" ]; then + red "FATAL: archive target already exists: $ARCHIVE_DIR" + exit 1 +fi + +step_ok "Archive target available" + +# --- Step 4: Sync delta specs → main specs --- + +sync_delta_specs() { + local delta_root="$CHANGE_DIR/specs" + if [ ! -d "$delta_root" ]; then + return 0 + fi + + for delta_spec_dir in "$delta_root"/*/; do + [ -d "$delta_spec_dir" ] || continue + local capability + capability=$(basename "$delta_spec_dir") + local delta_spec="$delta_spec_dir/spec.md" + local main_spec="openspec/specs/$capability/spec.md" + + if [ ! -f "$delta_spec" ]; then + continue + fi + + if [ "$DRY_RUN" -eq 1 ]; then + step_dry_run "Would sync: $capability → $main_spec" + continue + fi + + if [ ! -f "$main_spec" ]; then + mkdir -p "openspec/specs/$capability" + elif ! cmp -s "$main_spec" "$delta_spec"; then + yellow " [DIFF] Delta spec differs from main spec before sync: $capability" + diff -u "$main_spec" "$delta_spec" >&2 || true + fi + cp "$delta_spec" "$main_spec" + + step_ok "Delta spec synced: $capability → openspec/specs/$capability/spec.md" + done +} + +sync_delta_specs + +# --- Step 5: Annotate design doc frontmatter --- + +annotate_frontmatter() { + local file="$1" + local extra_fields="$2" + + if [ ! -f "$file" ]; then + return 0 + fi + + if [ "$DRY_RUN" -eq 1 ]; then + step_dry_run "Would annotate: $file" + return 0 + fi + + if head -1 "$file" | grep -q '^---'; then + local tmp_file + tmp_file=$(mktemp) + awk -v archive="$ARCHIVE_NAME" -v extra="$extra_fields" ' + /^archived-with:/ { next } + NR==1 && /^---/ { print; next } + /^---/ && NR>1 { + print "archived-with: " archive + if (extra != "") print extra + print; next + } + { print } + ' "$file" > "$tmp_file" + mv "$tmp_file" "$file" + else + local tmp_file + tmp_file=$(mktemp) + { + echo "---" + echo "archived-with: $ARCHIVE_NAME" + if [ -n "$extra_fields" ]; then + echo "$extra_fields" + fi + echo "status: final" + echo "---" + cat "$file" + } > "$tmp_file" + mv "$tmp_file" "$file" + fi + + step_ok "Annotated: $file" +} + +if [ -n "$DESIGN_DOC" ] && [ "$DESIGN_DOC" != "null" ]; then + annotate_frontmatter "$DESIGN_DOC" "status: final" +fi + +# --- Step 6: Annotate plan frontmatter --- + +if [ -n "$PLAN_PATH" ] && [ "$PLAN_PATH" != "null" ]; then + annotate_frontmatter "$PLAN_PATH" "" +fi + +# --- Step 7: Move change to archive --- + +if [ "$DRY_RUN" -eq 1 ]; then + step_dry_run "Would move: $CHANGE_DIR → $ARCHIVE_DIR" +else + mkdir -p "openspec/changes/archive" + mv "$CHANGE_DIR" "$ARCHIVE_DIR" + step_ok "Moved to: $ARCHIVE_DIR" +fi + +# --- Step 8: Mark archived via comet-state transition --- + +ARCHIVE_YAML="$ARCHIVE_DIR/.comet.yaml" + +if [ "$DRY_RUN" -eq 1 ]; then + step_dry_run "Would set archived: true in $ARCHIVE_YAML" +else + if [ -f "$ARCHIVE_YAML" ]; then + bash "$STATE_SH" transition "$ARCHIVE_NAME" archived >/dev/null + step_ok "archived: true" + else + step_fail "archived: true (.comet.yaml not found after move)" + fi +fi + +# --- Step 9: Print summary --- + +echo "" >&2 +if [ "$DRY_RUN" -eq 1 ]; then + yellow "Dry run complete. $STEPS_OK/$STEPS_TOTAL steps would succeed." +else + green "Archive complete. $STEPS_OK/$STEPS_TOTAL steps succeeded." +fi + +if [ "$STEPS_OK" -lt "$STEPS_TOTAL" ]; then + exit 1 +fi + +exit 0 diff --git a/.claude/skills/comet/scripts/comet-env.sh b/.claude/skills/comet/scripts/comet-env.sh new file mode 100644 index 0000000..25565b2 --- /dev/null +++ b/.claude/skills/comet/scripts/comet-env.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Comet script locator — source this file to export paths to bundled scripts. +# +# Usage: +# . /path/to/comet/scripts/comet-env.sh +# +# This file is sourced by workflow snippets. Do not set global shell options here. + +_comet_env_source="${BASH_SOURCE[0]:-$0}" +_comet_script_dir="$(cd "$(dirname "$_comet_env_source")" && pwd -P)" +_comet_env_sourced=0 +(return 0 2>/dev/null) && _comet_env_sourced=1 + +export COMET_GUARD="${COMET_GUARD:-${_comet_script_dir}/comet-guard.sh}" +export COMET_STATE="${COMET_STATE:-${_comet_script_dir}/comet-state.sh}" +export COMET_HANDOFF="${COMET_HANDOFF:-${_comet_script_dir}/comet-handoff.sh}" +export COMET_ARCHIVE="${COMET_ARCHIVE:-${_comet_script_dir}/comet-archive.sh}" +export COMET_YAML_VALIDATE="${COMET_YAML_VALIDATE:-${_comet_script_dir}/comet-yaml-validate.sh}" + +_comet_env_fail() { + echo "ERROR: Comet scripts not found. Ensure the comet skill is installed completely." >&2 + echo "Expected path pattern: */comet/scripts/comet-*.sh under project or platform skill directories" >&2 +} + +_comet_env_abort() { + local _comet_env_was_sourced="$_comet_env_sourced" + unset _comet_env_source _comet_script_dir _comet_script _comet_env_missing _comet_env_sourced + unset -f _comet_env_fail + if [ "$_comet_env_was_sourced" -eq 1 ]; then + unset -f _comet_env_abort + return 1 + fi + exit 1 +} + +_comet_env_missing=0 +for _comet_script in \ + "$COMET_GUARD" \ + "$COMET_STATE" \ + "$COMET_HANDOFF" \ + "$COMET_ARCHIVE" \ + "$COMET_YAML_VALIDATE"; do + if [ ! -f "$_comet_script" ]; then + _comet_env_fail + _comet_env_missing=1 + break + fi +done + +if [ "$_comet_env_missing" -ne 0 ]; then + _comet_env_abort +else + unset _comet_env_source _comet_script_dir _comet_script _comet_env_missing _comet_env_sourced + unset -f _comet_env_fail _comet_env_abort +fi diff --git a/.claude/skills/comet/scripts/comet-guard.sh b/.claude/skills/comet/scripts/comet-guard.sh new file mode 100644 index 0000000..cbaea2b --- /dev/null +++ b/.claude/skills/comet/scripts/comet-guard.sh @@ -0,0 +1,594 @@ +#!/bin/bash +# Comet Phase Guard — validates exit conditions before phase transitions +# Usage: comet-guard.sh <change-name> <current-phase> [--apply] +# Phases: open, design, build, verify, archive +# Exit 0 = all checks pass, exit 1 = blocked (reasons printed to stderr) +# shellcheck disable=SC2329 # Functions called indirectly via check() dispatch + +set -euo pipefail + +red() { echo -e "\033[31m$1\033[0m" >&2; } +green() { echo -e "\033[32m$1\033[0m" >&2; } +warn() { echo -e "\033[33m$1\033[0m" >&2; } + +# Input validation - prevent path traversal +validate_change_name() { + local name="$1" + # Reject empty names + if [ -z "$name" ]; then + red "ERROR: Change name cannot be empty" >&2 + exit 1 + fi + # Only allow alphanumeric, hyphens, and underscores + if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then + red "ERROR: Invalid change name: '$name'" >&2 + red "Valid characters: a-z, A-Z, 0-9, -, _" >&2 + exit 1 + fi + # Reject path traversal attempts + if [[ "$name" =~ \.\. ]]; then + red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2 + exit 1 + fi +} + +validate_change_name "$1" + +CHANGE="$1" +PHASE="$2" +APPLY=0 +SCRIPT_DIR="$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")" 2>/dev/null || dirname "$0")" +if [[ "${3:-}" == "--apply" ]]; then + APPLY=1 +fi +CHANGE_DIR="openspec/changes/$CHANGE" +if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then + CHANGE_DIR="openspec/changes/archive/$CHANGE" +fi + +BLOCK=0 +check() { + local desc="$1" + shift + local output + if output=$("$@" 2>&1); then + green " [PASS] $desc" + else + red " [FAIL] $desc" + if [ -n "$output" ]; then + while IFS= read -r line; do + red " $line" + done <<< "$output" + fi + BLOCK=1 + fi +} + +# --- Helper functions --- + +tasks_all_done() { + local tasks="$CHANGE_DIR/tasks.md" + if [ ! -f "$tasks" ]; then + echo "tasks.md is missing at $tasks" >&2 + echo "Next: restore or create tasks.md for this change before leaving build." >&2 + return 1 + fi + if ! grep -q '\- \[x\]' "$tasks"; then + echo "tasks.md has no completed tasks." >&2 + echo "Next: complete implementation tasks and mark them with '- [x]'." >&2 + return 1 + fi + if grep -q '\- \[ \]' "$tasks"; then + echo "Unfinished tasks:" >&2 + grep -n '\- \[ \]' "$tasks" >&2 || true + echo "Next: complete or explicitly remove unfinished tasks, then mark tasks.md with '- [x]'." >&2 + return 1 + fi + return 0 +} + +tasks_has_any() { + local tasks="$CHANGE_DIR/tasks.md" + [ -f "$tasks" ] && grep -q '\- \[' "$tasks" +} + +yaml_field_value() { + local field="$1" + local yaml="$CHANGE_DIR/.comet.yaml" + if [ -f "$yaml" ]; then + local value + value=$(grep "^${field}:" "$yaml" 2>/dev/null | sed "s/^${field}: *//" || true) + value=$(strip_inline_comment "$value") + strip_wrapping_quotes "$value" + fi +} + +strip_inline_comment() { + local value="$1" + printf '%s\n' "$value" | awk -v squote="'" ' + { + out = "" + quote = "" + for (i = 1; i <= length($0); i++) { + c = substr($0, i, 1) + if (quote == "") { + if (c == "\"" || c == squote) { + quote = c + } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) { + sub(/[[:space:]]+$/, "", out) + print out + next + } + } else if (c == quote) { + quote = "" + } + out = out c + } + print out + } + ' +} + +strip_wrapping_quotes() { + local value="$1" + case "$value" in + \"*\") + printf '%s\n' "${value:1:${#value}-2}" + ;; + \'*\') + printf '%s\n' "${value:1:${#value}-2}" + ;; + *) + printf '%s\n' "$value" + ;; + esac +} + +project_config_value() { + local field="$1" + local value + + value=$(yaml_field_value "$field" 2>/dev/null || true) + if [ -n "$value" ] && [ "$value" != "null" ]; then + echo "$value" + return 0 + fi + + for config in ".comet.yaml" "comet.yaml" ".comet.yml" "comet.yml"; do + if [ -f "$config" ]; then + value=$(grep "^${field}:" "$config" 2>/dev/null | sed "s/^${field}: *//" || true) + value=$(strip_inline_comment "$value") + value=$(strip_wrapping_quotes "$value") + if [ -n "$value" ] && [ "$value" != "null" ]; then + echo "$value" + return 0 + fi + fi + done +} + +file_nonempty() { + [ -f "$1" ] && [ -s "$1" ] +} + +is_windows_bash() { + case "$(uname -s 2>/dev/null || true)" in + MINGW*|MSYS*|CYGWIN*) return 0 ;; + *) return 1 ;; + esac +} + +run_command_string() { + local command="$1" + echo "+ $command" >&2 + bash -lc "$command" +} + +hash_stream() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 | awk '{print $1}' + else + echo "sha256sum or shasum is required" >&2 + return 1 + fi +} + +hash_file() { + local file="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$file" | awk '{print $1}' + else + echo "sha256sum or shasum is required" >&2 + return 1 + fi +} + +handoff_source_files() { + printf '%s\n' "$CHANGE_DIR/proposal.md" + printf '%s\n' "$CHANGE_DIR/design.md" + printf '%s\n' "$CHANGE_DIR/tasks.md" + if [ -d "$CHANGE_DIR/specs" ]; then + find "$CHANGE_DIR/specs" -path '*/spec.md' -type f 2>/dev/null | sort + fi +} + +compute_handoff_hash() { + handoff_source_files | while IFS= read -r file; do + if [ -f "$file" ]; then + printf 'path:%s\n' "$file" + printf 'sha256:%s\n' "$(hash_file "$file")" + fi + done | hash_stream +} + +preflight() { + + if [ ! -d "$CHANGE_DIR" ]; then + red "FATAL: change directory not found: $CHANGE_DIR" + exit 1 + fi + if [ ! -f "$CHANGE_DIR/.comet.yaml" ]; then + red "FATAL: .comet.yaml not found in $CHANGE_DIR" + exit 1 + fi + + # Schema validation + local validate_script + validate_script="$SCRIPT_DIR/comet-yaml-validate.sh" + if [ -f "$validate_script" ]; then + if ! bash "$validate_script" "$CHANGE" 2>/dev/null; then + bash "$validate_script" "$CHANGE" + red "FATAL: .comet.yaml schema validation failed" + exit 1 + fi + fi +} + +build_passes() { + if [ "${COMET_SKIP_BUILD:-0}" = "1" ]; then + return 0 + fi + local configured_build + configured_build=$(project_config_value "build_command" 2>/dev/null || true) + if [ -n "$configured_build" ]; then + run_command_string "$configured_build" + return $? + fi + if [ -f "package.json" ] && grep -q '"build"' "package.json"; then + npm run build + return $? + fi + if [ -f "pom.xml" ]; then + if [ -x "./mvnw" ]; then + ./mvnw compile -q + elif is_windows_bash && command -v mvn.cmd >/dev/null 2>&1; then + mvn.cmd compile -q + else + mvn compile -q + fi + return $? + fi + if [ -f "Cargo.toml" ]; then + cargo build + return $? + fi + return 1 +} + +verification_command_passes() { + if [ "${COMET_SKIP_BUILD:-0}" = "1" ]; then + return 0 + fi + local configured_verify + configured_verify=$(project_config_value "verify_command" 2>/dev/null || true) + if [ -n "$configured_verify" ]; then + run_command_string "$configured_verify" + return $? + fi + build_passes +} + +isolation_selected() { + local isolation + isolation=$(yaml_field_value "isolation" 2>/dev/null || true) + case "$isolation" in + branch|worktree) return 0 ;; + *) + echo "isolation must be branch or worktree, got '${isolation:-null}'" >&2 + echo "Next: ask the user to choose branch or worktree, create the chosen isolation, then run:" >&2 + echo " bash \"\$COMET_STATE\" set $CHANGE isolation <branch|worktree>" >&2 + return 1 + ;; + esac +} + +build_mode_selected() { + local build_mode + build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true) + case "$build_mode" in + subagent-driven-development|executing-plans|direct) return 0 ;; + *) + echo "build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2 + echo "Next: ask the user to choose an implementation mode, then run:" >&2 + echo " bash \"\$COMET_STATE\" set $CHANGE build_mode <subagent-driven-development|executing-plans>" >&2 + return 1 + ;; + esac +} + +build_mode_allowed_for_workflow() { + local workflow build_mode direct_override + workflow=$(yaml_field_value "workflow" 2>/dev/null || true) + build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true) + direct_override=$(yaml_field_value "direct_override" 2>/dev/null || true) + + if [ "$build_mode" != "direct" ]; then + return 0 + fi + case "$workflow" in + hotfix|tweak) return 0 ;; + *) + if [ "$direct_override" = "true" ]; then + return 0 + fi + echo "build_mode=direct is only allowed for hotfix/tweak unless direct_override: true is recorded" >&2 + echo "Next: switch build_mode to executing-plans or subagent-driven-development, or stop and ask the user for an explicit direct override." >&2 + return 1 + ;; + esac +} + +verify_result_is_pass() { + local result + result=$(yaml_field_value "verify_result" 2>/dev/null || true) + [ "$result" = "pass" ] +} + +verification_report_exists() { + local report + report=$(yaml_field_value "verification_report" 2>/dev/null || true) + [ -n "$report" ] && [ "$report" != "null" ] && [ -f "$report" ] +} + +branch_status_handled() { + local status + status=$(yaml_field_value "branch_status" 2>/dev/null || true) + [ "$status" = "handled" ] +} + +design_handoff_context_valid() { + local context recorded_hash actual_hash markdown + context=$(yaml_field_value "handoff_context" 2>/dev/null || true) + recorded_hash=$(yaml_field_value "handoff_hash" 2>/dev/null || true) + + if [ -z "$context" ] || [ "$context" = "null" ]; then + echo "handoff_context is missing from .comet.yaml" >&2 + echo "Next: run bash \"\$COMET_HANDOFF\" $CHANGE design --write before invoking Superpowers." >&2 + return 1 + fi + if [ ! -s "$context" ]; then + echo "handoff_context does not point to a non-empty file: $context" >&2 + echo "Next: regenerate the design handoff with comet-handoff.sh." >&2 + return 1 + fi + if [[ ! "$recorded_hash" =~ ^[a-f0-9]{64}$ ]]; then + echo "handoff_hash is missing or invalid: ${recorded_hash:-null}" >&2 + echo "Next: regenerate the design handoff with comet-handoff.sh." >&2 + return 1 + fi + + actual_hash=$(compute_handoff_hash) + if [ "$actual_hash" != "$recorded_hash" ]; then + echo "OpenSpec artifacts changed after handoff was generated." >&2 + echo "Expected handoff_hash: $recorded_hash" >&2 + echo "Actual handoff_hash: $actual_hash" >&2 + echo "Next: rerun comet-handoff.sh so Superpowers receives the current OpenSpec context." >&2 + return 1 + fi + + markdown="${context%.json}.md" + if [ ! -s "$markdown" ]; then + echo "design handoff markdown is missing or empty: $markdown" >&2 + echo "Next: regenerate the design handoff with comet-handoff.sh." >&2 + return 1 + fi +} + +design_handoff_markdown_traceable() { + local context markdown missing=0 + context=$(yaml_field_value "handoff_context" 2>/dev/null || true) + if [ -z "$context" ] || [ "$context" = "null" ]; then + echo "handoff_context is missing from .comet.yaml" >&2 + return 1 + fi + markdown="${context%.json}.md" + if [ ! -s "$markdown" ]; then + echo "design handoff markdown is missing or empty: $markdown" >&2 + return 1 + fi + grep -q '^Generated-by: comet-handoff\.sh$' "$markdown" || { + echo "handoff markdown is missing Generated-by marker" >&2 + missing=1 + } + grep -Eq '^- Mode: (compact|full)$' "$markdown" || { + echo "handoff markdown is missing Mode marker" >&2 + missing=1 + } + handoff_source_files | while IFS= read -r file; do + [ -f "$file" ] || continue + if ! grep -q "^- Source: $file$" "$markdown"; then + echo "handoff markdown is missing source reference: $file" >&2 + exit 2 + fi + if ! grep -q "^- SHA256: $(hash_file "$file")$" "$markdown"; then + echo "handoff markdown is missing current sha256 for: $file" >&2 + exit 2 + fi + done || missing=1 + + [ "$missing" -eq 0 ] +} + +design_doc_frontmatter_has() { + local design_doc="$1" + local field="$2" + local expected="$3" + awk ' + { + line = $0 + sub(/^\357\273\277/, "", line) + } + !in_fm && line == "---" { in_fm = 1; next } + in_fm && line == "---" { exit } + in_fm { print line } + ' "$design_doc" | grep -Eq "^${field}: ['\"]?${expected}['\"]?[[:space:]]*$" +} + +design_doc_links_current_change() { + local design_doc + design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true) + if [ -z "$design_doc" ] || [ "$design_doc" = "null" ] || [ ! -s "$design_doc" ]; then + echo "design_doc must point to an existing Superpowers Design Doc before leaving design." >&2 + return 1 + fi + design_doc_frontmatter_has "$design_doc" "comet_change" "$CHANGE" +} + +design_doc_declares_technical_role() { + local design_doc + design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true) + [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -s "$design_doc" ] && + design_doc_frontmatter_has "$design_doc" "role" "technical-design" +} + +design_doc_declares_canonical_spec() { + local design_doc + design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true) + [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -s "$design_doc" ] && + design_doc_frontmatter_has "$design_doc" "canonical_spec" "openspec" +} + +archived_is_true() { + local val + val=$(yaml_field_value "archived" 2>/dev/null || true) + [ "$val" = "true" ] +} + +# --- Phase-specific checks --- + +guard_open() { + echo "=== Guard: open → next ===" >&2 + + check "proposal.md exists and non-empty" file_nonempty "$CHANGE_DIR/proposal.md" + check "design.md exists and non-empty" file_nonempty "$CHANGE_DIR/design.md" + check "tasks.md exists and non-empty" file_nonempty "$CHANGE_DIR/tasks.md" + check "tasks.md has at least one task" tasks_has_any +} + +guard_design() { + echo "=== Guard: design → build ===" >&2 + + local design_doc + design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true) + + check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md" + check "design.md exists" file_nonempty "$CHANGE_DIR/design.md" + check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md" + check "design handoff context exists" design_handoff_context_valid + check "design handoff markdown is traceable" design_handoff_markdown_traceable + + if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then + check "Design Doc ($design_doc) exists" file_nonempty "$design_doc" + check "Design Doc frontmatter links current change" design_doc_links_current_change + check "Design Doc declares technical design role" design_doc_declares_technical_role + check "Design Doc declares OpenSpec as canonical spec" design_doc_declares_canonical_spec + else + warn " [WARN] No design_doc recorded in .comet.yaml" + fi +} + +guard_build() { + echo "=== Guard: build → verify ===" >&2 + + check "isolation selected" isolation_selected + check "build_mode selected" build_mode_selected + check "build_mode allowed for workflow" build_mode_allowed_for_workflow + check "tasks.md all tasks checked" tasks_all_done + check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md" + check "Build passes" build_passes +} + +guard_verify() { + echo "=== Guard: verify → archive ===" >&2 + + check "tasks.md all tasks checked" tasks_all_done + check "Build passes" verification_command_passes + check "verification_report exists" verification_report_exists + check "branch_status=handled" branch_status_handled +} + +guard_archive() { + echo "=== Guard: archive completeness ===" >&2 + + check "archived is true" archived_is_true + check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md" + check "tasks.md all tasks checked" tasks_all_done +} + +apply_state_update() { + local state_sh="$SCRIPT_DIR/comet-state.sh" + local p="$1" + + if [ -f "$state_sh" ]; then + case "$p" in + open) bash "$state_sh" transition "$CHANGE" open-complete ;; + design) bash "$state_sh" transition "$CHANGE" design-complete ;; + build) bash "$state_sh" transition "$CHANGE" build-complete ;; + verify) bash "$state_sh" transition "$CHANGE" verify-pass ;; + esac + else + red "FATAL: comet-state.sh not found; cannot apply state transition" + exit 1 + fi +} + +# --- Main --- + +case "$PHASE" in + open) preflight ; guard_open ;; + design) preflight ; guard_design ;; + build) preflight ; guard_build ;; + verify) preflight ; guard_verify ;; + archive) preflight ; guard_archive ;; + *) + red "Unknown phase: $PHASE" + echo "Valid phases: open, design, build, verify, archive" >&2 + exit 1 + ;; +esac + +if [ "$BLOCK" -eq 1 ]; then + echo "" >&2 + red "BLOCKED — fix failing checks before proceeding to next phase" + exit 1 +else + echo "" >&2 + green "ALL CHECKS PASSED — ready for next phase" + if [ "$APPLY" -eq 1 ]; then + apply_state_update "$PHASE" + case "$PHASE" in + open) + new_phase=$(yaml_field_value "phase") + green " [APPLY] .comet.yaml updated: phase=$new_phase" + ;; + design) green " [APPLY] .comet.yaml updated: phase=build" ;; + build) green " [APPLY] .comet.yaml updated: phase=verify, verify_result=pending" ;; + verify) green " [APPLY] .comet.yaml updated: phase=archive, verify_result=pass" ;; + esac + fi + exit 0 +fi diff --git a/.claude/skills/comet/scripts/comet-handoff.sh b/.claude/skills/comet/scripts/comet-handoff.sh new file mode 100644 index 0000000..6c2c5e1 --- /dev/null +++ b/.claude/skills/comet/scripts/comet-handoff.sh @@ -0,0 +1,261 @@ +#!/bin/bash +# Comet Handoff — creates machine-owned context packages between phases +# Usage: comet-handoff.sh <change-name> design --write [--full] + +set -euo pipefail + +red() { echo -e "\033[31m$1\033[0m" >&2; } +green() { echo -e "\033[32m$1\033[0m" >&2; } + +validate_change_name() { + local name="$1" + if [ -z "$name" ]; then + red "ERROR: Change name cannot be empty" + exit 1 + fi + if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then + red "ERROR: Invalid change name: '$name'" + red "Valid characters: a-z, A-Z, 0-9, -, _" + exit 1 + fi + if [[ "$name" =~ \.\. ]]; then + red "ERROR: Change name cannot contain '..' (path traversal not allowed)" + exit 1 + fi +} + +strip_wrapping_quotes() { + local value="$1" + case "$value" in + \"*\") printf '%s\n' "${value:1:${#value}-2}" ;; + \'*\') printf '%s\n' "${value:1:${#value}-2}" ;; + *) printf '%s\n' "$value" ;; + esac +} + +strip_inline_comment() { + local value="$1" + printf '%s\n' "$value" | awk -v squote="'" ' + { + out = "" + quote = "" + for (i = 1; i <= length($0); i++) { + c = substr($0, i, 1) + if (quote == "") { + if (c == "\"" || c == squote) { + quote = c + } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) { + sub(/[[:space:]]+$/, "", out) + print out + next + } + } else if (c == quote) { + quote = "" + } + out = out c + } + print out + } + ' +} + +yaml_field_value() { + local field="$1" + local yaml="$CHANGE_DIR/.comet.yaml" + local value + value=$(grep "^${field}:" "$yaml" 2>/dev/null | sed "s/^${field}: *//" || true) + value=$(strip_inline_comment "$value") + strip_wrapping_quotes "$value" +} + +hash_stream() { + if command -v sha256sum >/dev/null 2>&1; then + sha256sum | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 | awk '{print $1}' + else + red "ERROR: sha256sum or shasum is required" + exit 1 + fi +} + +hash_file() { + local file="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$file" | awk '{print $1}' + elif command -v shasum >/dev/null 2>&1; then + shasum -a 256 "$file" | awk '{print $1}' + else + red "ERROR: sha256sum or shasum is required" + exit 1 + fi +} + +source_files() { + printf '%s\n' "$CHANGE_DIR/proposal.md" + printf '%s\n' "$CHANGE_DIR/design.md" + printf '%s\n' "$CHANGE_DIR/tasks.md" + if [ -d "$CHANGE_DIR/specs" ]; then + find "$CHANGE_DIR/specs" -path '*/spec.md' -type f 2>/dev/null | sort + fi +} + +compute_context_hash() { + source_files | while IFS= read -r file; do + if [ -f "$file" ]; then + printf 'path:%s\n' "$file" + printf 'sha256:%s\n' "$(hash_file "$file")" + fi + done | hash_stream +} + +json_escape() { + printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' +} + +file_line_count() { + local file="$1" + wc -l < "$file" | tr -d ' ' +} + +write_file_excerpt() { + local file="$1" + local max_lines="$2" + local total_lines + total_lines=$(file_line_count "$file") + + echo "## $file" + echo "" + echo "- Source: $file" + echo "- Lines: 1-$total_lines" + echo "- SHA256: $(hash_file "$file")" + echo "" + + if [ "$HANDOFF_MODE" = "full" ] || [ "$total_lines" -le "$max_lines" ]; then + echo '```md' + cat "$file" + echo '```' + else + echo "[TRUNCATED]" + echo "" + echo '```md' + sed -n "1,${max_lines}p" "$file" + echo '```' + echo "" + echo "Full source: $file" + fi + echo "" +} + +write_markdown_context() { + local output="$1" + { + echo "# Comet Design Handoff" + echo "" + echo "- Change: $CHANGE" + echo "- Phase: design" + echo "- Mode: $HANDOFF_MODE" + echo "- Context hash: $CONTEXT_HASH" + echo "" + echo "Generated-by: comet-handoff.sh" + echo "" + echo "OpenSpec remains the canonical capability spec. This handoff is a deterministic, source-traceable context pack, not an agent-authored summary." + echo "" + source_files | while IFS= read -r file; do + [ -f "$file" ] || continue + write_file_excerpt "$file" 80 + done + } > "$output" +} + +write_json_context() { + local output="$1" + { + echo "{" + echo " \"change\": \"$(json_escape "$CHANGE")\"," + echo " \"phase\": \"design\"," + echo " \"mode\": \"$HANDOFF_MODE\"," + echo " \"canonical_spec\": \"openspec\"," + echo " \"generated_by\": \"comet-handoff.sh\"," + echo " \"context_hash\": \"$CONTEXT_HASH\"," + echo " \"files\": [" + local first=1 + while IFS= read -r file; do + [ -f "$file" ] || continue + if [ "$first" -eq 0 ]; then + echo "," + fi + first=0 + printf ' { "path": "%s", "sha256": "%s" }' "$(json_escape "$file")" "$(hash_file "$file")" + done < <(source_files) + echo "" + echo " ]" + echo "}" + } > "$output" +} + +CHANGE="${1:-}" +PHASE="${2:-}" +MODE="${3:-}" +FULL_FLAG="${4:-}" + +validate_change_name "$CHANGE" + +if [ "$PHASE" != "design" ] || [ "$MODE" != "--write" ]; then + red "Usage: comet-handoff.sh <change-name> design --write [--full]" + exit 1 +fi +case "$FULL_FLAG" in + "") HANDOFF_MODE="compact" ;; + --full) HANDOFF_MODE="full" ;; + *) + red "Usage: comet-handoff.sh <change-name> design --write [--full]" + exit 1 + ;; +esac + +CHANGE_DIR="openspec/changes/$CHANGE" +YAML="$CHANGE_DIR/.comet.yaml" +SCRIPT_DIR="$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")" 2>/dev/null || dirname "$0")" +STATE_SH="$SCRIPT_DIR/comet-state.sh" + +if [ ! -d "$CHANGE_DIR" ]; then + red "ERROR: change directory not found: $CHANGE_DIR" + exit 1 +fi +if [ ! -f "$YAML" ]; then + red "ERROR: .comet.yaml not found at $YAML" + exit 1 +fi +if [ "$(yaml_field_value phase)" != "design" ]; then + red "ERROR: design handoff requires phase: design" + exit 1 +fi + +for required in proposal.md design.md tasks.md; do + if [ ! -s "$CHANGE_DIR/$required" ]; then + red "ERROR: required OpenSpec artifact missing or empty: $CHANGE_DIR/$required" + exit 1 + fi +done + +HANDOFF_DIR="$CHANGE_DIR/.comet/handoff" +CONTEXT_JSON="$HANDOFF_DIR/design-context.json" +CONTEXT_MD="$HANDOFF_DIR/design-context.md" +mkdir -p "$HANDOFF_DIR" + +CONTEXT_HASH="$(compute_context_hash)" +write_markdown_context "$CONTEXT_MD" +write_json_context "$CONTEXT_JSON" + +if [ -x "$STATE_SH" ] || [ -f "$STATE_SH" ]; then + bash "$STATE_SH" set "$CHANGE" handoff_context "$CONTEXT_JSON" >/dev/null + bash "$STATE_SH" set "$CHANGE" handoff_hash "$CONTEXT_HASH" >/dev/null +else + red "ERROR: comet-state.sh not found; cannot record handoff fields" + exit 1 +fi + +green "[HANDOFF] wrote $CONTEXT_JSON" +green "[HANDOFF] wrote $CONTEXT_MD" +green "[HANDOFF] handoff_hash=$CONTEXT_HASH" diff --git a/.claude/skills/comet/scripts/comet-state.sh b/.claude/skills/comet/scripts/comet-state.sh new file mode 100644 index 0000000..49a03ae --- /dev/null +++ b/.claude/skills/comet/scripts/comet-state.sh @@ -0,0 +1,896 @@ +#!/bin/bash +# Comet State — unified interface for .comet.yaml state management +# Usage: comet-state.sh <subcommand> <change-name> [args...] +# +# Subcommands: +# init <change-name> <workflow> — Initialize .comet.yaml with workflow defaults +# get <change-name> <field> — Read a field value from .comet.yaml +# set <change-name> <field> <val> — Update a field value +# transition <change-name> <event> — Apply a validated state transition +# check <change-name> <phase> — Verify entry requirements for a phase +# check <change-name> <phase> --recover — Output structured recovery context for compaction resume +# scale <change-name> — Assess and set verification mode based on metrics +# +# Workflows: full, hotfix, tweak +# Phases for check: open, design, build, verify, archive + +set -euo pipefail + +# --- Color output helpers --- + +red() { echo -e "\033[31m$1\033[0m" >&2; } +green() { echo -e "\033[32m$1\033[0m" >&2; } +yellow() { echo -e "\033[33m$1\033[0m" >&2; } + +# --- Script location --- + +# shellcheck disable=SC2034 +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# --- Input validation --- + +validate_change_name() { + local name="$1" + # Reject empty names + if [ -z "$name" ]; then + red "ERROR: Change name cannot be empty" >&2 + exit 1 + fi + # Only allow alphanumeric, hyphens, and underscores + if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then + red "ERROR: Invalid change name: '$name'" >&2 + red "Valid characters: a-z, A-Z, 0-9, -, _" >&2 + exit 1 + fi + # Reject path traversal attempts + if [[ "$name" =~ \.\. ]]; then + red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2 + exit 1 + fi +} + +validate_enum() { + local value="$1" + shift + local valid_values=("$@") + + for valid in "${valid_values[@]}"; do + if [ "$value" = "$valid" ]; then + return 0 + fi + done + + red "ERROR: Invalid value: '$value'" >&2 + red "Valid values: ${valid_values[*]}" >&2 + exit 1 +} + +# --- Helper functions --- + +yaml_field() { + local field="$1" + local yaml_file="$2" + if [ -f "$yaml_file" ]; then + local value + value=$(grep "^${field}:" "$yaml_file" 2>/dev/null | sed "s/^${field}: *//" || true) + value=$(strip_inline_comment "$value") + strip_wrapping_quotes "$value" + fi +} + +strip_inline_comment() { + local value="$1" + printf '%s\n' "$value" | awk -v squote="'" ' + { + out = "" + quote = "" + for (i = 1; i <= length($0); i++) { + c = substr($0, i, 1) + if (quote == "") { + if (c == "\"" || c == squote) { + quote = c + } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) { + sub(/[[:space:]]+$/, "", out) + print out + next + } + } else if (c == quote) { + quote = "" + } + out = out c + } + print out + } + ' +} + +strip_wrapping_quotes() { + local value="$1" + case "$value" in + \"*\") + printf '%s\n' "${value:1:${#value}-2}" + ;; + \'*\') + printf '%s\n' "${value:1:${#value}-2}" + ;; + *) + printf '%s\n' "$value" + ;; + esac +} + +replace_yaml_field() { + local yaml_file="$1" + local field="$2" + local value="$3" + local tmp_file + + tmp_file=$(mktemp) + awk -v field="$field" -v value="$value" ' + index($0, field ":") == 1 { print field ": " value; next } + { print } + ' "$yaml_file" > "$tmp_file" + mv "$tmp_file" "$yaml_file" +} + +file_nonempty() { + [ -f "$1" ] && [ -s "$1" ] +} + +change_dir_for() { + local change_name="$1" + if [ -d "openspec/changes/$change_name" ]; then + echo "openspec/changes/$change_name" + elif [ -d "openspec/changes/archive/$change_name" ]; then + echo "openspec/changes/archive/$change_name" + else + echo "openspec/changes/$change_name" + fi +} + +yaml_file_for() { + local change_name="$1" + local change_dir + change_dir=$(change_dir_for "$change_name") + echo "$change_dir/.comet.yaml" +} + +# --- Subcommands --- + +cmd_init() { + local change_name="$1" + local workflow="$2" + + validate_change_name "$change_name" + validate_enum "$workflow" "full" "hotfix" "tweak" + + local change_dir yaml_file + change_dir=$(change_dir_for "$change_name") + yaml_file=$(yaml_file_for "$change_name") + + # Check if .comet.yaml already exists + if [ -f "$yaml_file" ]; then + red "ERROR: .comet.yaml already exists at $yaml_file" + exit 1 + fi + + # Create change directory if it doesn't exist + mkdir -p "$change_dir" + + # Set workflow-appropriate defaults + local phase build_mode isolation verify_mode + phase="open" + + case "$workflow" in + full) + build_mode="null" + isolation="null" + verify_mode="null" + ;; + hotfix|tweak) + build_mode="direct" + isolation="branch" + verify_mode="light" + ;; + esac + + # Write .comet.yaml + # Record current HEAD as base_ref for scale assessment fallback + local base_ref="null" + if git rev-parse --verify HEAD >/dev/null 2>&1; then + base_ref=$(git rev-parse HEAD 2>/dev/null || echo "null") + fi + + cat > "$yaml_file" <<EOF +workflow: $workflow +phase: $phase +build_mode: $build_mode +isolation: $isolation +verify_mode: $verify_mode +base_ref: $base_ref +design_doc: null +plan: null +verify_result: pending +verification_report: null +branch_status: pending +created_at: $(date +%Y-%m-%d) +verified_at: null +archived: false +EOF + + green "Initialized: $yaml_file (workflow=$workflow)" +} + +cmd_get() { + local change_name="$1" + local field="$2" + + validate_change_name "$change_name" + + local yaml_file + yaml_file=$(yaml_file_for "$change_name") + + # Check if .comet.yaml exists + if [ ! -f "$yaml_file" ]; then + red "ERROR: .comet.yaml not found at $yaml_file" + exit 1 + fi + + # Read and output the field value + local value + value=$(yaml_field "$field" "$yaml_file") + echo "${value:-}" +} + +cmd_set() { + local change_name="$1" + local field="$2" + local value="$3" + + validate_change_name "$change_name" + + local yaml_file + yaml_file=$(yaml_file_for "$change_name") + + # Check if .comet.yaml exists + if [ ! -f "$yaml_file" ]; then + red "ERROR: .comet.yaml not found at $yaml_file" + exit 1 + fi + + # Validate field name + case "$field" in + phase) + yellow "WARNING: Setting 'phase' directly bypasses state machine constraints." >&2 + yellow " Consider using: comet-state.sh transition <change-name> <event>" >&2 + ;; + workflow|build_mode|isolation|verify_mode|verify_result|verification_report|branch_status|archived|design_doc|plan|verified_at|created_at|direct_override|build_command|verify_command|handoff_context|handoff_hash|base_ref) + # Valid field + ;; + *) + red "ERROR: Unknown field: '$field'" >&2 + red "Valid fields:" >&2 + red " workflow, phase, design_doc, plan, build_mode, isolation," >&2 + red " verify_mode, verify_result, verification_report, branch_status," >&2 + red " verified_at, created_at, archived, base_ref, direct_override," >&2 + red " build_command, verify_command, handoff_context, handoff_hash" >&2 + exit 1 + ;; + esac + + # Validate enum values + case "$field" in + workflow) + validate_enum "$value" "full" "hotfix" "tweak" + ;; + phase) + validate_enum "$value" "open" "design" "build" "verify" "archive" + ;; + build_mode) + validate_enum "$value" "subagent-driven-development" "executing-plans" "direct" + ;; + isolation) + validate_enum "$value" "branch" "worktree" + ;; + verify_mode) + validate_enum "$value" "light" "full" + ;; + verify_result) + validate_enum "$value" "pending" "pass" "fail" + ;; + branch_status) + validate_enum "$value" "pending" "handled" + ;; + archived) + validate_enum "$value" "true" "false" + ;; + direct_override) + validate_enum "$value" "true" "false" + ;; + design_doc|plan|verification_report|verified_at|created_at|build_command|verify_command|handoff_context|handoff_hash) + # No validation for path fields, date fields, or project command strings + ;; + esac + + # Write or update the field + if grep -q "^${field}:" "$yaml_file"; then + replace_yaml_field "$yaml_file" "$field" "$value" + else + # Field doesn't exist, append it + echo "${field}: ${value}" >> "$yaml_file" + fi + + green "[SET] ${field}=${value}" +} + +require_phase() { + local change_name="$1" + local expected="$2" + local actual + actual=$(cmd_get "$change_name" "phase") + if [ "$actual" != "$expected" ]; then + red "ERROR: Cannot transition '$change_name': expected phase ${expected}, got ${actual}" >&2 + exit 1 + fi +} + +require_verification_evidence() { + local change_name="$1" + local report branch_status + report=$(cmd_get "$change_name" "verification_report") + branch_status=$(cmd_get "$change_name" "branch_status") + + if [ -z "$report" ] || [ "$report" = "null" ] || [ ! -f "$report" ]; then + red "ERROR: Cannot transition '$change_name': verification_report must point to an existing report file" >&2 + exit 1 + fi + + if [ "$branch_status" != "handled" ]; then + red "ERROR: Cannot transition '$change_name': branch_status must be handled" >&2 + exit 1 + fi +} + +require_build_decisions() { + local change_name="$1" + local workflow build_mode isolation direct_override + workflow=$(cmd_get "$change_name" "workflow") + build_mode=$(cmd_get "$change_name" "build_mode") + isolation=$(cmd_get "$change_name" "isolation") + direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true) + + case "$isolation" in + branch|worktree) ;; + *) + red "ERROR: Cannot transition '$change_name': isolation must be branch or worktree, got '${isolation:-null}'" >&2 + exit 1 + ;; + esac + + case "$build_mode" in + subagent-driven-development|executing-plans|direct) ;; + *) + red "ERROR: Cannot transition '$change_name': build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2 + exit 1 + ;; + esac + + if [ "$build_mode" = "direct" ] && [ "$workflow" != "hotfix" ] && [ "$workflow" != "tweak" ] && [ "$direct_override" != "true" ]; then + red "ERROR: Cannot transition '$change_name': build_mode=direct is only allowed for hotfix/tweak unless direct_override=true" >&2 + exit 1 + fi +} + +cmd_transition() { + local change_name="$1" + local event="$2" + + validate_change_name "$change_name" + validate_enum "$event" "open-complete" "design-complete" "build-complete" "verify-pass" "verify-fail" "archived" + + case "$event" in + open-complete) + require_phase "$change_name" "open" + local workflow + workflow=$(cmd_get "$change_name" "workflow") + if [ "$workflow" = "full" ]; then + cmd_set "$change_name" phase design + else + cmd_set "$change_name" phase build + fi + ;; + design-complete) + require_phase "$change_name" "design" + cmd_set "$change_name" phase build + ;; + build-complete) + require_phase "$change_name" "build" + require_build_decisions "$change_name" + cmd_set "$change_name" phase verify + cmd_set "$change_name" verify_result pending + cmd_set "$change_name" verification_report null + cmd_set "$change_name" branch_status pending + ;; + verify-pass) + require_phase "$change_name" "verify" + require_verification_evidence "$change_name" + cmd_set "$change_name" verify_result pass + cmd_set "$change_name" phase archive + cmd_set "$change_name" verified_at "$(date +%Y-%m-%d)" + ;; + verify-fail) + require_phase "$change_name" "verify" + cmd_set "$change_name" verify_result fail + cmd_set "$change_name" phase build + cmd_set "$change_name" branch_status pending + ;; + archived) + require_phase "$change_name" "archive" + cmd_set "$change_name" archived true + ;; + esac + + green "[TRANSITION] ${event}" +} + +# --- Check helpers for entry verification --- + +CHECK_BLOCK=0 + +check_pass() { + local msg="$1" + echo " $(green "[PASS]") $msg" +} + +check_fail() { + local msg="$1" + echo " $(red "[FAIL]") $msg" + CHECK_BLOCK=1 +} + +check_nonempty() { + local desc="$1" + local path="$2" + if file_nonempty "$path"; then + check_pass "$desc non-empty" + else + check_fail "$desc missing or empty" + fi +} + +check_yaml_is() { + local field="$1" + local expected="$2" + local change_name="$3" + local actual + actual=$(cmd_get "$change_name" "$field") + if [ "$actual" = "$expected" ]; then + check_pass "${field}=${actual} (expected: ${expected})" + else + check_fail "${field}=${actual} (expected: ${expected})" + fi +} + +check_yaml_empty() { + local field="$1" + local change_name="$2" + local value + value=$(cmd_get "$change_name" "$field") + if [ -z "$value" ] || [ "$value" = "null" ]; then + check_pass "${field} is empty/null" + else + check_fail "${field}=${value} (expected: empty/null)" + fi +} + +check_file_not_exists() { + local desc="$1" + local path="$2" + if [ ! -f "$path" ]; then + check_pass "$desc does not exist" + else + check_fail "$desc exists (should not exist)" + fi +} + +cmd_check() { + local change_name="$1" + local phase="$2" + + validate_change_name "$change_name" + validate_enum "$phase" "open" "design" "build" "verify" "archive" + + local change_dir="openspec/changes/$change_name" + local yaml_file="$change_dir/.comet.yaml" + local proposal_file="$change_dir/proposal.md" + local design_file="$change_dir/design.md" + local tasks_file="$change_dir/tasks.md" + + echo "=== Entry Check: comet-${phase} ===" + + # .comet.yaml must exist for all phases (state machine core) + if [ ! -f "$yaml_file" ]; then + red "ERROR: .comet.yaml not found at $yaml_file" + exit 1 + fi + + # Phase-specific checks + case "$phase" in + open) + check_pass ".comet.yaml exists" + check_yaml_is "phase" "open" "$change_name" + ;; + design) + check_pass ".comet.yaml exists" + check_yaml_is "phase" "design" "$change_name" + check_yaml_is "workflow" "full" "$change_name" + check_yaml_empty "design_doc" "$change_name" + check_nonempty "proposal.md" "$proposal_file" + check_nonempty "design.md" "$design_file" + check_nonempty "tasks.md" "$tasks_file" + ;; + build) + check_pass ".comet.yaml exists" + check_yaml_is "phase" "build" "$change_name" + # design_doc required for full workflow only + local workflow + workflow=$(cmd_get "$change_name" "workflow") + if [ "$workflow" = "full" ]; then + local design_doc + design_doc=$(cmd_get "$change_name" "design_doc") + if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then + check_pass "design_doc=${design_doc} (file exists)" + else + check_fail "design_doc=${design_doc} (expected: non-null and file exists)" + fi + else + check_pass "workflow=${workflow} (design_doc not required)" + fi + check_nonempty "proposal.md" "$proposal_file" + check_nonempty "tasks.md" "$tasks_file" + ;; + verify) + check_pass ".comet.yaml exists" + check_yaml_is "phase" "verify" "$change_name" + # Check verify_result is pending or null + local verify_result + verify_result=$(cmd_get "$change_name" "verify_result") + if [ "$verify_result" = "pending" ] || [ -z "$verify_result" ] || [ "$verify_result" = "null" ]; then + check_pass "verify_result=${verify_result} (expected: pending or null)" + else + check_fail "verify_result=${verify_result} (expected: pending or null)" + fi + ;; + archive) + check_pass ".comet.yaml exists" + check_yaml_is "phase" "archive" "$change_name" + check_yaml_is "verify_result" "pass" "$change_name" + # Check archived is NOT true + local archived + archived=$(cmd_get "$change_name" "archived") + if [ "$archived" != "true" ]; then + check_pass "archived=${archived} (expected: not true)" + else + check_fail "archived=${archived} (expected: not true)" + fi + ;; + *) + red "ERROR: Unknown phase for check: $phase" + exit 1 + ;; + esac + + echo "" + if [ "$CHECK_BLOCK" -eq 1 ]; then + red "BLOCKED — fix failing checks before proceeding" + exit 1 + else + green "ALL CHECKS PASSED — ready to proceed" + exit 0 + fi +} + +# --- Recovery context for compaction resume --- + +field_status() { + # Args: field_name value [file_path] + # Prints: "field_name: DONE (value)" or "field_name: PENDING" + local field="$1" + local value="$2" + local file_path="${3:-}" + + if [ -z "$value" ] || [ "$value" = "null" ]; then + echo " - ${field}: PENDING" + elif [ -n "$file_path" ] && [ ! -f "$file_path" ]; then + echo " - ${field}: BROKEN (path ${value} does not exist)" + else + echo " - ${field}: DONE (${value})" + fi +} + +cmd_recover() { + local change_name="$1" + + validate_change_name "$change_name" + + local change_dir="openspec/changes/$change_name" + local yaml_file="$change_dir/.comet.yaml" + + if [ ! -f "$yaml_file" ]; then + red "ERROR: .comet.yaml not found at $yaml_file" + exit 1 + fi + + local phase workflow + phase=$(cmd_get "$change_name" "phase") + workflow=$(cmd_get "$change_name" "workflow") + + echo "=== Recovery Context: ${change_name} ===" + echo "Phase: ${phase}" + echo "Workflow: ${workflow}" + echo "" + + # Read all relevant fields + local design_doc plan verify_result verify_mode verification_report + local branch_status handoff_context handoff_hash isolation build_mode direct_override + design_doc=$(cmd_get "$change_name" "design_doc") + plan=$(cmd_get "$change_name" "plan") + verify_result=$(cmd_get "$change_name" "verify_result") + verify_mode=$(cmd_get "$change_name" "verify_mode") + verification_report=$(cmd_get "$change_name" "verification_report") + branch_status=$(cmd_get "$change_name" "branch_status") + handoff_context=$(cmd_get "$change_name" "handoff_context") + handoff_hash=$(cmd_get "$change_name" "handoff_hash") + isolation=$(cmd_get "$change_name" "isolation") + build_mode=$(cmd_get "$change_name" "build_mode") + direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true) + + echo "State fields:" + + # Phase-specific field reporting + case "$phase" in + open) + echo " Artifacts:" + for f in proposal.md design.md tasks.md; do + if file_nonempty "$change_dir/$f"; then + echo " - ${f}: DONE" + else + echo " - ${f}: PENDING" + fi + done + echo "" + echo "Recovery action: Create or complete missing artifacts, then use AskUserQuestion for user confirmation." + ;; + design) + echo " Artifacts:" + for f in proposal.md design.md tasks.md; do + if file_nonempty "$change_dir/$f"; then + echo " - ${f}: DONE" + else + echo " - ${f}: MISSING (unexpected in design phase)" + fi + done + echo "" + echo " Design progress:" + field_status "handoff_context" "$handoff_context" "$handoff_context" + field_status "handoff_hash" "$handoff_hash" + field_status "design_doc" "$design_doc" "$design_doc" + echo "" + if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then + echo "Recovery action: Design Doc already created and linked. Run guard to transition to build." + elif [ -n "$handoff_context" ] && [ "$handoff_context" != "null" ] && [ -f "$handoff_context" ]; then + echo "Recovery action: Handoff generated but Design Doc not yet created. Resume from brainstorming confirmation (Step 1c)." + else + echo "Recovery action: No handoff generated yet. Start from Step 1a (generate handoff package)." + fi + ;; + build) + echo " Build decisions:" + field_status "isolation" "$isolation" + field_status "build_mode" "$build_mode" + if [ "$build_mode" = "direct" ] && [ "$workflow" != "hotfix" ] && [ "$workflow" != "tweak" ]; then + field_status "direct_override" "$direct_override" + fi + echo "" + echo " Plan:" + field_status "plan" "$plan" "$plan" + echo "" + # Count completed vs pending tasks + local tasks_file="$change_dir/tasks.md" + local total=0 done=0 pending=0 + if [ -f "$tasks_file" ]; then + total=$(grep -c '^\- \[' "$tasks_file" 2>/dev/null || echo "0") + done=$(grep -c '^\- \[x\]' "$tasks_file" 2>/dev/null || echo "0") + pending=$((total - done)) + echo " Tasks: ${done}/${total} done, ${pending} pending" + else + echo " Tasks: tasks.md MISSING" + fi + echo "" + if [ "$isolation" = "null" ] || [ -z "$isolation" ]; then + echo "Recovery action: Isolation not selected. Use AskUserQuestion to ask user for branch/worktree choice." + elif [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; then + echo "Recovery action: Build mode not selected. Use AskUserQuestion to ask user for execution method." + elif [ ! -f "$tasks_file" ]; then + echo "Recovery action: tasks.md missing. Verify change directory integrity." + elif [ "$pending" -gt 0 ]; then + echo "Recovery action: Read tasks.md and continue from first unchecked task." + else + echo "Recovery action: All tasks done. Run guard to transition to verify." + fi + ;; + verify) + echo " Verification:" + field_status "verify_result" "$verify_result" + field_status "verify_mode" "$verify_mode" + field_status "verification_report" "$verification_report" "$verification_report" + field_status "branch_status" "$branch_status" + echo "" + if [ "$verify_result" = "pass" ] && [ "$branch_status" = "handled" ]; then + echo "Recovery action: Verification complete. Run guard to transition to archive." + elif [ "$verify_result" = "pass" ]; then + echo "Recovery action: Verification passed but branch not yet handled. Complete branch handling and set branch_status to handled." + elif [ "$verify_result" = "fail" ]; then + echo "Recovery action: Verification failed and rolled back to build. Resume from /comet-build." + else + echo "Recovery action: Verification not yet started or in progress. Run scale assessment then verify." + fi + ;; + archive) + echo " Archive:" + field_status "verify_result" "$verify_result" + field_status "archived" "$(cmd_get "$change_name" "archived")" + echo "" + echo "Recovery action: Run /comet-archive to complete archiving." + ;; + *) + red "ERROR: Unknown phase: $phase" + exit 1 + ;; + esac + + echo "" + echo "=== End Recovery Context ===" +} + +cmd_scale() { + local change_name="$1" + + validate_change_name "$change_name" + + local change_dir="openspec/changes/$change_name" + local yaml_file="$change_dir/.comet.yaml" + + # Verify .comet.yaml exists + if [ ! -f "$yaml_file" ]; then + red "ERROR: .comet.yaml not found at $yaml_file" + exit 1 + fi + + # Read metrics + # 1. Task count: count lines matching `- [` in tasks.md + local tasks_file="$change_dir/tasks.md" + local task_count=0 + if [ -f "$tasks_file" ]; then + task_count=$(grep -c '^\- \[' "$tasks_file" 2>/dev/null || echo "0") + fi + + # 2. Delta spec count: count files named spec.md under specs/*/spec.md + local delta_spec_count=0 + if [ -d "$change_dir/specs" ]; then + delta_spec_count=$(find "$change_dir/specs" -name "spec.md" -type f 2>/dev/null | wc -l | tr -d ' ') + fi + + # 3. Changed files: prefer plan base-ref, then .comet.yaml base_ref, fall back to worktree diff + local changed_files=0 + if git rev-parse --git-dir > /dev/null 2>&1; then + local plan_file base_ref="" + plan_file=$(cmd_get "$change_name" "plan" 2>/dev/null || true) + if [ -n "$plan_file" ] && [ "$plan_file" != "null" ] && [ -f "$plan_file" ]; then + base_ref=$(grep '^base-ref:' "$plan_file" 2>/dev/null | head -1 | sed 's/^base-ref: *//') + fi + # Fallback to base_ref stored in .comet.yaml (set during init) + if [ -z "$base_ref" ] || [ "$base_ref" = "null" ]; then + base_ref=$(cmd_get "$change_name" "base_ref" 2>/dev/null || true) + fi + + if [ -n "${base_ref:-}" ] && [ "$base_ref" != "null" ] && git rev-parse --verify "$base_ref" >/dev/null 2>&1; then + changed_files=$(git diff --name-only "$base_ref"...HEAD 2>/dev/null | wc -l | tr -d ' ') + else + changed_files=$(git diff --name-only HEAD 2>/dev/null | wc -l | tr -d ' ') + fi + fi + + # Decision rules + local result="light" + if [ "$task_count" -gt 3 ] || [ "$delta_spec_count" -gt 1 ] || [ "$changed_files" -gt 4 ]; then + result="full" + fi + + # Output assessment to stderr + echo "=== Scale Assessment: $change_name ===" >&2 + echo " Tasks: $task_count (threshold: 3)" >&2 + echo " Delta specs: $delta_spec_count capabilities (threshold: 1)" >&2 + echo " Changed files: $changed_files (threshold: 4)" >&2 + echo " → Result: $result" >&2 + + # Update verify_mode in .comet.yaml + replace_yaml_field "$yaml_file" "verify_mode" "$result" + + green "[SCALE] verify_mode=$result" +} + +# --- Main --- + +SUBCOMMAND="${1:-}" +shift || true + +case "$SUBCOMMAND" in + init) + if [ $# -lt 2 ]; then + red "Usage: comet-state.sh init <change-name> <workflow>" >&2 + red "Workflows: full, hotfix, tweak" >&2 + exit 1 + fi + cmd_init "$@" + ;; + get) + if [ $# -lt 2 ]; then + red "Usage: comet-state.sh get <change-name> <field>" >&2 + exit 1 + fi + cmd_get "$@" + ;; + set) + if [ $# -lt 3 ]; then + red "Usage: comet-state.sh set <change-name> <field> <value>" >&2 + exit 1 + fi + cmd_set "$@" + ;; + transition) + if [ $# -lt 2 ]; then + red "Usage: comet-state.sh transition <change-name> <event>" >&2 + red "Events: open-complete, design-complete, build-complete, verify-pass, verify-fail, archived" >&2 + exit 1 + fi + cmd_transition "$@" + ;; + check) + if [ $# -lt 2 ]; then + red "Usage: comet-state.sh check <change-name> <phase> [--recover]" >&2 + red "Phases: open, design, build, verify, archive" >&2 + exit 1 + fi + # Detect --recover flag (3rd argument) + if [ "${3:-}" = "--recover" ]; then + cmd_recover "$1" + else + cmd_check "$@" + fi + ;; + scale) + if [ $# -lt 1 ]; then + red "Usage: comet-state.sh scale <change-name>" >&2 + exit 1 + fi + cmd_scale "$@" + ;; + *) + red "Unknown subcommand: $SUBCOMMAND" >&2 + echo "" >&2 + echo "Usage: comet-state.sh <subcommand> <change-name> [args...]" >&2 + echo "" >&2 + echo "Subcommands:" >&2 + echo " init <change-name> <workflow> — Initialize .comet.yaml with workflow defaults" >&2 + echo " get <change-name> <field> — Read a field value from .comet.yaml" >&2 + echo " set <change-name> <field> <val> — Update a field value in .comet.yaml" >&2 + echo " transition <change-name> <event> — Apply a validated state transition" >&2 + echo " check <change-name> <phase> — Verify entry requirements for a phase" >&2 + echo " scale <change-name> — Assess and set verification mode based on metrics" >&2 + echo "" >&2 + echo "Workflows: full, hotfix, tweak" >&2 + echo "Phases for check: open, design, build, verify, archive" >&2 + exit 1 + ;; +esac diff --git a/.claude/skills/comet/scripts/comet-yaml-validate.sh b/.claude/skills/comet/scripts/comet-yaml-validate.sh new file mode 100644 index 0000000..5deae27 --- /dev/null +++ b/.claude/skills/comet/scripts/comet-yaml-validate.sh @@ -0,0 +1,198 @@ +#!/bin/bash +# Comet YAML Schema Validator — validates .comet.yaml structure +# Usage: comet-yaml-validate.sh <change-name> +# Exit 0 = valid, exit 1 = errors found (printed to stderr) + +set -euo pipefail + +red() { echo -e "\033[31m$1\033[0m" >&2; } +green() { echo -e "\033[32m$1\033[0m" >&2; } +warn() { echo -e "\033[33m$1\033[0m" >&2; } + +# Input validation - prevent path traversal +validate_change_name() { + local name="$1" + # Reject empty names + if [ -z "$name" ]; then + red "ERROR: Change name cannot be empty" >&2 + exit 1 + fi + # Only allow alphanumeric, hyphens, and underscores + if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then + red "ERROR: Invalid change name: '$name'" >&2 + red "Valid characters: a-z, A-Z, 0-9, -, _" >&2 + exit 1 + fi + # Reject path traversal attempts + if [[ "$name" =~ \.\. ]]; then + red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2 + exit 1 + fi +} + +validate_change_name "$1" + +CHANGE="$1" +CHANGE_DIR="openspec/changes/$CHANGE" +if [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then + CHANGE_DIR="openspec/changes/archive/$CHANGE" +fi +YAML="$CHANGE_DIR/.comet.yaml" + +ERRORS=0 +WARNINGS=0 + +# Helper: get value of a top-level field (handles null, empty, quoted) +field_value() { + local value + value=$(grep "^${1}:" "$YAML" 2>/dev/null | sed "s/^${1}: *//" || true) + value=$(strip_inline_comment "$value") + strip_wrapping_quotes "$value" +} + +strip_inline_comment() { + local value="$1" + printf '%s\n' "$value" | awk -v squote="'" ' + { + out = "" + quote = "" + for (i = 1; i <= length($0); i++) { + c = substr($0, i, 1) + if (quote == "") { + if (c == "\"" || c == squote) { + quote = c + } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) { + sub(/[[:space:]]+$/, "", out) + print out + next + } + } else if (c == quote) { + quote = "" + } + out = out c + } + print out + } + ' +} + +strip_wrapping_quotes() { + local value="$1" + case "$value" in + \"*\") + printf '%s\n' "${value:1:${#value}-2}" + ;; + \'*\') + printf '%s\n' "${value:1:${#value}-2}" + ;; + *) + printf '%s\n' "$value" + ;; + esac +} + +fail() { red " FAIL: $1"; ERRORS=$((ERRORS + 1)); } +warn_msg() { warn " WARN: $1"; WARNINGS=$((WARNINGS + 1)); } + +echo "[VALIDATE] $YAML" >&2 + +# --- Required fields --- +REQUIRED_FIELDS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verified_at archived" +for field in $REQUIRED_FIELDS; do + if ! grep -q "^${field}:" "$YAML" 2>/dev/null; then + fail "missing required field '$field'" + fi +done + +# --- Enum validation --- +validate_enum() { + local field="$1" value="$2" + shift 2 + local valid_values="$*" + + # null or empty is always acceptable + if [ -z "$value" ] || [ "$value" = "null" ]; then + return 0 + fi + + for v in $valid_values; do + if [ "$value" = "$v" ]; then + return 0 + fi + done + fail "$field='$value' is not valid. Expected: $valid_values" +} + +workflow=$(field_value "workflow") +phase=$(field_value "phase") +build_mode=$(field_value "build_mode") +isolation=$(field_value "isolation") +verify_mode=$(field_value "verify_mode") +verify_result=$(field_value "verify_result") +branch_status=$(field_value "branch_status") +archived=$(field_value "archived") +direct_override=$(field_value "direct_override") +design_doc=$(field_value "design_doc") +plan=$(field_value "plan") +handoff_context=$(field_value "handoff_context") +handoff_hash=$(field_value "handoff_hash") + +validate_enum "workflow" "$workflow" "full hotfix tweak" +validate_enum "phase" "$phase" "open design build verify archive" +validate_enum "build_mode" "$build_mode" "subagent-driven-development executing-plans direct" +validate_enum "isolation" "$isolation" "branch worktree" +validate_enum "verify_mode" "$verify_mode" "light full" +validate_enum "verify_result" "$verify_result" "pending pass fail" +validate_enum "branch_status" "$branch_status" "pending handled" +validate_enum "archived" "$archived" "true false" +validate_enum "direct_override" "$direct_override" "true false" + +# --- Path validation --- + +if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then + if [ ! -f "$design_doc" ]; then + fail "design_doc='$design_doc' does not exist on disk" + fi +fi + +if [ -n "$plan" ] && [ "$plan" != "null" ]; then + if [ ! -f "$plan" ]; then + fail "plan='$plan' does not exist on disk" + fi +fi + +if [ -n "$handoff_context" ] && [ "$handoff_context" != "null" ]; then + if [ ! -f "$handoff_context" ]; then + fail "handoff_context='$handoff_context' does not exist on disk" + fi +fi + +if [ -n "$handoff_hash" ] && [ "$handoff_hash" != "null" ]; then + if [[ ! "$handoff_hash" =~ ^[a-f0-9]{64}$ ]]; then + fail "handoff_hash='$handoff_hash' is not a sha256 hex digest" + fi +fi + +# --- Unknown keys check --- +KNOWN_KEYS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verification_report branch_status verified_at created_at archived direct_override build_command verify_command handoff_context handoff_hash base_ref" +while IFS=: read -r key _; do + key="${key// /}" + [ -z "$key" ] && continue + found=0 + for known in $KNOWN_KEYS; do + [ "$key" = "$known" ] && found=1 && break + done + if [ "$found" -eq 0 ]; then + warn_msg "unknown field '$key' found" + fi +done < "$YAML" + +# --- Summary --- +echo "" >&2 +if [ "$ERRORS" -gt 0 ]; then + red "$ERRORS error(s), $WARNINGS warning(s) — validation FAILED" + exit 1 +else + green "0 errors, $WARNINGS warning(s) — validation PASSED" + exit 0 +fi diff --git a/.claude/skills/evolve b/.claude/skills/evolve new file mode 120000 index 0000000..15707cf --- /dev/null +++ b/.claude/skills/evolve @@ -0,0 +1 @@ +../../.agents/skills/evolve \ No newline at end of file diff --git a/.claude/skills/openspec-apply-change/SKILL.md b/.claude/skills/openspec-apply-change/SKILL.md new file mode 100644 index 0000000..70fbdb8 --- /dev/null +++ b/.claude/skills/openspec-apply-change/SKILL.md @@ -0,0 +1,156 @@ +--- +name: openspec-apply-change +description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: <name>" and how to override (e.g., `/opsx:apply <other>`). + +2. **Check status to understand the schema** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "<name>" --json + ``` + + This returns: + - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read every file path listed under `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: <change-name> (schema: <schema-name>) + +Working on task 3/7: <task description> +[...implementation happening...] +✓ Task complete + +Working on task 4/7: <task description> +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! Ready to archive this change. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** <change-name> +**Schema:** <schema-name> +**Progress:** 4/7 tasks complete + +### Issue Encountered +<description of the issue> + +**Options:** +1. <option 1> +2. <option 2> +3. Other approach + +What would you like to do? +``` + +**Guardrails** +- Keep going through tasks until done or blocked +- Always read context files before starting (from the apply instructions output) +- If task is ambiguous, pause and ask before implementing +- If implementation reveals issues, pause and suggest artifact updates +- Keep code changes minimal and scoped to each task +- Update task checkbox immediately after completing each task +- Pause on errors, blockers, or unclear requirements - don't guess +- Use contextFiles from CLI output, don't assume specific file names + +**Fluid Workflow Integration** + +This skill supports the "actions on a change" model: + +- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions +- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly diff --git a/.claude/skills/openspec-archive-change/SKILL.md b/.claude/skills/openspec-archive-change/SKILL.md new file mode 100644 index 0000000..12e2f70 --- /dev/null +++ b/.claude/skills/openspec-archive-change/SKILL.md @@ -0,0 +1,114 @@ +--- +name: openspec-archive-change +description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Archive a completed change in the experimental workflow. + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show only active changes (not already archived). + Include the schema used for each change if available. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check artifact completion status** + + Run `openspec status --change "<name>" --json` to check artifact completion. + + Parse the JSON to understand: + - `schemaName`: The workflow being used + - `artifacts`: List of artifacts with their status (`done` or other) + + **If any artifacts are not `done`:** + - Display warning listing incomplete artifacts + - Use **AskUserQuestion tool** to confirm user wants to proceed + - Proceed if user confirms + +3. **Check task completion status** + + Read the tasks file (typically `tasks.md`) to check for incomplete tasks. + + Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete). + + **If incomplete tasks found:** + - Display warning showing count of incomplete tasks + - Use **AskUserQuestion tool** to confirm user wants to proceed + - Proceed if user confirms + + **If no tasks file exists:** Proceed without task-related warning. + +4. **Assess delta spec sync state** + + Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt. + + **If delta specs exist:** + - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md` + - Determine what changes would be applied (adds, modifications, removals, renames) + - Show a combined summary before prompting + + **Prompt options:** + - If changes needed: "Sync now (recommended)", "Archive without syncing" + - If already synced: "Archive now", "Sync anyway", "Cancel" + + If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice. + +5. **Perform the archive** + + Create the archive directory if it doesn't exist: + ```bash + mkdir -p openspec/changes/archive + ``` + + Generate target name using current date: `YYYY-MM-DD-<change-name>` + + **Check if target already exists:** + - If yes: Fail with error, suggest renaming existing archive or using different date + - If no: Move the change directory to archive + + ```bash + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + +6. **Display summary** + + Show archive completion summary including: + - Change name + - Schema that was used + - Archive location + - Whether specs were synced (if applicable) + - Note about any warnings (incomplete artifacts/tasks) + +**Output On Success** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped") + +All artifacts complete. All tasks complete. +``` + +**Guardrails** +- Always prompt for change selection if not provided +- Use artifact graph (openspec status --json) for completion checking +- Don't block archive on warnings - just inform and confirm +- Preserve .openspec.yaml when moving to archive (it moves with the directory) +- Show clear summary of what happened +- If sync is requested, use openspec-sync-specs approach (agent-driven) +- If delta specs exist, always run the sync assessment and show the combined summary before prompting diff --git a/.claude/skills/openspec-bulk-archive-change/SKILL.md b/.claude/skills/openspec-bulk-archive-change/SKILL.md new file mode 100644 index 0000000..5be81af --- /dev/null +++ b/.claude/skills/openspec-bulk-archive-change/SKILL.md @@ -0,0 +1,246 @@ +--- +name: openspec-bulk-archive-change +description: Archive multiple completed changes at once. Use when archiving several parallel changes. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Archive multiple completed changes in a single operation. + +This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented. + +**Input**: None required (prompts for selection) + +**Steps** + +1. **Get active changes** + + Run `openspec list --json` to get all active changes. + + If no active changes exist, inform user and stop. + +2. **Prompt for change selection** + + Use **AskUserQuestion tool** with multi-select to let user choose changes: + - Show each change with its schema + - Include an option for "All changes" + - Allow any number of selections (1+ works, 2+ is the typical use case) + + **IMPORTANT**: Do NOT auto-select. Always let the user choose. + +3. **Batch validation - gather status for all selected changes** + + For each selected change, collect: + + a. **Artifact status** - Run `openspec status --change "<name>" --json` + - Parse `schemaName` and `artifacts` list + - Note which artifacts are `done` vs other states + + b. **Task completion** - Read `openspec/changes/<name>/tasks.md` + - Count `- [ ]` (incomplete) vs `- [x]` (complete) + - If no tasks file exists, note as "No tasks" + + c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory + - List which capability specs exist + - For each, extract requirement names (lines matching `### Requirement: <name>`) + +4. **Detect spec conflicts** + + Build a map of `capability -> [changes that touch it]`: + + ``` + auth -> [change-a, change-b] <- CONFLICT (2+ changes) + api -> [change-c] <- OK (only 1 change) + ``` + + A conflict exists when 2+ selected changes have delta specs for the same capability. + +5. **Resolve conflicts agentically** + + **For each conflict**, investigate the codebase: + + a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify + + b. **Search the codebase** for implementation evidence: + - Look for code implementing requirements from each delta spec + - Check for related files, functions, or tests + + c. **Determine resolution**: + - If only one change is actually implemented -> sync that one's specs + - If both implemented -> apply in chronological order (older first, newer overwrites) + - If neither implemented -> skip spec sync, warn user + + d. **Record resolution** for each conflict: + - Which change's specs to apply + - In what order (if both) + - Rationale (what was found in codebase) + +6. **Show consolidated status table** + + Display a table summarizing all changes: + + ``` + | Change | Artifacts | Tasks | Specs | Conflicts | Status | + |---------------------|-----------|-------|---------|-----------|--------| + | schema-management | Done | 5/5 | 2 delta | None | Ready | + | project-config | Done | 3/3 | 1 delta | None | Ready | + | add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* | + | add-verify-skill | 1 left | 2/5 | None | None | Warn | + ``` + + For conflicts, show the resolution: + ``` + * Conflict resolution: + - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) + ``` + + For incomplete changes, show warnings: + ``` + Warnings: + - add-verify-skill: 1 incomplete artifact, 3 incomplete tasks + ``` + +7. **Confirm batch operation** + + Use **AskUserQuestion tool** with a single confirmation: + + - "Archive N changes?" with options based on status + - Options might include: + - "Archive all N changes" + - "Archive only N ready changes (skip incomplete)" + - "Cancel" + + If there are incomplete changes, make clear they'll be archived with warnings. + +8. **Execute archive for each confirmed change** + + Process changes in the determined order (respecting conflict resolution): + + a. **Sync specs** if delta specs exist: + - Use the openspec-sync-specs approach (agent-driven intelligent merge) + - For conflicts, apply in resolved order + - Track if sync was done + + b. **Perform the archive**: + ```bash + mkdir -p openspec/changes/archive + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + + c. **Track outcome** for each change: + - Success: archived successfully + - Failed: error during archive (record error) + - Skipped: user chose not to archive (if applicable) + +9. **Display summary** + + Show final results: + + ``` + ## Bulk Archive Complete + + Archived 3 changes: + - schema-management-cli -> archive/2026-01-19-schema-management-cli/ + - project-config -> archive/2026-01-19-project-config/ + - add-oauth -> archive/2026-01-19-add-oauth/ + + Skipped 1 change: + - add-verify-skill (user chose not to archive incomplete) + + Spec sync summary: + - 4 delta specs synced to main specs + - 1 conflict resolved (auth: applied both in chronological order) + ``` + + If any failures: + ``` + Failed 1 change: + - some-change: Archive directory already exists + ``` + +**Conflict Resolution Examples** + +Example 1: Only one implemented +``` +Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt] + +Checking add-oauth: +- Delta adds "OAuth Provider Integration" requirement +- Searching codebase... found src/auth/oauth.ts implementing OAuth flow + +Checking add-jwt: +- Delta adds "JWT Token Handling" requirement +- Searching codebase... no JWT implementation found + +Resolution: Only add-oauth is implemented. Will sync add-oauth specs only. +``` + +Example 2: Both implemented +``` +Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql] + +Checking add-rest-api (created 2026-01-10): +- Delta adds "REST Endpoints" requirement +- Searching codebase... found src/api/rest.ts + +Checking add-graphql (created 2026-01-15): +- Delta adds "GraphQL Schema" requirement +- Searching codebase... found src/api/graphql.ts + +Resolution: Both implemented. Will apply add-rest-api specs first, +then add-graphql specs (chronological order, newer takes precedence). +``` + +**Output On Success** + +``` +## Bulk Archive Complete + +Archived N changes: +- <change-1> -> archive/YYYY-MM-DD-<change-1>/ +- <change-2> -> archive/YYYY-MM-DD-<change-2>/ + +Spec sync summary: +- N delta specs synced to main specs +- No conflicts (or: M conflicts resolved) +``` + +**Output On Partial Success** + +``` +## Bulk Archive Complete (partial) + +Archived N changes: +- <change-1> -> archive/YYYY-MM-DD-<change-1>/ + +Skipped M changes: +- <change-2> (user chose not to archive incomplete) + +Failed K changes: +- <change-3>: Archive directory already exists +``` + +**Output When No Changes** + +``` +## No Changes to Archive + +No active changes found. Create a new change to get started. +``` + +**Guardrails** +- Allow any number of changes (1+ is fine, 2+ is the typical use case) +- Always prompt for selection, never auto-select +- Detect spec conflicts early and resolve by checking codebase +- When both changes are implemented, apply specs in chronological order +- Skip spec sync only when implementation is missing (warn user) +- Show clear per-change status before confirming +- Use single confirmation for entire batch +- Track and report all outcomes (success/skip/fail) +- Preserve .openspec.yaml when moving to archive +- Archive directory target uses current date: YYYY-MM-DD-<name> +- If archive target exists, fail that change but continue with others diff --git a/.claude/skills/openspec-continue-change/SKILL.md b/.claude/skills/openspec-continue-change/SKILL.md new file mode 100644 index 0000000..4f2c3dc --- /dev/null +++ b/.claude/skills/openspec-continue-change/SKILL.md @@ -0,0 +1,118 @@ +--- +name: openspec-continue-change +description: Continue working on an OpenSpec change by creating the next artifact. Use when the user wants to progress their change, create the next artifact, or continue their workflow. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Continue working on a change by creating the next artifact. + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on. + + Present the top 3-4 most recently modified changes as options, showing: + - Change name + - Schema (from `schema` field if present, otherwise "spec-driven") + - Status (e.g., "0/5 tasks", "complete", "no tasks") + - How recently it was modified (from `lastModified` field) + + Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check current status** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand current state. The response includes: + - `schemaName`: The workflow schema being used (e.g., "spec-driven") + - `artifacts`: Array of artifacts with their status ("done", "ready", "blocked") + - `isComplete`: Boolean indicating if all artifacts are complete + +3. **Act based on status**: + + --- + + **If all artifacts are complete (`isComplete: true`)**: + - Congratulate the user + - Show final status including the schema used + - Suggest: "All artifacts created! You can now implement this change or archive it." + - STOP + + --- + + **If artifacts are ready to create** (status shows artifacts with `status: "ready"`): + - Pick the FIRST artifact with `status: "ready"` from the status output + - Get its instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - Parse the JSON. The key fields are: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - **Create the artifact file**: + - Read any completed dependency files for context + - Use `template` as the structure - fill in its sections + - Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file + - Write to the output path specified in instructions + - Show what was created and what's now unlocked + - STOP after creating ONE artifact + + --- + + **If no artifacts are ready (all blocked)**: + - This shouldn't happen with a valid schema + - Show status and suggest checking for issues + +4. **After creating an artifact, show progress** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After each invocation, show: +- Which artifact was created +- Schema workflow being used +- Current progress (N/M complete) +- What artifacts are now unlocked +- Prompt: "Want to continue? Just ask me to continue or tell me what to do next." + +**Artifact Creation Guidelines** + +The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create. + +Common artifact patterns: + +**spec-driven schema** (proposal → specs → design → tasks): +- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact. + - The Capabilities section is critical - each capability listed will need a spec file. +- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name). +- **design.md**: Document technical decisions, architecture, and implementation approach. +- **tasks.md**: Break down implementation into checkboxed tasks. + +For other schemas, follow the `instruction` field from the CLI output. + +**Guardrails** +- Create ONE artifact per invocation +- Always read dependency artifacts before creating a new one +- Never skip artifacts or create out of order +- If context is unclear, ask the user before creating +- Verify the artifact file exists after writing before marking progress +- Use the schema's artifact sequence, don't assume specific artifact names +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output diff --git a/.claude/skills/openspec-explore/SKILL.md b/.claude/skills/openspec-explore/SKILL.md new file mode 100644 index 0000000..6858d3f --- /dev/null +++ b/.claude/skills/openspec-explore/SKILL.md @@ -0,0 +1,288 @@ +--- +name: openspec-explore +description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. + +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. + +**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. + +--- + +## The Stance + +- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script +- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions. +- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking +- **Adaptive** - Follow interesting threads, pivot when new information emerges +- **Patient** - Don't rush to conclusions, let the shape of the problem emerge +- **Grounded** - Explore the actual codebase when relevant, don't just theorize + +--- + +## What You Might Do + +Depending on what the user brings, you might: + +**Explore the problem space** +- Ask clarifying questions that emerge from what they said +- Challenge assumptions +- Reframe the problem +- Find analogies + +**Investigate the codebase** +- Map existing architecture relevant to the discussion +- Find integration points +- Identify patterns already in use +- Surface hidden complexity + +**Compare options** +- Brainstorm multiple approaches +- Build comparison tables +- Sketch tradeoffs +- Recommend a path (if asked) + +**Visualize** +``` +┌─────────────────────────────────────────┐ +│ Use ASCII diagrams liberally │ +├─────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ │ +│ │ State │────────▶│ State │ │ +│ │ A │ │ B │ │ +│ └────────┘ └────────┘ │ +│ │ +│ System diagrams, state machines, │ +│ data flows, architecture sketches, │ +│ dependency graphs, comparison tables │ +│ │ +└─────────────────────────────────────────┘ +``` + +**Surface risks and unknowns** +- Identify what could go wrong +- Find gaps in understanding +- Suggest spikes or investigations + +--- + +## OpenSpec Awareness + +You have full context of the OpenSpec system. Use it naturally, don't force it. + +### Check for context + +At the start, quickly check what exists: +```bash +openspec list --json +``` + +This tells you: +- If there are active changes +- Their names, schemas, and status +- What the user might be working on + +### When no change exists + +Think freely. When insights crystallize, you might offer: + +- "This feels solid enough to start a change. Want me to create a proposal?" +- Or keep exploring - no pressure to formalize + +### When a change exists + +If the user mentions a change or you detect one is relevant: + +1. **Read existing artifacts for context** + - `openspec/changes/<name>/proposal.md` + - `openspec/changes/<name>/design.md` + - `openspec/changes/<name>/tasks.md` + - etc. + +2. **Reference them naturally in conversation** + - "Your design mentions using Redis, but we just realized SQLite fits better..." + - "The proposal scopes this to premium users, but we're now thinking everyone..." + +3. **Offer to capture when decisions are made** + + | Insight Type | Where to Capture | + |----------------------------|--------------------------------| + | New requirement discovered | `specs/<capability>/spec.md` | + | Requirement changed | `specs/<capability>/spec.md` | + | Design decision made | `design.md` | + | Scope changed | `proposal.md` | + | New work identified | `tasks.md` | + | Assumption invalidated | Relevant artifact | + + Example offers: + - "That's a design decision. Capture it in design.md?" + - "This is a new requirement. Add it to specs?" + - "This changes scope. Update the proposal?" + +4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture. + +--- + +## What You Don't Have To Do + +- Follow a script +- Ask the same questions every time +- Produce a specific artifact +- Reach a conclusion +- Stay on topic if a tangent is valuable +- Be brief (this is thinking time) + +--- + +## Handling Different Entry Points + +**User brings a vague idea:** +``` +User: I'm thinking about adding real-time collaboration + +You: Real-time collab is a big space. Let me think about this... + + COLLABORATION SPECTRUM + ════════════════════════════════════════════ + + Awareness Coordination Sync + │ │ │ + ▼ ▼ ▼ + ┌────────┐ ┌────────┐ ┌────────┐ + │Presence│ │Cursors │ │ CRDT │ + │ "3 │ │ Multi │ │Conflict│ + │online" │ │ select │ │ free │ + └────────┘ └────────┘ └────────┘ + │ │ │ + trivial moderate complex + + Where's your head at? +``` + +**User brings a specific problem:** +``` +User: The auth system is a mess + +You: [reads codebase] + + ┌─────────────────────────────────────────────┐ + │ CURRENT AUTH FLOW │ + └─────────────────────────────────────────────┘ + │ + ┌─────────────┼─────────────┐ + ▼ ▼ ▼ + ┌─────────┐ ┌─────────┐ ┌─────────┐ + │ Google │ │ GitHub │ │ Email │ + │ OAuth │ │ OAuth │ │ Magic │ + └────┬────┘ └────┬────┘ └────┬────┘ + │ │ │ + └─────────────┼─────────────┘ + ▼ + ┌───────────┐ + │ Session │ + └─────┬─────┘ + │ + ▼ + ┌───────────┐ + │ Perms │ + └───────────┘ + + I see three tangles. Which one's burning? +``` + +**User is stuck mid-implementation:** +``` +User: /opsx:explore add-auth-system + The OAuth integration is more complex than expected + +You: [reads change artifacts] + + You're on task 4: "Implement OAuth flow" + + Let me trace what's involved... + + [draws diagram, explores options, suggests paths] + + Want to update the design to reflect this? + Or add a spike task to investigate? +``` + +**User wants to compare options:** +``` +User: Should we use Postgres or SQLite? + +You: Generic answer is boring. What's the context? + +User: A CLI tool that tracks local dev environments + +You: That changes everything. + + ┌─────────────────────────────────────────────────┐ + │ CLI TOOL DATA STORAGE │ + └─────────────────────────────────────────────────┘ + + Key constraints: + • No daemon running + • Must work offline + • Single user + + SQLite Postgres + Deployment embedded ✓ needs server ✗ + Offline yes ✓ no ✗ + Single file yes ✓ no ✗ + + SQLite. Not even close. + + Unless... is there a sync component? +``` + +--- + +## Ending Discovery + +There's no required ending. Discovery might: + +- **Flow into a proposal**: "Ready to start? I can create a change proposal." +- **Result in artifact updates**: "Updated design.md with these decisions" +- **Just provide clarity**: User has what they need, moves on +- **Continue later**: "We can pick this up anytime" + +When it feels like things are crystallizing, you might summarize: + +``` +## What We Figured Out + +**The problem**: [crystallized understanding] + +**The approach**: [if one emerged] + +**Open questions**: [if any remain] + +**Next steps** (if ready): +- Create a change proposal +- Keep exploring: just keep talking +``` + +But this summary is optional. Sometimes the thinking IS the value. + +--- + +## Guardrails + +- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't fake understanding** - If something is unclear, dig deeper +- **Don't rush** - Discovery is thinking time, not task time +- **Don't force structure** - Let patterns emerge naturally +- **Don't auto-capture** - Offer to save insights, don't just do it +- **Do visualize** - A good diagram is worth many paragraphs +- **Do explore the codebase** - Ground discussions in reality +- **Do question assumptions** - Including the user's and your own diff --git a/.claude/skills/openspec-ff-change/SKILL.md b/.claude/skills/openspec-ff-change/SKILL.md new file mode 100644 index 0000000..43f2632 --- /dev/null +++ b/.claude/skills/openspec-ff-change/SKILL.md @@ -0,0 +1,101 @@ +--- +name: openspec-ff-change +description: Fast-forward through OpenSpec artifact creation. Use when the user wants to quickly create all artifacts needed for implementation without stepping through each one individually. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Fast-forward through artifact creation - generate everything needed to start implementation in one go. + +**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build. + +**Steps** + +1. **If no clear input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change at `openspec/changes/<name>/`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "✓ Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, suggest continuing that change instead +- Verify each artifact file exists after writing before proceeding to next diff --git a/.claude/skills/openspec-new-change/SKILL.md b/.claude/skills/openspec-new-change/SKILL.md new file mode 100644 index 0000000..1af41c7 --- /dev/null +++ b/.claude/skills/openspec-new-change/SKILL.md @@ -0,0 +1,74 @@ +--- +name: openspec-new-change +description: Start a new OpenSpec change using the experimental artifact workflow. Use when the user wants to create a new feature, fix, or modification with a structured step-by-step approach. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Start a new change using the experimental artifact-driven approach. + +**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build. + +**Steps** + +1. **If no clear input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Determine the workflow schema** + + Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow. + + **Use a different schema only if the user mentions:** + - A specific schema name → use `--schema <name>` + - "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose + + **Otherwise**: Omit `--schema` to use the default. + +3. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + Add `--schema <name>` only if the user requested a specific workflow. + This creates a scaffolded change at `openspec/changes/<name>/` with the selected schema. + +4. **Show the artifact status** + ```bash + openspec status --change "<name>" + ``` + This shows which artifacts need to be created and which are ready (dependencies satisfied). + +5. **Get instructions for the first artifact** + The first artifact depends on the schema (e.g., `proposal` for spec-driven). + Check the status output to find the first artifact with status "ready". + ```bash + openspec instructions <first-artifact-id> --change "<name>" + ``` + This outputs the template and context for creating the first artifact. + +6. **STOP and wait for user direction** + +**Output** + +After completing the steps, summarize: +- Change name and location +- Schema/workflow being used and its artifact sequence +- Current status (0/N artifacts complete) +- The template for the first artifact +- Prompt: "Ready to create the first artifact? Just describe what this change is about and I'll draft it, or ask me to continue." + +**Guardrails** +- Do NOT create any artifacts yet - just show the instructions +- Do NOT advance beyond showing the first artifact template +- If the name is invalid (not kebab-case), ask for a valid name +- If a change with that name already exists, suggest continuing that change instead +- Pass --schema if using a non-default workflow diff --git a/.claude/skills/openspec-onboard/SKILL.md b/.claude/skills/openspec-onboard/SKILL.md new file mode 100644 index 0000000..b6c734d --- /dev/null +++ b/.claude/skills/openspec-onboard/SKILL.md @@ -0,0 +1,554 @@ +--- +name: openspec-onboard +description: Guided onboarding for OpenSpec - walk through a complete workflow cycle with narration and real codebase work. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step. + +--- + +## Preflight + +Before starting, check if the OpenSpec CLI is installed: + +```bash +# Unix/macOS +openspec --version 2>&1 || echo "CLI_NOT_INSTALLED" +# Windows (PowerShell) +# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" } +``` + +**If CLI not installed:** +> OpenSpec CLI is not installed. Install it first, then come back to `/opsx:onboard`. + +Stop here if not installed. + +--- + +## Phase 1: Welcome + +Display: + +``` +## Welcome to OpenSpec! + +I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it. + +**What we'll do:** +1. Pick a small, real task in your codebase +2. Explore the problem briefly +3. Create a change (the container for our work) +4. Build the artifacts: proposal → specs → design → tasks +5. Implement the tasks +6. Archive the completed change + +**Time:** ~15-20 minutes + +Let's start by finding something to work on. +``` + +--- + +## Phase 2: Task Selection + +### Codebase Analysis + +Scan the codebase for small improvement opportunities. Look for: + +1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files +2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch +3. **Functions without tests** - Cross-reference `src/` with test directories +4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`) +5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code +6. **Missing validation** - User input handlers without validation + +Also check recent git activity: +```bash +# Unix/macOS +git log --oneline -10 2>/dev/null || echo "No git history" +# Windows (PowerShell) +# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" } +``` + +### Present Suggestions + +From your analysis, present 3-4 specific suggestions: + +``` +## Task Suggestions + +Based on scanning your codebase, here are some good starter tasks: + +**1. [Most promising task]** + Location: `src/path/to/file.ts:42` + Scope: ~1-2 files, ~20-30 lines + Why it's good: [brief reason] + +**2. [Second task]** + Location: `src/another/file.ts` + Scope: ~1 file, ~15 lines + Why it's good: [brief reason] + +**3. [Third task]** + Location: [location] + Scope: [estimate] + Why it's good: [brief reason] + +**4. Something else?** + Tell me what you'd like to work on. + +Which task interests you? (Pick a number or describe your own) +``` + +**If nothing found:** Fall back to asking what the user wants to build: +> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix? + +### Scope Guardrail + +If the user picks or describes something too large (major feature, multi-day work): + +``` +That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through. + +For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details. + +**Options:** +1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]? +2. **Pick something else** - One of the other suggestions, or a different small task? +3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer. + +What would you prefer? +``` + +Let the user override if they insist—this is a soft guardrail. + +--- + +## Phase 3: Explore Demo + +Once a task is selected, briefly demonstrate explore mode: + +``` +Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction. +``` + +Spend 1-2 minutes investigating the relevant code: +- Read the file(s) involved +- Draw a quick ASCII diagram if it helps +- Note any considerations + +``` +## Quick Exploration + +[Your brief analysis—what you found, any considerations] + +┌─────────────────────────────────────────┐ +│ [Optional: ASCII diagram if helpful] │ +└─────────────────────────────────────────┘ + +Explore mode (`/opsx:explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem. + +Now let's create a change to hold our work. +``` + +**PAUSE** - Wait for user acknowledgment before proceeding. + +--- + +## Phase 4: Create the Change + +**EXPLAIN:** +``` +## Creating a Change + +A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks. + +Let me create one for our task. +``` + +**DO:** Create the change with a derived kebab-case name: +```bash +openspec new change "<derived-name>" +``` + +**SHOW:** +``` +Created: `openspec/changes/<name>/` + +The folder structure: +``` +openspec/changes/<name>/ +├── proposal.md ← Why we're doing this (empty, we'll fill it) +├── design.md ← How we'll build it (empty) +├── specs/ ← Detailed requirements (empty) +└── tasks.md ← Implementation checklist (empty) +``` + +Now let's fill in the first artifact—the proposal. +``` + +--- + +## Phase 5: Proposal + +**EXPLAIN:** +``` +## The Proposal + +The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work. + +I'll draft one based on our task. +``` + +**DO:** Draft the proposal content (don't save yet): + +``` +Here's a draft proposal: + +--- + +## Why + +[1-2 sentences explaining the problem/opportunity] + +## What Changes + +[Bullet points of what will be different] + +## Capabilities + +### New Capabilities +- `<capability-name>`: [brief description] + +### Modified Capabilities +<!-- If modifying existing behavior --> + +## Impact + +- `src/path/to/file.ts`: [what changes] +- [other files if applicable] + +--- + +Does this capture the intent? I can adjust before we save it. +``` + +**PAUSE** - Wait for user approval/feedback. + +After approval, save the proposal: +```bash +openspec instructions proposal --change "<name>" --json +``` +Then write the content to `openspec/changes/<name>/proposal.md`. + +``` +Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves. + +Next up: specs. +``` + +--- + +## Phase 6: Specs + +**EXPLAIN:** +``` +## Specs + +Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear. + +For a small task like this, we might only need one spec file. +``` + +**DO:** Create the spec file: +```bash +# Unix/macOS +mkdir -p openspec/changes/<name>/specs/<capability-name> +# Windows (PowerShell) +# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>" +``` + +Draft the spec content: + +``` +Here's the spec: + +--- + +## ADDED Requirements + +### Requirement: <Name> + +<Description of what the system should do> + +#### Scenario: <Scenario name> + +- **WHEN** <trigger condition> +- **THEN** <expected outcome> +- **AND** <additional outcome if needed> + +--- + +This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases. +``` + +Save to `openspec/changes/<name>/specs/<capability>/spec.md`. + +--- + +## Phase 7: Design + +**EXPLAIN:** +``` +## Design + +The design captures **how** we'll build it—technical decisions, tradeoffs, approach. + +For small changes, this might be brief. That's fine—not every change needs deep design discussion. +``` + +**DO:** Draft design.md: + +``` +Here's the design: + +--- + +## Context + +[Brief context about the current state] + +## Goals / Non-Goals + +**Goals:** +- [What we're trying to achieve] + +**Non-Goals:** +- [What's explicitly out of scope] + +## Decisions + +### Decision 1: [Key decision] + +[Explanation of approach and rationale] + +--- + +For a small task, this captures the key decisions without over-engineering. +``` + +Save to `openspec/changes/<name>/design.md`. + +--- + +## Phase 8: Tasks + +**EXPLAIN:** +``` +## Tasks + +Finally, we break the work into implementation tasks—checkboxes that drive the apply phase. + +These should be small, clear, and in logical order. +``` + +**DO:** Generate tasks based on specs and design: + +``` +Here are the implementation tasks: + +--- + +## 1. [Category or file] + +- [ ] 1.1 [Specific task] +- [ ] 1.2 [Specific task] + +## 2. Verify + +- [ ] 2.1 [Verification step] + +--- + +Each checkbox becomes a unit of work in the apply phase. Ready to implement? +``` + +**PAUSE** - Wait for user to confirm they're ready to implement. + +Save to `openspec/changes/<name>/tasks.md`. + +--- + +## Phase 9: Apply (Implementation) + +**EXPLAIN:** +``` +## Implementation + +Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach. +``` + +**DO:** For each task: + +1. Announce: "Working on task N: [description]" +2. Implement the change in the codebase +3. Reference specs/design naturally: "The spec says X, so I'm doing Y" +4. Mark complete in tasks.md: `- [ ]` → `- [x]` +5. Brief status: "✓ Task N complete" + +Keep narration light—don't over-explain every line of code. + +After all tasks: + +``` +## Implementation Complete + +All tasks done: +- [x] Task 1 +- [x] Task 2 +- [x] ... + +The change is implemented! One more step—let's archive it. +``` + +--- + +## Phase 10: Archive + +**EXPLAIN:** +``` +## Archiving + +When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`. + +Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way. +``` + +**DO:** +```bash +openspec archive "<name>" +``` + +**SHOW:** +``` +Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/` + +The change is now part of your project's history. The code is in your codebase, the decision record is preserved. +``` + +--- + +## Phase 11: Recap & Next Steps + +``` +## Congratulations! + +You just completed a full OpenSpec cycle: + +1. **Explore** - Thought through the problem +2. **New** - Created a change container +3. **Proposal** - Captured WHY +4. **Specs** - Defined WHAT in detail +5. **Design** - Decided HOW +6. **Tasks** - Broke it into steps +7. **Apply** - Implemented the work +8. **Archive** - Preserved the record + +This same rhythm works for any size change—a small fix or a major feature. + +--- + +## Command Reference + +**Core workflow:** + + | Command | What it does | + |-------------------|--------------------------------------------| + | `/opsx:propose` | Create a change and generate all artifacts | + | `/opsx:explore` | Think through problems before/during work | + | `/opsx:apply` | Implement tasks from a change | + | `/opsx:archive` | Archive a completed change | + +**Additional commands:** + + | Command | What it does | + |--------------------|----------------------------------------------------------| + | `/opsx:new` | Start a new change, step through artifacts one at a time | + | `/opsx:continue` | Continue working on an existing change | + | `/opsx:ff` | Fast-forward: create all artifacts at once | + | `/opsx:verify` | Verify implementation matches artifacts | + +--- + +## What's Next? + +Try `/opsx:propose` on something you actually want to build. You've got the rhythm now! +``` + +--- + +## Graceful Exit Handling + +### User wants to stop mid-way + +If the user says they need to stop, want to pause, or seem disengaged: + +``` +No problem! Your change is saved at `openspec/changes/<name>/`. + +To pick up where we left off later: +- `/opsx:continue <name>` - Resume artifact creation +- `/opsx:apply <name>` - Jump to implementation (if tasks exist) + +The work won't be lost. Come back whenever you're ready. +``` + +Exit gracefully without pressure. + +### User just wants command reference + +If the user says they just want to see the commands or skip the tutorial: + +``` +## OpenSpec Quick Reference + +**Core workflow:** + + | Command | What it does | + |--------------------------|--------------------------------------------| + | `/opsx:propose <name>` | Create a change and generate all artifacts | + | `/opsx:explore` | Think through problems (no code changes) | + | `/opsx:apply <name>` | Implement tasks | + | `/opsx:archive <name>` | Archive when done | + +**Additional commands:** + + | Command | What it does | + |---------------------------|-------------------------------------| + | `/opsx:new <name>` | Start a new change, step by step | + | `/opsx:continue <name>` | Continue an existing change | + | `/opsx:ff <name>` | Fast-forward: all artifacts at once | + | `/opsx:verify <name>` | Verify implementation | + +Try `/opsx:propose` to start your first change. +``` + +Exit gracefully. + +--- + +## Guardrails + +- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive) +- **Keep narration light** during implementation—teach without lecturing +- **Don't skip phases** even if the change is small—the goal is teaching the workflow +- **Pause for acknowledgment** at marked points, but don't over-pause +- **Handle exits gracefully**—never pressure the user to continue +- **Use real codebase tasks**—don't simulate or use fake examples +- **Adjust scope gently**—guide toward smaller tasks but respect user choice diff --git a/.claude/skills/openspec-propose/SKILL.md b/.claude/skills/openspec-propose/SKILL.md new file mode 100644 index 0000000..4b7e204 --- /dev/null +++ b/.claude/skills/openspec-propose/SKILL.md @@ -0,0 +1,110 @@ +--- +name: openspec-propose +description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Propose a new change - create the change and generate all artifacts in one step. + +I'll create a change with artifacts: +- proposal.md (what & why) +- design.md (how) +- tasks.md (implementation steps) + +When ready to implement, run /opsx:apply + +--- + +**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build. + +**Steps** + +1. **If no clear input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx:apply` or ask me to implement to start working on the tasks." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, ask if user wants to continue it or create a new one +- Verify each artifact file exists after writing before proceeding to next diff --git a/.claude/skills/openspec-sync-specs/SKILL.md b/.claude/skills/openspec-sync-specs/SKILL.md new file mode 100644 index 0000000..f2ed9ec --- /dev/null +++ b/.claude/skills/openspec-sync-specs/SKILL.md @@ -0,0 +1,138 @@ +--- +name: openspec-sync-specs +description: Sync delta specs from a change to main specs. Use when the user wants to update main specs with changes from a delta spec, without archiving the change. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Sync delta specs from a change to main specs. + +This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement). + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have delta specs (under `specs/` directory). + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Find delta specs** + + Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`. + + Each delta spec file contains sections like: + - `## ADDED Requirements` - New requirements to add + - `## MODIFIED Requirements` - Changes to existing requirements + - `## REMOVED Requirements` - Requirements to remove + - `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format) + + If no delta specs found, inform user and stop. + +3. **For each delta spec, apply changes to main specs** + + For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`: + + a. **Read the delta spec** to understand the intended changes + + b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet) + + c. **Apply changes intelligently**: + + **ADDED Requirements:** + - If requirement doesn't exist in main spec → add it + - If requirement already exists → update it to match (treat as implicit MODIFIED) + + **MODIFIED Requirements:** + - Find the requirement in main spec + - Apply the changes - this can be: + - Adding new scenarios (don't need to copy existing ones) + - Modifying existing scenarios + - Changing the requirement description + - Preserve scenarios/content not mentioned in the delta + + **REMOVED Requirements:** + - Remove the entire requirement block from main spec + + **RENAMED Requirements:** + - Find the FROM requirement, rename to TO + + d. **Create new main spec** if capability doesn't exist yet: + - Create `openspec/specs/<capability>/spec.md` + - Add Purpose section (can be brief, mark as TBD) + - Add Requirements section with the ADDED requirements + +4. **Show summary** + + After applying all changes, summarize: + - Which capabilities were updated + - What changes were made (requirements added/modified/removed/renamed) + +**Delta Spec Format Reference** + +```markdown +## ADDED Requirements + +### Requirement: New Feature +The system SHALL do something new. + +#### Scenario: Basic case +- **WHEN** user does X +- **THEN** system does Y + +## MODIFIED Requirements + +### Requirement: Existing Feature +#### Scenario: New scenario to add +- **WHEN** user does A +- **THEN** system does B + +## REMOVED Requirements + +### Requirement: Deprecated Feature + +## RENAMED Requirements + +- FROM: `### Requirement: Old Name` +- TO: `### Requirement: New Name` +``` + +**Key Principle: Intelligent Merging** + +Unlike programmatic merging, you can apply **partial updates**: +- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios +- The delta represents *intent*, not a wholesale replacement +- Use your judgment to merge changes sensibly + +**Output On Success** + +``` +## Specs Synced: <change-name> + +Updated main specs: + +**<capability-1>**: +- Added requirement: "New Feature" +- Modified requirement: "Existing Feature" (added 1 scenario) + +**<capability-2>**: +- Created new spec file +- Added requirement: "Another Feature" + +Main specs are now updated. The change remains active - archive when implementation is complete. +``` + +**Guardrails** +- Read both delta and main specs before making changes +- Preserve existing content not mentioned in delta +- If something is unclear, ask for clarification +- Show what you're changing as you go +- The operation should be idempotent - running twice should give same result diff --git a/.claude/skills/openspec-verify-change/SKILL.md b/.claude/skills/openspec-verify-change/SKILL.md new file mode 100644 index 0000000..00d0532 --- /dev/null +++ b/.claude/skills/openspec-verify-change/SKILL.md @@ -0,0 +1,168 @@ +--- +name: openspec-verify-change +description: Verify implementation matches change artifacts. Use when the user wants to validate that implementation is complete, correct, and coherent before archiving. +license: MIT +compatibility: Requires openspec CLI. +metadata: + author: openspec + version: "1.0" + generatedBy: "1.3.1" +--- + +Verify that an implementation matches the change artifacts (specs, tasks, design). + +**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have implementation tasks (tasks artifact exists). + Include the schema used for each change if available. + Mark changes with incomplete tasks as "(In Progress)". + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check status to understand the schema** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifacts exist for this change + +3. **Get the change directory and load artifacts** + + ```bash + openspec instructions apply --change "<name>" --json + ``` + + This returns the change directory and `contextFiles` (artifact ID -> array of concrete file paths). Read all available artifacts from `contextFiles`. + +4. **Initialize verification report structure** + + Create a report structure with three dimensions: + - **Completeness**: Track tasks and spec coverage + - **Correctness**: Track requirement implementation and scenario coverage + - **Coherence**: Track design adherence and pattern consistency + + Each dimension can have CRITICAL, WARNING, or SUGGESTION issues. + +5. **Verify Completeness** + + **Task Completion**: + - If `contextFiles.tasks` exists, read every file path in it + - Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete) + - Count complete vs total tasks + - If incomplete tasks exist: + - Add CRITICAL issue for each incomplete task + - Recommendation: "Complete task: <description>" or "Mark as done if already implemented" + + **Spec Coverage**: + - If delta specs exist in `openspec/changes/<name>/specs/`: + - Extract all requirements (marked with "### Requirement:") + - For each requirement: + - Search codebase for keywords related to the requirement + - Assess if implementation likely exists + - If requirements appear unimplemented: + - Add CRITICAL issue: "Requirement not found: <requirement name>" + - Recommendation: "Implement requirement X: <description>" + +6. **Verify Correctness** + + **Requirement Implementation Mapping**: + - For each requirement from delta specs: + - Search codebase for implementation evidence + - If found, note file paths and line ranges + - Assess if implementation matches requirement intent + - If divergence detected: + - Add WARNING: "Implementation may diverge from spec: <details>" + - Recommendation: "Review <file>:<lines> against requirement X" + + **Scenario Coverage**: + - For each scenario in delta specs (marked with "#### Scenario:"): + - Check if conditions are handled in code + - Check if tests exist covering the scenario + - If scenario appears uncovered: + - Add WARNING: "Scenario not covered: <scenario name>" + - Recommendation: "Add test or implementation for scenario: <description>" + +7. **Verify Coherence** + + **Design Adherence**: + - If `contextFiles.design` exists: + - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:") + - Verify implementation follows those decisions + - If contradiction detected: + - Add WARNING: "Design decision not followed: <decision>" + - Recommendation: "Update implementation or revise design.md to match reality" + - If no design.md: Skip design adherence check, note "No design.md to verify against" + + **Code Pattern Consistency**: + - Review new code for consistency with project patterns + - Check file naming, directory structure, coding style + - If significant deviations found: + - Add SUGGESTION: "Code pattern deviation: <details>" + - Recommendation: "Consider following project pattern: <example>" + +8. **Generate Verification Report** + + **Summary Scorecard**: + ``` + ## Verification Report: <change-name> + + ### Summary + | Dimension | Status | + |--------------|------------------| + | Completeness | X/Y tasks, N reqs| + | Correctness | M/N reqs covered | + | Coherence | Followed/Issues | + ``` + + **Issues by Priority**: + + 1. **CRITICAL** (Must fix before archive): + - Incomplete tasks + - Missing requirement implementations + - Each with specific, actionable recommendation + + 2. **WARNING** (Should fix): + - Spec/design divergences + - Missing scenario coverage + - Each with specific recommendation + + 3. **SUGGESTION** (Nice to fix): + - Pattern inconsistencies + - Minor improvements + - Each with specific recommendation + + **Final Assessment**: + - If CRITICAL issues: "X critical issue(s) found. Fix before archiving." + - If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)." + - If all clear: "All checks passed. Ready for archive." + +**Verification Heuristics** + +- **Completeness**: Focus on objective checklist items (checkboxes, requirements list) +- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty +- **Coherence**: Look for glaring inconsistencies, don't nitpick style +- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL +- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable + +**Graceful Degradation** + +- If only tasks.md exists: verify task completion only, skip spec/design checks +- If tasks + specs exist: verify completeness and correctness, skip design +- If full artifacts: verify all three dimensions +- Always note which checks were skipped and why + +**Output Format** + +Use clear markdown with: +- Table for summary scorecard +- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION) +- Code references in format: `file.ts:123` +- Specific, actionable recommendations +- No vague suggestions like "consider reviewing" diff --git a/.pi/prompts/opsx-apply.md b/.pi/prompts/opsx-apply.md new file mode 100644 index 0000000..d92897f --- /dev/null +++ b/.pi/prompts/opsx-apply.md @@ -0,0 +1,150 @@ +--- +description: Implement tasks from an OpenSpec change (Experimental) +--- + +Implement tasks from an OpenSpec change. + +**Input**: Optionally specify a change name (e.g., `/opsx-apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. +**Provided arguments**: $@ + +**Steps** + +1. **Select the change** + + If a name is provided, use it. Otherwise: + - Infer from conversation context if the user mentioned a change + - Auto-select if only one active change exists + - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select + + Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`). + +2. **Check status to understand the schema** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others) + +3. **Get apply instructions** + + ```bash + openspec instructions apply --change "<name>" --json + ``` + + This returns: + - `contextFiles`: artifact ID -> array of concrete file paths (varies by schema) + - Progress (total, complete, remaining) + - Task list with status + - Dynamic instruction based on current state + + **Handle states:** + - If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx-continue` + - If `state: "all_done"`: congratulate, suggest archive + - Otherwise: proceed to implementation + +4. **Read context files** + + Read every file path listed under `contextFiles` from the apply instructions output. + The files depend on the schema being used: + - **spec-driven**: proposal, specs, design, tasks + - Other schemas: follow the contextFiles from CLI output + +5. **Show current progress** + + Display: + - Schema being used + - Progress: "N/M tasks complete" + - Remaining tasks overview + - Dynamic instruction from CLI + +6. **Implement tasks (loop until done or blocked)** + + For each pending task: + - Show which task is being worked on + - Make the code changes required + - Keep changes minimal and focused + - Mark task complete in the tasks file: `- [ ]` → `- [x]` + - Continue to next task + + **Pause if:** + - Task is unclear → ask for clarification + - Implementation reveals a design issue → suggest updating artifacts + - Error or blocker encountered → report and wait for guidance + - User interrupts + +7. **On completion or pause, show status** + + Display: + - Tasks completed this session + - Overall progress: "N/M tasks complete" + - If all done: suggest archive + - If paused: explain why and wait for guidance + +**Output During Implementation** + +``` +## Implementing: <change-name> (schema: <schema-name>) + +Working on task 3/7: <task description> +[...implementation happening...] +✓ Task complete + +Working on task 4/7: <task description> +[...implementation happening...] +✓ Task complete +``` + +**Output On Completion** + +``` +## Implementation Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Progress:** 7/7 tasks complete ✓ + +### Completed This Session +- [x] Task 1 +- [x] Task 2 +... + +All tasks complete! You can archive this change with `/opsx-archive`. +``` + +**Output On Pause (Issue Encountered)** + +``` +## Implementation Paused + +**Change:** <change-name> +**Schema:** <schema-name> +**Progress:** 4/7 tasks complete + +### Issue Encountered +<description of the issue> + +**Options:** +1. <option 1> +2. <option 2> +3. Other approach + +What would you like to do? +``` + +**Guardrails** +- Keep going through tasks until done or blocked +- Always read context files before starting (from the apply instructions output) +- If task is ambiguous, pause and ask before implementing +- If implementation reveals issues, pause and suggest artifact updates +- Keep code changes minimal and scoped to each task +- Update task checkbox immediately after completing each task +- Pause on errors, blockers, or unclear requirements - don't guess +- Use contextFiles from CLI output, don't assume specific file names + +**Fluid Workflow Integration** + +This skill supports the "actions on a change" model: + +- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions +- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly diff --git a/.pi/prompts/opsx-archive.md b/.pi/prompts/opsx-archive.md new file mode 100644 index 0000000..245f889 --- /dev/null +++ b/.pi/prompts/opsx-archive.md @@ -0,0 +1,155 @@ +--- +description: Archive a completed change in the experimental workflow +--- + +Archive a completed change in the experimental workflow. + +**Input**: Optionally specify a change name after `/opsx-archive` (e.g., `/opsx-archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. +**Provided arguments**: $@ + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show only active changes (not already archived). + Include the schema used for each change if available. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check artifact completion status** + + Run `openspec status --change "<name>" --json` to check artifact completion. + + Parse the JSON to understand: + - `schemaName`: The workflow being used + - `artifacts`: List of artifacts with their status (`done` or other) + + **If any artifacts are not `done`:** + - Display warning listing incomplete artifacts + - Prompt user for confirmation to continue + - Proceed if user confirms + +3. **Check task completion status** + + Read the tasks file (typically `tasks.md`) to check for incomplete tasks. + + Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete). + + **If incomplete tasks found:** + - Display warning showing count of incomplete tasks + - Prompt user for confirmation to continue + - Proceed if user confirms + + **If no tasks file exists:** Proceed without task-related warning. + +4. **Assess delta spec sync state** + + Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt. + + **If delta specs exist:** + - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md` + - Determine what changes would be applied (adds, modifications, removals, renames) + - Show a combined summary before prompting + + **Prompt options:** + - If changes needed: "Sync now (recommended)", "Archive without syncing" + - If already synced: "Archive now", "Sync anyway", "Cancel" + + If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice. + +5. **Perform the archive** + + Create the archive directory if it doesn't exist: + ```bash + mkdir -p openspec/changes/archive + ``` + + Generate target name using current date: `YYYY-MM-DD-<change-name>` + + **Check if target already exists:** + - If yes: Fail with error, suggest renaming existing archive or using different date + - If no: Move the change directory to archive + + ```bash + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + +6. **Display summary** + + Show archive completion summary including: + - Change name + - Schema that was used + - Archive location + - Spec sync status (synced / sync skipped / no delta specs) + - Note about any warnings (incomplete artifacts/tasks) + +**Output On Success** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** ✓ Synced to main specs + +All artifacts complete. All tasks complete. +``` + +**Output On Success (No Delta Specs)** + +``` +## Archive Complete + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** No delta specs + +All artifacts complete. All tasks complete. +``` + +**Output On Success With Warnings** + +``` +## Archive Complete (with warnings) + +**Change:** <change-name> +**Schema:** <schema-name> +**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/ +**Specs:** Sync skipped (user chose to skip) + +**Warnings:** +- Archived with 2 incomplete artifacts +- Archived with 3 incomplete tasks +- Delta spec sync was skipped (user chose to skip) + +Review the archive if this was not intentional. +``` + +**Output On Error (Archive Exists)** + +``` +## Archive Failed + +**Change:** <change-name> +**Target:** openspec/changes/archive/YYYY-MM-DD-<name>/ + +Target archive directory already exists. + +**Options:** +1. Rename the existing archive +2. Delete the existing archive if it's a duplicate +3. Wait until a different date to archive +``` + +**Guardrails** +- Always prompt for change selection if not provided +- Use artifact graph (openspec status --json) for completion checking +- Don't block archive on warnings - just inform and confirm +- Preserve .openspec.yaml when moving to archive (it moves with the directory) +- Show clear summary of what happened +- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven) +- If delta specs exist, always run the sync assessment and show the combined summary before prompting diff --git a/.pi/prompts/opsx-bulk-archive.md b/.pi/prompts/opsx-bulk-archive.md new file mode 100644 index 0000000..7dfbe1f --- /dev/null +++ b/.pi/prompts/opsx-bulk-archive.md @@ -0,0 +1,240 @@ +--- +description: Archive multiple completed changes at once +--- + +Archive multiple completed changes in a single operation. + +This skill allows you to batch-archive changes, handling spec conflicts intelligently by checking the codebase to determine what's actually implemented. + +**Input**: None required (prompts for selection) +**Provided arguments**: $@ + +**Steps** + +1. **Get active changes** + + Run `openspec list --json` to get all active changes. + + If no active changes exist, inform user and stop. + +2. **Prompt for change selection** + + Use **AskUserQuestion tool** with multi-select to let user choose changes: + - Show each change with its schema + - Include an option for "All changes" + - Allow any number of selections (1+ works, 2+ is the typical use case) + + **IMPORTANT**: Do NOT auto-select. Always let the user choose. + +3. **Batch validation - gather status for all selected changes** + + For each selected change, collect: + + a. **Artifact status** - Run `openspec status --change "<name>" --json` + - Parse `schemaName` and `artifacts` list + - Note which artifacts are `done` vs other states + + b. **Task completion** - Read `openspec/changes/<name>/tasks.md` + - Count `- [ ]` (incomplete) vs `- [x]` (complete) + - If no tasks file exists, note as "No tasks" + + c. **Delta specs** - Check `openspec/changes/<name>/specs/` directory + - List which capability specs exist + - For each, extract requirement names (lines matching `### Requirement: <name>`) + +4. **Detect spec conflicts** + + Build a map of `capability -> [changes that touch it]`: + + ``` + auth -> [change-a, change-b] <- CONFLICT (2+ changes) + api -> [change-c] <- OK (only 1 change) + ``` + + A conflict exists when 2+ selected changes have delta specs for the same capability. + +5. **Resolve conflicts agentically** + + **For each conflict**, investigate the codebase: + + a. **Read the delta specs** from each conflicting change to understand what each claims to add/modify + + b. **Search the codebase** for implementation evidence: + - Look for code implementing requirements from each delta spec + - Check for related files, functions, or tests + + c. **Determine resolution**: + - If only one change is actually implemented -> sync that one's specs + - If both implemented -> apply in chronological order (older first, newer overwrites) + - If neither implemented -> skip spec sync, warn user + + d. **Record resolution** for each conflict: + - Which change's specs to apply + - In what order (if both) + - Rationale (what was found in codebase) + +6. **Show consolidated status table** + + Display a table summarizing all changes: + + ``` + | Change | Artifacts | Tasks | Specs | Conflicts | Status | + |---------------------|-----------|-------|---------|-----------|--------| + | schema-management | Done | 5/5 | 2 delta | None | Ready | + | project-config | Done | 3/3 | 1 delta | None | Ready | + | add-oauth | Done | 4/4 | 1 delta | auth (!) | Ready* | + | add-verify-skill | 1 left | 2/5 | None | None | Warn | + ``` + + For conflicts, show the resolution: + ``` + * Conflict resolution: + - auth spec: Will apply add-oauth then add-jwt (both implemented, chronological order) + ``` + + For incomplete changes, show warnings: + ``` + Warnings: + - add-verify-skill: 1 incomplete artifact, 3 incomplete tasks + ``` + +7. **Confirm batch operation** + + Use **AskUserQuestion tool** with a single confirmation: + + - "Archive N changes?" with options based on status + - Options might include: + - "Archive all N changes" + - "Archive only N ready changes (skip incomplete)" + - "Cancel" + + If there are incomplete changes, make clear they'll be archived with warnings. + +8. **Execute archive for each confirmed change** + + Process changes in the determined order (respecting conflict resolution): + + a. **Sync specs** if delta specs exist: + - Use the openspec-sync-specs approach (agent-driven intelligent merge) + - For conflicts, apply in resolved order + - Track if sync was done + + b. **Perform the archive**: + ```bash + mkdir -p openspec/changes/archive + mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name> + ``` + + c. **Track outcome** for each change: + - Success: archived successfully + - Failed: error during archive (record error) + - Skipped: user chose not to archive (if applicable) + +9. **Display summary** + + Show final results: + + ``` + ## Bulk Archive Complete + + Archived 3 changes: + - schema-management-cli -> archive/2026-01-19-schema-management-cli/ + - project-config -> archive/2026-01-19-project-config/ + - add-oauth -> archive/2026-01-19-add-oauth/ + + Skipped 1 change: + - add-verify-skill (user chose not to archive incomplete) + + Spec sync summary: + - 4 delta specs synced to main specs + - 1 conflict resolved (auth: applied both in chronological order) + ``` + + If any failures: + ``` + Failed 1 change: + - some-change: Archive directory already exists + ``` + +**Conflict Resolution Examples** + +Example 1: Only one implemented +``` +Conflict: specs/auth/spec.md touched by [add-oauth, add-jwt] + +Checking add-oauth: +- Delta adds "OAuth Provider Integration" requirement +- Searching codebase... found src/auth/oauth.ts implementing OAuth flow + +Checking add-jwt: +- Delta adds "JWT Token Handling" requirement +- Searching codebase... no JWT implementation found + +Resolution: Only add-oauth is implemented. Will sync add-oauth specs only. +``` + +Example 2: Both implemented +``` +Conflict: specs/api/spec.md touched by [add-rest-api, add-graphql] + +Checking add-rest-api (created 2026-01-10): +- Delta adds "REST Endpoints" requirement +- Searching codebase... found src/api/rest.ts + +Checking add-graphql (created 2026-01-15): +- Delta adds "GraphQL Schema" requirement +- Searching codebase... found src/api/graphql.ts + +Resolution: Both implemented. Will apply add-rest-api specs first, +then add-graphql specs (chronological order, newer takes precedence). +``` + +**Output On Success** + +``` +## Bulk Archive Complete + +Archived N changes: +- <change-1> -> archive/YYYY-MM-DD-<change-1>/ +- <change-2> -> archive/YYYY-MM-DD-<change-2>/ + +Spec sync summary: +- N delta specs synced to main specs +- No conflicts (or: M conflicts resolved) +``` + +**Output On Partial Success** + +``` +## Bulk Archive Complete (partial) + +Archived N changes: +- <change-1> -> archive/YYYY-MM-DD-<change-1>/ + +Skipped M changes: +- <change-2> (user chose not to archive incomplete) + +Failed K changes: +- <change-3>: Archive directory already exists +``` + +**Output When No Changes** + +``` +## No Changes to Archive + +No active changes found. Create a new change to get started. +``` + +**Guardrails** +- Allow any number of changes (1+ is fine, 2+ is the typical use case) +- Always prompt for selection, never auto-select +- Detect spec conflicts early and resolve by checking codebase +- When both changes are implemented, apply specs in chronological order +- Skip spec sync only when implementation is missing (warn user) +- Show clear per-change status before confirming +- Use single confirmation for entire batch +- Track and report all outcomes (success/skip/fail) +- Preserve .openspec.yaml when moving to archive +- Archive directory target uses current date: YYYY-MM-DD-<name> +- If archive target exists, fail that change but continue with others diff --git a/.pi/prompts/opsx-continue.md b/.pi/prompts/opsx-continue.md new file mode 100644 index 0000000..582c622 --- /dev/null +++ b/.pi/prompts/opsx-continue.md @@ -0,0 +1,112 @@ +--- +description: Continue working on a change - create the next artifact (Experimental) +--- + +Continue working on a change by creating the next artifact. + +**Input**: Optionally specify a change name after `/opsx-continue` (e.g., `/opsx-continue add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. +**Provided arguments**: $@ + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes sorted by most recently modified. Then use the **AskUserQuestion tool** to let the user select which change to work on. + + Present the top 3-4 most recently modified changes as options, showing: + - Change name + - Schema (from `schema` field if present, otherwise "spec-driven") + - Status (e.g., "0/5 tasks", "complete", "no tasks") + - How recently it was modified (from `lastModified` field) + + Mark the most recently modified change as "(Recommended)" since it's likely what the user wants to continue. + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check current status** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand current state. The response includes: + - `schemaName`: The workflow schema being used (e.g., "spec-driven") + - `artifacts`: Array of artifacts with their status ("done", "ready", "blocked") + - `isComplete`: Boolean indicating if all artifacts are complete + +3. **Act based on status**: + + --- + + **If all artifacts are complete (`isComplete: true`)**: + - Congratulate the user + - Show final status including the schema used + - Suggest: "All artifacts created! You can now implement this change with `/opsx-apply` or archive it with `/opsx-archive`." + - STOP + + --- + + **If artifacts are ready to create** (status shows artifacts with `status: "ready"`): + - Pick the FIRST artifact with `status: "ready"` from the status output + - Get its instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - Parse the JSON. The key fields are: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - **Create the artifact file**: + - Read any completed dependency files for context + - Use `template` as the structure - fill in its sections + - Apply `context` and `rules` as constraints when writing - but do NOT copy them into the file + - Write to the output path specified in instructions + - Show what was created and what's now unlocked + - STOP after creating ONE artifact + + --- + + **If no artifacts are ready (all blocked)**: + - This shouldn't happen with a valid schema + - Show status and suggest checking for issues + +4. **After creating an artifact, show progress** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After each invocation, show: +- Which artifact was created +- Schema workflow being used +- Current progress (N/M complete) +- What artifacts are now unlocked +- Prompt: "Run `/opsx-continue` to create the next artifact" + +**Artifact Creation Guidelines** + +The artifact types and their purpose depend on the schema. Use the `instruction` field from the instructions output to understand what to create. + +Common artifact patterns: + +**spec-driven schema** (proposal → specs → design → tasks): +- **proposal.md**: Ask user about the change if not clear. Fill in Why, What Changes, Capabilities, Impact. + - The Capabilities section is critical - each capability listed will need a spec file. +- **specs/<capability>/spec.md**: Create one spec per capability listed in the proposal's Capabilities section (use the capability name, not the change name). +- **design.md**: Document technical decisions, architecture, and implementation approach. +- **tasks.md**: Break down implementation into checkboxed tasks. + +For other schemas, follow the `instruction` field from the CLI output. + +**Guardrails** +- Create ONE artifact per invocation +- Always read dependency artifacts before creating a new one +- Never skip artifacts or create out of order +- If context is unclear, ask the user before creating +- Verify the artifact file exists after writing before marking progress +- Use the schema's artifact sequence, don't assume specific artifact names +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output diff --git a/.pi/prompts/opsx-explore.md b/.pi/prompts/opsx-explore.md new file mode 100644 index 0000000..153badb --- /dev/null +++ b/.pi/prompts/opsx-explore.md @@ -0,0 +1,171 @@ +--- +description: "Enter explore mode - think through ideas, investigate problems, clarify requirements" +--- + +Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes. + +**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing. + +**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore. + +**Input**: The argument after `/opsx-explore` is whatever the user wants to think about. Could be: +**Provided arguments**: $@ +- A vague idea: "real-time collaboration" +- A specific problem: "the auth system is getting unwieldy" +- A change name: "add-dark-mode" (to explore in context of that change) +- A comparison: "postgres vs sqlite for this" +- Nothing (just enter explore mode) + +--- + +## The Stance + +- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script +- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions. +- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking +- **Adaptive** - Follow interesting threads, pivot when new information emerges +- **Patient** - Don't rush to conclusions, let the shape of the problem emerge +- **Grounded** - Explore the actual codebase when relevant, don't just theorize + +--- + +## What You Might Do + +Depending on what the user brings, you might: + +**Explore the problem space** +- Ask clarifying questions that emerge from what they said +- Challenge assumptions +- Reframe the problem +- Find analogies + +**Investigate the codebase** +- Map existing architecture relevant to the discussion +- Find integration points +- Identify patterns already in use +- Surface hidden complexity + +**Compare options** +- Brainstorm multiple approaches +- Build comparison tables +- Sketch tradeoffs +- Recommend a path (if asked) + +**Visualize** +``` +┌─────────────────────────────────────────┐ +│ Use ASCII diagrams liberally │ +├─────────────────────────────────────────┤ +│ │ +│ ┌────────┐ ┌────────┐ │ +│ │ State │────────▶│ State │ │ +│ │ A │ │ B │ │ +│ └────────┘ └────────┘ │ +│ │ +│ System diagrams, state machines, │ +│ data flows, architecture sketches, │ +│ dependency graphs, comparison tables │ +│ │ +└─────────────────────────────────────────┘ +``` + +**Surface risks and unknowns** +- Identify what could go wrong +- Find gaps in understanding +- Suggest spikes or investigations + +--- + +## OpenSpec Awareness + +You have full context of the OpenSpec system. Use it naturally, don't force it. + +### Check for context + +At the start, quickly check what exists: +```bash +openspec list --json +``` + +This tells you: +- If there are active changes +- Their names, schemas, and status +- What the user might be working on + +If the user mentioned a specific change name, read its artifacts for context. + +### When no change exists + +Think freely. When insights crystallize, you might offer: + +- "This feels solid enough to start a change. Want me to create a proposal?" +- Or keep exploring - no pressure to formalize + +### When a change exists + +If the user mentions a change or you detect one is relevant: + +1. **Read existing artifacts for context** + - `openspec/changes/<name>/proposal.md` + - `openspec/changes/<name>/design.md` + - `openspec/changes/<name>/tasks.md` + - etc. + +2. **Reference them naturally in conversation** + - "Your design mentions using Redis, but we just realized SQLite fits better..." + - "The proposal scopes this to premium users, but we're now thinking everyone..." + +3. **Offer to capture when decisions are made** + + | Insight Type | Where to Capture | + |----------------------------|--------------------------------| + | New requirement discovered | `specs/<capability>/spec.md` | + | Requirement changed | `specs/<capability>/spec.md` | + | Design decision made | `design.md` | + | Scope changed | `proposal.md` | + | New work identified | `tasks.md` | + | Assumption invalidated | Relevant artifact | + + Example offers: + - "That's a design decision. Capture it in design.md?" + - "This is a new requirement. Add it to specs?" + - "This changes scope. Update the proposal?" + +4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture. + +--- + +## What You Don't Have To Do + +- Follow a script +- Ask the same questions every time +- Produce a specific artifact +- Reach a conclusion +- Stay on topic if a tangent is valuable +- Be brief (this is thinking time) + +--- + +## Ending Discovery + +There's no required ending. Discovery might: + +- **Flow into a proposal**: "Ready to start? I can create a change proposal." +- **Result in artifact updates**: "Updated design.md with these decisions" +- **Just provide clarity**: User has what they need, moves on +- **Continue later**: "We can pick this up anytime" + +When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value. + +--- + +## Guardrails + +- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not. +- **Don't fake understanding** - If something is unclear, dig deeper +- **Don't rush** - Discovery is thinking time, not task time +- **Don't force structure** - Let patterns emerge naturally +- **Don't auto-capture** - Offer to save insights, don't just do it +- **Do visualize** - A good diagram is worth many paragraphs +- **Do explore the codebase** - Ground discussions in reality +- **Do question assumptions** - Including the user's and your own diff --git a/.pi/prompts/opsx-ff.md b/.pi/prompts/opsx-ff.md new file mode 100644 index 0000000..05a516f --- /dev/null +++ b/.pi/prompts/opsx-ff.md @@ -0,0 +1,95 @@ +--- +description: Create a change and generate all artifacts needed for implementation in one go +--- + +Fast-forward through artifact creation - generate everything needed to start implementation. + +**Input**: The argument after `/opsx-ff` is the change name (kebab-case), OR a description of what the user wants to build. +**Provided arguments**: $@ + +**Steps** + +1. **If no input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change at `openspec/changes/<name>/`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "✓ Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx-apply` to start implementing." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, ask if user wants to continue it or create a new one +- Verify each artifact file exists after writing before proceeding to next diff --git a/.pi/prompts/opsx-new.md b/.pi/prompts/opsx-new.md new file mode 100644 index 0000000..1be9632 --- /dev/null +++ b/.pi/prompts/opsx-new.md @@ -0,0 +1,67 @@ +--- +description: Start a new change using the experimental artifact workflow (OPSX) +--- + +Start a new change using the experimental artifact-driven approach. + +**Input**: The argument after `/opsx-new` is the change name (kebab-case), OR a description of what the user wants to build. +**Provided arguments**: $@ + +**Steps** + +1. **If no input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Determine the workflow schema** + + Use the default schema (omit `--schema`) unless the user explicitly requests a different workflow. + + **Use a different schema only if the user mentions:** + - A specific schema name → use `--schema <name>` + - "show workflows" or "what workflows" → run `openspec schemas --json` and let them choose + + **Otherwise**: Omit `--schema` to use the default. + +3. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + Add `--schema <name>` only if the user requested a specific workflow. + This creates a scaffolded change at `openspec/changes/<name>/` with the selected schema. + +4. **Show the artifact status** + ```bash + openspec status --change "<name>" + ``` + This shows which artifacts need to be created and which are ready (dependencies satisfied). + +5. **Get instructions for the first artifact** + The first artifact depends on the schema. Check the status output to find the first artifact with status "ready". + ```bash + openspec instructions <first-artifact-id> --change "<name>" + ``` + This outputs the template and context for creating the first artifact. + +6. **STOP and wait for user direction** + +**Output** + +After completing the steps, summarize: +- Change name and location +- Schema/workflow being used and its artifact sequence +- Current status (0/N artifacts complete) +- The template for the first artifact +- Prompt: "Ready to create the first artifact? Run `/opsx-continue` or just describe what this change is about and I'll draft it." + +**Guardrails** +- Do NOT create any artifacts yet - just show the instructions +- Do NOT advance beyond showing the first artifact template +- If the name is invalid (not kebab-case), ask for a valid name +- If a change with that name already exists, suggest using `/opsx-continue` instead +- Pass --schema if using a non-default workflow diff --git a/.pi/prompts/opsx-onboard.md b/.pi/prompts/opsx-onboard.md new file mode 100644 index 0000000..23ccc06 --- /dev/null +++ b/.pi/prompts/opsx-onboard.md @@ -0,0 +1,547 @@ +--- +description: Guided onboarding - walk through a complete OpenSpec workflow cycle with narration +--- + +Guide the user through their first complete OpenSpec workflow cycle. This is a teaching experience—you'll do real work in their codebase while explaining each step. + +--- + +## Preflight + +Before starting, check if the OpenSpec CLI is installed: + +```bash +# Unix/macOS +openspec --version 2>&1 || echo "CLI_NOT_INSTALLED" +# Windows (PowerShell) +# if (Get-Command openspec -ErrorAction SilentlyContinue) { openspec --version } else { echo "CLI_NOT_INSTALLED" } +``` + +**If CLI not installed:** +> OpenSpec CLI is not installed. Install it first, then come back to `/opsx-onboard`. + +Stop here if not installed. + +--- + +## Phase 1: Welcome + +Display: + +``` +## Welcome to OpenSpec! + +I'll walk you through a complete change cycle—from idea to implementation—using a real task in your codebase. Along the way, you'll learn the workflow by doing it. + +**What we'll do:** +1. Pick a small, real task in your codebase +2. Explore the problem briefly +3. Create a change (the container for our work) +4. Build the artifacts: proposal → specs → design → tasks +5. Implement the tasks +6. Archive the completed change + +**Time:** ~15-20 minutes + +Let's start by finding something to work on. +``` + +--- + +## Phase 2: Task Selection + +### Codebase Analysis + +Scan the codebase for small improvement opportunities. Look for: + +1. **TODO/FIXME comments** - Search for `TODO`, `FIXME`, `HACK`, `XXX` in code files +2. **Missing error handling** - `catch` blocks that swallow errors, risky operations without try-catch +3. **Functions without tests** - Cross-reference `src/` with test directories +4. **Type issues** - `any` types in TypeScript files (`: any`, `as any`) +5. **Debug artifacts** - `console.log`, `console.debug`, `debugger` statements in non-debug code +6. **Missing validation** - User input handlers without validation + +Also check recent git activity: +```bash +# Unix/macOS +git log --oneline -10 2>/dev/null || echo "No git history" +# Windows (PowerShell) +# git log --oneline -10 2>$null; if ($LASTEXITCODE -ne 0) { echo "No git history" } +``` + +### Present Suggestions + +From your analysis, present 3-4 specific suggestions: + +``` +## Task Suggestions + +Based on scanning your codebase, here are some good starter tasks: + +**1. [Most promising task]** + Location: `src/path/to/file.ts:42` + Scope: ~1-2 files, ~20-30 lines + Why it's good: [brief reason] + +**2. [Second task]** + Location: `src/another/file.ts` + Scope: ~1 file, ~15 lines + Why it's good: [brief reason] + +**3. [Third task]** + Location: [location] + Scope: [estimate] + Why it's good: [brief reason] + +**4. Something else?** + Tell me what you'd like to work on. + +Which task interests you? (Pick a number or describe your own) +``` + +**If nothing found:** Fall back to asking what the user wants to build: +> I didn't find obvious quick wins in your codebase. What's something small you've been meaning to add or fix? + +### Scope Guardrail + +If the user picks or describes something too large (major feature, multi-day work): + +``` +That's a valuable task, but it's probably larger than ideal for your first OpenSpec run-through. + +For learning the workflow, smaller is better—it lets you see the full cycle without getting stuck in implementation details. + +**Options:** +1. **Slice it smaller** - What's the smallest useful piece of [their task]? Maybe just [specific slice]? +2. **Pick something else** - One of the other suggestions, or a different small task? +3. **Do it anyway** - If you really want to tackle this, we can. Just know it'll take longer. + +What would you prefer? +``` + +Let the user override if they insist—this is a soft guardrail. + +--- + +## Phase 3: Explore Demo + +Once a task is selected, briefly demonstrate explore mode: + +``` +Before we create a change, let me quickly show you **explore mode**—it's how you think through problems before committing to a direction. +``` + +Spend 1-2 minutes investigating the relevant code: +- Read the file(s) involved +- Draw a quick ASCII diagram if it helps +- Note any considerations + +``` +## Quick Exploration + +[Your brief analysis—what you found, any considerations] + +┌─────────────────────────────────────────┐ +│ [Optional: ASCII diagram if helpful] │ +└─────────────────────────────────────────┘ + +Explore mode (`/opsx-explore`) is for this kind of thinking—investigating before implementing. You can use it anytime you need to think through a problem. + +Now let's create a change to hold our work. +``` + +**PAUSE** - Wait for user acknowledgment before proceeding. + +--- + +## Phase 4: Create the Change + +**EXPLAIN:** +``` +## Creating a Change + +A "change" in OpenSpec is a container for all the thinking and planning around a piece of work. It lives in `openspec/changes/<name>/` and holds your artifacts—proposal, specs, design, tasks. + +Let me create one for our task. +``` + +**DO:** Create the change with a derived kebab-case name: +```bash +openspec new change "<derived-name>" +``` + +**SHOW:** +``` +Created: `openspec/changes/<name>/` + +The folder structure: +``` +openspec/changes/<name>/ +├── proposal.md ← Why we're doing this (empty, we'll fill it) +├── design.md ← How we'll build it (empty) +├── specs/ ← Detailed requirements (empty) +└── tasks.md ← Implementation checklist (empty) +``` + +Now let's fill in the first artifact—the proposal. +``` + +--- + +## Phase 5: Proposal + +**EXPLAIN:** +``` +## The Proposal + +The proposal captures **why** we're making this change and **what** it involves at a high level. It's the "elevator pitch" for the work. + +I'll draft one based on our task. +``` + +**DO:** Draft the proposal content (don't save yet): + +``` +Here's a draft proposal: + +--- + +## Why + +[1-2 sentences explaining the problem/opportunity] + +## What Changes + +[Bullet points of what will be different] + +## Capabilities + +### New Capabilities +- `<capability-name>`: [brief description] + +### Modified Capabilities +<!-- If modifying existing behavior --> + +## Impact + +- `src/path/to/file.ts`: [what changes] +- [other files if applicable] + +--- + +Does this capture the intent? I can adjust before we save it. +``` + +**PAUSE** - Wait for user approval/feedback. + +After approval, save the proposal: +```bash +openspec instructions proposal --change "<name>" --json +``` +Then write the content to `openspec/changes/<name>/proposal.md`. + +``` +Proposal saved. This is your "why" document—you can always come back and refine it as understanding evolves. + +Next up: specs. +``` + +--- + +## Phase 6: Specs + +**EXPLAIN:** +``` +## Specs + +Specs define **what** we're building in precise, testable terms. They use a requirement/scenario format that makes expected behavior crystal clear. + +For a small task like this, we might only need one spec file. +``` + +**DO:** Create the spec file: +```bash +# Unix/macOS +mkdir -p openspec/changes/<name>/specs/<capability-name> +# Windows (PowerShell) +# New-Item -ItemType Directory -Force -Path "openspec/changes/<name>/specs/<capability-name>" +``` + +Draft the spec content: + +``` +Here's the spec: + +--- + +## ADDED Requirements + +### Requirement: <Name> + +<Description of what the system should do> + +#### Scenario: <Scenario name> + +- **WHEN** <trigger condition> +- **THEN** <expected outcome> +- **AND** <additional outcome if needed> + +--- + +This format—WHEN/THEN/AND—makes requirements testable. You can literally read them as test cases. +``` + +Save to `openspec/changes/<name>/specs/<capability>/spec.md`. + +--- + +## Phase 7: Design + +**EXPLAIN:** +``` +## Design + +The design captures **how** we'll build it—technical decisions, tradeoffs, approach. + +For small changes, this might be brief. That's fine—not every change needs deep design discussion. +``` + +**DO:** Draft design.md: + +``` +Here's the design: + +--- + +## Context + +[Brief context about the current state] + +## Goals / Non-Goals + +**Goals:** +- [What we're trying to achieve] + +**Non-Goals:** +- [What's explicitly out of scope] + +## Decisions + +### Decision 1: [Key decision] + +[Explanation of approach and rationale] + +--- + +For a small task, this captures the key decisions without over-engineering. +``` + +Save to `openspec/changes/<name>/design.md`. + +--- + +## Phase 8: Tasks + +**EXPLAIN:** +``` +## Tasks + +Finally, we break the work into implementation tasks—checkboxes that drive the apply phase. + +These should be small, clear, and in logical order. +``` + +**DO:** Generate tasks based on specs and design: + +``` +Here are the implementation tasks: + +--- + +## 1. [Category or file] + +- [ ] 1.1 [Specific task] +- [ ] 1.2 [Specific task] + +## 2. Verify + +- [ ] 2.1 [Verification step] + +--- + +Each checkbox becomes a unit of work in the apply phase. Ready to implement? +``` + +**PAUSE** - Wait for user to confirm they're ready to implement. + +Save to `openspec/changes/<name>/tasks.md`. + +--- + +## Phase 9: Apply (Implementation) + +**EXPLAIN:** +``` +## Implementation + +Now we implement each task, checking them off as we go. I'll announce each one and occasionally note how the specs/design informed the approach. +``` + +**DO:** For each task: + +1. Announce: "Working on task N: [description]" +2. Implement the change in the codebase +3. Reference specs/design naturally: "The spec says X, so I'm doing Y" +4. Mark complete in tasks.md: `- [ ]` → `- [x]` +5. Brief status: "✓ Task N complete" + +Keep narration light—don't over-explain every line of code. + +After all tasks: + +``` +## Implementation Complete + +All tasks done: +- [x] Task 1 +- [x] Task 2 +- [x] ... + +The change is implemented! One more step—let's archive it. +``` + +--- + +## Phase 10: Archive + +**EXPLAIN:** +``` +## Archiving + +When a change is complete, we archive it. This moves it from `openspec/changes/` to `openspec/changes/archive/YYYY-MM-DD-<name>/`. + +Archived changes become your project's decision history—you can always find them later to understand why something was built a certain way. +``` + +**DO:** +```bash +openspec archive "<name>" +``` + +**SHOW:** +``` +Archived to: `openspec/changes/archive/YYYY-MM-DD-<name>/` + +The change is now part of your project's history. The code is in your codebase, the decision record is preserved. +``` + +--- + +## Phase 11: Recap & Next Steps + +``` +## Congratulations! + +You just completed a full OpenSpec cycle: + +1. **Explore** - Thought through the problem +2. **New** - Created a change container +3. **Proposal** - Captured WHY +4. **Specs** - Defined WHAT in detail +5. **Design** - Decided HOW +6. **Tasks** - Broke it into steps +7. **Apply** - Implemented the work +8. **Archive** - Preserved the record + +This same rhythm works for any size change—a small fix or a major feature. + +--- + +## Command Reference + +**Core workflow:** + + | Command | What it does | + |-------------------|--------------------------------------------| + | `/opsx-propose` | Create a change and generate all artifacts | + | `/opsx-explore` | Think through problems before/during work | + | `/opsx-apply` | Implement tasks from a change | + | `/opsx-archive` | Archive a completed change | + +**Additional commands:** + + | Command | What it does | + |--------------------|----------------------------------------------------------| + | `/opsx-new` | Start a new change, step through artifacts one at a time | + | `/opsx-continue` | Continue working on an existing change | + | `/opsx-ff` | Fast-forward: create all artifacts at once | + | `/opsx-verify` | Verify implementation matches artifacts | + +--- + +## What's Next? + +Try `/opsx-propose` on something you actually want to build. You've got the rhythm now! +``` + +--- + +## Graceful Exit Handling + +### User wants to stop mid-way + +If the user says they need to stop, want to pause, or seem disengaged: + +``` +No problem! Your change is saved at `openspec/changes/<name>/`. + +To pick up where we left off later: +- `/opsx-continue <name>` - Resume artifact creation +- `/opsx-apply <name>` - Jump to implementation (if tasks exist) + +The work won't be lost. Come back whenever you're ready. +``` + +Exit gracefully without pressure. + +### User just wants command reference + +If the user says they just want to see the commands or skip the tutorial: + +``` +## OpenSpec Quick Reference + +**Core workflow:** + + | Command | What it does | + |--------------------------|--------------------------------------------| + | `/opsx-propose <name>` | Create a change and generate all artifacts | + | `/opsx-explore` | Think through problems (no code changes) | + | `/opsx-apply <name>` | Implement tasks | + | `/opsx-archive <name>` | Archive when done | + +**Additional commands:** + + | Command | What it does | + |---------------------------|-------------------------------------| + | `/opsx-new <name>` | Start a new change, step by step | + | `/opsx-continue <name>` | Continue an existing change | + | `/opsx-ff <name>` | Fast-forward: all artifacts at once | + | `/opsx-verify <name>` | Verify implementation | + +Try `/opsx-propose` to start your first change. +``` + +Exit gracefully. + +--- + +## Guardrails + +- **Follow the EXPLAIN → DO → SHOW → PAUSE pattern** at key transitions (after explore, after proposal draft, after tasks, after archive) +- **Keep narration light** during implementation—teach without lecturing +- **Don't skip phases** even if the change is small—the goal is teaching the workflow +- **Pause for acknowledgment** at marked points, but don't over-pause +- **Handle exits gracefully**—never pressure the user to continue +- **Use real codebase tasks**—don't simulate or use fake examples +- **Adjust scope gently**—guide toward smaller tasks but respect user choice diff --git a/.pi/prompts/opsx-propose.md b/.pi/prompts/opsx-propose.md new file mode 100644 index 0000000..60acdff --- /dev/null +++ b/.pi/prompts/opsx-propose.md @@ -0,0 +1,104 @@ +--- +description: Propose a new change - create it and generate all artifacts in one step +--- + +Propose a new change - create the change and generate all artifacts in one step. + +I'll create a change with artifacts: +- proposal.md (what & why) +- design.md (how) +- tasks.md (implementation steps) + +When ready to implement, run /opsx-apply + +--- + +**Input**: The argument after `/opsx-propose` is the change name (kebab-case), OR a description of what the user wants to build. +**Provided arguments**: $@ + +**Steps** + +1. **If no input provided, ask what they want to build** + + Use the **AskUserQuestion tool** (open-ended, no preset options) to ask: + > "What change do you want to work on? Describe what you want to build or fix." + + From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`). + + **IMPORTANT**: Do NOT proceed without understanding what the user wants to build. + +2. **Create the change directory** + ```bash + openspec new change "<name>" + ``` + This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`. + +3. **Get the artifact build order** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to get: + - `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`) + - `artifacts`: list of all artifacts with their status and dependencies + +4. **Create artifacts in sequence until apply-ready** + + Use the **TodoWrite tool** to track progress through the artifacts. + + Loop through artifacts in dependency order (artifacts with no pending dependencies first): + + a. **For each artifact that is `ready` (dependencies satisfied)**: + - Get instructions: + ```bash + openspec instructions <artifact-id> --change "<name>" --json + ``` + - The instructions JSON includes: + - `context`: Project background (constraints for you - do NOT include in output) + - `rules`: Artifact-specific rules (constraints for you - do NOT include in output) + - `template`: The structure to use for your output file + - `instruction`: Schema-specific guidance for this artifact type + - `outputPath`: Where to write the artifact + - `dependencies`: Completed artifacts to read for context + - Read any completed dependency files for context + - Create the artifact file using `template` as the structure + - Apply `context` and `rules` as constraints - but do NOT copy them into the file + - Show brief progress: "Created <artifact-id>" + + b. **Continue until all `applyRequires` artifacts are complete** + - After creating each artifact, re-run `openspec status --change "<name>" --json` + - Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array + - Stop when all `applyRequires` artifacts are done + + c. **If an artifact requires user input** (unclear context): + - Use **AskUserQuestion tool** to clarify + - Then continue with creation + +5. **Show final status** + ```bash + openspec status --change "<name>" + ``` + +**Output** + +After completing all artifacts, summarize: +- Change name and location +- List of artifacts created with brief descriptions +- What's ready: "All artifacts created! Ready for implementation." +- Prompt: "Run `/opsx-apply` to start implementing." + +**Artifact Creation Guidelines** + +- Follow the `instruction` field from `openspec instructions` for each artifact type +- The schema defines what each artifact should contain - follow it +- Read dependency artifacts for context before creating new ones +- Use `template` as the structure for your output file - fill in its sections +- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file + - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact + - These guide what you write, but should never appear in the output + +**Guardrails** +- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`) +- Always read dependency artifacts before creating a new one +- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum +- If a change with that name already exists, ask if user wants to continue it or create a new one +- Verify each artifact file exists after writing before proceeding to next diff --git a/.pi/prompts/opsx-sync.md b/.pi/prompts/opsx-sync.md new file mode 100644 index 0000000..d70327b --- /dev/null +++ b/.pi/prompts/opsx-sync.md @@ -0,0 +1,132 @@ +--- +description: Sync delta specs from a change to main specs +--- + +Sync delta specs from a change to main specs. + +This is an **agent-driven** operation - you will read delta specs and directly edit main specs to apply the changes. This allows intelligent merging (e.g., adding a scenario without copying the entire requirement). + +**Input**: Optionally specify a change name after `/opsx-sync` (e.g., `/opsx-sync add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. +**Provided arguments**: $@ + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have delta specs (under `specs/` directory). + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Find delta specs** + + Look for delta spec files in `openspec/changes/<name>/specs/*/spec.md`. + + Each delta spec file contains sections like: + - `## ADDED Requirements` - New requirements to add + - `## MODIFIED Requirements` - Changes to existing requirements + - `## REMOVED Requirements` - Requirements to remove + - `## RENAMED Requirements` - Requirements to rename (FROM:/TO: format) + + If no delta specs found, inform user and stop. + +3. **For each delta spec, apply changes to main specs** + + For each capability with a delta spec at `openspec/changes/<name>/specs/<capability>/spec.md`: + + a. **Read the delta spec** to understand the intended changes + + b. **Read the main spec** at `openspec/specs/<capability>/spec.md` (may not exist yet) + + c. **Apply changes intelligently**: + + **ADDED Requirements:** + - If requirement doesn't exist in main spec → add it + - If requirement already exists → update it to match (treat as implicit MODIFIED) + + **MODIFIED Requirements:** + - Find the requirement in main spec + - Apply the changes - this can be: + - Adding new scenarios (don't need to copy existing ones) + - Modifying existing scenarios + - Changing the requirement description + - Preserve scenarios/content not mentioned in the delta + + **REMOVED Requirements:** + - Remove the entire requirement block from main spec + + **RENAMED Requirements:** + - Find the FROM requirement, rename to TO + + d. **Create new main spec** if capability doesn't exist yet: + - Create `openspec/specs/<capability>/spec.md` + - Add Purpose section (can be brief, mark as TBD) + - Add Requirements section with the ADDED requirements + +4. **Show summary** + + After applying all changes, summarize: + - Which capabilities were updated + - What changes were made (requirements added/modified/removed/renamed) + +**Delta Spec Format Reference** + +```markdown +## ADDED Requirements + +### Requirement: New Feature +The system SHALL do something new. + +#### Scenario: Basic case +- **WHEN** user does X +- **THEN** system does Y + +## MODIFIED Requirements + +### Requirement: Existing Feature +#### Scenario: New scenario to add +- **WHEN** user does A +- **THEN** system does B + +## REMOVED Requirements + +### Requirement: Deprecated Feature + +## RENAMED Requirements + +- FROM: `### Requirement: Old Name` +- TO: `### Requirement: New Name` +``` + +**Key Principle: Intelligent Merging** + +Unlike programmatic merging, you can apply **partial updates**: +- To add a scenario, just include that scenario under MODIFIED - don't copy existing scenarios +- The delta represents *intent*, not a wholesale replacement +- Use your judgment to merge changes sensibly + +**Output On Success** + +``` +## Specs Synced: <change-name> + +Updated main specs: + +**<capability-1>**: +- Added requirement: "New Feature" +- Modified requirement: "Existing Feature" (added 1 scenario) + +**<capability-2>**: +- Created new spec file +- Added requirement: "Another Feature" + +Main specs are now updated. The change remains active - archive when implementation is complete. +``` + +**Guardrails** +- Read both delta and main specs before making changes +- Preserve existing content not mentioned in delta +- If something is unclear, ask for clarification +- Show what you're changing as you go +- The operation should be idempotent - running twice should give same result diff --git a/.pi/prompts/opsx-verify.md b/.pi/prompts/opsx-verify.md new file mode 100644 index 0000000..66d4779 --- /dev/null +++ b/.pi/prompts/opsx-verify.md @@ -0,0 +1,162 @@ +--- +description: Verify implementation matches change artifacts before archiving +--- + +Verify that an implementation matches the change artifacts (specs, tasks, design). + +**Input**: Optionally specify a change name after `/opsx-verify` (e.g., `/opsx-verify add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes. +**Provided arguments**: $@ + +**Steps** + +1. **If no change name provided, prompt for selection** + + Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select. + + Show changes that have implementation tasks (tasks artifact exists). + Include the schema used for each change if available. + Mark changes with incomplete tasks as "(In Progress)". + + **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose. + +2. **Check status to understand the schema** + ```bash + openspec status --change "<name>" --json + ``` + Parse the JSON to understand: + - `schemaName`: The workflow being used (e.g., "spec-driven") + - Which artifacts exist for this change + +3. **Get the change directory and load artifacts** + + ```bash + openspec instructions apply --change "<name>" --json + ``` + + This returns the change directory and `contextFiles` (artifact ID -> array of concrete file paths). Read all available artifacts from `contextFiles`. + +4. **Initialize verification report structure** + + Create a report structure with three dimensions: + - **Completeness**: Track tasks and spec coverage + - **Correctness**: Track requirement implementation and scenario coverage + - **Coherence**: Track design adherence and pattern consistency + + Each dimension can have CRITICAL, WARNING, or SUGGESTION issues. + +5. **Verify Completeness** + + **Task Completion**: + - If `contextFiles.tasks` exists, read every file path in it + - Parse checkboxes: `- [ ]` (incomplete) vs `- [x]` (complete) + - Count complete vs total tasks + - If incomplete tasks exist: + - Add CRITICAL issue for each incomplete task + - Recommendation: "Complete task: <description>" or "Mark as done if already implemented" + + **Spec Coverage**: + - If delta specs exist in `openspec/changes/<name>/specs/`: + - Extract all requirements (marked with "### Requirement:") + - For each requirement: + - Search codebase for keywords related to the requirement + - Assess if implementation likely exists + - If requirements appear unimplemented: + - Add CRITICAL issue: "Requirement not found: <requirement name>" + - Recommendation: "Implement requirement X: <description>" + +6. **Verify Correctness** + + **Requirement Implementation Mapping**: + - For each requirement from delta specs: + - Search codebase for implementation evidence + - If found, note file paths and line ranges + - Assess if implementation matches requirement intent + - If divergence detected: + - Add WARNING: "Implementation may diverge from spec: <details>" + - Recommendation: "Review <file>:<lines> against requirement X" + + **Scenario Coverage**: + - For each scenario in delta specs (marked with "#### Scenario:"): + - Check if conditions are handled in code + - Check if tests exist covering the scenario + - If scenario appears uncovered: + - Add WARNING: "Scenario not covered: <scenario name>" + - Recommendation: "Add test or implementation for scenario: <description>" + +7. **Verify Coherence** + + **Design Adherence**: + - If `contextFiles.design` exists: + - Extract key decisions (look for sections like "Decision:", "Approach:", "Architecture:") + - Verify implementation follows those decisions + - If contradiction detected: + - Add WARNING: "Design decision not followed: <decision>" + - Recommendation: "Update implementation or revise design.md to match reality" + - If no design.md: Skip design adherence check, note "No design.md to verify against" + + **Code Pattern Consistency**: + - Review new code for consistency with project patterns + - Check file naming, directory structure, coding style + - If significant deviations found: + - Add SUGGESTION: "Code pattern deviation: <details>" + - Recommendation: "Consider following project pattern: <example>" + +8. **Generate Verification Report** + + **Summary Scorecard**: + ``` + ## Verification Report: <change-name> + + ### Summary + | Dimension | Status | + |--------------|------------------| + | Completeness | X/Y tasks, N reqs| + | Correctness | M/N reqs covered | + | Coherence | Followed/Issues | + ``` + + **Issues by Priority**: + + 1. **CRITICAL** (Must fix before archive): + - Incomplete tasks + - Missing requirement implementations + - Each with specific, actionable recommendation + + 2. **WARNING** (Should fix): + - Spec/design divergences + - Missing scenario coverage + - Each with specific recommendation + + 3. **SUGGESTION** (Nice to fix): + - Pattern inconsistencies + - Minor improvements + - Each with specific recommendation + + **Final Assessment**: + - If CRITICAL issues: "X critical issue(s) found. Fix before archiving." + - If only warnings: "No critical issues. Y warning(s) to consider. Ready for archive (with noted improvements)." + - If all clear: "All checks passed. Ready for archive." + +**Verification Heuristics** + +- **Completeness**: Focus on objective checklist items (checkboxes, requirements list) +- **Correctness**: Use keyword search, file path analysis, reasonable inference - don't require perfect certainty +- **Coherence**: Look for glaring inconsistencies, don't nitpick style +- **False Positives**: When uncertain, prefer SUGGESTION over WARNING, WARNING over CRITICAL +- **Actionability**: Every issue must have a specific recommendation with file/line references where applicable + +**Graceful Degradation** + +- If only tasks.md exists: verify task completion only, skip spec/design checks +- If tasks + specs exist: verify completeness and correctness, skip design +- If full artifacts: verify all three dimensions +- Always note which checks were skipped and why + +**Output Format** + +Use clear markdown with: +- Table for summary scorecard +- Grouped lists for issues (CRITICAL/WARNING/SUGGESTION) +- Code references in format: `file.ts:123` +- Specific, actionable recommendations +- No vague suggestions like "consider reviewing" diff --git a/.pi/skills/brainstorming/SKILL.md b/.pi/skills/brainstorming/SKILL.md new file mode 100644 index 0000000..06cd0a2 --- /dev/null +++ b/.pi/skills/brainstorming/SKILL.md @@ -0,0 +1,164 @@ +--- +name: brainstorming +description: "You MUST use this before any creative work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation." +--- + +# Brainstorming Ideas Into Designs + +Help turn ideas into fully formed designs and specs through natural collaborative dialogue. + +Start by understanding the current project context, then ask questions one at a time to refine the idea. Once you understand what you're building, present the design and get user approval. + +<HARD-GATE> +Do NOT invoke any implementation skill, write any code, scaffold any project, or take any implementation action until you have presented a design and the user has approved it. This applies to EVERY project regardless of perceived simplicity. +</HARD-GATE> + +## Anti-Pattern: "This Is Too Simple To Need A Design" + +Every project goes through this process. A todo list, a single-function utility, a config change — all of them. "Simple" projects are where unexamined assumptions cause the most wasted work. The design can be short (a few sentences for truly simple projects), but you MUST present it and get approval. + +## Checklist + +You MUST create a task for each of these items and complete them in order: + +1. **Explore project context** — check files, docs, recent commits +2. **Offer visual companion** (if topic will involve visual questions) — this is its own message, not combined with a clarifying question. See the Visual Companion section below. +3. **Ask clarifying questions** — one at a time, understand purpose/constraints/success criteria +4. **Propose 2-3 approaches** — with trade-offs and your recommendation +5. **Present design** — in sections scaled to their complexity, get user approval after each section +6. **Write design doc** — save to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` and commit +7. **Spec self-review** — quick inline check for placeholders, contradictions, ambiguity, scope (see below) +8. **User reviews written spec** — ask user to review the spec file before proceeding +9. **Transition to implementation** — invoke writing-plans skill to create implementation plan + +## Process Flow + +```dot +digraph brainstorming { + "Explore project context" [shape=box]; + "Visual questions ahead?" [shape=diamond]; + "Offer Visual Companion\n(own message, no other content)" [shape=box]; + "Ask clarifying questions" [shape=box]; + "Propose 2-3 approaches" [shape=box]; + "Present design sections" [shape=box]; + "User approves design?" [shape=diamond]; + "Write design doc" [shape=box]; + "Spec self-review\n(fix inline)" [shape=box]; + "User reviews spec?" [shape=diamond]; + "Invoke writing-plans skill" [shape=doublecircle]; + + "Explore project context" -> "Visual questions ahead?"; + "Visual questions ahead?" -> "Offer Visual Companion\n(own message, no other content)" [label="yes"]; + "Visual questions ahead?" -> "Ask clarifying questions" [label="no"]; + "Offer Visual Companion\n(own message, no other content)" -> "Ask clarifying questions"; + "Ask clarifying questions" -> "Propose 2-3 approaches"; + "Propose 2-3 approaches" -> "Present design sections"; + "Present design sections" -> "User approves design?"; + "User approves design?" -> "Present design sections" [label="no, revise"]; + "User approves design?" -> "Write design doc" [label="yes"]; + "Write design doc" -> "Spec self-review\n(fix inline)"; + "Spec self-review\n(fix inline)" -> "User reviews spec?"; + "User reviews spec?" -> "Write design doc" [label="changes requested"]; + "User reviews spec?" -> "Invoke writing-plans skill" [label="approved"]; +} +``` + +**The terminal state is invoking writing-plans.** Do NOT invoke frontend-design, mcp-builder, or any other implementation skill. The ONLY skill you invoke after brainstorming is writing-plans. + +## The Process + +**Understanding the idea:** + +- Check out the current project state first (files, docs, recent commits) +- Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first. +- If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle. +- For appropriately-scoped projects, ask questions one at a time to refine the idea +- Prefer multiple choice questions when possible, but open-ended is fine too +- Only one question per message - if a topic needs more exploration, break it into multiple questions +- Focus on understanding: purpose, constraints, success criteria + +**Exploring approaches:** + +- Propose 2-3 different approaches with trade-offs +- Present options conversationally with your recommendation and reasoning +- Lead with your recommended option and explain why + +**Presenting the design:** + +- Once you believe you understand what you're building, present the design +- Scale each section to its complexity: a few sentences if straightforward, up to 200-300 words if nuanced +- Ask after each section whether it looks right so far +- Cover: architecture, components, data flow, error handling, testing +- Be ready to go back and clarify if something doesn't make sense + +**Design for isolation and clarity:** + +- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently +- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on? +- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work. +- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much. + +**Working in existing codebases:** + +- Explore the current structure before proposing changes. Follow existing patterns. +- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in. +- Don't propose unrelated refactoring. Stay focused on what serves the current goal. + +## After the Design + +**Documentation:** + +- Write the validated design (spec) to `docs/superpowers/specs/YYYY-MM-DD-<topic>-design.md` + - (User preferences for spec location override this default) +- Use elements-of-style:writing-clearly-and-concisely skill if available +- Commit the design document to git + +**Spec Self-Review:** +After writing the spec document, look at it with fresh eyes: + +1. **Placeholder scan:** Any "TBD", "TODO", incomplete sections, or vague requirements? Fix them. +2. **Internal consistency:** Do any sections contradict each other? Does the architecture match the feature descriptions? +3. **Scope check:** Is this focused enough for a single implementation plan, or does it need decomposition? +4. **Ambiguity check:** Could any requirement be interpreted two different ways? If so, pick one and make it explicit. + +Fix any issues inline. No need to re-review — just fix and move on. + +**User Review Gate:** +After the spec review loop passes, ask the user to review the written spec before proceeding: + +> "Spec written and committed to `<path>`. Please review it and let me know if you want to make any changes before we start writing out the implementation plan." + +Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves. + +**Implementation:** + +- Invoke the writing-plans skill to create a detailed implementation plan +- Do NOT invoke any other skill. writing-plans is the next step. + +## Key Principles + +- **One question at a time** - Don't overwhelm with multiple questions +- **Multiple choice preferred** - Easier to answer than open-ended when possible +- **YAGNI ruthlessly** - Remove unnecessary features from all designs +- **Explore alternatives** - Always propose 2-3 approaches before settling +- **Incremental validation** - Present design, get approval before moving on +- **Be flexible** - Go back and clarify when something doesn't make sense + +## Visual Companion + +A browser-based companion for showing mockups, diagrams, and visual options during brainstorming. Available as a tool — not a mode. Accepting the companion means it's available for questions that benefit from visual treatment; it does NOT mean every question goes through the browser. + +**Offering the companion:** When you anticipate that upcoming questions will involve visual content (mockups, layouts, diagrams), offer it once for consent: +> "Some of what we're working on might be easier to explain if I can show it to you in a web browser. I can put together mockups, diagrams, comparisons, and other visuals as we go. This feature is still new and can be token-intensive. Want to try it? (Requires opening a local URL)" + +**This offer MUST be its own message.** Do not combine it with clarifying questions, context summaries, or any other content. The message should contain ONLY the offer above and nothing else. Wait for the user's response before continuing. If they decline, proceed with text-only brainstorming. + +**Per-question decision:** Even after the user accepts, decide FOR EACH QUESTION whether to use the browser or the terminal. The test: **would the user understand this better by seeing it than reading it?** + +- **Use the browser** for content that IS visual — mockups, wireframes, layout comparisons, architecture diagrams, side-by-side visual designs +- **Use the terminal** for content that is text — requirements questions, conceptual choices, tradeoff lists, A/B/C/D text options, scope decisions + +A question about a UI topic is not automatically a visual question. "What does personality mean in this context?" is a conceptual question — use the terminal. "Which wizard layout works better?" is a visual question — use the browser. + +If they agree to the companion, read the detailed guide before proceeding: +`skills/brainstorming/visual-companion.md` diff --git a/.pi/skills/brainstorming/scripts/frame-template.html b/.pi/skills/brainstorming/scripts/frame-template.html new file mode 100644 index 0000000..dcfe018 --- /dev/null +++ b/.pi/skills/brainstorming/scripts/frame-template.html @@ -0,0 +1,214 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Superpowers Brainstorming + + + +
+

Superpowers Brainstorming

+
Connected
+
+ +
+
+ +
+
+ +
+ Click an option above, then return to the terminal +
+ + + diff --git a/.pi/skills/brainstorming/scripts/helper.js b/.pi/skills/brainstorming/scripts/helper.js new file mode 100644 index 0000000..111f97f --- /dev/null +++ b/.pi/skills/brainstorming/scripts/helper.js @@ -0,0 +1,88 @@ +(function() { + const WS_URL = 'ws://' + window.location.host; + let ws = null; + let eventQueue = []; + + function connect() { + ws = new WebSocket(WS_URL); + + ws.onopen = () => { + eventQueue.forEach(e => ws.send(JSON.stringify(e))); + eventQueue = []; + }; + + ws.onmessage = (msg) => { + const data = JSON.parse(msg.data); + if (data.type === 'reload') { + window.location.reload(); + } + }; + + ws.onclose = () => { + setTimeout(connect, 1000); + }; + } + + function sendEvent(event) { + event.timestamp = Date.now(); + if (ws && ws.readyState === WebSocket.OPEN) { + ws.send(JSON.stringify(event)); + } else { + eventQueue.push(event); + } + } + + // Capture clicks on choice elements + document.addEventListener('click', (e) => { + const target = e.target.closest('[data-choice]'); + if (!target) return; + + sendEvent({ + type: 'click', + text: target.textContent.trim(), + choice: target.dataset.choice, + id: target.id || null + }); + + // Update indicator bar (defer so toggleSelect runs first) + setTimeout(() => { + const indicator = document.getElementById('indicator-text'); + if (!indicator) return; + const container = target.closest('.options') || target.closest('.cards'); + const selected = container ? container.querySelectorAll('.selected') : []; + if (selected.length === 0) { + indicator.textContent = 'Click an option above, then return to the terminal'; + } else if (selected.length === 1) { + const label = selected[0].querySelector('h3, .content h3, .card-body h3')?.textContent?.trim() || selected[0].dataset.choice; + indicator.innerHTML = '' + label + ' selected — return to terminal to continue'; + } else { + indicator.innerHTML = '' + selected.length + ' selected — return to terminal to continue'; + } + }, 0); + }); + + // Frame UI: selection tracking + window.selectedChoice = null; + + window.toggleSelect = function(el) { + const container = el.closest('.options') || el.closest('.cards'); + const multi = container && container.dataset.multiselect !== undefined; + if (container && !multi) { + container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected')); + } + if (multi) { + el.classList.toggle('selected'); + } else { + el.classList.add('selected'); + } + window.selectedChoice = el.dataset.choice; + }; + + // Expose API for explicit use + window.brainstorm = { + send: sendEvent, + choice: (value, metadata = {}) => sendEvent({ type: 'choice', value, ...metadata }) + }; + + connect(); +})(); diff --git a/.pi/skills/brainstorming/scripts/server.cjs b/.pi/skills/brainstorming/scripts/server.cjs new file mode 100644 index 0000000..562c17f --- /dev/null +++ b/.pi/skills/brainstorming/scripts/server.cjs @@ -0,0 +1,354 @@ +const crypto = require('crypto'); +const http = require('http'); +const fs = require('fs'); +const path = require('path'); + +// ========== WebSocket Protocol (RFC 6455) ========== + +const OPCODES = { TEXT: 0x01, CLOSE: 0x08, PING: 0x09, PONG: 0x0A }; +const WS_MAGIC = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; + +function computeAcceptKey(clientKey) { + return crypto.createHash('sha1').update(clientKey + WS_MAGIC).digest('base64'); +} + +function encodeFrame(opcode, payload) { + const fin = 0x80; + const len = payload.length; + let header; + + if (len < 126) { + header = Buffer.alloc(2); + header[0] = fin | opcode; + header[1] = len; + } else if (len < 65536) { + header = Buffer.alloc(4); + header[0] = fin | opcode; + header[1] = 126; + header.writeUInt16BE(len, 2); + } else { + header = Buffer.alloc(10); + header[0] = fin | opcode; + header[1] = 127; + header.writeBigUInt64BE(BigInt(len), 2); + } + + return Buffer.concat([header, payload]); +} + +function decodeFrame(buffer) { + if (buffer.length < 2) return null; + + const secondByte = buffer[1]; + const opcode = buffer[0] & 0x0F; + const masked = (secondByte & 0x80) !== 0; + let payloadLen = secondByte & 0x7F; + let offset = 2; + + if (!masked) throw new Error('Client frames must be masked'); + + if (payloadLen === 126) { + if (buffer.length < 4) return null; + payloadLen = buffer.readUInt16BE(2); + offset = 4; + } else if (payloadLen === 127) { + if (buffer.length < 10) return null; + payloadLen = Number(buffer.readBigUInt64BE(2)); + offset = 10; + } + + const maskOffset = offset; + const dataOffset = offset + 4; + const totalLen = dataOffset + payloadLen; + if (buffer.length < totalLen) return null; + + const mask = buffer.slice(maskOffset, dataOffset); + const data = Buffer.alloc(payloadLen); + for (let i = 0; i < payloadLen; i++) { + data[i] = buffer[dataOffset + i] ^ mask[i % 4]; + } + + return { opcode, payload: data, bytesConsumed: totalLen }; +} + +// ========== Configuration ========== + +const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383)); +const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1'; +const URL_HOST = process.env.BRAINSTORM_URL_HOST || (HOST === '127.0.0.1' ? 'localhost' : HOST); +const SESSION_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm'; +const CONTENT_DIR = path.join(SESSION_DIR, 'content'); +const STATE_DIR = path.join(SESSION_DIR, 'state'); +let ownerPid = process.env.BRAINSTORM_OWNER_PID ? Number(process.env.BRAINSTORM_OWNER_PID) : null; + +const MIME_TYPES = { + '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', + '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml' +}; + +// ========== Templates and Constants ========== + +const WAITING_PAGE = ` + +Brainstorm Companion + + +

Brainstorm Companion

+

Waiting for the agent to push a screen...

`; + +const frameTemplate = fs.readFileSync(path.join(__dirname, 'frame-template.html'), 'utf-8'); +const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); +const helperInjection = ''; + +// ========== Helper Functions ========== + +function isFullDocument(html) { + const trimmed = html.trimStart().toLowerCase(); + return trimmed.startsWith('', content); +} + +function getNewestScreen() { + const files = fs.readdirSync(CONTENT_DIR) + .filter(f => f.endsWith('.html')) + .map(f => { + const fp = path.join(CONTENT_DIR, f); + return { path: fp, mtime: fs.statSync(fp).mtime.getTime() }; + }) + .sort((a, b) => b.mtime - a.mtime); + return files.length > 0 ? files[0].path : null; +} + +// ========== HTTP Request Handler ========== + +function handleRequest(req, res) { + touchActivity(); + if (req.method === 'GET' && req.url === '/') { + const screenFile = getNewestScreen(); + let html = screenFile + ? (raw => isFullDocument(raw) ? raw : wrapInFrame(raw))(fs.readFileSync(screenFile, 'utf-8')) + : WAITING_PAGE; + + if (html.includes('')) { + html = html.replace('', helperInjection + '\n'); + } else { + html += helperInjection; + } + + res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); + res.end(html); + } else if (req.method === 'GET' && req.url.startsWith('/files/')) { + const fileName = req.url.slice(7); + const filePath = path.join(CONTENT_DIR, path.basename(fileName)); + if (!fs.existsSync(filePath)) { + res.writeHead(404); + res.end('Not found'); + return; + } + const ext = path.extname(filePath).toLowerCase(); + const contentType = MIME_TYPES[ext] || 'application/octet-stream'; + res.writeHead(200, { 'Content-Type': contentType }); + res.end(fs.readFileSync(filePath)); + } else { + res.writeHead(404); + res.end('Not found'); + } +} + +// ========== WebSocket Connection Handling ========== + +const clients = new Set(); + +function handleUpgrade(req, socket) { + const key = req.headers['sec-websocket-key']; + if (!key) { socket.destroy(); return; } + + const accept = computeAcceptKey(key); + socket.write( + 'HTTP/1.1 101 Switching Protocols\r\n' + + 'Upgrade: websocket\r\n' + + 'Connection: Upgrade\r\n' + + 'Sec-WebSocket-Accept: ' + accept + '\r\n\r\n' + ); + + let buffer = Buffer.alloc(0); + clients.add(socket); + + socket.on('data', (chunk) => { + buffer = Buffer.concat([buffer, chunk]); + while (buffer.length > 0) { + let result; + try { + result = decodeFrame(buffer); + } catch (e) { + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + } + if (!result) break; + buffer = buffer.slice(result.bytesConsumed); + + switch (result.opcode) { + case OPCODES.TEXT: + handleMessage(result.payload.toString()); + break; + case OPCODES.CLOSE: + socket.end(encodeFrame(OPCODES.CLOSE, Buffer.alloc(0))); + clients.delete(socket); + return; + case OPCODES.PING: + socket.write(encodeFrame(OPCODES.PONG, result.payload)); + break; + case OPCODES.PONG: + break; + default: { + const closeBuf = Buffer.alloc(2); + closeBuf.writeUInt16BE(1003); + socket.end(encodeFrame(OPCODES.CLOSE, closeBuf)); + clients.delete(socket); + return; + } + } + } + }); + + socket.on('close', () => clients.delete(socket)); + socket.on('error', () => clients.delete(socket)); +} + +function handleMessage(text) { + let event; + try { + event = JSON.parse(text); + } catch (e) { + console.error('Failed to parse WebSocket message:', e.message); + return; + } + touchActivity(); + console.log(JSON.stringify({ source: 'user-event', ...event })); + if (event.choice) { + const eventsFile = path.join(STATE_DIR, 'events'); + fs.appendFileSync(eventsFile, JSON.stringify(event) + '\n'); + } +} + +function broadcast(msg) { + const frame = encodeFrame(OPCODES.TEXT, Buffer.from(JSON.stringify(msg))); + for (const socket of clients) { + try { socket.write(frame); } catch (e) { clients.delete(socket); } + } +} + +// ========== Activity Tracking ========== + +const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +let lastActivity = Date.now(); + +function touchActivity() { + lastActivity = Date.now(); +} + +// ========== File Watching ========== + +const debounceTimers = new Map(); + +// ========== Server Startup ========== + +function startServer() { + if (!fs.existsSync(CONTENT_DIR)) fs.mkdirSync(CONTENT_DIR, { recursive: true }); + if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true }); + + // Track known files to distinguish new screens from updates. + // macOS fs.watch reports 'rename' for both new files and overwrites, + // so we can't rely on eventType alone. + const knownFiles = new Set( + fs.readdirSync(CONTENT_DIR).filter(f => f.endsWith('.html')) + ); + + const server = http.createServer(handleRequest); + server.on('upgrade', handleUpgrade); + + const watcher = fs.watch(CONTENT_DIR, (eventType, filename) => { + if (!filename || !filename.endsWith('.html')) return; + + if (debounceTimers.has(filename)) clearTimeout(debounceTimers.get(filename)); + debounceTimers.set(filename, setTimeout(() => { + debounceTimers.delete(filename); + const filePath = path.join(CONTENT_DIR, filename); + + if (!fs.existsSync(filePath)) return; // file was deleted + touchActivity(); + + if (!knownFiles.has(filename)) { + knownFiles.add(filename); + const eventsFile = path.join(STATE_DIR, 'events'); + if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile); + console.log(JSON.stringify({ type: 'screen-added', file: filePath })); + } else { + console.log(JSON.stringify({ type: 'screen-updated', file: filePath })); + } + + broadcast({ type: 'reload' }); + }, 100)); + }); + watcher.on('error', (err) => console.error('fs.watch error:', err.message)); + + function shutdown(reason) { + console.log(JSON.stringify({ type: 'server-stopped', reason })); + const infoFile = path.join(STATE_DIR, 'server-info'); + if (fs.existsSync(infoFile)) fs.unlinkSync(infoFile); + fs.writeFileSync( + path.join(STATE_DIR, 'server-stopped'), + JSON.stringify({ reason, timestamp: Date.now() }) + '\n' + ); + watcher.close(); + clearInterval(lifecycleCheck); + server.close(() => process.exit(0)); + } + + function ownerAlive() { + if (!ownerPid) return true; + try { process.kill(ownerPid, 0); return true; } catch (e) { return e.code === 'EPERM'; } + } + + // Check every 60s: exit if owner process died or idle for 30 minutes + const lifecycleCheck = setInterval(() => { + if (!ownerAlive()) shutdown('owner process exited'); + else if (Date.now() - lastActivity > IDLE_TIMEOUT_MS) shutdown('idle timeout'); + }, 60 * 1000); + lifecycleCheck.unref(); + + // Validate owner PID at startup. If it's already dead, the PID resolution + // was wrong (common on WSL, Tailscale SSH, and cross-user scenarios). + // Disable monitoring and rely on the idle timeout instead. + if (ownerPid) { + try { process.kill(ownerPid, 0); } + catch (e) { + if (e.code !== 'EPERM') { + console.log(JSON.stringify({ type: 'owner-pid-invalid', pid: ownerPid, reason: 'dead at startup' })); + ownerPid = null; + } + } + } + + server.listen(PORT, HOST, () => { + const info = JSON.stringify({ + type: 'server-started', port: Number(PORT), host: HOST, + url_host: URL_HOST, url: 'http://' + URL_HOST + ':' + PORT, + screen_dir: CONTENT_DIR, state_dir: STATE_DIR + }); + console.log(info); + fs.writeFileSync(path.join(STATE_DIR, 'server-info'), info + '\n'); + }); +} + +if (require.main === module) { + startServer(); +} + +module.exports = { computeAcceptKey, encodeFrame, decodeFrame, OPCODES }; diff --git a/.pi/skills/brainstorming/scripts/start-server.sh b/.pi/skills/brainstorming/scripts/start-server.sh new file mode 100755 index 0000000..9ef6dcb --- /dev/null +++ b/.pi/skills/brainstorming/scripts/start-server.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# Start the brainstorm server and output connection info +# Usage: start-server.sh [--project-dir ] [--host ] [--url-host ] [--foreground] [--background] +# +# Starts server on a random high port, outputs JSON with URL. +# Each session gets its own directory to avoid conflicts. +# +# Options: +# --project-dir Store session files under /.superpowers/brainstorm/ +# instead of /tmp. Files persist after server stops. +# --host Host/interface to bind (default: 127.0.0.1). +# Use 0.0.0.0 in remote/containerized environments. +# --url-host Hostname shown in returned URL JSON. +# --foreground Run server in the current terminal (no backgrounding). +# --background Force background mode (overrides Codex auto-foreground). + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +# Parse arguments +PROJECT_DIR="" +FOREGROUND="false" +FORCE_BACKGROUND="false" +BIND_HOST="127.0.0.1" +URL_HOST="" +while [[ $# -gt 0 ]]; do + case "$1" in + --project-dir) + PROJECT_DIR="$2" + shift 2 + ;; + --host) + BIND_HOST="$2" + shift 2 + ;; + --url-host) + URL_HOST="$2" + shift 2 + ;; + --foreground|--no-daemon) + FOREGROUND="true" + shift + ;; + --background|--daemon) + FORCE_BACKGROUND="true" + shift + ;; + *) + echo "{\"error\": \"Unknown argument: $1\"}" + exit 1 + ;; + esac +done + +if [[ -z "$URL_HOST" ]]; then + if [[ "$BIND_HOST" == "127.0.0.1" || "$BIND_HOST" == "localhost" ]]; then + URL_HOST="localhost" + else + URL_HOST="$BIND_HOST" + fi +fi + +# Some environments reap detached/background processes. Auto-foreground when detected. +if [[ -n "${CODEX_CI:-}" && "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then + FOREGROUND="true" +fi + +# Windows/Git Bash reaps nohup background processes. Auto-foreground when detected. +if [[ "$FOREGROUND" != "true" && "$FORCE_BACKGROUND" != "true" ]]; then + case "${OSTYPE:-}" in + msys*|cygwin*|mingw*) FOREGROUND="true" ;; + esac + if [[ -n "${MSYSTEM:-}" ]]; then + FOREGROUND="true" + fi +fi + +# Generate unique session directory +SESSION_ID="$$-$(date +%s)" + +if [[ -n "$PROJECT_DIR" ]]; then + SESSION_DIR="${PROJECT_DIR}/.superpowers/brainstorm/${SESSION_ID}" +else + SESSION_DIR="/tmp/brainstorm-${SESSION_ID}" +fi + +STATE_DIR="${SESSION_DIR}/state" +PID_FILE="${STATE_DIR}/server.pid" +LOG_FILE="${STATE_DIR}/server.log" + +# Create fresh session directory with content and state peers +mkdir -p "${SESSION_DIR}/content" "$STATE_DIR" + +# Kill any existing server +if [[ -f "$PID_FILE" ]]; then + old_pid=$(cat "$PID_FILE") + kill "$old_pid" 2>/dev/null + rm -f "$PID_FILE" +fi + +cd "$SCRIPT_DIR" + +# Resolve the harness PID (grandparent of this script). +# $PPID is the ephemeral shell the harness spawned to run us — it dies +# when this script exits. The harness itself is $PPID's parent. +OWNER_PID="$(ps -o ppid= -p "$PPID" 2>/dev/null | tr -d ' ')" +if [[ -z "$OWNER_PID" || "$OWNER_PID" == "1" ]]; then + OWNER_PID="$PPID" +fi + +# Foreground mode for environments that reap detached/background processes. +if [[ "$FOREGROUND" == "true" ]]; then + echo "$$" > "$PID_FILE" + env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs + exit $? +fi + +# Start server, capturing output to log file +# Use nohup to survive shell exit; disown to remove from job table +nohup env BRAINSTORM_DIR="$SESSION_DIR" BRAINSTORM_HOST="$BIND_HOST" BRAINSTORM_URL_HOST="$URL_HOST" BRAINSTORM_OWNER_PID="$OWNER_PID" node server.cjs > "$LOG_FILE" 2>&1 & +SERVER_PID=$! +disown "$SERVER_PID" 2>/dev/null +echo "$SERVER_PID" > "$PID_FILE" + +# Wait for server-started message (check log file) +for i in {1..50}; do + if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then + # Verify server is still alive after a short window (catches process reapers) + alive="true" + for _ in {1..20}; do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + alive="false" + break + fi + sleep 0.1 + done + if [[ "$alive" != "true" ]]; then + echo "{\"error\": \"Server started but was killed. Retry in a persistent terminal with: $SCRIPT_DIR/start-server.sh${PROJECT_DIR:+ --project-dir $PROJECT_DIR} --host $BIND_HOST --url-host $URL_HOST --foreground\"}" + exit 1 + fi + grep "server-started" "$LOG_FILE" | head -1 + exit 0 + fi + sleep 0.1 +done + +# Timeout - server didn't start +echo '{"error": "Server failed to start within 5 seconds"}' +exit 1 diff --git a/.pi/skills/brainstorming/scripts/stop-server.sh b/.pi/skills/brainstorming/scripts/stop-server.sh new file mode 100755 index 0000000..a6b94e6 --- /dev/null +++ b/.pi/skills/brainstorming/scripts/stop-server.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Stop the brainstorm server and clean up +# Usage: stop-server.sh +# +# Kills the server process. Only deletes session directory if it's +# under /tmp (ephemeral). Persistent directories (.superpowers/) are +# kept so mockups can be reviewed later. + +SESSION_DIR="$1" + +if [[ -z "$SESSION_DIR" ]]; then + echo '{"error": "Usage: stop-server.sh "}' + exit 1 +fi + +STATE_DIR="${SESSION_DIR}/state" +PID_FILE="${STATE_DIR}/server.pid" + +if [[ -f "$PID_FILE" ]]; then + pid=$(cat "$PID_FILE") + + # Try to stop gracefully, fallback to force if still alive + kill "$pid" 2>/dev/null || true + + # Wait for graceful shutdown (up to ~2s) + for i in {1..20}; do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + sleep 0.1 + done + + # If still running, escalate to SIGKILL + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + + # Give SIGKILL a moment to take effect + sleep 0.1 + fi + + if kill -0 "$pid" 2>/dev/null; then + echo '{"status": "failed", "error": "process still running"}' + exit 1 + fi + + rm -f "$PID_FILE" "${STATE_DIR}/server.log" + + # Only delete ephemeral /tmp directories + if [[ "$SESSION_DIR" == /tmp/* ]]; then + rm -rf "$SESSION_DIR" + fi + + echo '{"status": "stopped"}' +else + echo '{"status": "not_running"}' +fi diff --git a/.pi/skills/brainstorming/spec-document-reviewer-prompt.md b/.pi/skills/brainstorming/spec-document-reviewer-prompt.md new file mode 100644 index 0000000..35acbb6 --- /dev/null +++ b/.pi/skills/brainstorming/spec-document-reviewer-prompt.md @@ -0,0 +1,49 @@ +# Spec Document Reviewer Prompt Template + +Use this template when dispatching a spec document reviewer subagent. + +**Purpose:** Verify the spec is complete, consistent, and ready for implementation planning. + +**Dispatch after:** Spec document is written to docs/superpowers/specs/ + +``` +Task tool (general-purpose): + description: "Review spec document" + prompt: | + You are a spec document reviewer. Verify this spec is complete and ready for planning. + + **Spec to review:** [SPEC_FILE_PATH] + + ## What to Check + + | Category | What to Look For | + |----------|------------------| + | Completeness | TODOs, placeholders, "TBD", incomplete sections | + | Consistency | Internal contradictions, conflicting requirements | + | Clarity | Requirements ambiguous enough to cause someone to build the wrong thing | + | Scope | Focused enough for a single plan — not covering multiple independent subsystems | + | YAGNI | Unrequested features, over-engineering | + + ## Calibration + + **Only flag issues that would cause real problems during implementation planning.** + A missing section, a contradiction, or a requirement so ambiguous it could be + interpreted two different ways — those are issues. Minor wording improvements, + stylistic preferences, and "sections less detailed than others" are not. + + Approve unless there are serious gaps that would lead to a flawed plan. + + ## Output Format + + ## Spec Review + + **Status:** Approved | Issues Found + + **Issues (if any):** + - [Section X]: [specific issue] - [why it matters for planning] + + **Recommendations (advisory, do not block approval):** + - [suggestions for improvement] +``` + +**Reviewer returns:** Status, Issues (if any), Recommendations diff --git a/.pi/skills/brainstorming/visual-companion.md b/.pi/skills/brainstorming/visual-companion.md new file mode 100644 index 0000000..2113863 --- /dev/null +++ b/.pi/skills/brainstorming/visual-companion.md @@ -0,0 +1,287 @@ +# Visual Companion Guide + +Browser-based visual brainstorming companion for showing mockups, diagrams, and options. + +## When to Use + +Decide per-question, not per-session. The test: **would the user understand this better by seeing it than reading it?** + +**Use the browser** when the content itself is visual: + +- **UI mockups** — wireframes, layouts, navigation structures, component designs +- **Architecture diagrams** — system components, data flow, relationship maps +- **Side-by-side visual comparisons** — comparing two layouts, two color schemes, two design directions +- **Design polish** — when the question is about look and feel, spacing, visual hierarchy +- **Spatial relationships** — state machines, flowcharts, entity relationships rendered as diagrams + +**Use the terminal** when the content is text or tabular: + +- **Requirements and scope questions** — "what does X mean?", "which features are in scope?" +- **Conceptual A/B/C choices** — picking between approaches described in words +- **Tradeoff lists** — pros/cons, comparison tables +- **Technical decisions** — API design, data modeling, architectural approach selection +- **Clarifying questions** — anything where the answer is words, not a visual preference + +A question *about* a UI topic is not automatically a visual question. "What kind of wizard do you want?" is conceptual — use the terminal. "Which of these wizard layouts feels right?" is visual — use the browser. + +## How It Works + +The server watches a directory for HTML files and serves the newest one to the browser. You write HTML content to `screen_dir`, the user sees it in their browser and can click to select options. Selections are recorded to `state_dir/events` that you read on your next turn. + +**Content fragments vs full documents:** If your HTML file starts with `/.superpowers/brainstorm/` for the session directory. + +**Note:** Pass the project root as `--project-dir` so mockups persist in `.superpowers/brainstorm/` and survive server restarts. Without it, files go to `/tmp` and get cleaned up. Remind the user to add `.superpowers/` to `.gitignore` if it's not already there. + +**Launching the server by platform:** + +**Claude Code (macOS / Linux):** +```bash +# Default mode works — the script backgrounds the server itself +scripts/start-server.sh --project-dir /path/to/project +``` + +**Claude Code (Windows):** +```bash +# Windows auto-detects and uses foreground mode, which blocks the tool call. +# Use run_in_background: true on the Bash tool call so the server survives +# across conversation turns. +scripts/start-server.sh --project-dir /path/to/project +``` +When calling this via the Bash tool, set `run_in_background: true`. Then read `$STATE_DIR/server-info` on the next turn to get the URL and port. + +**Codex:** +```bash +# Codex reaps background processes. The script auto-detects CODEX_CI and +# switches to foreground mode. Run it normally — no extra flags needed. +scripts/start-server.sh --project-dir /path/to/project +``` + +**Gemini CLI:** +```bash +# Use --foreground and set is_background: true on your shell tool call +# so the process survives across turns +scripts/start-server.sh --project-dir /path/to/project --foreground +``` + +**Other environments:** The server must keep running in the background across conversation turns. If your environment reaps detached processes, use `--foreground` and launch the command with your platform's background execution mechanism. + +If the URL is unreachable from your browser (common in remote/containerized setups), bind a non-loopback host: + +```bash +scripts/start-server.sh \ + --project-dir /path/to/project \ + --host 0.0.0.0 \ + --url-host localhost +``` + +Use `--url-host` to control what hostname is printed in the returned URL JSON. + +## The Loop + +1. **Check server is alive**, then **write HTML** to a new file in `screen_dir`: + - Before each write, check that `$STATE_DIR/server-info` exists. If it doesn't (or `$STATE_DIR/server-stopped` exists), the server has shut down — restart it with `start-server.sh` before continuing. The server auto-exits after 30 minutes of inactivity. + - Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html` + - **Never reuse filenames** — each screen gets a fresh file + - Use Write tool — **never use cat/heredoc** (dumps noise into terminal) + - Server automatically serves the newest file + +2. **Tell user what to expect and end your turn:** + - Remind them of the URL (every step, not just first) + - Give a brief text summary of what's on screen (e.g., "Showing 3 layout options for the homepage") + - Ask them to respond in the terminal: "Take a look and let me know what you think. Click to select an option if you'd like." + +3. **On your next turn** — after the user responds in the terminal: + - Read `$STATE_DIR/events` if it exists — this contains the user's browser interactions (clicks, selections) as JSON lines + - Merge with the user's terminal text to get the full picture + - The terminal message is the primary feedback; `state_dir/events` provides structured interaction data + +4. **Iterate or advance** — if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to the next question when the current step is validated. + +5. **Unload when returning to terminal** — when the next step doesn't need the browser (e.g., a clarifying question, a tradeoff discussion), push a waiting screen to clear the stale content: + + ```html + +
+

Continuing in terminal...

+
+ ``` + + This prevents the user from staring at a resolved choice while the conversation has moved on. When the next visual question comes up, push a new content file as usual. + +6. Repeat until done. + +## Writing Content Fragments + +Write just the content that goes inside the page. The server wraps it in the frame template automatically (header, theme CSS, selection indicator, and all interactive infrastructure). + +**Minimal example:** + +```html +

Which layout works better?

+

Consider readability and visual hierarchy

+ +
+
+
A
+
+

Single Column

+

Clean, focused reading experience

+
+
+
+
B
+
+

Two Column

+

Sidebar navigation with main content

+
+
+
+``` + +That's it. No ``, no CSS, no `