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
5 changes: 3 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
"name": "look",
"source": "./src",
"description": "Sequential code review with fresh agent contexts. Runs multiple independent review passes to catch more issues.",
"version": "0.5.0",
"version": "0.5.1",
"author": { "name": "HartBrook" },
"repository": "https://github.com/HartBrook/lookagain",
"license": "MIT",
"keywords": ["code-review", "quality", "iterative", "multi-pass"],
"commands": ["./commands/again.md", "./commands/tidy.md"],
"agents": ["./agents/lookagain-reviewer.md"],
"skills": ["./skills/again", "./skills/tidy", "./skills/lookagain-output-format"],
"skills": ["./skills/lookagain-output-format"],
"strict": false
}
]
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.5.1] - 2026-05-12

### Changed

- Migrated `/look:again` and `/look:tidy` back from the `skills/` directory format to the `commands/` directory format so they appear under the `/look:` namespace in the slash command picker (matching the README) and surface their description chip when typed. The underlying multi-command misrouting bug that motivated the 0.4.0 migration was fixed upstream in Claude Code 2.1.101 (release notes: "Fixed several plugin issues: slash commands resolving to the wrong plugin with duplicate `name:` frontmatter ..."), so the workaround is no longer needed. As a defense-in-depth measure, the `name:` frontmatter field is now omitted from both command files - the slash name is derived from the filename, which removes any possibility of a duplicate-name collision regardless of the resolver version.
- The `lookagain-output-format` skill remains under `src/skills/` because the reviewer subagent invokes it via the Skill tool; it is not a user-facing slash command and would break if moved to `commands/`.
- Updated `scripts/test.sh`, `scripts/package.sh`, `evals/promptfooconfig.yaml`, and `CONTRIBUTING.md` to reflect the new file layout.

## [0.5.0] - 2026-05-12

### Added
Expand Down
21 changes: 12 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,26 +123,29 @@ You can also test the plugin through the marketplace install flow, which is clos

### Key Files

- **[src/skills/again/SKILL.md](src/skills/again/SKILL.md)**: Main orchestrator logic. Controls pass execution, auto-fixing, and aggregation.
- **[src/skills/tidy/SKILL.md](src/skills/tidy/SKILL.md)**: Tidy skill for pruning old review runs.
- **[src/commands/again.md](src/commands/again.md)**: Main orchestrator logic. Controls pass execution, auto-fixing, and aggregation.
- **[src/commands/tidy.md](src/commands/tidy.md)**: Tidy command for pruning old review runs.
- **[src/agents/lookagain-reviewer.md](src/agents/lookagain-reviewer.md)**: Reviewer subagent. Defines how individual review passes work.
- **[src/skills/lookagain-output-format/SKILL.md](src/skills/lookagain-output-format/SKILL.md)**: JSON output format specification.
- **[src/skills/lookagain-output-format/SKILL.md](src/skills/lookagain-output-format/SKILL.md)**: JSON output format specification (invoked by the reviewer subagent via the Skill tool).
- **[src/dot-claude-plugin/plugin.json](src/dot-claude-plugin/plugin.json)**: Plugin metadata and version.
- **[.claude-plugin/marketplace.json](.claude-plugin/marketplace.json)**: Marketplace manifest for plugin discovery and installation.

### Writing Skill Prompts
### Writing Command Prompts

When editing or adding skills in `src/skills/`:
When editing or adding commands in `src/commands/`:

- Each skill is a directory containing a `SKILL.md` file (e.g., `src/skills/again/SKILL.md`).
- **Use `$ARGUMENTS` for the raw string.** Claude Code replaces `$ARGUMENTS` with whatever the user typed after the skill name. There is no `$ARGUMENTS.name` dot-access syntax only `$ARGUMENTS` (whole string) and `$ARGUMENTS[N]` (positional). Do NOT use an `arguments:` array in frontmatter it is not a supported Claude Code feature and will not be interpolated.
- Each command is a single `.md` file (e.g., `src/commands/again.md`). The slash name is derived from the filename, so `again.md` becomes `/look:again`. Do not add a `name:` field to the frontmatter - that historically caused cross-plugin name collisions (see CHANGELOG 0.5.1).
- **Use `$ARGUMENTS` for the raw string.** Claude Code replaces `$ARGUMENTS` with whatever the user typed after the command. There is no `$ARGUMENTS.name` dot-access syntax - only `$ARGUMENTS` (whole string) and `$ARGUMENTS[N]` (positional). Do NOT use an `arguments:` array in frontmatter - it is not a supported Claude Code feature and will not be interpolated.
- **Add `argument-hint`** in frontmatter to document expected input format (e.g., `argument-hint: "[key=value ...]"`).
- **Add `disable-model-invocation: true`** for user-triggered actions to prevent Claude from auto-invoking them.
- **Include a defaults table** in the body listing each key, its default, and a description. Instruct the agent to parse `key=value` pairs from `$ARGUMENTS` and fall back to defaults for missing keys.
- **Log the resolved configuration** so it is visible in output and reviewable in evals.
- `make test` enforces that skills using `$ARGUMENTS` have `argument-hint` in frontmatter, a defaults table in the body, and do NOT use the unsupported `arguments:` frontmatter array.
- `make test` enforces that commands using `$ARGUMENTS` have `argument-hint` in frontmatter, a defaults table in the body, and do NOT use the unsupported `arguments:` frontmatter array.
- After changing prompt logic, run `make eval` to verify models still interpret the arguments correctly.

### Writing Skill Prompts

`src/skills/` is reserved for skills invoked by other components via the Skill tool (currently only `lookagain-output-format`, which the reviewer subagent calls to get the JSON output spec). Skills are not user-invokable slash commands; if you need a new user-facing slash command, add it under `src/commands/` instead.

## Pull Requests

1. Fork the repository
Expand Down
28 changes: 14 additions & 14 deletions evals/promptfooconfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ tests:
# ==================================================================
- description: "no arguments → model uses all defaults (auto-fix=true, passes=3, thorough)"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
assert:
- type: llm-rubric
value: >
Expand All @@ -30,7 +30,7 @@ tests:
# ==================================================================
- description: "auto-fix=true → model plans to apply fixes"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
arg_passes: "3"
arg_target: staged
arg_auto-fix: "true"
Expand All @@ -45,7 +45,7 @@ tests:

- description: "auto-fix=false → model skips fixes"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
arg_passes: "3"
arg_target: staged
arg_auto-fix: "false"
Expand All @@ -63,7 +63,7 @@ tests:
# ==================================================================
- description: "passes=5 → model plans exactly 5 initial passes"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
arg_passes: "5"
arg_target: staged
arg_auto-fix: "true"
Expand All @@ -83,7 +83,7 @@ tests:
# ==================================================================
- description: "model=fast → reviewer uses haiku"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
arg_passes: "3"
arg_target: staged
arg_auto-fix: "true"
Expand All @@ -99,7 +99,7 @@ tests:

- description: "model=thorough → no explicit model override"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
arg_passes: "3"
arg_target: staged
arg_auto-fix: "true"
Expand All @@ -117,7 +117,7 @@ tests:
# ==================================================================
- description: "target=branch → branch-based diff scope"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
arg_passes: "3"
arg_target: branch
arg_auto-fix: "true"
Expand All @@ -135,7 +135,7 @@ tests:
# ==================================================================
- description: "only auto-fix=false → defaults for everything else, no fixing"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
arg_auto-fix: "false"
assert:
- type: llm-rubric
Expand All @@ -150,7 +150,7 @@ tests:
# ==================================================================
- description: "summary tables include Suggested Fix column and global ID"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
assert:
- type: icontains
value: "Suggested Fix"
Expand All @@ -166,7 +166,7 @@ tests:

- description: "aggregate.md preserves full Suggested Fix text; conversation may truncate"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
assert:
- type: llm-rubric
value: >
Expand All @@ -178,7 +178,7 @@ tests:

- description: "must_fix table includes a Fixed column; should_fix and suggestions do not"
vars:
prompt_file: src/skills/again/SKILL.md
prompt_file: src/commands/again.md
assert:
- type: llm-rubric
value: >
Expand All @@ -192,7 +192,7 @@ tests:
# ==================================================================
- description: "all=true → removes all runs"
vars:
prompt_file: src/skills/tidy/SKILL.md
prompt_file: src/commands/tidy.md
arg_keep: "1"
arg_all: "true"
assert:
Expand All @@ -203,7 +203,7 @@ tests:

- description: "all=false, keep=3 → date-based retention"
vars:
prompt_file: src/skills/tidy/SKILL.md
prompt_file: src/commands/tidy.md
arg_keep: "3"
arg_all: "false"
assert:
Expand All @@ -218,7 +218,7 @@ tests:
# ==================================================================
- description: "tidy with no arguments → keep=1, all=false"
vars:
prompt_file: src/skills/tidy/SKILL.md
prompt_file: src/commands/tidy.md
assert:
- type: llm-rubric
value: >
Expand Down
1 change: 1 addition & 0 deletions scripts/package.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ cp -r "$PROJECT_ROOT/src/dot-claude" "$DIST_DIR/lookagain/.claude"
cp -r "$PROJECT_ROOT/src/dot-claude-plugin" "$DIST_DIR/lookagain/.claude-plugin"
# Overlay marketplace.json from repo root into the dist .claude-plugin/ directory
cp "$PROJECT_ROOT/.claude-plugin/marketplace.json" "$DIST_DIR/lookagain/.claude-plugin/"
cp -r "$PROJECT_ROOT/src/commands" "$DIST_DIR/lookagain/commands"
cp -r "$PROJECT_ROOT/src/agents" "$DIST_DIR/lookagain/agents"
cp -r "$PROJECT_ROOT/src/skills" "$DIST_DIR/lookagain/skills"

Expand Down
35 changes: 23 additions & 12 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ test_plugin_json() {
fi

# Required fields
for field in name version description author agents skills; do
for field in name version description author commands agents skills; do
check_json_field "$pjson" "$field"
done

Expand Down Expand Up @@ -111,8 +111,8 @@ sys.exit(0 if isinstance(d.get('author'), dict) and 'name' in d['author'] else 1

test_required_files() {
check_file "src/dot-claude-plugin/plugin.json"
check_file "src/skills/again/SKILL.md"
check_file "src/skills/tidy/SKILL.md"
check_file "src/commands/again.md"
check_file "src/commands/tidy.md"
check_file "src/agents/lookagain-reviewer.md"
check_file "src/skills/lookagain-output-format/SKILL.md"
check_file "src/dot-claude/settings.local.json"
Expand All @@ -123,21 +123,22 @@ test_required_files() {
}

test_frontmatter() {
check_frontmatter "$PROJECT_ROOT/src/skills/again/SKILL.md" name description
check_frontmatter "$PROJECT_ROOT/src/skills/tidy/SKILL.md" name description
# Commands derive their name from the filename, so only description is required.
check_frontmatter "$PROJECT_ROOT/src/commands/again.md" description
check_frontmatter "$PROJECT_ROOT/src/commands/tidy.md" description
check_frontmatter "$PROJECT_ROOT/src/agents/lookagain-reviewer.md" name description tools
check_frontmatter "$PROJECT_ROOT/src/skills/lookagain-output-format/SKILL.md" name description
}

test_argument_handling() {
# Verify that skill files using arguments follow the correct pattern:
# Verify that command and skill files using arguments follow the correct pattern:
# 1. Frontmatter has argument-hint (not the unsupported arguments: array)
# 2. Body contains $ARGUMENTS placeholder for the raw argument string
# 3. Body contains a defaults table with Key/Default columns
# This ensures the agent receives and parses arguments at runtime
# rather than relying on non-existent compile-time interpolation.

for file in "$PROJECT_ROOT"/src/skills/*/SKILL.md; do
for file in "$PROJECT_ROOT"/src/commands/*.md "$PROJECT_ROOT"/src/skills/*/SKILL.md; do
local relpath="${file#"$PROJECT_ROOT"/}"

local frontmatter
Expand Down Expand Up @@ -178,13 +179,13 @@ test_argument_handling() {
}

test_again_numbering_format() {
# Verify the again skill documents the global-ID + Suggested Fix format
# Verify the again command documents the global-ID + Suggested Fix format
# added on feat/numbered-suggested-fixes. These checks catch regressions
# if a future edit removes the numbering scheme or the Suggested Fix
# column from the summary template.

local skill="$PROJECT_ROOT/src/skills/again/SKILL.md"
local relpath="src/skills/again/SKILL.md"
local skill="$PROJECT_ROOT/src/commands/again.md"
local relpath="src/commands/again.md"

if [[ ! -f "$skill" ]]; then
fail "$relpath not found"
Expand Down Expand Up @@ -252,6 +253,16 @@ test_again_numbering_format() {
test_cross_references() {
local pjson="$PROJECT_ROOT/src/dot-claude-plugin/plugin.json"

# Commands resolve as files
while IFS= read -r cmd; do
local resolved="$PROJECT_ROOT/src/${cmd#./}"
if [[ -f "$resolved" ]]; then
pass "command $cmd resolves"
else
fail "command $cmd not found at src/${cmd#./}"
fi
done < <(python3 -c "import json,sys; [print(c) for c in json.load(open(sys.argv[1])).get('commands', [])]" "$pjson")

# Agents resolve
while IFS= read -r agent; do
local resolved="$PROJECT_ROOT/src/${agent#./}"
Expand Down Expand Up @@ -305,7 +316,7 @@ test_build() {
fail "dist marketplace.json is invalid"
fi

for f in agents/lookagain-reviewer.md skills/again/SKILL.md skills/tidy/SKILL.md skills/lookagain-output-format/SKILL.md README.md; do
for f in agents/lookagain-reviewer.md commands/again.md commands/tidy.md skills/lookagain-output-format/SKILL.md README.md; do
if [[ -f "$dist/$f" ]]; then
pass "dist/$f exists"
else
Expand Down Expand Up @@ -401,7 +412,7 @@ sys.exit(0 if pv == mv else 1)
fi

# Commands, agents, skills match plugin.json
for field in agents skills; do
for field in commands agents skills; do
if python3 -c "
import json, sys
p = sorted(json.load(open(sys.argv[1])).get(sys.argv[3], []))
Expand Down
2 changes: 0 additions & 2 deletions src/skills/again/SKILL.md → src/commands/again.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
---
name: again
description: Run sequential code review passes with fresh contexts to catch more issues
disable-model-invocation: true
argument-hint: "[key=value ...]"
---

Expand Down
2 changes: 0 additions & 2 deletions src/skills/tidy/SKILL.md → src/commands/tidy.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
---
name: tidy
description: Remove old lookagain review runs, keeping today's results by default
disable-model-invocation: true
allowed-tools: Glob, Bash(rm -rf .lookagain/????-??-??T??-??-??)
argument-hint: "[key=value ...]"
---
Expand Down
5 changes: 3 additions & 2 deletions src/dot-claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "look",
"version": "0.5.0",
"version": "0.5.1",
"description": "Sequential code review with fresh agent contexts. Runs multiple independent review passes to catch more issues.",
"author": { "name": "HartBrook" },
"repository": "https://github.com/HartBrook/lookagain",
"keywords": ["code-review", "quality", "iterative", "multi-pass"],
"commands": ["./commands/again.md", "./commands/tidy.md"],
"agents": ["./agents/lookagain-reviewer.md"],
"skills": ["./skills/again", "./skills/tidy", "./skills/lookagain-output-format"]
"skills": ["./skills/lookagain-output-format"]
}
Loading