@bonesofspring/ai-rules 0.1.40 → 0.1.42

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.
Files changed (119) hide show
  1. package/README.md +12 -3
  2. package/bin/cli.js +211 -50
  3. package/package.json +2 -1
  4. package/presets/claude/next/CLAUDE.md +11 -7
  5. package/presets/claude/next/README.md +2 -2
  6. package/presets/claude/next/agents/README.md +50 -1
  7. package/presets/claude/next/agents/accessibility-reviewer.md +65 -0
  8. package/presets/claude/next/agents/api-contract-reviewer.md +69 -0
  9. package/presets/claude/next/agents/build-verifier.md +62 -0
  10. package/presets/claude/next/agents/ci-investigator.md +62 -0
  11. package/presets/claude/next/agents/code-reviewer.md +7 -1
  12. package/presets/claude/next/agents/debugger.md +7 -2
  13. package/presets/claude/next/agents/feature-developer.md +11 -16
  14. package/presets/claude/next/agents/migration-specialist.md +69 -0
  15. package/presets/claude/next/agents/performance-auditor.md +68 -0
  16. package/presets/claude/next/agents/qa-tester.md +4 -2
  17. package/presets/claude/next/agents/security-reviewer.md +64 -0
  18. package/presets/claude/next/agents/task-analyst.md +6 -2
  19. package/presets/claude/next/agents/task-router.md +83 -16
  20. package/presets/claude/next/agents/tech-writer.md +60 -0
  21. package/presets/claude/next/agents/unit-test-generator.md +37 -0
  22. package/presets/claude/next/agents/unit-test-healer.md +38 -0
  23. package/presets/claude/next/agents/unit-test-planner.md +62 -0
  24. package/presets/claude/next/commands/README.md +1 -1
  25. package/presets/claude/next/commands/feature-continue.md +5 -0
  26. package/presets/claude/next/commands/feature-start.md +5 -0
  27. package/presets/claude/next/commands/task-continue.md +8 -2
  28. package/presets/claude/next/commands/task.md +10 -0
  29. package/presets/claude/next/commands/technical-retro.md +6 -1
  30. package/presets/claude/next/hooks/README.md +5 -8
  31. package/presets/claude/next/hooks/chain-team-phases.sh +258 -39
  32. package/presets/claude/next/hooks/guard-shell-command.sh +77 -0
  33. package/presets/claude/next/rules/README.md +20 -44
  34. package/presets/claude/next/rules/architecture/api-public-imports.md +6 -0
  35. package/presets/claude/next/rules/architecture/architecture-boundaries.md +5 -0
  36. package/presets/claude/next/rules/architecture/feature-delivery-workflow.md +5 -0
  37. package/presets/claude/next/rules/architecture/layer-barrel-exports.md +5 -0
  38. package/presets/claude/next/rules/architecture/reference-features.md +34 -0
  39. package/presets/claude/next/rules/architecture/types-public-imports.md +6 -0
  40. package/presets/claude/next/rules/stack/arrow-functions.md +5 -0
  41. package/presets/claude/next/rules/stack/navigation-router.md +5 -0
  42. package/presets/claude/next/rules/stack/next-app-router.md +36 -0
  43. package/presets/claude/next/rules/stack/no-type-assertion.md +6 -0
  44. package/presets/claude/next/rules/testing/playwright-agents.md +5 -0
  45. package/presets/claude/next/rules/tooling-and-review/agent-team-orchestrator.md +32 -8
  46. package/presets/claude/next/rules/ui-and-accessibility/component-styles.md +6 -0
  47. package/presets/claude/next/rules/ui-and-accessibility/css-property-order.md +6 -0
  48. package/presets/claude/next/rules/ui-and-accessibility/no-props-spread.md +5 -0
  49. package/presets/claude/next/rules/ui-and-accessibility/react-a11y-coding.md +37 -0
  50. package/presets/claude/next/skills/README.md +11 -1
  51. package/presets/claude/next/skills/ci-investigation/SKILL.md +36 -0
  52. package/presets/claude/next/skills/code-review/SKILL.md +26 -0
  53. package/presets/claude/next/skills/debug-investigation/SKILL.md +28 -0
  54. package/presets/claude/next/skills/feature-delivery/SKILL.md +30 -0
  55. package/presets/claude/next/skills/playwright-e2e/SKILL.md +31 -0
  56. package/presets/claude/next/skills/technical-retro/SKILL.md +40 -0
  57. package/presets/claude/next/skills/unit-testing/SKILL.md +32 -0
  58. package/presets/claude/next/team/README.md +25 -43
  59. package/presets/cursor/next/AGENTS.md +43 -0
  60. package/presets/cursor/next/BUGBOT.md +14 -0
  61. package/presets/cursor/next/agents/README.md +52 -0
  62. package/presets/cursor/next/agents/accessibility-reviewer.md +67 -0
  63. package/presets/cursor/next/agents/api-contract-reviewer.md +71 -0
  64. package/presets/cursor/next/agents/build-verifier.md +64 -0
  65. package/presets/cursor/next/agents/ci-investigator.md +64 -0
  66. package/presets/cursor/next/agents/code-reviewer.md +8 -1
  67. package/presets/cursor/next/agents/debugger.md +7 -2
  68. package/presets/cursor/next/agents/feature-developer.md +10 -8
  69. package/presets/cursor/next/agents/migration-specialist.md +71 -0
  70. package/presets/cursor/next/agents/performance-auditor.md +70 -0
  71. package/presets/cursor/next/agents/playwright-test-generator.md +26 -0
  72. package/presets/cursor/next/agents/playwright-test-healer.md +26 -0
  73. package/presets/cursor/next/agents/playwright-test-planner.md +28 -0
  74. package/presets/cursor/next/agents/qa-tester.md +4 -2
  75. package/presets/cursor/next/agents/security-reviewer.md +66 -0
  76. package/presets/cursor/next/agents/task-analyst.md +3 -2
  77. package/presets/cursor/next/agents/task-router.md +84 -17
  78. package/presets/cursor/next/agents/tech-writer.md +62 -0
  79. package/presets/cursor/next/agents/unit-test-generator.md +39 -0
  80. package/presets/cursor/next/agents/unit-test-healer.md +40 -0
  81. package/presets/cursor/next/agents/unit-test-planner.md +64 -0
  82. package/presets/cursor/next/commands/README.md +17 -1
  83. package/presets/cursor/next/commands/feature-continue.md +5 -0
  84. package/presets/cursor/next/commands/feature-start.md +5 -0
  85. package/presets/cursor/next/commands/task-continue.md +8 -2
  86. package/presets/cursor/next/commands/task.md +10 -0
  87. package/presets/cursor/next/commands/technical-retro.md +6 -1
  88. package/presets/cursor/next/hooks/README.md +8 -0
  89. package/presets/cursor/next/hooks/chain-team-phases.sh +192 -28
  90. package/presets/cursor/next/hooks/guard-shell-command.sh +77 -0
  91. package/presets/cursor/next/hooks.json +7 -1
  92. package/presets/cursor/next/rules/README.md +16 -6
  93. package/presets/cursor/next/rules/agent-team-orchestrator.mdc +32 -8
  94. package/presets/cursor/next/rules/api-public-imports.mdc +2 -1
  95. package/presets/cursor/next/rules/architecture-boundaries.mdc +2 -1
  96. package/presets/cursor/next/rules/arrow-functions.mdc +2 -1
  97. package/presets/cursor/next/rules/code-review-mr.mdc +1 -1
  98. package/presets/cursor/next/rules/css-property-order-stylelint.mdc +4 -1
  99. package/presets/cursor/next/rules/feature-delivery-flow.mdc +1 -1
  100. package/presets/cursor/next/rules/feature-delivery-workflow.mdc +2 -2
  101. package/presets/cursor/next/rules/layer-barrel-exports.mdc +2 -1
  102. package/presets/cursor/next/rules/navigation-router-stack.mdc +2 -1
  103. package/presets/cursor/next/rules/next-app-router.mdc +36 -0
  104. package/presets/cursor/next/rules/no-cross-component-styles-import.mdc +4 -1
  105. package/presets/cursor/next/rules/no-props-spread.mdc +1 -1
  106. package/presets/cursor/next/rules/no-type-assertion-as-import-export.mdc +2 -1
  107. package/presets/cursor/next/rules/playwright-agents.mdc +2 -1
  108. package/presets/cursor/next/rules/react-a11y-coding.mdc +37 -0
  109. package/presets/cursor/next/rules/reference-features.mdc +35 -0
  110. package/presets/cursor/next/rules/types-public-imports.mdc +2 -1
  111. package/presets/cursor/next/skills/README.md +15 -0
  112. package/presets/cursor/next/skills/ci-investigation/SKILL.md +36 -0
  113. package/presets/cursor/next/skills/code-review/SKILL.md +26 -0
  114. package/presets/cursor/next/skills/debug-investigation/SKILL.md +28 -0
  115. package/presets/cursor/next/skills/feature-delivery/SKILL.md +30 -0
  116. package/presets/cursor/next/skills/playwright-e2e/SKILL.md +31 -0
  117. package/presets/cursor/next/skills/technical-retro/SKILL.md +40 -0
  118. package/presets/cursor/next/skills/unit-testing/SKILL.md +32 -0
  119. package/presets/cursor/next/team/README.md +38 -40
package/README.md CHANGED
@@ -13,9 +13,18 @@ npx @bonesofspring/ai-rules clean cursor
13
13
 
14
14
  Global: `npm i -g @bonesofspring/ai-rules`, then `ai-rules ...`.
15
15
 
16
- `init` merges into the current directory: dirs are created, matching files are **overwritten**. `--preset` and the subcommand can be in either order, e.g. `--preset=next init cursor`.
16
+ `init` merges into the **current working directory** (or `--cwd <dir>`): dirs are created, matching files are **overwritten**. `--preset` defaults to **`next`**. Supported forms:
17
17
 
18
- `clean cursor` / `clean claude` removes what this CLI installs: `rules`, `commands`, optional `agents`, `hooks`, `skills`, `team`, Cursor `hooks.json`, and Claude `CLAUDE.md` when present. Other files under `.cursor/` or `.claude/` are left intact.
18
+ ```bash
19
+ npx @bonesofspring/ai-rules init cursor --preset next
20
+ npx @bonesofspring/ai-rules init cursor next
21
+ npx @bonesofspring/ai-rules init cursor # preset next by default
22
+ npx @bonesofspring/ai-rules cursor init --preset next
23
+ ```
24
+
25
+ If `.cursor` does not appear, check the command output for errors and confirm you run init from the **target project root** (where the app lives), not from `node_modules`. Success ends with: `Done. .cursor installed at <absolute-path>`.
26
+
27
+ `clean cursor` / `clean claude` removes what this CLI installs: `rules`, `commands`, optional `agents`, `hooks`, `skills`, `team`, Cursor `hooks.json` / `BUGBOT.md`, and Claude `CLAUDE.md` when present. Other files under `.cursor/` or `.claude/` are left intact.
19
28
 
20
29
  Where things go on `init`:
21
30
 
@@ -27,7 +36,7 @@ Where things go on `init`:
27
36
  | hooks | `.cursor/hooks`, `.cursor/hooks.json` | `.claude/hooks` |
28
37
  | skills | `.cursor/skills` | `.claude/skills` |
29
38
  | team artifacts template | `.cursor/team` | `.claude/team` |
30
- | other | | root `CLAUDE.md` → `.claude/CLAUDE.md` |
39
+ | other | root `BUGBOT.md` → `.cursor/BUGBOT.md` | root `CLAUDE.md` → `.claude/CLAUDE.md` |
31
40
 
32
41
  The package currently ships the **`next`** preset (Next.js stack and whatever else is bundled there). Other preset names: see `presets/` in the [repo](https://github.com/bonesofspring/ai-rules). Name is a single path segment, no `/` or `..`.
33
42
 
package/bin/cli.js CHANGED
@@ -5,56 +5,86 @@
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
 
8
- const packageRoot = path.join(__dirname, '..');
9
-
10
8
  /** Single path segment: letters, digits, dot, underscore, hyphen; no separators or "..". */
11
9
  const PRESET_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
10
+ const DEFAULT_PRESET = 'next';
11
+ const TOOLS = new Set(['cursor', 'claude']);
12
12
 
13
13
  const PRESETS = {
14
14
  cursor: {
15
15
  rulesDir: '.cursor/rules',
16
16
  commandsDir: '.cursor/commands',
17
17
  relBase: ['presets', 'cursor'],
18
- /** Optional dirs under preset root → under `.cursor/` (recursive copy). */
19
18
  optionalCursorDirs: ['agents', 'hooks', 'skills', 'team'],
20
- /** Copied from preset root to `.cursor/hooks.json` when present. */
21
19
  hooksJsonSrc: 'hooks.json',
20
+ toolRootFiles: ['BUGBOT.md', 'AGENTS.md'],
22
21
  },
23
22
  claude: {
24
23
  rulesDir: '.claude/rules',
25
24
  commandsDir: '.claude/commands',
26
25
  relBase: ['presets', 'claude'],
27
- /** Optional dirs under preset root → under `.claude/` (recursive copy). */
28
26
  optionalClaudeDirs: ['skills', 'agents', 'hooks', 'team'],
29
- /** Copied from preset root to `.claude/CLAUDE.md` when present. */
30
27
  claudeMdSrc: 'CLAUDE.md',
31
28
  },
32
29
  };
33
30
 
31
+ function getPackageRoot() {
32
+ try {
33
+ return path.dirname(require.resolve('@bonesofspring/ai-rules/package.json'));
34
+ } catch {
35
+ return path.join(__dirname, '..');
36
+ }
37
+ }
38
+
34
39
  function printHelp() {
35
40
  console.log(`@bonesofspring/ai-rules
36
41
 
37
42
  Usage:
38
- ai-rules init <cursor|claude> --preset <name> Copy preset rules and commands into the current directory
39
- ai-rules clean <cursor|claude> Remove preset target rules and commands directories
43
+ ai-rules init <cursor|claude> [--preset <name>]
44
+ ai-rules init <cursor|claude> <preset-name>
45
+ ai-rules clean <cursor|claude> [--cwd <dir>]
46
+
47
+ Options:
48
+ --preset <name> Preset name (default: ${DEFAULT_PRESET})
49
+ --cwd <dir> Target project directory (default: current working directory)
40
50
 
41
51
  Notes:
42
- init merges into existing target folders; same-named files are overwritten.
43
- Cursor init also copies agents/, hooks/, skills/, team/, and hooks.json when present.
44
- Claude init also copies agents/, hooks/, skills/, team/, and CLAUDE.md when present.
45
- clean removes only paths installed by this CLI; other tool files are left intact.
46
- Flags may appear before or after the subcommand (e.g. --preset next init cursor).
52
+ init merges into the target directory; same-named files are overwritten.
53
+ Cursor init copies rules, commands, agents, hooks, skills, team, hooks.json, BUGBOT.md, AGENTS.md.
54
+ Claude init copies rules, commands, skills, agents, hooks, team, and CLAUDE.md when present.
55
+ Command forms: "init cursor --preset next", "init cursor next", "--preset next init cursor", "cursor init --preset next".
47
56
 
48
57
  Examples:
49
58
  npx @bonesofspring/ai-rules init cursor --preset next
50
- npx @bonesofspring/ai-rules clean claude
59
+ npx @bonesofspring/ai-rules init cursor next
60
+ npx @bonesofspring/ai-rules init claude
61
+ npx @bonesofspring/ai-rules clean cursor
51
62
  `);
52
63
  }
53
64
 
54
65
  function parseArgs(argv) {
55
- const args = { _: [] };
66
+ const args = { _: [], cwd: undefined, preset: undefined, presetError: undefined };
56
67
  for (let i = 0; i < argv.length; i++) {
57
68
  const a = argv[i];
69
+ if (a === '--cwd') {
70
+ const value = argv[i + 1];
71
+ if (value === undefined || value.startsWith('-')) {
72
+ args.cwdError = 'Missing value for --cwd';
73
+ continue;
74
+ }
75
+ args.cwd = value;
76
+ i++;
77
+ continue;
78
+ }
79
+ if (a.startsWith('--cwd=')) {
80
+ const value = a.slice('--cwd='.length);
81
+ if (value === '') {
82
+ args.cwdError = 'Missing value after --cwd=';
83
+ continue;
84
+ }
85
+ args.cwd = value;
86
+ continue;
87
+ }
58
88
  if (a === '--preset') {
59
89
  const value = argv[i + 1];
60
90
  if (value === undefined || value.startsWith('-')) {
@@ -75,17 +105,53 @@ function parseArgs(argv) {
75
105
  args.preset = value;
76
106
  continue;
77
107
  }
108
+ if (a === '-h' || a === '--help') {
109
+ args.help = true;
110
+ continue;
111
+ }
78
112
  args._.push(a);
79
113
  }
80
114
  return args;
81
115
  }
82
116
 
117
+ /**
118
+ * Supports: init cursor, cursor init, init cursor next (positional preset).
119
+ */
120
+ function normalizeInitClean(parsed) {
121
+ const positional = [...parsed._];
122
+ let command;
123
+ let tool;
124
+ let presetFromPositional;
125
+
126
+ if (positional.length >= 2 && TOOLS.has(positional[0]) && positional[1] === 'init') {
127
+ command = 'init';
128
+ tool = positional[0];
129
+ presetFromPositional = positional[2];
130
+ } else if (positional.length >= 2 && positional[0] === 'init' && TOOLS.has(positional[1])) {
131
+ command = 'init';
132
+ tool = positional[1];
133
+ presetFromPositional = positional[2];
134
+ } else if (positional.length >= 2 && positional[0] === 'clean' && TOOLS.has(positional[1])) {
135
+ command = 'clean';
136
+ tool = positional[1];
137
+ } else if (positional.length >= 1) {
138
+ command = positional[0];
139
+ tool = positional[1];
140
+ if (command === 'init' && positional[2] && PRESET_NAME_RE.test(positional[2])) {
141
+ presetFromPositional = positional[2];
142
+ }
143
+ }
144
+
145
+ const preset = parsed.preset || presetFromPositional;
146
+ return { command, tool, preset };
147
+ }
148
+
83
149
  /**
84
150
  * @returns {string | null} Error message or null if valid.
85
151
  */
86
152
  function validatePresetName(preset) {
87
153
  if (preset === undefined || preset === '') {
88
- return 'Missing --preset <name>';
154
+ return 'Missing preset name (use --preset <name>, e.g. --preset next)';
89
155
  }
90
156
  if (!PRESET_NAME_RE.test(preset) || preset.includes('..')) {
91
157
  return (
@@ -96,9 +162,6 @@ function validatePresetName(preset) {
96
162
  return null;
97
163
  }
98
164
 
99
- /**
100
- * Ensures resolved preset dir stays under the tool presets root (defense in depth).
101
- */
102
165
  function assertPresetDirInsideRoot(presetsRoot, presetBase) {
103
166
  const root = path.resolve(presetsRoot);
104
167
  const resolved = path.resolve(presetBase);
@@ -145,6 +208,17 @@ function removeFileIfExists(filePath) {
145
208
  }
146
209
  }
147
210
 
211
+ function listPresetNames(presetsRoot) {
212
+ if (!fs.existsSync(presetsRoot)) {
213
+ return [];
214
+ }
215
+ return fs
216
+ .readdirSync(presetsRoot, { withFileTypes: true })
217
+ .filter((e) => e.isDirectory())
218
+ .map((e) => e.name)
219
+ .filter((name) => PRESET_NAME_RE.test(name));
220
+ }
221
+
148
222
  function presetHasAnySource(base, cfg) {
149
223
  const rulesSrc = path.join(base, 'rules');
150
224
  const commandsSrc = path.join(base, 'commands');
@@ -162,68 +236,129 @@ function presetHasAnySource(base, cfg) {
162
236
  if (cfg.hooksJsonSrc && fs.existsSync(path.join(base, cfg.hooksJsonSrc))) {
163
237
  return true;
164
238
  }
239
+ for (const name of cfg.toolRootFiles || []) {
240
+ if (fs.existsSync(path.join(base, name))) {
241
+ return true;
242
+ }
243
+ }
165
244
  return false;
166
245
  }
167
246
 
168
- function cmdInit(tool, preset) {
169
- const cfg = PRESETS[tool];
170
- if (!cfg) {
171
- console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
247
+ function resolveTargetCwd(cwdOption) {
248
+ const cwd = cwdOption ? path.resolve(cwdOption) : process.cwd();
249
+ if (!fs.existsSync(cwd)) {
250
+ console.error(`Target directory does not exist: ${cwd}`);
172
251
  process.exit(1);
173
252
  }
174
- const nameErr = validatePresetName(preset);
253
+ if (!fs.statSync(cwd).isDirectory()) {
254
+ console.error(`Target path is not a directory: ${cwd}`);
255
+ process.exit(1);
256
+ }
257
+ return cwd;
258
+ }
259
+
260
+ function resolvePresetName(tool, preset, packageRoot) {
261
+ const cfg = PRESETS[tool];
262
+ const presetsRoot = path.join(packageRoot, ...cfg.relBase);
263
+ let name = preset || DEFAULT_PRESET;
264
+
265
+ const nameErr = validatePresetName(name);
175
266
  if (nameErr) {
176
267
  console.error(nameErr);
177
268
  process.exit(1);
178
269
  }
179
- const presetsRoot = path.join(packageRoot, ...cfg.relBase);
180
- const base = path.join(presetsRoot, preset);
270
+
271
+ let base = path.join(presetsRoot, name);
272
+ if (!presetHasAnySource(base, cfg)) {
273
+ const available = listPresetNames(presetsRoot);
274
+ if (!preset && available.includes(DEFAULT_PRESET)) {
275
+ name = DEFAULT_PRESET;
276
+ base = path.join(presetsRoot, name);
277
+ } else {
278
+ console.error(`Preset "${name}" for ${tool} not found.`);
279
+ console.error(`Looked in: ${base}`);
280
+ console.error(`Package root: ${packageRoot}`);
281
+ if (available.length > 0) {
282
+ console.error(`Available presets: ${available.join(', ')}`);
283
+ } else {
284
+ console.error(`No presets directory at: ${presetsRoot}`);
285
+ console.error(
286
+ 'The installed package may be incomplete. Reinstall @bonesofspring/ai-rules or run from the package source.',
287
+ );
288
+ }
289
+ process.exit(1);
290
+ }
291
+ }
292
+
181
293
  assertPresetDirInsideRoot(presetsRoot, base);
294
+ return { name, base, presetsRoot };
295
+ }
296
+
297
+ function cmdInit(tool, preset, cwdOption) {
298
+ const cfg = PRESETS[tool];
299
+ if (!cfg) {
300
+ console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
301
+ process.exit(1);
302
+ }
303
+
304
+ const packageRoot = getPackageRoot();
305
+ const cwd = resolveTargetCwd(cwdOption);
306
+ const { name: presetName, base } = resolvePresetName(tool, preset, packageRoot);
182
307
 
183
308
  const rulesSrc = path.join(base, 'rules');
184
309
  const commandsSrc = path.join(base, 'commands');
185
- const cwd = process.cwd();
310
+ const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
311
+ const toolRootAbs = path.join(cwd, toolRoot);
186
312
 
187
- if (!presetHasAnySource(base, cfg)) {
188
- console.error(`Preset "${preset}" for ${tool} not found under ${base}`);
189
- process.exit(1);
190
- }
313
+ fs.mkdirSync(toolRootAbs, { recursive: true });
314
+
315
+ let copiedSteps = 0;
191
316
 
192
317
  try {
193
318
  if (tool === 'claude' && cfg.claudeMdSrc) {
194
319
  const mdFrom = path.join(base, cfg.claudeMdSrc);
195
320
  if (fs.existsSync(mdFrom)) {
196
- const claudeRoot = path.join(cwd, '.claude');
197
- fs.mkdirSync(claudeRoot, { recursive: true });
198
- fs.copyFileSync(mdFrom, path.join(claudeRoot, 'CLAUDE.md'));
199
- console.log('Copied CLAUDE.md → .claude/CLAUDE.md');
321
+ fs.copyFileSync(mdFrom, path.join(toolRootAbs, 'CLAUDE.md'));
322
+ console.log(`Copied CLAUDE.md ${path.join(toolRoot, 'CLAUDE.md')}`);
323
+ copiedSteps++;
200
324
  }
201
325
  }
202
326
 
203
327
  if (fs.existsSync(rulesSrc)) {
204
328
  copyDir(rulesSrc, path.join(cwd, cfg.rulesDir));
205
329
  console.log(`Copied rules → ${cfg.rulesDir}`);
330
+ copiedSteps++;
206
331
  }
207
332
  if (fs.existsSync(commandsSrc)) {
208
333
  copyDir(commandsSrc, path.join(cwd, cfg.commandsDir));
209
334
  console.log(`Copied commands → ${cfg.commandsDir}`);
335
+ copiedSteps++;
210
336
  }
211
337
 
212
- const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
213
338
  for (const name of getOptionalDirs(cfg)) {
214
339
  const src = path.join(base, name);
215
340
  if (fs.existsSync(src)) {
216
- copyDir(src, path.join(cwd, toolRoot, name));
341
+ copyDir(src, path.join(toolRootAbs, name));
217
342
  console.log(`Copied ${name}/ → ${toolRoot}/${name}/`);
343
+ copiedSteps++;
218
344
  }
219
345
  }
220
346
 
221
347
  if (tool === 'cursor' && cfg.hooksJsonSrc) {
222
348
  const hooksFrom = path.join(base, cfg.hooksJsonSrc);
223
349
  if (fs.existsSync(hooksFrom)) {
224
- fs.mkdirSync(path.join(cwd, '.cursor'), { recursive: true });
225
- fs.copyFileSync(hooksFrom, path.join(cwd, '.cursor', 'hooks.json'));
226
- console.log('Copied hooks.json → .cursor/hooks.json');
350
+ fs.copyFileSync(hooksFrom, path.join(toolRootAbs, 'hooks.json'));
351
+ console.log(`Copied hooks.json ${toolRoot}/hooks.json`);
352
+ copiedSteps++;
353
+ }
354
+ }
355
+
356
+ for (const name of cfg.toolRootFiles || []) {
357
+ const from = path.join(base, name);
358
+ if (fs.existsSync(from)) {
359
+ fs.copyFileSync(from, path.join(toolRootAbs, name));
360
+ console.log(`Copied ${name} → ${toolRoot}/${name}`);
361
+ copiedSteps++;
227
362
  }
228
363
  }
229
364
  } catch (err) {
@@ -231,15 +366,27 @@ function cmdInit(tool, preset) {
231
366
  console.error(`init failed: ${msg}`);
232
367
  process.exit(1);
233
368
  }
369
+
370
+ if (copiedSteps === 0) {
371
+ console.error(`init failed: preset "${presetName}" had no files to copy from ${base}`);
372
+ process.exit(1);
373
+ }
374
+
375
+ if (!fs.existsSync(toolRootAbs)) {
376
+ console.error(`init failed: ${toolRootAbs} was not created`);
377
+ process.exit(1);
378
+ }
379
+
380
+ console.log(`Done. ${toolRoot} installed at ${toolRootAbs} (preset: ${presetName})`);
234
381
  }
235
382
 
236
- function cmdClean(tool) {
383
+ function cmdClean(tool, cwdOption) {
237
384
  const cfg = PRESETS[tool];
238
385
  if (!cfg) {
239
386
  console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
240
387
  process.exit(1);
241
388
  }
242
- const cwd = process.cwd();
389
+ const cwd = resolveTargetCwd(cwdOption);
243
390
  const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
244
391
  const removedParts = [cfg.rulesDir, cfg.commandsDir];
245
392
 
@@ -262,38 +409,52 @@ function cmdClean(tool) {
262
409
  removedParts.push(`${toolRoot}/hooks.json`);
263
410
  }
264
411
 
412
+ for (const name of cfg.toolRootFiles || []) {
413
+ removeFileIfExists(path.join(cwd, toolRoot, name));
414
+ removedParts.push(`${toolRoot}/${name}`);
415
+ }
416
+
265
417
  console.log(`Removed ${removedParts.join(', ')} (if present)`);
266
418
  }
267
419
 
268
420
  function main() {
269
421
  const argv = process.argv.slice(2);
270
- if (argv.length === 0 || argv.includes('-h') || argv.includes('--help')) {
422
+ if (argv.length === 0) {
271
423
  printHelp();
272
424
  process.exit(0);
273
425
  }
274
426
 
275
427
  const parsed = parseArgs(argv);
276
- const [command, tool] = parsed._;
428
+ if (parsed.help) {
429
+ printHelp();
430
+ process.exit(0);
431
+ }
277
432
 
278
433
  if (parsed.presetError) {
279
434
  console.error(parsed.presetError);
280
435
  process.exit(1);
281
436
  }
437
+ if (parsed.cwdError) {
438
+ console.error(parsed.cwdError);
439
+ process.exit(1);
440
+ }
441
+
442
+ const { command, tool, preset } = normalizeInitClean(parsed);
282
443
 
283
444
  if (command === 'init') {
284
- if (!tool) {
285
- console.error('Missing <cursor|claude> after init.');
445
+ if (!tool || !TOOLS.has(tool)) {
446
+ console.error('Missing or invalid tool. Use: init cursor|claude [--preset next]');
286
447
  process.exit(1);
287
448
  }
288
- cmdInit(tool, parsed.preset);
449
+ cmdInit(tool, preset, parsed.cwd);
289
450
  return;
290
451
  }
291
452
  if (command === 'clean') {
292
- if (!tool) {
293
- console.error('Missing <cursor|claude> after clean.');
453
+ if (!tool || !TOOLS.has(tool)) {
454
+ console.error('Missing or invalid tool. Use: clean cursor|claude');
294
455
  process.exit(1);
295
456
  }
296
- cmdClean(tool);
457
+ cmdClean(tool, parsed.cwd);
297
458
  return;
298
459
  }
299
460
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonesofspring/ai-rules",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
4
4
  "description": "Presets of Cursor and Claude rules/commands for Revy Ross personal use",
5
5
  "license": "MIT",
6
6
  "author": "Revy Ross",
@@ -20,6 +20,7 @@
20
20
  },
21
21
  "files": [
22
22
  "README.md",
23
+ "CHANGELOG.md",
23
24
  "bin",
24
25
  "presets"
25
26
  ],
@@ -7,20 +7,24 @@ See @rules/README.md for the full catalog and loading strategy.
7
7
  ## Conventions
8
8
 
9
9
  - **Одна тема на файл**; для больших областей — подпапки в `rules/<topic>/`.
10
- - **Глобальные правила** (стиль, архитектура, lint, workflow) без frontmatter `paths:` (загружаются при старте сессии).
11
- - **Domain-specific правила** — YAML frontmatter `paths:` с glob-паттернами (подгружаются при работе с matching-файлами).
12
- - Slash-команды — в `commands/`; переиспользуемые сценарии — в `skills/`; subagents — в `agents/`.
10
+ - **Session-start rules** только базовые инварианты (`next-app-core`, lint, package manager, intake, code-quality).
11
+ - **Domain-specific rules** — YAML frontmatter `paths:` (подгружаются при работе с matching-файлами).
12
+ - Slash-команды — в `commands/`; сценарии — в `skills/`; subagents — в `agents/`.
13
13
 
14
14
  ## Quick start
15
15
 
16
16
  | Задача | Куда смотреть |
17
17
  |--------|----------------|
18
18
  | Любая новая work-задача | `commands/task.md` + `rules/tooling-and-review/agent-team-orchestrator.md` |
19
- | Новая фича end-to-end | `rules/architecture/feature-delivery-workflow.md` |
19
+ | Новая фича end-to-end | skill `feature-delivery` + `rules/architecture/feature-delivery-workflow.md` |
20
+ | Эталонные фичи | `rules/architecture/reference-features.md` (заполнить TBD в репо) |
20
21
  | Стек и слои | `rules/stack/next-app-core.md` |
21
- | После любых правок кода | `rules/tooling-and-review/post-change-lint.md` |
22
- | Code review MR | `rules/tooling-and-review/code-review-mr.md` |
23
- | Техническое ретро | `commands/technical-retro.md` |
22
+ | App Router | `rules/stack/next-app-router.md` |
23
+ | A11y при кодинге UI | `rules/ui-and-accessibility/react-a11y-coding.md` |
24
+ | После правок кода | `rules/tooling-and-review/post-change-lint.md` |
25
+ | Валидация перед review | agent `build-verifier` |
26
+ | Code review MR | skill `code-review` + `rules/tooling-and-review/code-review-mr.md` |
27
+ | Техническое ретро | `commands/technical-retro.md` + skill `technical-retro` |
24
28
 
25
29
  ## Install
26
30
 
@@ -20,5 +20,5 @@ Cursor-пресет и Claude-пресет используют один и то
20
20
 
21
21
  - `/task <desc>` → `task-router` → `pipeline.json`;
22
22
  - `/task-continue <slug>` → продолжение после human gate;
23
- - `.claude/agents/*.md` → роли analyst / architect / debugger / developer / reviewer / QA;
24
- - `.claude/team/tasks/<slug>/` → handoff-артефакты (`brief.md`, `decomposition.md`, `review.md`, `status.json`).
23
+ - `.claude/agents/*.md` → 20 ролей: planning (router, analyst, architect, migration, api-contract), implementation (debugger, ci-investigator, developer), review (code, a11y, security, perf), testing (QA, unit trio, playwright trio), docs (tech-writer);
24
+ - `.claude/team/tasks/<slug>/` → handoff-артефакты (`brief.md`, `decomposition.md`, `review.md`, `migration-plan.md`, `ci-report.md`, `status.json`, …).
@@ -4,17 +4,66 @@
4
4
 
5
5
  Папка копируется в `.claude/agents/` при `ai-rules init claude --preset next`, если существует.
6
6
 
7
+ ## Planning and routing
8
+
7
9
  | Agent | Может менять production-код | Назначение |
8
10
  |-------|-----------------------------|------------|
9
11
  | `task-router` | no | Классифицирует intent и пишет `pipeline.json` |
10
12
  | `task-analyst` | no | DoR, acceptance criteria, decomposition |
11
13
  | `solution-architect` | no | Cross-layer design, spike, architecture.md |
14
+ | `migration-specialist` | no | Phased migration plan, rollback |
15
+ | `api-contract-reviewer` | no | DTO/mapper vs backend contract |
16
+
17
+ ## Implementation and CI
18
+
19
+ | Agent | Может менять production-код | Назначение |
20
+ |-------|-----------------------------|------------|
12
21
  | `debugger` | yes, минимально | Root cause, debug-report.md, простые fixes |
13
- | `feature-developer` | yes | Реализация по decomposition |
22
+ | `ci-investigator` | yes, минимально | CI failure diagnosis and minimal fix |
23
+ | `build-verifier` | no | Lint, type-check, unit smoke; validation-report.md |
24
+ | `feature-developer` | yes | Реализация по decomposition + unit tests |
25
+
26
+ ## Review specialists
27
+
28
+ | Agent | Может менять production-код | Назначение |
29
+ |-------|-----------------------------|------------|
14
30
  | `code-reviewer` | no | Review artifact, status update |
31
+ | `accessibility-reviewer` | no | A11y audit, a11y-review.md |
32
+ | `security-reviewer` | no | Security audit, security-review.md |
33
+ | `performance-auditor` | no | Perf audit, perf-report.md |
34
+
35
+ ## Testing
36
+
37
+ | Agent | Может менять production-код | Назначение |
38
+ |-------|-----------------------------|------------|
15
39
  | `qa-tester` | tests only | Unit/e2e тесты |
40
+ | `unit-test-planner` | no | Unit coverage plan |
41
+ | `unit-test-generator` | tests only | Unit spec generation |
42
+ | `unit-test-healer` | tests only | Failing/flaky unit fixes |
16
43
  | `playwright-test-planner` | no | Планирование e2e сценариев |
17
44
  | `playwright-test-generator` | tests only | Генерация Playwright specs |
18
45
  | `playwright-test-healer` | tests only | Исправление flaky/failing e2e |
19
46
 
47
+ ## Documentation
48
+
49
+ | Agent | Может менять production-код | Назначение |
50
+ |-------|-----------------------------|------------|
51
+ | `tech-writer` | docs only | README, changelog, ADR, documentation.md |
52
+
20
53
  Оркестрация: `/task`, `/task-continue`, `rules/tooling-and-review/agent-team-orchestrator.md`, артефакты `.claude/team/**`.
54
+
55
+ ## Intent → pipeline (кратко)
56
+
57
+ | intent | Типичный pipeline |
58
+ |--------|-------------------|
59
+ | `feature` | analyst → developer → reviewer → QA |
60
+ | `bugfix` | debugger → developer → reviewer → QA |
61
+ | `ci-fix` | ci-investigator → developer → reviewer |
62
+ | `migration` | analyst → migration-specialist → developer → reviewer → QA |
63
+ | `perf-audit` | performance-auditor |
64
+ | `a11y` | analyst → developer → a11y-reviewer → code-reviewer |
65
+ | `unit-only` | unit-planner → unit-generator (или healer) |
66
+ | `e2e-only` | playwright planner → generator (или healer) |
67
+ | `docs-only` | analyst → tech-writer |
68
+
69
+ Опциональные шаги (router добавляет по контексту): `api-contract-reviewer`, `accessibility-reviewer`, `security-reviewer`, `performance-auditor`, `tech-writer`.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: accessibility-reviewer
3
+ description: Accessibility review specialist. Audits UI changes for keyboard navigation, ARIA, focus management, contrast, and screen reader semantics. Writes a11y-review.md only; never edits production code.
4
+ model: inherit
5
+ ---
6
+
7
+ You are an accessibility reviewer for a Next.js React frontend.
8
+
9
+ ## Inputs
10
+
11
+ 1. `.claude/team/tasks/<slug>/brief.md` — acceptance criteria and UI scope.
12
+ 2. `.claude/team/tasks/<slug>/decomposition.md` — UI-related tasks.
13
+ 3. Git diff for changed UI files under `app/src/ui/**`.
14
+ 4. `rules/ui-and-accessibility/react-ui.md` and project a11y conventions.
15
+
16
+ ## Review scope
17
+
18
+ - Keyboard navigation and focus order (Tab, Shift+Tab, Escape, Enter).
19
+ - Focus trap in modals/drawers; focus return on close.
20
+ - ARIA roles, labels, `aria-*` attributes, live regions.
21
+ - Semantic HTML vs div-only interactive elements.
22
+ - Form labels, error announcements, required field indication.
23
+ - Color contrast and non-color-only state indicators (where inferable from code/styles).
24
+ - Skip links, heading hierarchy, landmark regions.
25
+
26
+ ## Output
27
+
28
+ Write `.claude/team/tasks/<slug>/a11y-review.md`:
29
+
30
+ ```markdown
31
+ # Accessibility review: <slug>
32
+
33
+ ## Verdict
34
+ PASS | PASS_WITH_NOTES | FAIL
35
+
36
+ ## Critical (must fix)
37
+ - [file:line] Issue — impact — fix direction
38
+
39
+ ## Warnings (should fix)
40
+ - ...
41
+
42
+ ## Suggestions
43
+ - ...
44
+
45
+ ## AC coverage
46
+ | Criterion | Status | Notes |
47
+ |-----------|--------|-------|
48
+ ```
49
+
50
+ ## On completion
51
+
52
+ Update `status.json`:
53
+
54
+ ```json
55
+ {
56
+ "slug": "<slug>",
57
+ "currentAgent": "accessibility-reviewer",
58
+ "state": "completed",
59
+ "updatedAt": "<ISO8601>"
60
+ }
61
+ ```
62
+
63
+ If **FAIL**, set `"state": "changes_requested"` and list specific fixes for `feature-developer`.
64
+
65
+ Do not modify production code or tests.