Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions .claude/skills/opencode-plugin-idioms/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: opencode-plugin-idioms
description: Event hook payload shapes, session lifecycle timing, toast/TUI-route render boundaries, and child-session tagging conventions for OpenCode server (@opencode-ai/plugin) and TUI (@opencode-ai/plugin/tui) plugins, grounded in the vendored OpenCode source at references/opencode.
type: prompt
whenToUse: Load before modifying src/server.ts, src/tui.tsx, src/board.tsx, src/commands.tsx, src/store.ts, src/intake.ts, or src/validator.ts — or when debugging a missing toast, a session-creation race, a permission/gate ordering bug, or a plugin-spawned child session re-triggering its own handler.
whenToUse: Load before modifying src/server.ts, src/tui.tsx, src/board.tsx, src/commands.tsx, src/store.tsx, src/intake.ts, or src/validator.ts — or when debugging a missing toast, a session-creation race, a permission/gate ordering bug, or a plugin-spawned child session re-triggering its own handler.
---

Version check: repo pins `@opencode-ai/plugin@1.17.13`; vendored copy at `references/opencode/packages/plugin/package.json` should match — verify there, never from memory.
Expand Down Expand Up @@ -52,7 +52,23 @@ const unsubscribe =

## 2. Reacting to session creation

`session.created` (§1) already covers "instant reaction to a new session" — no need for a different hook. This repo's `src/server.ts` currently only branches on `session.updated`/`session.idle`/`session.deleted` (`src/server.ts:245,251,308`) and relies on a `lastColumn` Map with `prev === undefined` as a proxy for "first time seen" (`src/server.ts:257`) — that's a workaround for not listening to `session.created` directly; listening to `session.created` for board-column bootstrapping would be more direct and remove the `undefined`-sentinel indirection.
`session.created` (§1) covers instant reaction to a new session. This repo's `src/server.ts` `event` hook
switches on `session.created`, `session.updated`, `session.idle`, and others (`src/server.ts:477-491`).

`handleSessionCreated` (`src/server.ts:317-324`) runs on `session.created`: skips helper/parented
sessions (`role` or `parentID`), then for board tasks with no `lastGatedStatus` yet seeds it from
`getStatus(metadata)` via `patchKagan`. That establishes the baseline column before
`handleSessionUpdated` sees status changes.

`handleSessionUpdated` (`src/server.ts:326-386`) reads `lastGatedStatus` from persisted
`metadata.kagan` (not an in-memory Map). On first update where `prev === undefined`, it seeds
`lastGatedStatus` for board tasks (covers sessions created before the field existed or when
`session.created` raced). When `newCol !== prev`, it enforces column-move gates and updates
`lastGatedStatus` on allowed moves.

The `undefined` sentinel on `lastGatedStatus` distinguishes "never gated" from an explicit column
value — `session.created` seeds it early so subsequent `session.updated` events can gate column
transitions correctly.

Full `Hooks` interface (all hook names a plugin may implement) — `references/opencode/packages/plugin/src/index.ts:222-335`:

Expand All @@ -76,7 +92,7 @@ for (const hook of s.hooks) {
}
```

So a later-registered plugin can silently overwrite an earlier plugin's `output.status` (`permission.ask`) or `output.args` (`tool.execute.before`). This repo's own `permission.ask` and `tool.execute.before` handlers (`src/server.ts:335-343`, `345-363`) only ever read `output`/set `output.status` or throw — they don't assume they're the only plugin present, which is correct defensive practice given this ordering.
So a later-registered plugin can silently overwrite an earlier plugin's `output.status` (`permission.ask`, if wired) or `output.args` (`tool.execute.before`). This repo's own `tool.execute.before` handler reads `output` and throws only when it must block a command; it doesn't assume it's the only plugin present, which is correct defensive practice given this ordering.

**`permission.ask` is declared in the `Hooks` type but has zero trigger call sites** anywhere in `packages/opencode/src` in this snapshot (`grep -rn '"permission.ask"\|trigger("permission' packages/opencode/src` returns nothing outside the type definition and this repo's own handler). Don't assume it's guaranteed to fire on every permission prompt without testing against the actual runtime version in use — treat it as declared-but-unverified-wired for 1.17.x and confirm empirically if a permission gate silently doesn't trigger.

Expand Down Expand Up @@ -132,7 +148,7 @@ This repo already implements the correct pattern in `src/intake.ts:9-19` and `sr

- Set `parentID: parentSessionID` on `session.create()` for the structural link.
- Set `metadata.kagan.role` to a discriminator (`"intake"` / `"validator"`) plus a back-pointer (`intakeParent` / `validatorParent`) so a downstream `event` handler can distinguish a helper session from a real task session by reading `session.metadata.kagan.role` before doing anything session-lifecycle-driven.
- `src/server.ts`'s `event` handler never explicitly checks `role` before running `maybeRunReviewEntry`/`maybeRunIntakeEntry` — it relies on the child session's `metadata.kagan.status` never reaching `"review"`/`"backlog"` naturally (since intake/validator sessions are prompted directly via `session.prompt`, not moved through the board). If a future column-move handler is added that fires on any `session.updated` regardless of column, it must check `metadata?.kagan?.role` first to avoid an intake/validator session recursively spawning its own intake/validator.
- `src/server.ts`'s `event` handler skips helper and parented sessions in `handleSessionUpdated` via `if (infoView.role || info.parentID) return` (`src/server.ts:328`) before running column-move gates — any new lifecycle handler on `session.updated` must apply the same guard first to avoid an intake/validator session recursively spawning its own intake/validator.

Setting `parentID` is not purely structural bookkeeping — it changes real framework behavior worth relying on for helper/child sessions:

Expand Down
136 changes: 136 additions & 0 deletions .claude/skills/self-align/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
name: self-align
description: Adversarial self-audit of the Kagan repo through four lenses — code quality & YAGNI, security, spec alignment, docs/AGENTS.md freshness — each finding self-assessed and reported by severity and confidence for the developer to action. Use only when explicitly asked to self-align, audit, or health-check the codebase; it spawns multiple agents and is not for routine questions.
disable-model-invocation: true
allowed-tools: Read, Grep, Glob, Bash, Agent
argument-hint: "[quality, security, spec, docs]"
---

# Self-Align Audit

Audit this repo through four adversarial lenses, then self-assess every finding before reporting.
Written for any agent (Claude Code, OpenCode, Codex): tool names below are capabilities, not
specific tools — map them to your environment.

## Lenses

| Lens | Criteria file | Targets |
| -------- | ------------------------------------ | ------------------------------------------------------ |
| quality | [lens-quality.md](lens-quality.md) | `src/`, `test/`, `scripts/` — quality, YAGNI, idioms |
| security | [lens-security.md](lens-security.md) | `src/`, `scripts/` — input→sink paths, destructive ops |
| spec | [lens-spec.md](lens-spec.md) | `.specs/` vs `src/` behavior |
| docs | [lens-docs.md](lens-docs.md) | `README.md`, `docs/`, `CONTRIBUTING.md`, `AGENTS.md` |

Do not load the criteria files yourself — each lens agent reads its own. Load one only in the
sequential fallback, one at a time.

## Ground rules (all phases, all agents)

- Never read `references/` (vendored, out of scope), `node_modules/`, `dist/`, `.plans/`, or `bun.lock`.
- Never run linters, type checks, or tests — `bun run check` owns those; re-deriving them wastes tokens.
- Read-only audit: no writes, no network, no state-changing commands.
- Every finding must cite evidence actually read, not assumed.

Each invocation is fresh: re-read the repo state and discard findings, verdicts, and reports from
any earlier run in this session — the code may have changed since.

## Phase 1: Scope

Arguments (text after the skill name, e.g. `/self-align security docs`) name the lenses to run.
Empty or unrecognized → run all four. Done when the active lens list is fixed.

## Phase 2: Dispatch lens agents

Launch one subagent per active lens, all in parallel (Claude Code: Agent tool calls in a single
message; OpenCode: task tool). Each agent's prompt is this template with `{LENS}` and
`{LENS_FILE}` filled in:

```
You are one adversarial lens in a self-align audit of the Kagan repo at {REPO_ROOT}.
Read {LENS_FILE} and audit exactly that lens — nothing outside it. Read AGENTS.md first for orientation.

Rules: never read references/, node_modules/, dist/, .plans/, or bun.lock. Do not run linters,
type checks, tests, or any write/network command. Be adversarial — hunt for what is wrong, stale,
or unjustified — but cite only evidence you actually read.

Severity anchors:
- critical: exploitable vulnerability, or loss/corruption of user sessions, worktrees, or uncommitted work
- high: contradicts a numbered spec requirement; a documented instruction fails as written; realistic unsafe input path
- medium: spec/docs drift, YAGNI violation, dead code, stale or unproven claim
- low: polish, minor inconsistency

Return at most 8 findings, most severe first, in exactly this format and nothing else:

## {LENS} findings
- severity: <critical|high|medium|low>
location: <path:line or doc heading>
issue: <one sentence>
evidence: <the exact line(s) or fact proving it>
fix: <one sentence>

If the lens is clean, return: ## {LENS} findings — none.
```

**Sequential fallback** (no subagent mechanism, e.g. Codex): run the lenses one at a time in order
security → spec → quality → docs. For each, read its criteria file, audit, and write the findings
block in the same format before moving on. Phase 3 still runs as a separate pass afterwards.

Done when every active lens has returned its findings block (or "none").

## Phase 3: Self-assessment

Done when every finding carries a confidence score or a recorded refutation. For every finding,
in this order:

1. **Dedupe** — same root cause reported by two lenses: merge, keep the higher severity, note both lenses.
2. **Refute against the counter-source** — a finding survives only after checking the source most
likely to disprove it, not just the cited location:
- spec findings: the full text of the cited requirement — it may say the opposite of the paraphrase
- dead-code and duplication findings: grep for callers, including same-file usage
- docs findings: the exact code or manifest line the doc allegedly contradicts
- all findings: tests. A passing test that asserts the flagged behavior means the behavior is
intentional — re-file as a spec/docs question at reduced severity, not a code bug.

Then ask: would I file this myself from this evidence? If not, or the evidence doesn't hold,
discard it — but record it (Phase 4).

3. **Score confidence** for survivors. A finding whose counter-source was not checked is not
scored — verify it or discard it.
- **high** — counter-source checked, defect held, stated concretely and reproducibly
- **medium** — counter-source checked and the defect holds, but severity or the right fix is debatable
- **low** — judgment call; reasonable maintainers could disagree
4. **Re-score severity** against the anchors above; downgrade anything inflated. A code comment at
the flagged site documenting a deliberate tradeoff caps severity at medium — report it as a
tradeoff to revisit, not a defect.

## Phase 4: Report

```markdown
# Self-Align Report

**Scope:** {lenses run} · **Findings:** {N} ({counts per severity}) · **Discarded:** {M}

## Action items

Sorted by severity (critical → low), then confidence (high → low).

### 1. [{SEVERITY} · {confidence} confidence · {lens}] {one-line title}

- **Where:** {path:line or doc heading}
- **Issue:** {what is wrong}
- **Fix:** {what to do}

## Discarded in self-assessment

- {original claim} — {one-line refutation}

## Clean

{lenses or areas with nothing to report}
```

Omit empty sections. The report is the final output — no preamble or process narration around it.

## Keywords

self-align, codebase audit, health check, spec drift, docs freshness, stale docs, yagni, over-engineering, adversarial review, alignment audit
24 changes: 24 additions & 0 deletions .claude/skills/self-align/lens-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Lens: Docs freshness

Scope: `README.md`, `docs/` (VitePress site), `CONTRIBUTING.md`, `AGENTS.md`, and
`.claude/skills/*/SKILL.md`.

Every finding must pair the doc line with the contradicting fact in code or a manifest
(`package.json`, lock-adjacent pins) — "feels stale" is not a finding.

## Checks

1. **Commands** — every documented command (`bun run ...`, install steps, CLI invocations) exists
in `package.json` scripts or the actual tool, and its described behavior matches. Verify by
reading, not by running.
2. **Paths and names** — referenced files, directories, exports, and keybindings exist as stated.
Check the AGENTS.md Map against the real contents of `src/`.
3. **Behavior claims** — described flows (install, quickstart, lifecycle, gates, board
interactions) match what the code does; flag any doc statement `src/` contradicts.
4. **Version and pin claims** — versions stated in docs match the manifests they describe.
5. **Unproven claims** — capabilities described but not implemented, superlatives with nothing
behind them, numbers with no source.
6. **Coverage gaps** — user-visible features (commands, settings, keybindings) absent from user
docs entirely.
7. **Agent-guide accuracy** — AGENTS.md instructions an agent would follow that now fail or
mislead; this file steers every agent session, so staleness here compounds.
38 changes: 38 additions & 0 deletions .claude/skills/self-align/lens-quality.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Lens: Code quality & YAGNI

Scope: `src/`, `test/`, `scripts/`.

The bar is AGENTS.md "Style and discipline" — read it before auditing; apply it, not generic
best practice. The codebase is deliberately strict-YAGNI: unused flexibility is a defect, not a
nicety.

## Checks

1. **Speculative generality** — options, parameters, branches, or config knobs with no current
caller or setter (grep call sites before flagging); abstraction layers with a single
implementation; extensibility hooks nothing uses.
2. **Dead code** — unused exports, unreachable branches, tests pinning deleted behavior. Grep for
callers before flagging; same-file usage counts as usage.
3. **Redundant guards** — branches re-checking what a schema or an earlier guard already
guarantees.
4. **Duplication** — near-identical logic or user-facing messages for indistinguishable cases that
should share one path.
5. **Compat residue** — shims, fallbacks, adapters, or commented-out legacy kept after a
replacement landed.
6. **Idiom drift** — ad hoc `session.metadata.kagan` reads instead of `task.ts`'s `kagan()` parsed
view; metadata writes bypassing `patchKagan`/`tuiPatchKagan`; blocking `prompt` instead of
`promptAsync`; `api.ui.toast` from board code instead of `store.notify`; shapes that ignore the
established `DialogSelect`/`DialogPrompt` and mock-`PluginInput` patterns.
7. **Comment noise** — comments narrating what code does; missing comments where the code alone
would make a reader mispredict behavior (external constraint, deliberate deviation).
8. **Test depth mismatch** — dedicated suites for thin glue; pure logic without direct tests;
assertions that cannot fail meaningfully.
9. **Unnecessary complexity** — cleverness where straight-line code does the same job; state or
indirection a reader must hold in their head without payoff.

## Skip

- Formatting — prettier is config-as-law.
- Anything oxlint or tsc would report.
- The `.tsx`-extension rule for Solid/OpenTUI imports — a test gate already enforces it.
- Style preferences not grounded in AGENTS.md.
30 changes: 30 additions & 0 deletions .claude/skills/self-align/lens-security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Lens: Security

Scope: `src/`, `scripts/`.

Threat model: Kagan is a local, single-user dev tool that spawns git worktrees and AI agent
sessions. What matters is untrusted or agent-generated content (task titles, descriptions, branch
names, agent output) reaching a shell, git, the filesystem, or another agent's prompt — and
destructive operations running without their intended guard. Not in scope: network hardening,
multi-tenant isolation, or checklist items with no local attack path. Report only findings with a
concrete trigger: name the input, the sink, and the path between them.

## Checks

1. **Command construction** — `src/git.ts`, `src/check.ts`, `scripts/`: untrusted strings
interpolated into shell commands, or passed as git arguments where a value like `--force` or
`--upload-pack=...` would be parsed as a flag (missing `--` separation, unvalidated branch
names).
2. **Path handling** — worktree or file paths derived from task/user input that can escape the
intended root (`../`, absolute paths, symlinks).
3. **Prompt injection** — user-typed task text is trusted input by design; do not flag it. Flag
only agent-generated content (task output, diffs, findings) flowing into another agent's prompt
(`handoff.ts`, `intake.ts`, `validator.ts`) where it could override the supervisor's
instructions or forge its output format.
4. **Destructive git operations** — merge, reset, branch/worktree deletion, or send-back flows
that can destroy uncommitted user work without the guard the spec intends (cross-check
`.specs/` for the intended gate).
5. **Secret leakage** — tokens or environment values written into session metadata, logs, error
messages, or files that get committed.
6. **Install/setup writes** — `scripts/install-plugin.ts`, `scripts/setup.mjs`: writes outside the
expected target directory, or silent overwrite of user files.
21 changes: 21 additions & 0 deletions .claude/skills/self-align/lens-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Lens: Spec alignment

Scope: `.specs/` vs actual behavior in `src/`.

Read `.specs/README.md` first — it defines the authority order: `requirements.md` (numbered
R-criteria) wins over `design.md`, which wins over `mental-model.md`.

Method: work from the spec toward the code. Sweep every numbered requirement shallowly (grep for
the implementing code) rather than deep-reading a few files; deepen only where the trace looks
wrong. Cite the requirement number in every finding that involves one.

## Checks

1. **Contradiction** — code behavior that violates a numbered requirement. Highest-value finding
this lens can produce; verify the trace before reporting.
2. **Spec drift** — user-visible behavior in `src/` (commands, gates, lifecycle transitions,
settings) with no corresponding requirement: the spec fell behind the code.
3. **Design claims** — `design.md` statements about architecture, the metadata model, or key flows
that are no longer true of the code.
4. **Anti-goal violations** — features that contradict `mental-model.md`'s intent or anti-goals,
or resurrect ideas it records as already evaluated and rejected.
Loading