From 27f33c02d2a73339dbff41a3f67494244082b592 Mon Sep 17 00:00:00 2001 From: Will Pike <6687499+pike00@users.noreply.github.com> Date: Tue, 12 May 2026 16:33:31 -0500 Subject: [PATCH] Migrate /look:again and /look:tidy back to commands/ format The underlying bug that drove the 0.4.0 commands -> skills migration (multi-command plugins misrouting due to duplicate name: frontmatter) was fixed upstream in Claude Code 2.1.101. The skills/ workaround now breaks the /look: namespace in the slash-command picker because skills surface by their bare name field, not under the plugin prefix. - Restore src/commands/{again,tidy}.md from src/skills/{again,tidy}/SKILL.md - Omit the name: frontmatter field so the slash name derives from the filename, removing any future duplicate-name collision vector - Keep lookagain-output-format under skills/ - the reviewer subagent invokes it via the Skill tool, it is not a user-facing command - Update plugin.json, marketplace.json, scripts/test.sh, scripts/package.sh, evals/promptfooconfig.yaml, and CONTRIBUTING.md for the new layout - Bump version to 0.5.1 make test: 59 passed, 0 failed make eval: 14 passed, 0 failed --- .claude-plugin/marketplace.json | 5 +-- CHANGELOG.md | 8 +++++ CONTRIBUTING.md | 21 ++++++----- evals/promptfooconfig.yaml | 28 +++++++-------- scripts/package.sh | 1 + scripts/test.sh | 35 ++++++++++++------- .../again/SKILL.md => commands/again.md} | 2 -- .../tidy/SKILL.md => commands/tidy.md} | 2 -- src/dot-claude-plugin/plugin.json | 5 +-- 9 files changed, 64 insertions(+), 43 deletions(-) rename src/{skills/again/SKILL.md => commands/again.md} (99%) rename src/{skills/tidy/SKILL.md => commands/tidy.md} (97%) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 415548c..c35300a 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -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 } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 85afcb1..f5a20b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d125523..c21f32b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/evals/promptfooconfig.yaml b/evals/promptfooconfig.yaml index 05952a4..6eb47a5 100644 --- a/evals/promptfooconfig.yaml +++ b/evals/promptfooconfig.yaml @@ -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: > @@ -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" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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" @@ -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 @@ -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" @@ -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: > @@ -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: > @@ -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: @@ -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: @@ -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: > diff --git a/scripts/package.sh b/scripts/package.sh index ed4025d..44940b3 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -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" diff --git a/scripts/test.sh b/scripts/test.sh index 52e1c86..05a4191 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -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 @@ -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" @@ -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 @@ -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" @@ -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#./}" @@ -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 @@ -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], [])) diff --git a/src/skills/again/SKILL.md b/src/commands/again.md similarity index 99% rename from src/skills/again/SKILL.md rename to src/commands/again.md index c17e7b2..5051c82 100644 --- a/src/skills/again/SKILL.md +++ b/src/commands/again.md @@ -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 ...]" --- diff --git a/src/skills/tidy/SKILL.md b/src/commands/tidy.md similarity index 97% rename from src/skills/tidy/SKILL.md rename to src/commands/tidy.md index d16aea5..fd31f15 100644 --- a/src/skills/tidy/SKILL.md +++ b/src/commands/tidy.md @@ -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 ...]" --- diff --git a/src/dot-claude-plugin/plugin.json b/src/dot-claude-plugin/plugin.json index 56148bf..fce31af 100644 --- a/src/dot-claude-plugin/plugin.json +++ b/src/dot-claude-plugin/plugin.json @@ -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"] }