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
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
mkdir -p staging
cp "target/${{ matrix.target }}/release/splus-engine" staging/
cp dist-release/mcp.cjs staging/
cp -R skills staging/skills
tar -czf "splus-${{ matrix.name }}.tar.gz" -C staging .

- uses: actions/upload-artifact@v7
Expand Down
34 changes: 34 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,40 @@ this project uses [semantic versioning](https://semver.org) (pre-1.0: minor vers

## [Unreleased]

## [0.9.2] — 2026-06-05

### Changed
- **`SPLUS.md` replaces `splus.md`.** The per-repo review contract is now
uppercase (repo root and `~/.splus/SPLUS.md`). No fallback: rename your file.
- **Skills are installed, not just shipped.** `install.sh` now places the review
protocol into every detected agent — Claude Code (`~/.claude/skills/splus-review`,
`splus-prefs`), Codex (`~/.codex/prompts/splus-review.md`), OpenCode
(`~/.config/opencode/command/splus-review.md`) — with the canonical copy at
`~/.splus/skills`. Skills refresh on `splus update` even when MCP wiring is
skipped. The protocol no longer depends on agents reading MCP tool descriptions.
- **The review directive teaches contract tracing.** `review` output now includes
a deterministic CHANGED SYMBOLS block (engine exports ∩ diff hunks) and a
TRACE CONTRACTS stage: enumerate each changed function's return/throw shape on
every path, open every caller, report every assumption mismatch — the
most-missed real-bug class on the Martian bench. A new anti-checklist clause
bans generic hardening nits unless the diff introduces the flaw (the #1 noise
source). The same disciplines are encoded in the review skill's lenses.

### Added
- `@splus/shared`: `diffText`, `changedLineRanges`, `changedExportedSymbols` —
deterministic change-surface extraction reused by the MCP server and the
benchmark pipeline.
- Benchmark triage pipeline: a second contract-tracing discovery lens (merged +
deduped with the sweep lens) aimed by the CHANGED SYMBOLS block.

### Fixed
- `claude -p` CLI client now fails closed: an `is_error` result or unparseable
forced-tool JSON throws instead of being recorded as a zero-finding review.
- `@splus/shared` tests are now actually wired into `pnpm -r test` (the package
had no test script).
- Removed the unused `@splus/triage` dependency from `@splus/mcp` — the MCP
path is agent-led by design; the triage pipeline serves the benchmark only.

## [0.9.1] — 2026-06-05

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ resolver = "2"
members = ["crates/splus-engine"]

[workspace.package]
version = "0.9.1"
version = "0.9.2"
edition = "2021"
license = "MIT"
repository = "https://github.com/kiwi-init/splus"
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ Your agent connects to the local server and calls these:

| Tool | What it does |
| ------------- | --------------------------------------------------------------------------- |
| `review` | Read `splus.md`, return the deterministic floor + a directive, drive the review. |
| `review` | Read `SPLUS.md`, return the deterministic floor + a directive, drive the review. |
| `inspect` | The engine **on tap**: `definition` · `callers` · `blast_radius` · `complexity` · `exports` · `imports` — investigate on demand. |
| `floor` | Re-ground on the deterministic finding floor for a scope (no directive). |
| `preferences` | Show the merged `splus.md` contract (repo + `~/.splus`). |
| `preferences` | Show the merged `SPLUS.md` contract (repo + `~/.splus`). |
| `recall` | Surface past confirmed findings / conventions relevant to a hunk. |
| `note` | Remember a repo convention you discovered (→ `recall`). |
| `dismiss` | Teach Splus a finding is noise — it generalizes to close variants. |
Expand All @@ -96,23 +96,23 @@ Your agent connects to the local server and calls these:
| `report` | Render the review as a standalone offline HTML report. |
| `index` | Build a SCIP index locally for the precise (compiler-grade) blast-radius tier. |

Agent-led, one flow: `review` injects the repo's `splus.md` contract and returns the grounded
Agent-led, one flow: `review` injects the repo's `SPLUS.md` contract and returns the grounded
deterministic floor; **you** drive the review — pull signal on demand with `inspect`, verify before
posting, then `report` and teach. No API key, ever — the model already in your editor does the
reasoning. Learnings stay per-repo in `.splus-cache/` (suppressions in `learnings.json`, memory in
`memory.json`) — they never leave your checkout.

### `splus.md` — the repo's review contract
### `SPLUS.md` — the repo's review contract

Drop a `splus.md` at the repo root (layered over your personal `~/.splus/splus.md`). Splus reads it
Drop a `SPLUS.md` at the repo root (layered over your personal `~/.splus/SPLUS.md`). Splus reads it
**first** on every review: prose preferences/nits guide the reviewer, and binding `mute: <ruleId>` /
`skip: <glob>` lines drop matching findings (and say so — never silently). The `prefs` skill scaffolds one.

### Skills

The `skills/` bundle drives the agent-led flow: `review` (fans out **fresh, unbiased sub-agents** per
unit — finder ≠ verifier — and degrades to a sequential pass where sub-agents aren't available) and
`prefs` (author `splus.md`).
`prefs` (author `SPLUS.md`).

**Full reference: [`docs/TOOLS.md`](docs/TOOLS.md)** — every tool, parameter, and return shape.

Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion bench/martian/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ async function main() {
);
} catch (e) {
// Not persisted → retried on the next run (this is how we survive rate limits).
process.stderr.write(` ⊘ ${pr.source_repo}: ${String(e.message || e).slice(0, 90)}\n`);
process.stderr.write(` ⊘ ${pr.source_repo}: ${String(e.message || e).slice(0, 500)}\n`);
} finally {
rmSync(dir, { recursive: true, force: true });
}
Expand Down
16 changes: 8 additions & 8 deletions docs/TOOLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ API key and no cloud step; the coding agent connected over stdio is the reviewer

| Tool | Mutates? | What it's for |
|---|---|---|
| [`review`](#review) | no | Read `splus.md`, return the floor, drive the agent's review. |
| [`review`](#review) | no | Read `SPLUS.md`, return the floor, drive the agent's review. |
| [`inspect`](#inspect) | no | The engine **on tap** — one code-intelligence question on demand. |
| [`floor`](#floor) | no | Re-ground on the deterministic finding floor for a scope. |
| [`preferences`](#preferences) | no | Show the merged `splus.md` contract. |
| [`preferences`](#preferences) | no | Show the merged `SPLUS.md` contract. |
| [`recall`](#recall) | no | Surface confirmed findings / conventions for a hunk. |
| [`note`](#note) | yes | Remember a discovered repo convention (→ `recall`). |
| [`dismiss`](#dismiss) | yes | Teach Splus a finding is **noise** (suppresses close variants). |
Expand Down Expand Up @@ -48,7 +48,7 @@ rule id, severity, confidence, a deterministic provenance **anchor**, an optiona
fix, and cross-file **blast radius**. Learned suppressions are applied first.

There is **one flow** and you are the driver: the response begins with the repo's
[`splus.md`](#preferences) contract (preferences injected, binding `mute:`/`skip:`
[`SPLUS.md`](#preferences) contract (preferences injected, binding `mute:`/`skip:`
rules already enforced) and ends with a **discovery directive** that drives *you*
(the agent) through the full protocol (triage → investigate → verify) over the
changed files. No API key — Splus grounds you with precise anchors and a toolbelt
Expand Down Expand Up @@ -112,7 +112,7 @@ return an honest empty answer.
## `floor`

Return the engine's deterministic finding **floor** for a scope as JSON — the same
grounded set `review` starts from, without the directive. The repo's `splus.md`
grounded set `review` starts from, without the directive. The repo's `SPLUS.md`
binding rules are applied; learned suppression is not. Use it to re-check a scope
mid-investigation.

Expand All @@ -126,11 +126,11 @@ mid-investigation.

## `preferences`

Return the merged [`splus.md`](#) review contract for this repo (`./splus.md`
layered over `~/.splus/splus.md`), including its binding `mute:`/`skip:` rules.
Return the merged [`SPLUS.md`](#) review contract for this repo (`./SPLUS.md`
layered over `~/.splus/SPLUS.md`), including its binding `mute:`/`skip:` rules.
`review` already injects it; call this to read it directly.

`splus.md` is the repo's review contract, read **first** on every review: prose
`SPLUS.md` is the repo's review contract, read **first** on every review: prose
preferences/nits guide the reviewer; `mute: <ruleId>` and `skip: <glob>` lines are
**binding** (matching findings are dropped and reported, never silently). The
`prefs` skill scaffolds one.
Expand Down Expand Up @@ -159,7 +159,7 @@ compounds across sessions. Semantic (embedding) match over `.splus-cache/memory.

Remember a repo convention you discovered while reviewing (e.g. "this module uses
`Result<T,E>`, never throws") so future reviews `recall` it. Complements `accept`.
Written to `.splus-cache/memory.json`; promotable into `splus.md` for a binding rule.
Written to `.splus-cache/memory.json`; promotable into `SPLUS.md` for a binding rule.

| Param | Type | Description |
|---|---|---|
Expand Down
69 changes: 67 additions & 2 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# SPLUS_VERSION pin a release tag (e.g. v0.3.0); default: latest
# SPLUS_INSTALL_DIR install prefix; default: $HOME/.splus
# SPLUS_LOCAL_DIST install from a local dir of built artifacts instead of
# downloading (expects splus-engine, mcp.cjs) —
# downloading (expects splus-engine, mcp.cjs, optionally skills/) —
# used for local testing of this script
# SPLUS_NO_MODIFY_PATH=1 don't touch shell rc files
# SPLUS_NO_WIRE=1 don't auto-wire coding agents
Expand Down Expand Up @@ -94,6 +94,7 @@ if [ -n "${SPLUS_LOCAL_DIST:-}" ]; then
detail "using local dist: $SPLUS_LOCAL_DIST"
cp "$SPLUS_LOCAL_DIST/splus-engine" "$tmp/" || die "missing splus-engine in SPLUS_LOCAL_DIST"
cp "$SPLUS_LOCAL_DIST/mcp.cjs" "$tmp/" || die "missing mcp.cjs in SPLUS_LOCAL_DIST"
[ -d "$SPLUS_LOCAL_DIST/skills" ] && cp -R "$SPLUS_LOCAL_DIST/skills" "$tmp/skills"
version="local"
else
if command -v curl >/dev/null 2>&1; then dl() { curl -fsSL "$1" -o "$2"; }
Expand Down Expand Up @@ -167,6 +168,16 @@ esac
EOF
chmod 0755 "$BIN_DIR/splus"
printf '%s\n' "$version" > "$INSTALL_DIR/version"

# The review-protocol skills — the canonical copy lives in ~/.splus/skills; the
# agent wiring below copies/points each coding agent at it. The protocol is a
# first-class artifact: agents load it explicitly instead of depending on MCP
# tool descriptions being read.
if [ -d "$tmp/skills" ]; then
rm -rf "$INSTALL_DIR/skills"
cp -R "$tmp/skills" "$INSTALL_DIR/skills"
fi

if [ "$updating" -eq 1 ]; then
ok "core updated"
else
Expand Down Expand Up @@ -301,11 +312,65 @@ EOF
[ "$wired" = 1 ] || warn "no coding agent detected — register \`$MCP_BIN\` as an MCP server manually"
fi

# --- install the review protocol as agent skills ----------------------------
# The skills ARE the product's review protocol — installing them per agent makes
# the protocol explicit and user-invocable instead of depending on the agent
# happening to read MCP tool descriptions. Unlike MCP wiring, this also runs on
# updates: a refreshed protocol is half the point of `splus update`.
if [ -z "${SPLUS_NO_WIRE:-}" ] && [ -d "$INSTALL_DIR/skills" ]; then
detail "installing agent skills"

# The SKILL.md body without its Claude-specific YAML frontmatter.
skill_body() { awk 'f>1 {print} /^---$/ {f++}' "$1"; }
# The one-line description from the frontmatter (for OpenCode's command header).
skill_desc() { awk '/^description: /{sub(/^description: /,""); print; exit}' "$1"; }
# Point non-Claude agents at the canonical per-stage reference files.
skill_refs() {
[ -d "$INSTALL_DIR/skills/$1/references" ] || return 0
printf '\n> Stage protocols — read each file in %s/skills/%s/references/ as you reach that stage.\n' "$INSTALL_DIR" "$1"
}

# Claude Code — native skills (auto-triggered by name + user-invocable).
if command -v claude >/dev/null 2>&1 || [ -d "$HOME/.claude" ]; then
mkdir -p "$HOME/.claude/skills"
for s in review prefs; do
[ -d "$INSTALL_DIR/skills/$s" ] || continue
rm -rf "$HOME/.claude/skills/splus-$s"
cp -R "$INSTALL_DIR/skills/$s" "$HOME/.claude/skills/splus-$s"
done
ok "Claude Code skills (splus-review, splus-prefs)"
fi

# Codex — custom prompts, slash-invocable (/splus-review).
if command -v codex >/dev/null 2>&1 || [ -d "$HOME/.codex" ]; then
mkdir -p "$HOME/.codex/prompts"
for s in review prefs; do
[ -f "$INSTALL_DIR/skills/$s/SKILL.md" ] || continue
{ skill_body "$INSTALL_DIR/skills/$s/SKILL.md"; skill_refs "$s"; } > "$HOME/.codex/prompts/splus-$s.md"
done
ok "Codex prompts (/splus-review, /splus-prefs)"
fi

# OpenCode — commands, slash-invocable (/splus-review).
if command -v opencode >/dev/null 2>&1 || [ -d "$HOME/.config/opencode" ]; then
mkdir -p "$HOME/.config/opencode/command"
for s in review prefs; do
[ -f "$INSTALL_DIR/skills/$s/SKILL.md" ] || continue
{
printf -- '---\ndescription: %s\n---\n' "$(skill_desc "$INSTALL_DIR/skills/$s/SKILL.md")"
skill_body "$INSTALL_DIR/skills/$s/SKILL.md"
skill_refs "$s"
} > "$HOME/.config/opencode/command/splus-$s.md"
done
ok "OpenCode commands (/splus-review, /splus-prefs)"
fi
fi

# --- done ------------------------------------------------------------------
if [ "$updating" -eq 1 ]; then
printf '\n%b\n' "${c_grn}${c_b}Splus is up to date.${c_0}"
else
printf '\n%b\n' "${c_grn}${c_b}Splus is installed.${c_0}"
printf '%b\n' " ${c_dim}then, in your agent:${c_0} \"review my staged changes with splus\""
printf '%b\n' " ${c_dim}then, in your agent:${c_0} /splus-review ${c_dim}(or \"review my staged changes with splus\")${c_0}"
printf '%b\n' " ${c_dim}update:${c_0} splus update"
fi
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "splus",
"version": "0.9.1",
"version": "0.9.2",
"private": true,
"description": "Splus — the precision-first, local-first code reviewer. A deterministic Rust engine your coding agent (Claude Code, Codex, OpenCode) calls over MCP. Open source, runs entirely on your machine.",
"license": "MIT",
Expand Down
3 changes: 1 addition & 2 deletions packages/mcp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splus/mcp",
"version": "0.9.1",
"version": "0.9.2",
"type": "module",
"description": "Splus MCP server (local) — a stdio MCP server your coding agent (Claude Code, Codex, OpenCode) connects to. Runs the deterministic review engine on your local checkout and applies this repo's learned suppressions. No account, no token, nothing leaves your machine.",
"bin": {
Expand All @@ -18,7 +18,6 @@
"@modelcontextprotocol/sdk": "^1.29.0",
"@splus/shared": "workspace:*",
"@splus/suppression": "workspace:*",
"@splus/triage": "workspace:*",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
Loading