@wbern/claude-instructions 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 wbern
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -21,6 +21,10 @@ TDD workflow commands for Claude Code CLI.
21
21
 
22
22
  ```bash
23
23
  npx @wbern/claude-instructions
24
+
25
+ // or
26
+
27
+ pnpm dlx @wbern/claude-instructions
24
28
  ```
25
29
 
26
30
  The interactive installer lets you choose:
@@ -57,13 +61,14 @@ This ensures commands are regenerated whenever anyone runs `npm install`, `pnpm
57
61
 
58
62
  | Option | Description |
59
63
  |--------|-------------|
60
- | `--variant=with-beads` | Include Beads MCP integration |
61
- | `--variant=without-beads` | Standard commands only |
62
- | `--scope=project` | Install to `.claude/commands` in current directory |
63
- | `--scope=user` | Install to `~/.claude/commands` (global) |
64
- | `--prefix=my-` | Add prefix to command names (e.g., `my-commit.md`) |
65
- | `--skip-template-injection` | Don't inject CLAUDE.md template content |
64
+ | `--variant=with-beads` | Command variant (with-beads, without-beads) |
65
+ | `--scope=project` | Installation scope (project, user) |
66
+ | `--prefix=my-` | Add prefix to command names |
66
67
  | `--commands=commit,red,green` | Install only specific commands |
68
+ | `--skip-template-injection` | Skip injecting project CLAUDE.md customizations |
69
+ | `--update-existing` | Only update already-installed commands |
70
+ | `--overwrite` | Overwrite conflicting files without prompting |
71
+ | `--skip-on-conflict` | Skip conflicting files without prompting |
67
72
 
68
73
  ## Customizing Commands
69
74
 
@@ -190,14 +195,6 @@ flowchart TB
190
195
  - `/issue` - Analyze GitHub issue and create TDD implementation plan
191
196
  - `/plan` - Create implementation plan from feature/requirement with PRD-style discovery and TDD acceptance criteria
192
197
 
193
- ### Workflow
194
-
195
- - `/commit` - Create a git commit following project standards
196
- - `/busycommit` - Create multiple atomic git commits, one logical change at a time
197
- - `/pr` - Creates a pull request using GitHub MCP
198
- - `/summarize` - Summarize conversation progress and next steps
199
- - `/gap` - Analyze conversation context for unaddressed items and gaps
200
-
201
198
  ### Test-Driven Development
202
199
 
203
200
  - `/spike` - Execute TDD Spike Phase - exploratory coding to understand problem space before TDD
@@ -207,6 +204,15 @@ flowchart TB
207
204
  - `/refactor` - Execute TDD Refactor Phase - improve code structure while keeping tests green
208
205
  - `/cycle` - Execute complete TDD cycle - Red, Green, and Refactor phases in sequence
209
206
 
207
+ ### Workflow
208
+
209
+ - `/commit` - Create a git commit following project standards
210
+ - `/busycommit` - Create multiple atomic git commits, one logical change at a time
211
+ - `/pr` - Creates a pull request using GitHub MCP
212
+ - `/summarize` - Summarize conversation progress and next steps
213
+ - `/gap` - Analyze conversation context for unaddressed items and gaps
214
+ - `/code-review` - Code review using dynamic category detection and domain-specific analysis
215
+
210
216
  ### Ship / Show / Ask
211
217
 
212
218
  - `/ship` - Ship code directly to main - for small, obvious changes that don't need review
package/bin/cli.js CHANGED
@@ -119,6 +119,7 @@ init_esm_shims();
119
119
  import {
120
120
  select,
121
121
  text,
122
+ multiselect,
122
123
  groupMultiselect,
123
124
  isCancel,
124
125
  intro,
@@ -496,7 +497,7 @@ var CATEGORY_ORDER = [
496
497
  "Utilities",
497
498
  "Ship / Show / Ask"
498
499
  ];
499
- async function getCommandsGroupedByCategory(variant) {
500
+ async function loadCommandsMetadata(variant) {
500
501
  const sourcePath = path2.join(
501
502
  __dirname2,
502
503
  "..",
@@ -505,7 +506,10 @@ async function getCommandsGroupedByCategory(variant) {
505
506
  );
506
507
  const metadataPath = path2.join(sourcePath, "commands-metadata.json");
507
508
  const metadataContent = await fs.readFile(metadataPath, "utf-8");
508
- const metadata = JSON.parse(metadataContent);
509
+ return JSON.parse(metadataContent);
510
+ }
511
+ async function getCommandsGroupedByCategory(variant) {
512
+ const metadata = await loadCommandsMetadata(variant);
509
513
  const grouped = {};
510
514
  for (const [filename, data] of Object.entries(metadata)) {
511
515
  const category = data.category;
@@ -519,6 +523,11 @@ async function getCommandsGroupedByCategory(variant) {
519
523
  selectedByDefault: data.selectedByDefault !== false
520
524
  });
521
525
  }
526
+ for (const category of Object.keys(grouped)) {
527
+ if (!CATEGORY_ORDER.includes(category)) {
528
+ throw new Error(`Unknown category: ${category}`);
529
+ }
530
+ }
522
531
  for (const category of Object.keys(grouped)) {
523
532
  grouped[category].sort((a, b) => {
524
533
  const orderA = metadata[a.value].order;
@@ -527,12 +536,7 @@ async function getCommandsGroupedByCategory(variant) {
527
536
  });
528
537
  }
529
538
  const sortedCategories = Object.keys(grouped).sort((a, b) => {
530
- const indexA = CATEGORY_ORDER.indexOf(a);
531
- const indexB = CATEGORY_ORDER.indexOf(b);
532
- if (indexA !== -1 && indexB !== -1) return indexA - indexB;
533
- if (indexA !== -1) return -1;
534
- if (indexB !== -1) return 1;
535
- return a.localeCompare(b);
539
+ return CATEGORY_ORDER.indexOf(a) - CATEGORY_ORDER.indexOf(b);
536
540
  });
537
541
  const sortedGrouped = {};
538
542
  for (const category of sortedCategories) {
@@ -540,6 +544,25 @@ async function getCommandsGroupedByCategory(variant) {
540
544
  }
541
545
  return sortedGrouped;
542
546
  }
547
+ function extractLabelFromTool(tool) {
548
+ const match = tool.match(/^Bash\(([^:]+):/);
549
+ return match ? match[1] : tool;
550
+ }
551
+ async function getRequestedToolsOptions(variant) {
552
+ const metadata = await loadCommandsMetadata(variant);
553
+ const allTools = /* @__PURE__ */ new Set();
554
+ for (const data of Object.values(metadata)) {
555
+ if (data["_requested-tools"]) {
556
+ for (const tool of data["_requested-tools"]) {
557
+ allTools.add(tool);
558
+ }
559
+ }
560
+ }
561
+ return Array.from(allTools).map((tool) => ({
562
+ value: tool,
563
+ label: extractLabelFromTool(tool)
564
+ }));
565
+ }
543
566
  function getDestinationPath(outputPath, scope) {
544
567
  if (outputPath) {
545
568
  return outputPath;
@@ -596,6 +619,20 @@ async function generateToDirectory(outputPath, variant, scope, options) {
596
619
  } else {
597
620
  await fs.copy(sourcePath, destinationPath, {});
598
621
  }
622
+ if (options?.allowedTools && options.allowedTools.length > 0) {
623
+ for (const file of files) {
624
+ const filePath = path2.join(destinationPath, file);
625
+ const content = await fs.readFile(filePath, "utf-8");
626
+ const allowedToolsYaml = `allowed-tools: ${options.allowedTools.join(", ")}`;
627
+ const modifiedContent = content.replace(
628
+ /^---\n/,
629
+ `---
630
+ ${allowedToolsYaml}
631
+ `
632
+ );
633
+ await fs.writeFile(filePath, modifiedContent);
634
+ }
635
+ }
599
636
  if (options?.commandPrefix) {
600
637
  for (const file of files) {
601
638
  const oldPath = path2.join(destinationPath, file);
@@ -770,11 +807,26 @@ async function main(args) {
770
807
  let scope;
771
808
  let commandPrefix;
772
809
  let selectedCommands;
810
+ let selectedAllowedTools;
811
+ let cachedExistingFiles;
773
812
  if (args?.variant && args?.scope && args?.prefix !== void 0) {
774
813
  variant = args.variant;
775
814
  scope = args.scope;
776
815
  commandPrefix = args.prefix;
777
816
  selectedCommands = args.commands;
817
+ if (args.updateExisting) {
818
+ cachedExistingFiles = await checkExistingFiles(
819
+ void 0,
820
+ variant,
821
+ scope,
822
+ { commandPrefix: commandPrefix || "" }
823
+ );
824
+ selectedCommands = cachedExistingFiles.map((f) => f.filename);
825
+ if (selectedCommands.length === 0) {
826
+ log.warn("No existing commands found in target directory");
827
+ return;
828
+ }
829
+ }
778
830
  } else {
779
831
  variant = await select({
780
832
  message: "Select variant",
@@ -799,9 +851,34 @@ async function main(args) {
799
851
  if (isCancel(commandPrefix)) {
800
852
  return;
801
853
  }
802
- const groupedCommands = await getCommandsGroupedByCategory(
854
+ let groupedCommands = await getCommandsGroupedByCategory(
803
855
  variant
804
856
  );
857
+ if (args?.updateExisting) {
858
+ cachedExistingFiles = await checkExistingFiles(
859
+ void 0,
860
+ variant,
861
+ scope,
862
+ { commandPrefix: commandPrefix || "" }
863
+ );
864
+ const existingFilenames = new Set(
865
+ cachedExistingFiles.map((f) => f.filename)
866
+ );
867
+ const filteredGrouped = {};
868
+ for (const [category, commands] of Object.entries(groupedCommands)) {
869
+ const filtered = commands.filter(
870
+ (cmd) => existingFilenames.has(cmd.value)
871
+ );
872
+ if (filtered.length > 0) {
873
+ filteredGrouped[category] = filtered;
874
+ }
875
+ }
876
+ groupedCommands = filteredGrouped;
877
+ if (Object.keys(groupedCommands).length === 0) {
878
+ log.warn("No existing commands found in target directory");
879
+ return;
880
+ }
881
+ }
805
882
  const enabledCommandValues = Object.values(groupedCommands).flat().filter((cmd) => cmd.selectedByDefault).map((cmd) => cmd.value);
806
883
  selectedCommands = await groupMultiselect({
807
884
  message: "Select commands to install (Enter to accept all)",
@@ -811,32 +888,47 @@ async function main(args) {
811
888
  if (isCancel(selectedCommands)) {
812
889
  return;
813
890
  }
814
- }
815
- const existingFiles = await checkExistingFiles(
816
- void 0,
817
- variant,
818
- scope,
819
- {
820
- commandPrefix,
821
- commands: selectedCommands
891
+ const requestedToolsOptions = await getRequestedToolsOptions(
892
+ variant
893
+ );
894
+ if (requestedToolsOptions.length > 0) {
895
+ selectedAllowedTools = await multiselect({
896
+ message: "Select allowed tools for commands (optional)",
897
+ options: requestedToolsOptions
898
+ });
899
+ if (isCancel(selectedAllowedTools)) {
900
+ return;
901
+ }
822
902
  }
823
- );
903
+ }
904
+ const existingFiles = cachedExistingFiles ?? await checkExistingFiles(void 0, variant, scope, {
905
+ commandPrefix,
906
+ commands: selectedCommands
907
+ });
824
908
  const skipFiles = [];
825
- for (const file of existingFiles) {
826
- if (file.isIdentical) {
827
- log.info(`${file.filename} is identical, skipping`);
828
- skipFiles.push(file.filename);
829
- continue;
909
+ if (!args?.overwrite && !args?.skipOnConflict) {
910
+ for (const file of existingFiles) {
911
+ if (file.isIdentical) {
912
+ log.info(`${file.filename} is identical, skipping`);
913
+ skipFiles.push(file.filename);
914
+ continue;
915
+ }
916
+ const stats = getDiffStats(file.existingContent, file.newContent);
917
+ const diff = formatCompactDiff(file.existingContent, file.newContent);
918
+ note(diff, `Diff: ${file.filename}`);
919
+ log.info(`+${stats.added} -${stats.removed}`);
920
+ const shouldOverwrite = await confirm({
921
+ message: `Overwrite ${file.filename}?`
922
+ });
923
+ if (!shouldOverwrite) {
924
+ skipFiles.push(file.filename);
925
+ }
830
926
  }
831
- const stats = getDiffStats(file.existingContent, file.newContent);
832
- const diff = formatCompactDiff(file.existingContent, file.newContent);
833
- note(diff, `Diff: ${file.filename}`);
834
- log.info(`+${stats.added} -${stats.removed}`);
835
- const shouldOverwrite = await confirm({
836
- message: `Overwrite ${file.filename}?`
837
- });
838
- if (!shouldOverwrite) {
839
- skipFiles.push(file.filename);
927
+ } else if (args?.skipOnConflict) {
928
+ for (const file of existingFiles) {
929
+ if (!file.isIdentical) {
930
+ skipFiles.push(file.filename);
931
+ }
840
932
  }
841
933
  }
842
934
  const result = await generateToDirectory(
@@ -847,7 +939,8 @@ async function main(args) {
847
939
  commandPrefix,
848
940
  skipTemplateInjection: args?.skipTemplateInjection,
849
941
  commands: selectedCommands,
850
- skipFiles
942
+ skipFiles,
943
+ allowedTools: selectedAllowedTools
851
944
  }
852
945
  );
853
946
  const fullPath = scope === "project" ? `${process.cwd()}/.claude/commands` : `${os2.homedir()}/.claude/commands`;
@@ -860,36 +953,111 @@ Happy TDD'ing!`
860
953
  );
861
954
  }
862
955
 
863
- // scripts/bin.ts
864
- var STRING_ARGS = ["variant", "scope", "prefix"];
865
- var ARRAY_ARGS = ["commands"];
866
- var BOOLEAN_FLAGS = [
867
- { flag: "--skip-template-injection", key: "skipTemplateInjection" }
956
+ // scripts/cli-options.ts
957
+ init_esm_shims();
958
+ var CLI_OPTIONS = [
959
+ {
960
+ flag: "--variant",
961
+ key: "variant",
962
+ type: "string",
963
+ description: "Command variant (with-beads, without-beads)",
964
+ example: "--variant=with-beads"
965
+ },
966
+ {
967
+ flag: "--scope",
968
+ key: "scope",
969
+ type: "string",
970
+ description: "Installation scope (project, user)",
971
+ example: "--scope=project"
972
+ },
973
+ {
974
+ flag: "--prefix",
975
+ key: "prefix",
976
+ type: "string",
977
+ description: "Add prefix to command names",
978
+ example: "--prefix=my-"
979
+ },
980
+ {
981
+ flag: "--commands",
982
+ key: "commands",
983
+ type: "array",
984
+ description: "Install only specific commands",
985
+ example: "--commands=commit,red,green"
986
+ },
987
+ {
988
+ flag: "--skip-template-injection",
989
+ key: "skipTemplateInjection",
990
+ type: "boolean",
991
+ description: "Skip injecting project CLAUDE.md customizations"
992
+ },
993
+ {
994
+ flag: "--update-existing",
995
+ key: "updateExisting",
996
+ type: "boolean",
997
+ description: "Only update already-installed commands"
998
+ },
999
+ {
1000
+ flag: "--overwrite",
1001
+ key: "overwrite",
1002
+ type: "boolean",
1003
+ description: "Overwrite conflicting files without prompting"
1004
+ },
1005
+ {
1006
+ flag: "--skip-on-conflict",
1007
+ key: "skipOnConflict",
1008
+ type: "boolean",
1009
+ description: "Skip conflicting files without prompting"
1010
+ }
868
1011
  ];
1012
+ function generateHelpText() {
1013
+ const lines = [
1014
+ "Usage: npx @wbern/claude-instructions [options]",
1015
+ "",
1016
+ "Options:"
1017
+ ];
1018
+ for (const opt of CLI_OPTIONS) {
1019
+ const suffix = opt.type === "string" ? "=<value>" : opt.type === "array" ? "=<list>" : "";
1020
+ const padding = 28 - (opt.flag.length + suffix.length);
1021
+ lines.push(
1022
+ ` ${opt.flag}${suffix}${" ".repeat(Math.max(1, padding))}${opt.description}`
1023
+ );
1024
+ }
1025
+ lines.push(" --help, -h Show this help message");
1026
+ return lines.join("\n");
1027
+ }
1028
+
1029
+ // scripts/bin.ts
869
1030
  function parseArgs(argv) {
870
1031
  const args = {};
1032
+ const booleanOpts = CLI_OPTIONS.filter((o) => o.type === "boolean");
1033
+ const stringOpts = CLI_OPTIONS.filter((o) => o.type === "string");
1034
+ const arrayOpts = CLI_OPTIONS.filter((o) => o.type === "array");
871
1035
  for (const arg of argv) {
872
- for (const { flag, key } of BOOLEAN_FLAGS) {
873
- if (arg === flag) {
874
- args[key] = true;
1036
+ for (const opt of booleanOpts) {
1037
+ if (arg === opt.flag) {
1038
+ args[opt.key] = true;
875
1039
  }
876
1040
  }
877
- for (const key of STRING_ARGS) {
878
- const prefix = `--${key}=`;
1041
+ for (const opt of stringOpts) {
1042
+ const prefix = `${opt.flag}=`;
879
1043
  if (arg.startsWith(prefix)) {
880
- args[key] = arg.slice(prefix.length);
1044
+ args[opt.key] = arg.slice(prefix.length);
881
1045
  }
882
1046
  }
883
- for (const key of ARRAY_ARGS) {
884
- const prefix = `--${key}=`;
1047
+ for (const opt of arrayOpts) {
1048
+ const prefix = `${opt.flag}=`;
885
1049
  if (arg.startsWith(prefix)) {
886
- args[key] = arg.slice(prefix.length).split(",");
1050
+ args[opt.key] = arg.slice(prefix.length).split(",");
887
1051
  }
888
1052
  }
889
1053
  }
890
1054
  return args;
891
1055
  }
892
1056
  async function run(argv) {
1057
+ if (argv.includes("--help") || argv.includes("-h")) {
1058
+ console.log(generateHelpText());
1059
+ return;
1060
+ }
893
1061
  const args = parseArgs(argv);
894
1062
  await main(args);
895
1063
  }
@@ -17,6 +17,22 @@ Create multiple atomic git commits, committing the smallest possible logical uni
17
17
 
18
18
  Include any of the following info if specified: $ARGUMENTS
19
19
 
20
+ ## Commit Message Rules
21
+
22
+ Follows [Conventional Commits](https://www.conventionalcommits.org/) standard.
23
+
24
+ 1. **Format**: `type(#issue): description`
25
+ - Use `#123` for local repo issues
26
+ - Use `owner/repo#123` for cross-repo issues
27
+ - Common types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
28
+
29
+ 2. **AI Credits**: **NEVER include AI credits in commit messages**
30
+ - No "Generated with Claude Code"
31
+ - No "Co-Authored-By: Claude" or "Co-Authored-By: Happy"
32
+ - Focus on the actual changes made, not conversation history
33
+
34
+ 3. **Content**: Write clear, concise commit messages describing what changed and why
35
+
20
36
  ## Process
21
37
 
22
38
  1. Run `git status` and `git diff` to review changes
@@ -0,0 +1,248 @@
1
+ ---
2
+ description: Code review using dynamic category detection and domain-specific analysis
3
+ argument-hint: (optional) [branch, PR#, or PR URL] - defaults to current branch
4
+ - Bash(git diff:*)
5
+ - Bash(git status:*)
6
+ - Bash(git log:*)
7
+ - Bash(git rev-parse:*)
8
+ - Bash(git merge-base:*)
9
+ - Bash(git branch:*)
10
+ ---
11
+
12
+ ## General Guidelines
13
+
14
+ ### Output Style
15
+
16
+ - **Never explicitly mention TDD** in code, comments, commits, PRs, or issues
17
+ - Write natural, descriptive code without meta-commentary about the development process
18
+ - The code should speak for itself - TDD is the process, not the product
19
+
20
+ Beads is available for task tracking. Use `mcp__beads__*` tools to manage issues (the user interacts via `bd` commands).
21
+
22
+ # Code Review
23
+
24
+ Perform a code review using dynamic category detection.
25
+
26
+ ## Phase 0: Setup & Categorization
27
+
28
+ ### Determine What to Review
29
+
30
+ Parse the argument to determine the review target:
31
+
32
+ | Input | Action |
33
+ |-------|--------|
34
+ | No argument | Detect divergence point, confirm scope with user |
35
+ | Branch name | Use specified branch as base |
36
+ | PR number (e.g., `123`) | Fetch PR diff from GitHub |
37
+ | PR URL (e.g., `https://github.com/owner/repo/pull/123`) | Extract PR number and fetch diff |
38
+
39
+ **For GitHub PRs:**
40
+
41
+ 1. Try GitHub MCP first: `mcp__github__pull_request_read` with `method: "get_diff"`
42
+ 2. Fall back to `gh` CLI: `gh pr diff <number>`
43
+ 3. If neither works, report error and stop
44
+
45
+ **For local branches (no argument or branch name provided):**
46
+
47
+ 1. **Get current branch**: `git rev-parse --abbrev-ref HEAD`
48
+
49
+ 2. **Check for uncommitted changes**: `git status --porcelain`
50
+ - If output is non-empty, note that uncommitted changes exist
51
+
52
+ 3. **Detect divergence point** (skip if branch name was provided as argument):
53
+ - Get all local branches except current: `git branch --format='%(refname:short)'`
54
+ - For each branch, find merge-base: `git merge-base HEAD <branch>`
55
+ - Count commits from merge-base to HEAD: `git rev-list --count <merge-base>..HEAD`
56
+ - The branch with the **fewest commits back** (closest merge-base) is the likely parent
57
+ - If no other branches exist, fall back to `main`, `master`, or `develop` if they exist as remote tracking branches
58
+
59
+ 4. **Confirm scope with user** using `AskUserQuestion`:
60
+
61
+ **Question 1 - "Review scope"** (header: "Base branch"):
62
+ - Option A: `From <detected-branch>` — "Review N commits since diverging from <branch>"
63
+ - Option B: `Different branch` — "Specify another branch to compare against"
64
+ - Option C: `Uncommitted only` — "Review only staged/unstaged changes, skip committed work"
65
+
66
+ **Question 2 - "Include uncommitted?"** (header: "Uncommitted", only ask if uncommitted changes exist AND user didn't pick option C):
67
+ - Option A: `Yes` — "Include N staged/unstaged files in review"
68
+ - Option B: `No` — "Review only committed changes"
69
+
70
+ 5. **Collect changed files** based on user selection:
71
+ - From branch: `git diff --name-only <base>...HEAD`
72
+ - Uncommitted unstaged: `git diff --name-only`
73
+ - Uncommitted staged: `git diff --name-only --cached`
74
+ - Combine and deduplicate the file list
75
+
76
+ 6. **If no changes**: Report "Nothing to review" and stop
77
+
78
+ ### Categorize Files
79
+
80
+ Check for CLAUDE.md - if it exists, note any project-specific review patterns.
81
+
82
+ Categorize each changed file into ONE primary category based on these patterns:
83
+
84
+ | Category | File Patterns |
85
+ |----------|---------------|
86
+ | Frontend/UI | `*.tsx`, `*.jsx`, `components/`, `pages/`, `views/`, `*.vue` |
87
+ | Frontend/Styling | `*.css`, `*.scss`, `*.less`, `styles/`, `*.tailwind*`, `*.styled.*` |
88
+ | Backend/API | `routes/`, `api/`, `controllers/`, `services/`, `*.controller.*`, `*.service.*`, `*.resolver.*` |
89
+ | Backend/Data | `migrations/`, `models/`, `prisma/`, `schema.*`, `*.model.*`, `*.entity.*` |
90
+ | Tooling/Config | `scripts/`, `*.config.*`, `package.json`, `tsconfig.*`, `vite.*`, `webpack.*`, `eslint.*` |
91
+ | CI/CD | `.github/`, `.gitlab-ci.*`, `Dockerfile`, `docker-compose.*`, `*.yml` in CI paths |
92
+ | Tests | `*.test.*`, `*.spec.*`, `__tests__/`, `__mocks__/`, `*.stories.*` |
93
+ | Docs | `*.md`, `docs/`, `README*`, `CHANGELOG*` |
94
+
95
+ Output the categorization:
96
+
97
+ ```
98
+ ## Categorization
99
+
100
+ Base branch: <branch>
101
+ Total files changed: <n>
102
+
103
+ | Category | Files |
104
+ |----------|-------|
105
+ | <category> | <count> |
106
+ ...
107
+ ```
108
+
109
+ ## Phase 1: Branch Brief
110
+
111
+ From the diff and recent commit messages (`git log <base>...HEAD --oneline`), infer:
112
+
113
+ - **Goal**: What this branch accomplishes (1-3 sentences)
114
+ - **Constraints**: Any implied requirements (security, performance, backwards compatibility)
115
+ - **Success checklist**: What must work after this change, what must not break
116
+
117
+ ```
118
+ ## Branch Brief
119
+
120
+ **Goal**: ...
121
+ **Constraints**: ...
122
+ **Checklist**:
123
+ - [ ] ...
124
+ ```
125
+
126
+ ## Phase 2: Category Reviews
127
+
128
+ For each detected category with changes, run a targeted review. Skip categories with no changes.
129
+
130
+ ### Frontend/UI Review Criteria
131
+
132
+ - Accessibility: ARIA attributes, keyboard navigation, screen reader support
133
+ - Component patterns: Composition, prop drilling, context usage
134
+ - State management: Unnecessary re-renders, stale closures
135
+ - Performance: memo/useMemo/useCallback usage, lazy loading, bundle impact
136
+
137
+ ### Frontend/Styling Review Criteria
138
+
139
+ - Responsive design: Breakpoints, mobile-first
140
+ - Design system: Token usage, consistent spacing/colors
141
+ - CSS specificity: Overly specific selectors, !important usage
142
+ - Theme support: Dark mode, CSS variables
143
+
144
+ ### Backend/API Review Criteria
145
+
146
+ - Input validation: Sanitization, type checking, bounds
147
+ - Security: Authentication checks, authorization, injection risks
148
+ - Error handling: Proper status codes, meaningful messages, logging
149
+ - Performance: N+1 queries, missing indexes, pagination
150
+
151
+ ### Backend/Data Review Criteria
152
+
153
+ - Migration safety: Reversibility, data preservation
154
+ - Data integrity: Constraints, foreign keys, nullability
155
+ - Index usage: Queries have appropriate indexes
156
+ - Backwards compatibility: Existing data still works
157
+
158
+ ### Tooling/Config Review Criteria
159
+
160
+ - Breaking changes: Does this affect developer workflow?
161
+ - Dependency compatibility: Version conflicts, peer deps
162
+ - Build performance: Added build time, bundle size
163
+
164
+ ### CI/CD Review Criteria
165
+
166
+ - Secrets exposure: Credentials in logs, env vars
167
+ - Pipeline efficiency: Caching, parallelization
168
+ - Failure handling: Notifications, rollback strategy
169
+
170
+ ### Tests Review Criteria
171
+
172
+ - Coverage: Edge cases, error paths, boundaries
173
+ - Assertion quality: Specific assertions, not just "no error"
174
+ - Flaky patterns: Timing dependencies, order dependencies, shared state
175
+
176
+ ### Docs Review Criteria
177
+
178
+ - Technical accuracy: Code examples work, APIs documented correctly
179
+ - Completeness: All new features documented
180
+ - Clarity: Easy to follow, good examples
181
+
182
+ **Output format per category:**
183
+
184
+ ```
185
+ ## <Category> Review (<n> files)
186
+
187
+ ### file:line - [blocker|risky|nit] Title
188
+ Description of the issue and why it matters.
189
+ Suggested fix or question to investigate.
190
+
191
+ ...
192
+ ```
193
+
194
+ ## Phase 3: Cross-Cutting Analysis
195
+
196
+ After reviewing all categories, check for cross-cutting issues:
197
+
198
+ - API changed but tests didn't update?
199
+ - New feature but no documentation?
200
+ - Migration added but no rollback tested?
201
+ - Config changed but README not updated?
202
+ - Security-sensitive code without corresponding test?
203
+
204
+ ```
205
+ ## Cross-Cutting Issues
206
+
207
+ - [ ] <issue description>
208
+ ...
209
+ ```
210
+
211
+ ## Phase 4: Summary
212
+
213
+ ### PR Description (draft)
214
+
215
+ Provide a ready-to-paste PR description:
216
+
217
+ ```
218
+ ## What changed
219
+ - <by category, 1-2 bullets each>
220
+
221
+ ## Why
222
+ - <motivation>
223
+
224
+ ## Testing
225
+ - <how to verify>
226
+
227
+ ## Notes
228
+ - <migration steps, breaking changes, etc.>
229
+ ```
230
+
231
+ ### Review Checklist
232
+
233
+ ```
234
+ ## Before Merge
235
+
236
+ ### Blockers (must fix)
237
+ - [ ] ...
238
+
239
+ ### Risky (highlight to reviewers)
240
+ - [ ] ...
241
+
242
+ ### Follow-ups (can defer)
243
+ - [ ] ...
244
+ ```
245
+
246
+ ---
247
+
248
+ Review target (branch name, PR number, or PR URL - leave empty for current branch): $ARGUMENTS
@@ -24,6 +24,20 @@
24
24
  "category": "Workflow",
25
25
  "order": 2
26
26
  },
27
+ "code-review.md": {
28
+ "description": "Code review using dynamic category detection and domain-specific analysis",
29
+ "hint": "Review code",
30
+ "category": "Workflow",
31
+ "order": 35,
32
+ "_requested-tools": [
33
+ "Bash(git diff:*)",
34
+ "Bash(git status:*)",
35
+ "Bash(git log:*)",
36
+ "Bash(git rev-parse:*)",
37
+ "Bash(git merge-base:*)",
38
+ "Bash(git branch:*)"
39
+ ]
40
+ },
27
41
  "commit.md": {
28
42
  "description": "Create a git commit following project standards",
29
43
  "hint": "Git commit",
@@ -17,6 +17,22 @@ Create a git commit following project standards
17
17
 
18
18
  Include any of the following info if specified: $ARGUMENTS
19
19
 
20
+ ## Commit Message Rules
21
+
22
+ Follows [Conventional Commits](https://www.conventionalcommits.org/) standard.
23
+
24
+ 1. **Format**: `type(#issue): description`
25
+ - Use `#123` for local repo issues
26
+ - Use `owner/repo#123` for cross-repo issues
27
+ - Common types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
28
+
29
+ 2. **AI Credits**: **NEVER include AI credits in commit messages**
30
+ - No "Generated with Claude Code"
31
+ - No "Co-Authored-By: Claude" or "Co-Authored-By: Happy"
32
+ - Focus on the actual changes made, not conversation history
33
+
34
+ 3. **Content**: Write clear, concise commit messages describing what changed and why
35
+
20
36
  ## Process
21
37
 
22
38
  1. Run `git status` and `git diff` to review changes
@@ -19,6 +19,7 @@ Analyze the current conversation context and identify things that have not yet b
19
19
  2. **Unused variables/results** - Values that were captured but never used
20
20
  3. **Missing tests** - Functionality without test coverage
21
21
  4. **Open issues** - Beads issues that are still open or in progress
22
+
22
23
  5. **User requests** - Things the user asked for that weren't fully completed
23
24
  6. **TODO comments** - Any TODOs mentioned in conversation
24
25
  7. **Error handling gaps** - Missing error cases or edge cases
@@ -21,8 +21,7 @@ Beads is available for task tracking. Use `mcp__beads__*` tools to manage issues
21
21
 
22
22
  $ARGUMENTS
23
23
 
24
- (If no input provided, check conversation context or run `bd ready` to see existing work
25
- )
24
+ (If no input provided, check conversation context or run `bd ready` to see existing work)
26
25
 
27
26
  ## Input Processing
28
27
 
@@ -15,6 +15,22 @@ Create multiple atomic git commits, committing the smallest possible logical uni
15
15
 
16
16
  Include any of the following info if specified: $ARGUMENTS
17
17
 
18
+ ## Commit Message Rules
19
+
20
+ Follows [Conventional Commits](https://www.conventionalcommits.org/) standard.
21
+
22
+ 1. **Format**: `type(#issue): description`
23
+ - Use `#123` for local repo issues
24
+ - Use `owner/repo#123` for cross-repo issues
25
+ - Common types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
26
+
27
+ 2. **AI Credits**: **NEVER include AI credits in commit messages**
28
+ - No "Generated with Claude Code"
29
+ - No "Co-Authored-By: Claude" or "Co-Authored-By: Happy"
30
+ - Focus on the actual changes made, not conversation history
31
+
32
+ 3. **Content**: Write clear, concise commit messages describing what changed and why
33
+
18
34
  ## Process
19
35
 
20
36
  1. Run `git status` and `git diff` to review changes
@@ -0,0 +1,246 @@
1
+ ---
2
+ description: Code review using dynamic category detection and domain-specific analysis
3
+ argument-hint: (optional) [branch, PR#, or PR URL] - defaults to current branch
4
+ - Bash(git diff:*)
5
+ - Bash(git status:*)
6
+ - Bash(git log:*)
7
+ - Bash(git rev-parse:*)
8
+ - Bash(git merge-base:*)
9
+ - Bash(git branch:*)
10
+ ---
11
+
12
+ ## General Guidelines
13
+
14
+ ### Output Style
15
+
16
+ - **Never explicitly mention TDD** in code, comments, commits, PRs, or issues
17
+ - Write natural, descriptive code without meta-commentary about the development process
18
+ - The code should speak for itself - TDD is the process, not the product
19
+
20
+ # Code Review
21
+
22
+ Perform a code review using dynamic category detection.
23
+
24
+ ## Phase 0: Setup & Categorization
25
+
26
+ ### Determine What to Review
27
+
28
+ Parse the argument to determine the review target:
29
+
30
+ | Input | Action |
31
+ |-------|--------|
32
+ | No argument | Detect divergence point, confirm scope with user |
33
+ | Branch name | Use specified branch as base |
34
+ | PR number (e.g., `123`) | Fetch PR diff from GitHub |
35
+ | PR URL (e.g., `https://github.com/owner/repo/pull/123`) | Extract PR number and fetch diff |
36
+
37
+ **For GitHub PRs:**
38
+
39
+ 1. Try GitHub MCP first: `mcp__github__pull_request_read` with `method: "get_diff"`
40
+ 2. Fall back to `gh` CLI: `gh pr diff <number>`
41
+ 3. If neither works, report error and stop
42
+
43
+ **For local branches (no argument or branch name provided):**
44
+
45
+ 1. **Get current branch**: `git rev-parse --abbrev-ref HEAD`
46
+
47
+ 2. **Check for uncommitted changes**: `git status --porcelain`
48
+ - If output is non-empty, note that uncommitted changes exist
49
+
50
+ 3. **Detect divergence point** (skip if branch name was provided as argument):
51
+ - Get all local branches except current: `git branch --format='%(refname:short)'`
52
+ - For each branch, find merge-base: `git merge-base HEAD <branch>`
53
+ - Count commits from merge-base to HEAD: `git rev-list --count <merge-base>..HEAD`
54
+ - The branch with the **fewest commits back** (closest merge-base) is the likely parent
55
+ - If no other branches exist, fall back to `main`, `master`, or `develop` if they exist as remote tracking branches
56
+
57
+ 4. **Confirm scope with user** using `AskUserQuestion`:
58
+
59
+ **Question 1 - "Review scope"** (header: "Base branch"):
60
+ - Option A: `From <detected-branch>` — "Review N commits since diverging from <branch>"
61
+ - Option B: `Different branch` — "Specify another branch to compare against"
62
+ - Option C: `Uncommitted only` — "Review only staged/unstaged changes, skip committed work"
63
+
64
+ **Question 2 - "Include uncommitted?"** (header: "Uncommitted", only ask if uncommitted changes exist AND user didn't pick option C):
65
+ - Option A: `Yes` — "Include N staged/unstaged files in review"
66
+ - Option B: `No` — "Review only committed changes"
67
+
68
+ 5. **Collect changed files** based on user selection:
69
+ - From branch: `git diff --name-only <base>...HEAD`
70
+ - Uncommitted unstaged: `git diff --name-only`
71
+ - Uncommitted staged: `git diff --name-only --cached`
72
+ - Combine and deduplicate the file list
73
+
74
+ 6. **If no changes**: Report "Nothing to review" and stop
75
+
76
+ ### Categorize Files
77
+
78
+ Check for CLAUDE.md - if it exists, note any project-specific review patterns.
79
+
80
+ Categorize each changed file into ONE primary category based on these patterns:
81
+
82
+ | Category | File Patterns |
83
+ |----------|---------------|
84
+ | Frontend/UI | `*.tsx`, `*.jsx`, `components/`, `pages/`, `views/`, `*.vue` |
85
+ | Frontend/Styling | `*.css`, `*.scss`, `*.less`, `styles/`, `*.tailwind*`, `*.styled.*` |
86
+ | Backend/API | `routes/`, `api/`, `controllers/`, `services/`, `*.controller.*`, `*.service.*`, `*.resolver.*` |
87
+ | Backend/Data | `migrations/`, `models/`, `prisma/`, `schema.*`, `*.model.*`, `*.entity.*` |
88
+ | Tooling/Config | `scripts/`, `*.config.*`, `package.json`, `tsconfig.*`, `vite.*`, `webpack.*`, `eslint.*` |
89
+ | CI/CD | `.github/`, `.gitlab-ci.*`, `Dockerfile`, `docker-compose.*`, `*.yml` in CI paths |
90
+ | Tests | `*.test.*`, `*.spec.*`, `__tests__/`, `__mocks__/`, `*.stories.*` |
91
+ | Docs | `*.md`, `docs/`, `README*`, `CHANGELOG*` |
92
+
93
+ Output the categorization:
94
+
95
+ ```
96
+ ## Categorization
97
+
98
+ Base branch: <branch>
99
+ Total files changed: <n>
100
+
101
+ | Category | Files |
102
+ |----------|-------|
103
+ | <category> | <count> |
104
+ ...
105
+ ```
106
+
107
+ ## Phase 1: Branch Brief
108
+
109
+ From the diff and recent commit messages (`git log <base>...HEAD --oneline`), infer:
110
+
111
+ - **Goal**: What this branch accomplishes (1-3 sentences)
112
+ - **Constraints**: Any implied requirements (security, performance, backwards compatibility)
113
+ - **Success checklist**: What must work after this change, what must not break
114
+
115
+ ```
116
+ ## Branch Brief
117
+
118
+ **Goal**: ...
119
+ **Constraints**: ...
120
+ **Checklist**:
121
+ - [ ] ...
122
+ ```
123
+
124
+ ## Phase 2: Category Reviews
125
+
126
+ For each detected category with changes, run a targeted review. Skip categories with no changes.
127
+
128
+ ### Frontend/UI Review Criteria
129
+
130
+ - Accessibility: ARIA attributes, keyboard navigation, screen reader support
131
+ - Component patterns: Composition, prop drilling, context usage
132
+ - State management: Unnecessary re-renders, stale closures
133
+ - Performance: memo/useMemo/useCallback usage, lazy loading, bundle impact
134
+
135
+ ### Frontend/Styling Review Criteria
136
+
137
+ - Responsive design: Breakpoints, mobile-first
138
+ - Design system: Token usage, consistent spacing/colors
139
+ - CSS specificity: Overly specific selectors, !important usage
140
+ - Theme support: Dark mode, CSS variables
141
+
142
+ ### Backend/API Review Criteria
143
+
144
+ - Input validation: Sanitization, type checking, bounds
145
+ - Security: Authentication checks, authorization, injection risks
146
+ - Error handling: Proper status codes, meaningful messages, logging
147
+ - Performance: N+1 queries, missing indexes, pagination
148
+
149
+ ### Backend/Data Review Criteria
150
+
151
+ - Migration safety: Reversibility, data preservation
152
+ - Data integrity: Constraints, foreign keys, nullability
153
+ - Index usage: Queries have appropriate indexes
154
+ - Backwards compatibility: Existing data still works
155
+
156
+ ### Tooling/Config Review Criteria
157
+
158
+ - Breaking changes: Does this affect developer workflow?
159
+ - Dependency compatibility: Version conflicts, peer deps
160
+ - Build performance: Added build time, bundle size
161
+
162
+ ### CI/CD Review Criteria
163
+
164
+ - Secrets exposure: Credentials in logs, env vars
165
+ - Pipeline efficiency: Caching, parallelization
166
+ - Failure handling: Notifications, rollback strategy
167
+
168
+ ### Tests Review Criteria
169
+
170
+ - Coverage: Edge cases, error paths, boundaries
171
+ - Assertion quality: Specific assertions, not just "no error"
172
+ - Flaky patterns: Timing dependencies, order dependencies, shared state
173
+
174
+ ### Docs Review Criteria
175
+
176
+ - Technical accuracy: Code examples work, APIs documented correctly
177
+ - Completeness: All new features documented
178
+ - Clarity: Easy to follow, good examples
179
+
180
+ **Output format per category:**
181
+
182
+ ```
183
+ ## <Category> Review (<n> files)
184
+
185
+ ### file:line - [blocker|risky|nit] Title
186
+ Description of the issue and why it matters.
187
+ Suggested fix or question to investigate.
188
+
189
+ ...
190
+ ```
191
+
192
+ ## Phase 3: Cross-Cutting Analysis
193
+
194
+ After reviewing all categories, check for cross-cutting issues:
195
+
196
+ - API changed but tests didn't update?
197
+ - New feature but no documentation?
198
+ - Migration added but no rollback tested?
199
+ - Config changed but README not updated?
200
+ - Security-sensitive code without corresponding test?
201
+
202
+ ```
203
+ ## Cross-Cutting Issues
204
+
205
+ - [ ] <issue description>
206
+ ...
207
+ ```
208
+
209
+ ## Phase 4: Summary
210
+
211
+ ### PR Description (draft)
212
+
213
+ Provide a ready-to-paste PR description:
214
+
215
+ ```
216
+ ## What changed
217
+ - <by category, 1-2 bullets each>
218
+
219
+ ## Why
220
+ - <motivation>
221
+
222
+ ## Testing
223
+ - <how to verify>
224
+
225
+ ## Notes
226
+ - <migration steps, breaking changes, etc.>
227
+ ```
228
+
229
+ ### Review Checklist
230
+
231
+ ```
232
+ ## Before Merge
233
+
234
+ ### Blockers (must fix)
235
+ - [ ] ...
236
+
237
+ ### Risky (highlight to reviewers)
238
+ - [ ] ...
239
+
240
+ ### Follow-ups (can defer)
241
+ - [ ] ...
242
+ ```
243
+
244
+ ---
245
+
246
+ Review target (branch name, PR number, or PR URL - leave empty for current branch): $ARGUMENTS
@@ -24,6 +24,20 @@
24
24
  "category": "Workflow",
25
25
  "order": 2
26
26
  },
27
+ "code-review.md": {
28
+ "description": "Code review using dynamic category detection and domain-specific analysis",
29
+ "hint": "Review code",
30
+ "category": "Workflow",
31
+ "order": 35,
32
+ "_requested-tools": [
33
+ "Bash(git diff:*)",
34
+ "Bash(git status:*)",
35
+ "Bash(git log:*)",
36
+ "Bash(git rev-parse:*)",
37
+ "Bash(git merge-base:*)",
38
+ "Bash(git branch:*)"
39
+ ]
40
+ },
27
41
  "commit.md": {
28
42
  "description": "Create a git commit following project standards",
29
43
  "hint": "Git commit",
@@ -15,6 +15,22 @@ Create a git commit following project standards
15
15
 
16
16
  Include any of the following info if specified: $ARGUMENTS
17
17
 
18
+ ## Commit Message Rules
19
+
20
+ Follows [Conventional Commits](https://www.conventionalcommits.org/) standard.
21
+
22
+ 1. **Format**: `type(#issue): description`
23
+ - Use `#123` for local repo issues
24
+ - Use `owner/repo#123` for cross-repo issues
25
+ - Common types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`
26
+
27
+ 2. **AI Credits**: **NEVER include AI credits in commit messages**
28
+ - No "Generated with Claude Code"
29
+ - No "Co-Authored-By: Claude" or "Co-Authored-By: Happy"
30
+ - Focus on the actual changes made, not conversation history
31
+
32
+ 3. **Content**: Write clear, concise commit messages describing what changed and why
33
+
18
34
  ## Process
19
35
 
20
36
  1. Run `git status` and `git diff` to review changes
@@ -16,6 +16,7 @@ Analyze the current conversation context and identify things that have not yet b
16
16
  1. **Incomplete implementations** - Code that was started but not finished
17
17
  2. **Unused variables/results** - Values that were captured but never used
18
18
  3. **Missing tests** - Functionality without test coverage
19
+
19
20
  4. **User requests** - Things the user asked for that weren't fully completed
20
21
  5. **TODO comments** - Any TODOs mentioned in conversation
21
22
  6. **Error handling gaps** - Missing error cases or edge cases
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wbern/claude-instructions",
3
- "version": "1.9.0",
3
+ "version": "1.10.0",
4
4
  "description": "TDD workflow commands for Claude Code CLI",
5
5
  "type": "module",
6
6
  "bin": "./bin/cli.js",
@@ -34,6 +34,7 @@
34
34
  "test:quick-manual": "pnpm build:cli && node bin/cli.js",
35
35
  "generate": "tsx scripts/cli-generator.ts",
36
36
  "test": "vitest run",
37
+ "test:coverage": "vitest run --coverage",
37
38
  "test:watch": "vitest",
38
39
  "typecheck": "tsc --noEmit",
39
40
  "knip": "knip",
@@ -45,13 +46,13 @@
45
46
  "@eslint/js": "^9.39.1",
46
47
  "@types/fs-extra": "^11.0.4",
47
48
  "@types/node": "^24.10.1",
49
+ "@vitest/coverage-v8": "^4.0.15",
48
50
  "diff": "^8.0.2",
49
51
  "eslint": "^9.39.1",
50
52
  "husky": "^9.1.7",
51
53
  "jscpd": "^4.0.5",
52
54
  "knip": "^5.70.2",
53
55
  "lint-staged": "^16.2.7",
54
- "markdown-magic": "^4.0.4",
55
56
  "markdownlint-cli": "^0.46.0",
56
57
  "picocolors": "^1.1.1",
57
58
  "prettier": "^3.7.2",
@@ -59,7 +60,7 @@
59
60
  "tsx": "^4.20.6",
60
61
  "typescript": "^5.9.3",
61
62
  "typescript-eslint": "^8.48.0",
62
- "vitest": "^4.0.8"
63
+ "vitest": "^4.0.15"
63
64
  },
64
65
  "dependencies": {
65
66
  "@clack/prompts": "^0.11.0",