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
2 changes: 1 addition & 1 deletion .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/tui/board/board.tsx, src/tui/board/commands.tsx, src/tui/board/store.tsx, src/tui/updates.ts, src/tui/update-manager.ts, src/server/intake.ts, or src/server/validator/spawn.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/tui/board/board.tsx, src/tui/board/commands.tsx, src/tui/board/store.tsx, src/tui/updates/, src/server/intake.ts, or src/server/validator/spawn.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.18`; vendored copy at `references/opencode/packages/plugin/package.json` should match — verify there, never from memory.
Expand Down
2 changes: 1 addition & 1 deletion .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/sh
set -e
bun run verify -- --check
bun run check
4 changes: 3 additions & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
fetch-depth: 0
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: 1.3.14
- run: bun ci
- run: bun run verify
- run: bun run check
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
with:
node-version: 24.10.0
- run: bun ci
- run: bun run verify
- run: bun run check
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ docs/.vitepress/dist
references

.opencode/

openspec/
5 changes: 5 additions & 0 deletions .oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 120,
"semi": false
}
42 changes: 42 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "oxc", "import"],
"ignorePatterns": ["dist", "coverage", "node_modules"],
"categories": {
"correctness": "error"
},
"rules": {
"typescript/no-explicit-any": "error",
"typescript/no-non-null-assertion": "error",
"typescript/consistent-type-definitions": ["error", "type"],
"typescript/consistent-type-imports": ["error", { "fixStyle": "separate-type-imports", "prefer": "type-imports" }],
"typescript/dot-notation": "error",
"typescript/prefer-optional-chain": "error",
"no-console": "error",
"no-useless-computed-key": "error",
"prefer-template": "error",
"array-callback-return": "error",
"unicorn/prefer-number-properties": "error",
"unicorn/prefer-node-protocol": "error",
"unicorn/no-useless-switch-case": "error",
"no-unused-vars": [
"error",
{ "argsIgnorePattern": "^_", "varsIgnorePattern": "^_", "caughtErrorsIgnorePattern": "^_" }
],
"import/no-duplicates": "error"
},
"overrides": [
{
"files": ["test/**"],
"rules": {
"typescript/no-non-null-assertion": "off"
}
},
{
"files": ["scripts/**"],
"rules": {
"no-console": "off"
}
}
]
}
153 changes: 92 additions & 61 deletions .specs/kagan-supervision-board/design.md

Large diffs are not rendered by default.

123 changes: 87 additions & 36 deletions .specs/kagan-supervision-board/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ status cues, so that I can see at a glance what needs my attention.
6. WHILE a Backlog task is intake-ready, its card SHALL display a distinct border color and an
`intake ok` badge indicating it is eligible to move to In Progress.
7. THE board footer SHALL display the plugin name and version.
8. WHERE automatic update status is ready or blocked, the board footer SHALL persist the prepared
version or the OpenCode compatibility requirement respectively (R18).
8. WHERE an update is available, installing, or installed-pending-restart, the board footer SHALL
persist that state and its version (R18).
9. WHILE a task is in Review and not yet approved, the board SHALL sort its card ahead of other
cards in that column.
10. WHILE a Backlog task's intake outcome is `failed`, the card SHALL display a distinct failed
Expand Down Expand Up @@ -125,10 +125,12 @@ code is written, so that the agent's understanding and my own are tested and ali
Kagan SHALL clear the intake session and automatically respawn it without user action; WHEN
retries are exhausted, Kagan SHALL set the intake outcome to `failed` and SHALL record the
failure's message.
9. WHILE a Backlog task's intake has started but not succeeded (failed, or spawned without an
outcome), Kagan SHALL offer a manual retry that clears the recorded intake session, outcome, and
attempt count so the state-based spawn respawns it — without affecting the task's worktree — so
recovery does not depend on a failure having been auto-detected first.
9. WHILE a Backlog task's intake has ever spawned (failed, succeeded, mid-run, or stuck without an
outcome), Kagan SHALL offer a manual restart that first clears the recorded intake session,
outcome, attempt count, and intake payload — so the state-based spawn respawns it — and only then
aborts any live intake session, so the abort's stale error/idle events cannot pass the failure
funnel's guard and trigger a duplicate spawn; the restart SHALL NOT affect the task's worktree or
generation, so recovery does not depend on a failure having been auto-detected first.
10. WHEN duplicate or concurrent session lifecycle events are delivered for a task THEN Kagan SHALL
spawn at most one intake session for it (a new spawn SHALL occur only after the recorded intake
state is cleared).
Expand All @@ -138,6 +140,15 @@ code is written, so that the agent's understanding and my own are tested and ali
12. WHEN the intake session records its assessment THEN Kagan SHALL persist an advisory mode
recommendation (autonomous, assisted, or manual) with a one-line rationale. This recommendation
SHALL be informational only and SHALL NOT appear in move-gating or approval-gating logic.
13. WHEN duplicate intake helper sessions race to record on the parent THEN Kagan SHALL ignore writes
from a superseded helper session whose id no longer matches the parent's recorded
`intakeSessionID`.
14. WHEN a read-only helper session (intake or validator) triggers a permission request THEN Kagan
SHALL auto-approve it, since these headless children have no human to answer the prompt and are
already tool-restricted to read plus their own recording tool; a permission prompt SHALL NOT be
able to strand a helper mid-run. This auto-approval SHALL apply only to read-only helper roles
and SHALL NOT cover board tasks, workers, or plain sessions, which follow the user's normal
permission flow (R17.4).

---

Expand Down Expand Up @@ -253,10 +264,13 @@ ranked before I decide.
`helperRetries`, Kagan SHALL clear the validator session and automatically respawn it without
user action; WHEN retries are exhausted, Kagan SHALL set the validator outcome to `failed` and
SHALL record the failure's message.
10. WHILE a Review task's validator has started but not succeeded (failed, or spawned without an
outcome), Kagan SHALL offer a manual retry that clears the recorded validator session, outcome,
and attempt count so the state-based spawn respawns it — without affecting the task's worktree —
so recovery does not depend on a failure having been auto-detected first.
10. WHILE a Review task's validator has ever spawned (failed, succeeded, mid-run, or stuck without an
outcome), Kagan SHALL offer a manual restart that first clears the recorded validator session,
outcome, attempt count, findings, check evidence, and approval stamp — so the state-based spawn
respawns it — and only then aborts any live validator session, so the abort's stale error/idle
events cannot pass the failure funnel's guard and trigger a duplicate spawn; the restart SHALL
NOT affect the task's worktree or generation, so recovery does not depend on a failure having
been auto-detected first.
11. WHERE the plugin options set `commands.check`, Kagan SHALL run each configured check whose `cwd` contains at least one changed file, or whose optional repo-relative `scope` regex matches at least one changed file, when the task enters Review.
12. WHEN scoped checks are evaluated THEN Kagan SHALL record every configured check as `ran` or `skipped`; a failed, timed-out, or unspawnable ran check SHALL be recorded honestly and SHALL NOT block review entry or validator spawn.
13. WHERE check evidence is recorded, the board card SHALL display `check ok` when every configured check ran and passed, `check failed` when any ran check failed, `check skipped` when every configured check skipped, and `check partial` when some checks ran and passed while others skipped.
Expand Down Expand Up @@ -432,9 +446,11 @@ so that failures, handoffs, and supervision evidence are visible instead of hidd
the same `setup ok` / `setup failed` / `setup skipped` / `setup partial` badge rules as checks.
3. WHERE a Review task is sent back at or above the configured `sendBackStopThreshold`, Kagan SHALL
ask whether to iterate again, let the human take over, or leave the task in Review.
4. WHEN a board task or its active iteration waits on a permission reply THEN Kagan SHALL record an
awaiting-input marker on the root task and surface a `needs you` badge until the permission is
answered.
4. WHEN a board task or its active iteration (worker) waits on a permission reply THEN Kagan SHALL
record an awaiting-input marker on the root task and surface a `needs you` badge until the
permission is answered. This applies to board-task and worker sessions; read-only helper sessions
(intake/validator) never reach this state because their permission requests are auto-approved
(R4.14).
5. WHEN a helper failure is newly observed THEN Kagan SHALL surface a board notice once for that
failure and keep the persistent failure badge until retry or success clears it.
6. WHEN the board opens for the first time in a run THEN Kagan SHALL offer the onboarding dialog,
Expand Down Expand Up @@ -462,30 +478,65 @@ so that failures, handoffs, and supervision evidence are visible instead of hidd

---

## Requirement 18 — Automatic npm updates
## Requirement 18 — Approved npm updates

**User Story:** As an npm user, I want Kagan to prepare compatible releases automatically and tell
me when OpenCode blocks one, so that updating requires only a restart and no compatibility judgment.
**User Story:** As an npm user, I want Kagan to tell me when a newer release exists and update itself
through OpenCode's own plugin command once I approve, so that updating is one confirmation plus a
restart and never touches OpenCode's private cache.

#### Acceptance Criteria

1. WHERE Kagan was loaded from bare `@kagan-sh/kagan` or explicit `@latest`, Kagan SHALL check npm
`latest` no more than once per successful one-hour cache window.
2. WHEN npm `latest` is newer and its `engines.opencode` range accepts the running OpenCode version,
Kagan SHALL prepare that exact release through the TUI plugin API without changing plugin config.
3. WHEN a compatible release has been prepared THEN Kagan SHALL keep the running wrapper unchanged
until TUI disposal, promote the prepared wrapper during disposal, and require an OpenCode restart
to activate it.
4. WHEN npm `latest` requires a different OpenCode version THEN Kagan SHALL leave the current
wrapper unchanged and SHALL persistently name the required OpenCode range on the board.
5. WHERE automatic update status is ready or blocked on a home or session route, Kagan SHALL show
one host toast; update status SHALL NOT enter the board Notice queue.
6. IF Kagan was loaded from an exact npm pin or a local/file source THEN Kagan SHALL NOT query npm,
prepare a release, or mutate its wrapper.
7. IF a registry request, manifest validation, download, import, or cache-path validation fails THEN
Kagan SHALL leave the current wrapper unchanged; WHEN cleanup or preparation fails due to local cache
state THEN Kagan SHALL persistently show that automatic updates are unavailable on the board
footer.
8. WHEN promotion of the prepared wrapper fails in-process after the current wrapper was moved to backup THEN Kagan SHALL restore the current wrapper immediately; WHEN promotion was interrupted by process exit THEN the host SHALL re-download `latest` on the next launch (requiring network) and Kagan's cleanup SHALL then remove the leftover backup, marker, and prepared directory.
9. WHEN the prepared version loads successfully after restart THEN Kagan SHALL remove its validated
backup and marker without deleting any broader OpenCode cache path.
1. WHERE Kagan was loaded as a stable npm release from a bare, `@latest`, or clean exact
`@kagan-sh/kagan` spec, Kagan SHALL check npm's stable `latest` no more than once per successful
one-hour cache window; a user-invoked update check SHALL bypass that window.
2. IF Kagan was loaded from a local/file source, a development version, or a prerelease version THEN
Kagan SHALL NOT query npm or change the installation.
3. WHEN npm `latest` is newer than the running version THEN Kagan SHALL record it as available and
persist that version in the board footer without downloading it or changing plugin config.
4. Kagan SHALL expose one update action through a conditional board `u` hint and binding, the command
palette, and `/kagan-update`; invoking it with no cached update SHALL check on demand and report
whether Kagan is current or a release is available.
5. Kagan SHALL require a confirmation naming the running and target versions before it downloads the
target or changes configuration; launch, discovery, and opening the board SHALL NOT be approval.
6. WHEN the user confirms THEN Kagan SHALL stage the exact target with `api.plugins.add` (which
performs the host compatibility check) and, only on success, run the current OpenCode executable's
`plugin <exact> --global --force` command without a shell; concurrent invocations SHALL share one
in-flight operation.
7. WHEN staging or the canonical command fails THEN Kagan SHALL keep the running version and existing
config usable, report the failure transiently, and leave the update available to retry.
8. WHEN the canonical command succeeds THEN Kagan SHALL persist an installed-pending-restart footer
state for that version, keep the running version active for the rest of the process, and require an
OpenCode restart to load the new version.
9. Kagan SHALL NOT read, rename, remove, or validate any OpenCode package-cache path, and SHALL NOT
migrate artifacts left by earlier updater versions.

---

## Requirement 19 — Conversational task creation

**User Story:** As a developer, I want to create one or more board tasks from a regular OpenCode
session through conversation, so that I can bulk-plan work without opening the board dialog for
each task.

#### Acceptance Criteria

1. Kagan SHALL register a `/kagan-task` command whose template guides the agent to refine tickets
with the user, confirm the final list, call `kagan_create_tasks` once, and report the result.
The template SHALL end with `$ARGUMENTS` and SHALL list configured scope `cwd` values when
present. Because the command runs inline in the user's current session, the template SHALL guide
the agent to base tickets on the current session when it contains relevant prior work — asking
the user exactly once which source to use when the session already holds substantial context —
and to plan from the command arguments otherwise.
2. Kagan SHALL expose a `kagan_create_tasks` tool available only in regular (non-supervised)
sessions.
3. WHEN `kagan_create_tasks` is invoked THEN Kagan SHALL request user confirmation through the tool
permission prompt before creating any task; IF the user denies THEN Kagan SHALL fail the tool and
SHALL create nothing.
4. THE tool SHALL accept between 1 and 10 tickets, each with a non-blank title and description,
optional base branch, and optional scope validated against configured scope `cwd` values.
5. WHEN a ticket omits `baseBranch` THEN Kagan SHALL default it to the repository's current branch.
6. WHEN creating multiple tickets THEN Kagan SHALL compute the starting task number once from project
sessions and SHALL create tickets sequentially, continuing after per-ticket failures and
returning a markdown summary.
7. Each created ticket SHALL follow the same worktree-first creation order and metadata shape as the
board create dialog (R1.6–8, R2, R17.1–2).
Loading