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
11 changes: 9 additions & 2 deletions .agents/plugins/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
{
"name": "sdd",
"description": "Codex plugins by nadiar: a bd-backed workflow (/specify, /refine, /board, /solve, /validate, /orchestrate) and context-writing helpers. Shares one skills/ tree with the Claude Code build.",
"description": "Codex plugins by nadiar: a bd-backed workflow (/specify, /refine, /board, /solve, /validate, /orchestrate), a multi-axis code reviewer, and context-writing helpers. Shares one skills/ tree with the Claude Code build.",
"plugins": [
{
"name": "sdd",
"source": { "source": "local", "path": "./plugins/sdd" },
"version": "3.0.0",
"version": "3.2.0",
"policy": { "installation": "AVAILABLE", "authentication": "ON_USE" },
"category": "Productivity"
},
{
"name": "code-review-quality",
"source": { "source": "local", "path": "./plugins/code-review-quality" },
"version": "1.0.0",
"policy": { "installation": "AVAILABLE", "authentication": "ON_USE" },
"category": "Productivity"
},
Expand Down
14 changes: 12 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
{
"name": "sdd",
"description": "Claude Code plugins by nadiar: workflow skills (/specify, /refine, /board, /solve, /validate, /orchestrate) and context-writing helpers.",
"description": "Claude Code plugins by nadiar: workflow skills (/specify, /refine, /board, /solve, /validate, /orchestrate), a multi-axis code reviewer, and context-writing helpers.",
"owner": { "name": "nadiar", "email": "codex@nadiar.id" },
"plugins": [
{
"name": "sdd",
"source": "./plugins/sdd",
"description": "A bd-backed, parallel-capable workflow: /specify authors stories/epics in Beads, /refine revises a story, /board shows the backlog, /solve implements one in an isolated worktree, /validate merges after human review, /orchestrate automates the solve-review-land loop for a whole epic behind one final PR. Requires the bd (Beads) CLI.",
"version": "3.0.0",
"version": "3.2.0",
"author": { "name": "nadiar", "email": "codex@nadiar.id" },
"license": "MIT",
"category": "workflow",
"keywords": ["workflow", "planning", "architect", "specify", "refine", "board", "solve", "validate", "beads", "bd", "epic", "parallel"]
},
{
"name": "code-review-quality",
"source": "./plugins/code-review-quality",
"description": "Multi-axis code review before merge — correctness, readability, architecture, security, performance — reported as severity-labelled findings and a verdict. Reviews the working diff by default, or a commit, branch, tag, path, or PR. --effort sets depth; --fix applies the fixes in place and leaves them uncommitted. Any model tier; no dependencies.",
"version": "1.0.0",
"author": { "name": "nadiar", "email": "codex@nadiar.id" },
"license": "MIT",
"category": "workflow",
"keywords": ["code-review", "review", "quality", "correctness", "security", "performance", "refactor", "diff", "pull-request"]
},
{
"name": "writing-claude-md",
"source": "./plugins/writing-claude-md",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: Checks

# Both hosts install this plugin by copying the repo — there is no build step between
# Every host installs this plugin by copying the repo — there is no build step between
# a commit and a user's machine, so whatever lands on master is what ships. This is
# the only gate; it has to hold here or nowhere.
#
Expand Down
194 changes: 191 additions & 3 deletions CHANGELOG.md

Large diffs are not rendered by default.

104 changes: 94 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ This repo ships agent **plugins**, not an application. The "source" is prompt fi
there is no unit-test suite — verifying a change usually means reading the prompt and the
`CHANGELOG.md`, then running the skill.

**Three hosts, one `skills/` tree.** The skill bodies are shared verbatim between Claude Code,
OpenAI Codex, and Kimi Code — the Claude Code and Codex plugin layouts mirror each other, so each
**Four hosts, one `skills/` tree.** The skill bodies are shared verbatim between Claude Code,
OpenAI Codex, Kimi Code, and opencode — the Claude Code and Codex plugin layouts mirror each other, so each
plugin carries two manifests
(`.claude-plugin/plugin.json` and `.codex-plugin/plugin.json`) over the *same* `skills/<name>/SKILL.md`
files, and the repo carries two marketplaces (`.claude-plugin/marketplace.json`,
Expand All @@ -25,8 +25,21 @@ Shared frontmatter must keep `disable-model-invocation: false` so Codex accepts
skill. The exception is behavioral guards that must hold on a budget model:
`plugins/sdd/tests/{claude,codex,kimi}/model-guard.sh` run `/specify`, `/refine`, and
`/orchestrate` headless across multiple trials (including override-injection descriptions) and
assert each Model Guard stops it. Claude uses Haiku and `/specify`; Codex uses `gpt-5.6-luna` plus
explicit `$sdd:specify` mentions; Kimi uses `kimi-code/kimi-for-coding` plus `/skill:specify`.
assert each Model Guard stops it. They assert **both directions and the classification itself**: a
below-frontier model (`-m`, `--below-tier budget|medium`) must stop, a frontier model (`-M`) must
classify `frontier` and continue, and every
trial must show the guard's `model-guard: id=<exact-id> tier=<tier>` line (`guard_line`, in
`tests/lib.sh`). Refusal-only assertions cannot fail — a host that never states its model ID
classifies `unsure`, and `unsure` refuses in the same words as `budget` — which is exactly how
Kimi's missing model identity went unnoticed. Claude uses Haiku and `/specify`; Codex uses `gpt-5.6-luna` plus
explicit `$sdd:specify` mentions; Kimi uses `kimi-code/kimi-for-coding` plus `/skill:specify`; opencode
uses `anthropic/claude-haiku-4-5-20251001` plus `opencode run --command specify` (a bare `/specify` in
the message is sent verbatim and never resolves), and **stages a throwaway config dir** rather than
mutating `~/.config/opencode` — the operator's `opencode.json` copied in for provider access, the
working tree layered on top. Because `run_clean_env` strips the environment, a host whose provider key
comes from `{env:VAR}` must name it in `SDD_TEST_ENV` or every trial fails to authenticate and scores
as inconclusive infra rather than the setup mistake it is; `tests/opencode/lib.sh` warns about that up
front, since opencode substitutes an *empty string* for an unset `{env:}` var instead of erroring.
Codex's generic base prompt names only GPT-5, so its default plugin hook reads the host-provided
`model` field and injects the exact slug. The harnesses call the real model, so they're slow and
probabilistic — run them when changing any Model Guard. All three run model CLIs through the shared minimal environment
Expand All @@ -35,9 +48,23 @@ helpers live under each host directory; `tests/lib.sh` keeps only the host-agnos

**Kimi Code breaks the per-plugin manifest pattern.** Its GitHub install reads the manifest at the
*repository* root only, so instead of per-plugin `.kimi-plugin/` dirs there is a single root
`kimi.plugin.json` declaring both `skills/` trees — both marketplace plugins ship to Kimi as one
plugin named `sdd`. The manifest also ports the session-primer hook (`SessionStart` +
`kimi.plugin.json` declaring every `skills/` tree — all three marketplace plugins ship to Kimi as one
plugin named `sdd`. Its root `version` therefore tracks the *bundle*, not the `sdd` plugin: adding or
removing a skill tree bumps it even when `plugins/sdd/` itself is untouched (`3.1.0` added
`code-review-quality` while `sdd` stayed `3.0.0`). The manifest also ports the session-primer hook (`SessionStart` +
`PreCompact`) via its `hooks` field, with `$KIMI_PLUGIN_ROOT` in place of `${CLAUDE_PLUGIN_ROOT}`.
It carries a third hook the other hosts don't need: **`UserPromptSubmit` →
`hooks/kimi-model-context.sh`**, which supplies the model ID Kimi never states. No Kimi hook payload
has a `model` field and its system prompt names only "Kimi Code CLI", so without this every gated
skill classifies `unsure` — or worse, guesses ("Kimi Code CLI runs on k3, k3 is frontier") and
authors on a budget model. `UserPromptSubmit` is used because it is the only Kimi hook documented to
append its output to context, and the only event whose payload carries the `session_id`; the hook
reads `modelAlias` from the session's own record under `$KIMI_CODE_HOME/sessions/*/`, last
occurrence winning so an interactive `/model` switch is picked up, and fails closed (silence →
`unsure` → the gated skills stop). Two wrong sources are documented in the hook so they are not
`simplified` back in: the launching process's argv (kimi overwrites its own argv, erasing `-m`) and
`default_model` from config.toml (stale under a `-m` override — it tells a budget session it is
frontier).
Two Kimi gaps are accepted and documented in the README: plugins can't ship subagent definitions —
worked around, not solved: Kimi's agent loader reads the Claude-format `agents/*.md` verbatim
(ignoring the `model:` pin), so users copy them into `~/.agents/agents/` and the reviewer pin keeps
Expand All @@ -46,13 +73,61 @@ one frontier rung, the session's own model (the Kimi branch in `skills/validate/
is no implicit-invocation gate equivalent to Codex's `agents/openai.yaml`, so slash-only-ness on
Kimi rests on skill prose.

**opencode has no manifest at all — its install surface is the config directory.** There is no plugin
manager to publish to, so the host port is a *script*: `plugins/sdd/hosts/opencode/install.sh` copies
into `~/.config/opencode` (or `$OPENCODE_CONFIG_DIR`, or a project `.opencode`) — `skill/<name>/SKILL.md`,
`agent/*.md`, `plugin/sdd-model-context.js`, and a generated `command/<name>.md` per skill. It records
its file list in `.sdd-installed` and removes that set before rewriting, so re-running it is the
update and a renamed skill can't linger as a duplicate. `plugins/sdd/hosts/<host>/` is the home for
anything a host needs that isn't a manifest; the source layout deliberately mirrors the destination
so the installer stays a copy. Details, including the flags, live in `OPENCODE.md` — the one host doc
big enough not to fit in the README.
Four things make this host different, none of them a reason to fork skill prose:
- **Slash commands are generated, not committed.** The installer builds each `command/<name>.md` from
that skill's own frontmatter (copying the `description:` line *verbatim* — it is already a valid
single-line YAML scalar, and re-quoting it is how you break a description containing quotes). Don't
commit command files; a second copy of a description is a second thing to drift.
- **Skill frontmatter is lenient, agent frontmatter is not.** opencode's skill loader requires only
`name` + `description` and ignores unknown keys, so `version`/`argument-hint`/`user-invocable` ride
along inert. Its *agent* loader is stricter and hard-fails the config: `tools` must be a
`Record<string, boolean>`, so the Claude format's comma-separated `tools: Read, Grep, …` throws, and
`model:` is split on `/`, so a bare `sonnet` yields an empty model ID. That's why
`hosts/opencode/agent/*.md` is a third agent format rather than a copy of the Claude `.md` — same
body verbatim, host-native pin fields only, exactly like the `.toml` pair.
- **`plugin/sdd-model-context.js` is load-bearing, and injects at two seams.** opencode's built-in
system context is cwd, project root, git, platform, and date — no model ID, so every gated skill
classifies `unsure` and stops on *any* model. The plugin appends the host-resolved ID via
`experimental.chat.system.transform` (the only hook onto the system prompt; its input carries the
resolved model, so unlike Kimi's hook nothing is reconstructed from session files or argv) **and** via
`tool.execute.after` on the `skill` tool, onto the loaded skill body. The second is not duplication —
it is the fix. opencode delivers a skill as a *tool result*, so the tier rubric lands far from the
system prompt, and a budget model doesn't look back: with seam 1 alone and the right ID in the system
prompt, `claude-haiku-4-5` emitted `model-guard: id=claude-opus-4-1 tier=frontier` — a confabulated ID
— and authored a story. Don't "simplify" either seam away. Same fail-closed rule as Kimi's: no ID →
emit nothing → `unsure` → stop. Never read `model` from `opencode.json` instead — that's the session's
model only when the user didn't override it with `-m` or `/models`, and asserting it tells a budget
session it is frontier. Verified on a live host across all three rungs and both providers
(Anthropic-native and OpenAI-compatible); test both directions, since `unsure` refuses in `budget`'s
words.
- **Reviewer pinning needs no new branch.** opencode honors a subagent's `model:` pin, so it lands in
the existing *native host* bucket of `skills/validate/SKILL.md`'s *Reviewer pinning by host* — both
rungs, cost-keyed, no skill edit. This is the capability-keyed map paying off; don't add a host name
to it. The one accepted gap is the Kimi one: no per-skill implicit-invocation gate, so `/solve` and
`/validate` rely on `permission.skill: ask` in the user's config plus their own prose.

## What this is

`.claude-plugin/marketplace.json` (Claude Code) and `.agents/plugins/marketplace.json` (Codex)
publish the same two plugins under `plugins/`:
publish the same three plugins under `plugins/`:

- **sdd** — `/specify`, `/refine`, `/board`, `/solve`, `/validate`, `/orchestrate`: a
bd-backed, parallel-capable coding workflow.
- **code-review-quality** — `/code-review-quality`: multi-axis review of a change before merge.
Standalone — no bd, no worktrees, no model gate. Report-only by default; `--fix true` applies the
findings and leaves them uncommitted. Deliberately *not* an `sdd` skill: it shares none of that
plugin's bd machinery. `sdd`'s own review path reaches it the other way round: `/validate`'s reviewer
agents prefer `/code-review-quality` and fall back to the host's `/code-review` when this plugin
isn't installed — a preference, never a dependency.
- **writing-claude-md** — `/writing-claude-md`: authoring lean, high-signal context files.

## Philosophy (this drives how every skill is worded)
Expand All @@ -67,7 +142,7 @@ publish the same two plugins under `plugins/`:
The **HOW** runs on whatever rung the story asks for: `/solve` writes code in an isolated
worktree+branch and carries no gate. The human tier is
`/validate`, the review-and-merge gate; its review pass doesn't bounce work back to
`/solve` but **delegates the fix to a rung-pinned `/code-review` subagent**, which applies it
`/solve` but **delegates the fix to a rung-pinned reviewer subagent**, which applies it
in place on `bd/<id>` and amends — `/validate` carries no model gate, so the reviewer's model is
pinned explicitly, never below `medium`, rather than inherited. Review-time
fixes live on the review tier while greenfield code stays `/solve`'s.
Expand Down Expand Up @@ -100,7 +175,10 @@ publish the same two plugins under `plugins/`:
they never auto-fire mid-conversation. The rest are model-invocable so plain-English asks route to
them: `/board` (read-only), `/refine` (names an id), `/specify` (authors a new story/epic — a
plain-English ask like "let's put our problem to a case" should reach it), and `/orchestrate`
(drives an epic). `/specify` and `/refine` write to bd but are backstopped the same way: the
(drives an epic). `/code-review-quality` is model-invocable too — report-only is its default, and
the one path that edits files (`--fix true`) is honored **only when the caller typed the flag**, so
an implicit invocation can never reach it; that typed-flag rule is what keeps the blast radius of an
auto-fire at zero, not the flag's default. `/specify` and `/refine` write to bd but are backstopped the same way: the
frontier-tier **Model Guard** runs first and **nothing is committed to bd until the user confirms**.
`/orchestrate` also runs its Model Guard before touching bd or git, and creates only a provisional
epic branch and final PR for human review. Model-invocable skills carry no
Expand All @@ -118,7 +196,9 @@ publish the same two plugins under `plugins/`:
- Bump the skill's frontmatter `version` and add a `CHANGELOG.md` entry in the same change. The
marketplace/plugin `version` tracks the published plugin, not the per-skill frontmatter versions —
bump it in **all five** manifests (`.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`,
both marketplaces, and the repo-root `kimi.plugin.json`) so the three hosts stay in lockstep.
both marketplaces, and the repo-root `kimi.plugin.json`) so the three hosts stay in lockstep. For a
plugin other than `sdd`, the same rule reads: its own two `plugin.json`s, its entry in both
marketplaces, and the kimi root (which versions the whole bundle — see above).
- `/specify` and `/refine` share the contract rubrics in `plugins/sdd/shared/contract-rubrics.md`
(Atomicity Gate, AC Quality Rubric, Pre-write Guard, Output Format). That file is
the single source, but it is **not read at runtime**: everything below its `BEGIN SHARED` marker is
Expand Down Expand Up @@ -147,6 +227,10 @@ publish the same two plugins under `plugins/`:
`plugins/sdd/tests/model-tiers-sync.sh --write`; never hand-edit a skill's generated block.
The script with no flag verifies all five copies and fails on drift — pure text comparison, wired
into CI like `rubrics-sync.sh`. Adding a model (or a host) is a one-file edit here, not five.
- **`code-review-quality` is intentionally outside that set** — it lives in another plugin and gates
on nothing, so it carries **no tier block and no tier vocabulary at all**. Don't add one: syncing
it would make one plugin's generated content depend on another plugin's file, and there is nothing
in it for a rung to decide.
- **Reviewer pinning lives natively in `skills/validate/SKILL.md`** (moved there in 2.24.1 —
`/validate` is its sole consumer — so it is *not* part of the synced block) and keys off host
*capability*, not a host list. A native Claude/Codex host
Expand Down
Loading
Loading