@bonesofspring/ai-rules 0.1.41 → 0.2.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.
Files changed (67) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +10 -1
  3. package/bin/cli.js +193 -53
  4. package/package.json +1 -1
  5. package/presets/claude/next/CLAUDE.md +1 -1
  6. package/presets/claude/next/agents/README.md +61 -0
  7. package/presets/claude/next/agents/api-contract-reviewer.md +1 -1
  8. package/presets/claude/next/agents/build-verifier.md +2 -0
  9. package/presets/claude/next/agents/feature-developer.md +8 -21
  10. package/presets/claude/next/agents/task-router.md +24 -126
  11. package/presets/claude/next/commands/task.md +1 -1
  12. package/presets/claude/next/rules/README.md +3 -3
  13. package/presets/claude/next/rules/api-and-data/api-services.md +3 -3
  14. package/presets/claude/next/rules/api-and-data/http-client.md +2 -2
  15. package/presets/claude/next/rules/api-and-data/store-rtk.md +2 -2
  16. package/presets/claude/next/rules/architecture/README.md +8 -11
  17. package/presets/claude/next/rules/architecture/api-public-imports.md +3 -26
  18. package/presets/claude/next/rules/architecture/architecture-boundaries.md +6 -6
  19. package/presets/claude/next/rules/architecture/feature-delivery-workflow.md +44 -69
  20. package/presets/claude/next/rules/architecture/layer-barrel-exports.md +5 -5
  21. package/presets/claude/next/rules/architecture/public-imports.md +46 -0
  22. package/presets/claude/next/rules/architecture/reference-features.md +4 -1
  23. package/presets/claude/next/rules/architecture/types-public-imports.md +3 -29
  24. package/presets/claude/next/rules/stack/next-app-core.md +20 -70
  25. package/presets/claude/next/rules/stack/no-type-assertion.md +3 -2
  26. package/presets/claude/next/rules/stack/types-jsdoc.md +1 -1
  27. package/presets/claude/next/rules/tooling-and-review/agent-team-intake.md +6 -0
  28. package/presets/claude/next/rules/tooling-and-review/agent-team-orchestrator.md +14 -1
  29. package/presets/claude/next/rules/tooling-and-review/code-quality.md +3 -11
  30. package/presets/claude/next/rules/tooling-and-review/code-review-mr.md +2 -2
  31. package/presets/claude/next/rules/tooling-and-review/package-manager.md +6 -15
  32. package/presets/claude/next/rules/tooling-and-review/post-change-lint.md +14 -24
  33. package/presets/claude/next/rules/ui-and-accessibility/css-property-order.md +6 -18
  34. package/presets/claude/next/rules/ui-and-accessibility/react-ui.md +1 -1
  35. package/presets/claude/next/skills/feature-delivery/SKILL.md +5 -20
  36. package/presets/cursor/next/AGENTS.md +1 -1
  37. package/presets/cursor/next/agents/README.md +81 -0
  38. package/presets/cursor/next/agents/api-contract-reviewer.md +1 -1
  39. package/presets/cursor/next/agents/build-verifier.md +2 -0
  40. package/presets/cursor/next/agents/code-reviewer.md +1 -1
  41. package/presets/cursor/next/agents/feature-developer.md +9 -30
  42. package/presets/cursor/next/agents/task-router.md +23 -125
  43. package/presets/cursor/next/commands/task.md +1 -1
  44. package/presets/cursor/next/rules/README.md +6 -7
  45. package/presets/cursor/next/rules/agent-team-intake.mdc +2 -2
  46. package/presets/cursor/next/rules/agent-team-orchestrator.mdc +14 -1
  47. package/presets/cursor/next/rules/api-public-imports.mdc +3 -24
  48. package/presets/cursor/next/rules/api-services.mdc +3 -3
  49. package/presets/cursor/next/rules/architecture-boundaries.mdc +6 -6
  50. package/presets/cursor/next/rules/code-quality-and-refactoring.mdc +3 -11
  51. package/presets/cursor/next/rules/code-review-mr.mdc +2 -2
  52. package/presets/cursor/next/rules/css-property-order-stylelint.mdc +6 -18
  53. package/presets/cursor/next/rules/feature-delivery-workflow.mdc +45 -22
  54. package/presets/cursor/next/rules/http-client.mdc +2 -2
  55. package/presets/cursor/next/rules/layer-barrel-exports.mdc +5 -5
  56. package/presets/cursor/next/rules/next-app-core.mdc +18 -71
  57. package/presets/cursor/next/rules/no-type-assertion-as-import-export.mdc +4 -1
  58. package/presets/cursor/next/rules/package-manager.mdc +6 -15
  59. package/presets/cursor/next/rules/post-change-lint.mdc +14 -24
  60. package/presets/cursor/next/rules/public-imports.mdc +48 -0
  61. package/presets/cursor/next/rules/react-ui.mdc +1 -1
  62. package/presets/cursor/next/rules/reference-features.mdc +5 -1
  63. package/presets/cursor/next/rules/store-rtk.mdc +2 -2
  64. package/presets/cursor/next/rules/types-jsdoc.mdc +1 -1
  65. package/presets/cursor/next/rules/types-public-imports.mdc +3 -26
  66. package/presets/cursor/next/skills/feature-delivery/SKILL.md +5 -20
  67. package/presets/cursor/next/rules/feature-delivery-flow.mdc +0 -74
package/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ ### Breaking
6
+
7
+ - **Import rules merged:** `types-public-imports.mdc` and `api-public-imports.mdc` → **`public-imports.mdc`** (Claude: `architecture/public-imports.md`). Old filenames remain as deprecated stubs for one release cycle.
8
+ - **`feature-delivery-flow.mdc` removed** — content merged into `feature-delivery-workflow.mdc`.
9
+ - **`agent-team-intake`** no longer always-on (Cursor: `alwaysApply: false`; Claude: `paths:` on commands/team). Use orchestrator or `/task`.
10
+
11
+ ### Changed
12
+
13
+ - Slimmed always-on rules: `next-app-core`, `code-quality-and-refactoring`, `post-change-lint`, `package-manager` (~9 KB baseline vs ~21 KB).
14
+ - Narrowed globs: `css-property-order-stylelint`, `no-type-assertion-as-import-export`, `reference-features`, `public-imports`.
15
+ - `css-property-order` stub — property list delegated to Stylelint `--fix`.
16
+ - Slimmed `task-router` agent; pipeline JSON examples moved to `agents/README.md`.
17
+ - Agents reference skills as SSOT; scoped lint for `feature-developer` when next step is `build-verifier`.
18
+ - Orchestrator: explicit fast-path when to skip pipeline.
19
+
20
+ ## 0.1.42
21
+
22
+ Prior releases — see git history.
package/README.md CHANGED
@@ -13,7 +13,16 @@ 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
+
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>`.
17
26
 
18
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
 
package/bin/cli.js CHANGED
@@ -5,58 +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',
22
- /** Optional files under preset root → under `.cursor/`. */
23
20
  toolRootFiles: ['BUGBOT.md', 'AGENTS.md'],
24
21
  },
25
22
  claude: {
26
23
  rulesDir: '.claude/rules',
27
24
  commandsDir: '.claude/commands',
28
25
  relBase: ['presets', 'claude'],
29
- /** Optional dirs under preset root → under `.claude/` (recursive copy). */
30
26
  optionalClaudeDirs: ['skills', 'agents', 'hooks', 'team'],
31
- /** Copied from preset root to `.claude/CLAUDE.md` when present. */
32
27
  claudeMdSrc: 'CLAUDE.md',
33
28
  },
34
29
  };
35
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
+
36
39
  function printHelp() {
37
40
  console.log(`@bonesofspring/ai-rules
38
41
 
39
42
  Usage:
40
- ai-rules init <cursor|claude> --preset <name> Copy preset rules and commands into the current directory
41
- 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)
42
50
 
43
51
  Notes:
44
- init merges into existing target folders; same-named files are overwritten.
45
- Cursor init also copies agents/, hooks/, skills/, team/, hooks.json, BUGBOT.md, and AGENTS.md when present.
46
- Claude init also copies agents/, hooks/, skills/, team/, and CLAUDE.md when present.
47
- clean removes only paths installed by this CLI; other tool files are left intact.
48
- 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".
49
56
 
50
57
  Examples:
51
58
  npx @bonesofspring/ai-rules init cursor --preset next
52
- 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
53
62
  `);
54
63
  }
55
64
 
56
65
  function parseArgs(argv) {
57
- const args = { _: [] };
66
+ const args = { _: [], cwd: undefined, preset: undefined, presetError: undefined };
58
67
  for (let i = 0; i < argv.length; i++) {
59
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
+ }
60
88
  if (a === '--preset') {
61
89
  const value = argv[i + 1];
62
90
  if (value === undefined || value.startsWith('-')) {
@@ -77,17 +105,53 @@ function parseArgs(argv) {
77
105
  args.preset = value;
78
106
  continue;
79
107
  }
108
+ if (a === '-h' || a === '--help') {
109
+ args.help = true;
110
+ continue;
111
+ }
80
112
  args._.push(a);
81
113
  }
82
114
  return args;
83
115
  }
84
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
+
85
149
  /**
86
150
  * @returns {string | null} Error message or null if valid.
87
151
  */
88
152
  function validatePresetName(preset) {
89
153
  if (preset === undefined || preset === '') {
90
- return 'Missing --preset <name>';
154
+ return 'Missing preset name (use --preset <name>, e.g. --preset next)';
91
155
  }
92
156
  if (!PRESET_NAME_RE.test(preset) || preset.includes('..')) {
93
157
  return (
@@ -98,9 +162,6 @@ function validatePresetName(preset) {
98
162
  return null;
99
163
  }
100
164
 
101
- /**
102
- * Ensures resolved preset dir stays under the tool presets root (defense in depth).
103
- */
104
165
  function assertPresetDirInsideRoot(presetsRoot, presetBase) {
105
166
  const root = path.resolve(presetsRoot);
106
167
  const resolved = path.resolve(presetBase);
@@ -147,6 +208,17 @@ function removeFileIfExists(filePath) {
147
208
  }
148
209
  }
149
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
+
150
222
  function presetHasAnySource(base, cfg) {
151
223
  const rulesSrc = path.join(base, 'rules');
152
224
  const commandsSrc = path.join(base, 'commands');
@@ -172,74 +244,121 @@ function presetHasAnySource(base, cfg) {
172
244
  return false;
173
245
  }
174
246
 
175
- function cmdInit(tool, preset) {
176
- const cfg = PRESETS[tool];
177
- if (!cfg) {
178
- 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}`);
179
251
  process.exit(1);
180
252
  }
181
- 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);
182
266
  if (nameErr) {
183
267
  console.error(nameErr);
184
268
  process.exit(1);
185
269
  }
186
- const presetsRoot = path.join(packageRoot, ...cfg.relBase);
187
- 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
+
188
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);
189
307
 
190
308
  const rulesSrc = path.join(base, 'rules');
191
309
  const commandsSrc = path.join(base, 'commands');
192
- const cwd = process.cwd();
310
+ const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
311
+ const toolRootAbs = path.join(cwd, toolRoot);
193
312
 
194
- if (!presetHasAnySource(base, cfg)) {
195
- console.error(`Preset "${preset}" for ${tool} not found under ${base}`);
196
- process.exit(1);
197
- }
313
+ fs.mkdirSync(toolRootAbs, { recursive: true });
314
+
315
+ let copiedSteps = 0;
198
316
 
199
317
  try {
200
318
  if (tool === 'claude' && cfg.claudeMdSrc) {
201
319
  const mdFrom = path.join(base, cfg.claudeMdSrc);
202
320
  if (fs.existsSync(mdFrom)) {
203
- const claudeRoot = path.join(cwd, '.claude');
204
- fs.mkdirSync(claudeRoot, { recursive: true });
205
- fs.copyFileSync(mdFrom, path.join(claudeRoot, 'CLAUDE.md'));
206
- 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++;
207
324
  }
208
325
  }
209
326
 
210
327
  if (fs.existsSync(rulesSrc)) {
211
328
  copyDir(rulesSrc, path.join(cwd, cfg.rulesDir));
212
329
  console.log(`Copied rules → ${cfg.rulesDir}`);
330
+ copiedSteps++;
213
331
  }
214
332
  if (fs.existsSync(commandsSrc)) {
215
333
  copyDir(commandsSrc, path.join(cwd, cfg.commandsDir));
216
334
  console.log(`Copied commands → ${cfg.commandsDir}`);
335
+ copiedSteps++;
217
336
  }
218
337
 
219
- const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
220
338
  for (const name of getOptionalDirs(cfg)) {
221
339
  const src = path.join(base, name);
222
340
  if (fs.existsSync(src)) {
223
- copyDir(src, path.join(cwd, toolRoot, name));
341
+ copyDir(src, path.join(toolRootAbs, name));
224
342
  console.log(`Copied ${name}/ → ${toolRoot}/${name}/`);
343
+ copiedSteps++;
225
344
  }
226
345
  }
227
346
 
228
347
  if (tool === 'cursor' && cfg.hooksJsonSrc) {
229
348
  const hooksFrom = path.join(base, cfg.hooksJsonSrc);
230
349
  if (fs.existsSync(hooksFrom)) {
231
- fs.mkdirSync(path.join(cwd, '.cursor'), { recursive: true });
232
- fs.copyFileSync(hooksFrom, path.join(cwd, '.cursor', 'hooks.json'));
233
- 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++;
234
353
  }
235
354
  }
236
355
 
237
356
  for (const name of cfg.toolRootFiles || []) {
238
357
  const from = path.join(base, name);
239
358
  if (fs.existsSync(from)) {
240
- fs.mkdirSync(path.join(cwd, toolRoot), { recursive: true });
241
- fs.copyFileSync(from, path.join(cwd, toolRoot, name));
359
+ fs.copyFileSync(from, path.join(toolRootAbs, name));
242
360
  console.log(`Copied ${name} → ${toolRoot}/${name}`);
361
+ copiedSteps++;
243
362
  }
244
363
  }
245
364
  } catch (err) {
@@ -247,15 +366,27 @@ function cmdInit(tool, preset) {
247
366
  console.error(`init failed: ${msg}`);
248
367
  process.exit(1);
249
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})`);
250
381
  }
251
382
 
252
- function cmdClean(tool) {
383
+ function cmdClean(tool, cwdOption) {
253
384
  const cfg = PRESETS[tool];
254
385
  if (!cfg) {
255
386
  console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
256
387
  process.exit(1);
257
388
  }
258
- const cwd = process.cwd();
389
+ const cwd = resolveTargetCwd(cwdOption);
259
390
  const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
260
391
  const removedParts = [cfg.rulesDir, cfg.commandsDir];
261
392
 
@@ -288,33 +419,42 @@ function cmdClean(tool) {
288
419
 
289
420
  function main() {
290
421
  const argv = process.argv.slice(2);
291
- if (argv.length === 0 || argv.includes('-h') || argv.includes('--help')) {
422
+ if (argv.length === 0) {
292
423
  printHelp();
293
424
  process.exit(0);
294
425
  }
295
426
 
296
427
  const parsed = parseArgs(argv);
297
- const [command, tool] = parsed._;
428
+ if (parsed.help) {
429
+ printHelp();
430
+ process.exit(0);
431
+ }
298
432
 
299
433
  if (parsed.presetError) {
300
434
  console.error(parsed.presetError);
301
435
  process.exit(1);
302
436
  }
437
+ if (parsed.cwdError) {
438
+ console.error(parsed.cwdError);
439
+ process.exit(1);
440
+ }
441
+
442
+ const { command, tool, preset } = normalizeInitClean(parsed);
303
443
 
304
444
  if (command === 'init') {
305
- if (!tool) {
306
- 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]');
307
447
  process.exit(1);
308
448
  }
309
- cmdInit(tool, parsed.preset);
449
+ cmdInit(tool, preset, parsed.cwd);
310
450
  return;
311
451
  }
312
452
  if (command === 'clean') {
313
- if (!tool) {
314
- console.error('Missing <cursor|claude> after clean.');
453
+ if (!tool || !TOOLS.has(tool)) {
454
+ console.error('Missing or invalid tool. Use: clean cursor|claude');
315
455
  process.exit(1);
316
456
  }
317
- cmdClean(tool);
457
+ cmdClean(tool, parsed.cwd);
318
458
  return;
319
459
  }
320
460
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bonesofspring/ai-rules",
3
- "version": "0.1.41",
3
+ "version": "0.2.0",
4
4
  "description": "Presets of Cursor and Claude rules/commands for Revy Ross personal use",
5
5
  "license": "MIT",
6
6
  "author": "Revy Ross",
@@ -7,7 +7,7 @@ See @rules/README.md for the full catalog and loading strategy.
7
7
  ## Conventions
8
8
 
9
9
  - **Одна тема на файл**; для больших областей — подпапки в `rules/<topic>/`.
10
- - **Session-start rules** — только базовые инварианты (`next-app-core`, lint, package manager, intake, code-quality).
10
+ - **Session-start rules** — только базовые инварианты (`next-app-core`, lint, package manager, code-quality).
11
11
  - **Domain-specific rules** — YAML frontmatter `paths:` (подгружаются при работе с matching-файлами).
12
12
  - Slash-команды — в `commands/`; сценарии — в `skills/`; subagents — в `agents/`.
13
13
 
@@ -67,3 +67,64 @@
67
67
  | `docs-only` | analyst → tech-writer |
68
68
 
69
69
  Опциональные шаги (router добавляет по контексту): `api-contract-reviewer`, `accessibility-reviewer`, `security-reviewer`, `performance-auditor`, `tech-writer`.
70
+
71
+ ## pipeline.json reference
72
+
73
+ ### Step fields
74
+
75
+ | Field | Required | Description |
76
+ |-------|----------|-------------|
77
+ | `agent` | yes | Subagent name (kebab-case) or array for `parallel: true` |
78
+ | `label` | yes | Short human-readable step name |
79
+ | `scope` | no | e.g. `unit-in-dev`, `e2e-only`, `regression`, `full` |
80
+ | `skipIf` | no | `debugger.fixed` \| `ci-investigator.resolved` |
81
+ | `parallel` | no | When `true` and `agent` is array — invoke all in one turn |
82
+
83
+ ### Minimal template
84
+
85
+ ```json
86
+ {
87
+ "slug": "<slug>",
88
+ "intent": "feature",
89
+ "summary": "One-line task summary",
90
+ "steps": [
91
+ { "agent": "task-analyst", "label": "Clarify and decompose" },
92
+ { "agent": "feature-developer", "label": "Implement + unit tests", "scope": "unit-in-dev" },
93
+ { "agent": "build-verifier", "label": "Lint, type-check, unit smoke" },
94
+ { "agent": "code-reviewer", "label": "Code review" }
95
+ ],
96
+ "humanGates": ["after:task-analyst"],
97
+ "autoChain": true,
98
+ "skipped": []
99
+ }
100
+ ```
101
+
102
+ ### Example: bugfix skipIf
103
+
104
+ ```json
105
+ {
106
+ "intent": "bugfix",
107
+ "steps": [
108
+ { "agent": "debugger", "label": "Root cause" },
109
+ { "agent": "feature-developer", "label": "Fix if needed", "skipIf": "debugger.fixed" },
110
+ { "agent": "build-verifier", "label": "Validation" },
111
+ { "agent": "code-reviewer", "label": "Review" }
112
+ ],
113
+ "humanGates": []
114
+ }
115
+ ```
116
+
117
+ ### Initial status.json
118
+
119
+ ```json
120
+ {
121
+ "slug": "<slug>",
122
+ "intent": "<intent>",
123
+ "pipelineIndex": 0,
124
+ "currentAgent": "<steps[0].agent>",
125
+ "phase": "executing",
126
+ "state": "in_progress",
127
+ "awaitingHumanGate": false,
128
+ "updatedAt": "<ISO8601>"
129
+ }
130
+ ```
@@ -12,7 +12,7 @@ You are an API contract reviewer for a layered Next.js frontend (`@/types`, `@/a
12
12
  2. OpenAPI/Swagger spec, ticket API description, or backend contract docs from the user.
13
13
  3. Changed files in `app/src/types/**`, `app/src/api/**`, mocks, store thunks.
14
14
 
15
- Apply **`rules/architecture/types-public-imports.md`**, **`rules/architecture/api-public-imports.md`**, **`rules/api-and-data/api-services.md`**, **`rules/api-and-data/http-client.md`**.
15
+ Apply **`rules/architecture/public-imports.md`**, **`rules/api-and-data/api-services.md`**, **`rules/api-and-data/http-client.md`**.
16
16
 
17
17
  ## Review scope
18
18
 
@@ -6,6 +6,8 @@ model: fast
6
6
 
7
7
  You are a build verification specialist. You **validate** implementation quality before code review — you do not edit production code or tests.
8
8
 
9
+ **SSOT for full validation in pipeline:** run complete **`lint:js`**, **`lint:css`**, and **`type-check`** from `app/` even if feature-developer ran scoped checks.
10
+
9
11
  ## Inputs
10
12
 
11
13
  1. `.claude/team/tasks/<slug>/brief.md` and `decomposition.md`.
@@ -8,33 +8,20 @@ You are a senior frontend developer working in a Next.js monorepo with strict la
8
8
 
9
9
  ## Before coding
10
10
 
11
- 1. Read `.claude/team/tasks/<slug>/brief.md` and `decomposition.md`.
12
- 2. Read `architecture.md`, `migration-plan.md`, `api-contract-review.md`, `debug-report.md`, `validation-report.md`, and `review.md` if present.
13
- 3. Read `status.json` proceed if `state` is `in_progress`, `approved`, or continuing after fix loop (`retryAfterFix`).
14
- 4. Read **`architecture/reference-features.md`** and mirror the closest reference feature structure.
15
- 5. Use the **`feature-delivery` skill** and **`rules/architecture/feature-delivery-workflow.md`**.
11
+ 1. Read `.claude/team/tasks/<slug>/brief.md`, `decomposition.md`, and prior artifacts if present.
12
+ 2. Read `status.json` proceed if `in_progress`, `approved`, or `retryAfterFix`.
13
+ 3. Read `pipeline.json` for step context and scope.
14
+ 4. Follow the **`feature-delivery` skill** (layer order, reference features, validation handoff).
16
15
 
17
16
  ## Task execution rules
18
17
 
19
18
  - Work tasks in dependency order (types → api → mocks → store → ui → **unit** → e2e).
20
- - **Unit tests are mandatory before handoff** for mappers, store logic, and non-trivial pure functions (`rules/testing/tests-unit.md`).
21
- - Mark completed tasks in `decomposition.md` with `[x]` or update **Done** column.
22
- - After code changes run **`rules/tooling-and-review/post-change-lint.md`**: `lint:js`, `lint:css`, `type-check` from `app/`.
19
+ - **Unit tests mandatory before handoff** for mappers, store logic, and non-trivial pure functions.
20
+ - Mark completed tasks in `decomposition.md`.
21
+ - Validation: **`rules/tooling-and-review/post-change-lint.md`**. If pipeline next step is **`build-verifier`**, scoped lint/type-check on **changed files** is enough; full project lint is build-verifier's job.
23
22
 
24
23
  ## On completion
25
24
 
26
- Update `status.json`:
27
-
28
- ```json
29
- {
30
- "slug": "<slug>",
31
- "currentAgent": "feature-developer",
32
- "phase": "executing",
33
- "state": "completed",
34
- "updatedAt": "<ISO8601>"
35
- }
36
- ```
37
-
38
- Handoff summary: tasks done, files by layer, unit specs added, lint/type-check result, known gaps.
25
+ Update `status.json` (`currentAgent`: `feature-developer`, `state`: `completed`). Handoff: tasks done, files by layer, unit specs, validation result, gaps for build-verifier/reviewer.
39
26
 
40
27
  Do not perform formal code review — that is the code-reviewer subagent's job.