@bonesofspring/ai-rules 0.2.0 → 0.2.2

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 (193) hide show
  1. package/README.md +10 -18
  2. package/bin/cli.js +73 -258
  3. package/package.json +3 -4
  4. package/presets/claude/ios-swift/CLAUDE.md +27 -0
  5. package/presets/claude/ios-swift/README.md +29 -0
  6. package/presets/claude/ios-swift/commands/README.md +3 -0
  7. package/presets/claude/ios-swift/rules/README.md +44 -0
  8. package/presets/claude/ios-swift/rules/api-and-data/README.md +9 -0
  9. package/presets/claude/ios-swift/rules/api-and-data/networking.md +49 -0
  10. package/presets/claude/ios-swift/rules/architecture/README.md +10 -0
  11. package/presets/claude/ios-swift/rules/architecture/boundaries.md +33 -0
  12. package/presets/claude/ios-swift/rules/architecture/feature-delivery.md +60 -0
  13. package/presets/claude/ios-swift/rules/stack/README.md +10 -0
  14. package/presets/claude/ios-swift/rules/stack/ios-app-core.md +42 -0
  15. package/presets/claude/ios-swift/rules/stack/swift-conventions.md +51 -0
  16. package/presets/claude/ios-swift/rules/testing/README.md +10 -0
  17. package/presets/claude/ios-swift/rules/testing/ui.md +41 -0
  18. package/presets/claude/ios-swift/rules/testing/unit.md +41 -0
  19. package/presets/claude/ios-swift/rules/tooling-and-review/README.md +11 -0
  20. package/presets/claude/ios-swift/rules/tooling-and-review/code-quality.md +38 -0
  21. package/presets/claude/ios-swift/rules/tooling-and-review/code-review.md +41 -0
  22. package/presets/claude/ios-swift/rules/tooling-and-review/post-change-build.md +31 -0
  23. package/presets/claude/ios-swift/rules/ui-and-accessibility/README.md +10 -0
  24. package/presets/claude/ios-swift/rules/ui-and-accessibility/swiftui.md +61 -0
  25. package/presets/claude/ios-swift/rules/ui-and-accessibility/viewmodels.md +43 -0
  26. package/presets/claude/next/CLAUDE.md +5 -29
  27. package/presets/claude/next/README.md +0 -10
  28. package/presets/claude/next/agents/README.md +0 -125
  29. package/presets/claude/next/commands/README.md +2 -10
  30. package/presets/claude/next/hooks/README.md +2 -5
  31. package/presets/claude/next/rules/README.md +11 -25
  32. package/presets/claude/next/rules/api-and-data/README.md +1 -7
  33. package/presets/claude/next/rules/architecture/README.md +2 -9
  34. package/presets/claude/next/rules/stack/README.md +1 -9
  35. package/presets/claude/next/rules/testing/README.md +1 -9
  36. package/presets/claude/next/rules/tooling-and-review/README.md +1 -14
  37. package/presets/claude/next/rules/ui-and-accessibility/README.md +1 -8
  38. package/presets/claude/next/skills/README.md +1 -11
  39. package/presets/cursor/ios-swift/README.md +17 -0
  40. package/presets/cursor/ios-swift/commands/README.md +3 -0
  41. package/presets/cursor/ios-swift/rules/README.md +33 -0
  42. package/presets/cursor/ios-swift/rules/architecture-boundaries.mdc +34 -0
  43. package/presets/cursor/ios-swift/rules/code-quality-and-refactoring.mdc +39 -0
  44. package/presets/cursor/ios-swift/rules/code-review-mr.mdc +42 -0
  45. package/presets/cursor/ios-swift/rules/feature-delivery-workflow.mdc +61 -0
  46. package/presets/cursor/ios-swift/rules/ios-app-core.mdc +43 -0
  47. package/presets/cursor/ios-swift/rules/networking-services.mdc +49 -0
  48. package/presets/cursor/ios-swift/rules/post-change-build.mdc +32 -0
  49. package/presets/cursor/ios-swift/rules/state-and-viewmodels.mdc +43 -0
  50. package/presets/cursor/ios-swift/rules/swift-conventions.mdc +51 -0
  51. package/presets/cursor/ios-swift/rules/swiftui-ui.mdc +61 -0
  52. package/presets/cursor/ios-swift/rules/tests-ui.mdc +41 -0
  53. package/presets/cursor/ios-swift/rules/tests-unit.mdc +41 -0
  54. package/presets/cursor/next/commands/README.md +1 -49
  55. package/presets/cursor/next/rules/README.md +0 -47
  56. package/presets/cursor/next/rules/api-services.mdc +10 -12
  57. package/presets/cursor/next/rules/architecture-boundaries.mdc +12 -31
  58. package/presets/cursor/next/rules/code-quality-and-refactoring.mdc +4 -5
  59. package/presets/cursor/next/rules/code-review-mr.mdc +7 -14
  60. package/presets/cursor/next/rules/next-app-core.mdc +61 -18
  61. package/presets/cursor/next/rules/no-props-spread.mdc +4 -27
  62. package/presets/cursor/next/rules/playwright-agents.mdc +1 -2
  63. package/presets/cursor/next/rules/react-ui.mdc +3 -32
  64. package/presets/cursor/next/rules/store-rtk.mdc +6 -13
  65. package/presets/cursor/next/rules/tests-unit.mdc +10 -30
  66. package/CHANGELOG.md +0 -22
  67. package/presets/claude/next/agents/accessibility-reviewer.md +0 -65
  68. package/presets/claude/next/agents/api-contract-reviewer.md +0 -69
  69. package/presets/claude/next/agents/build-verifier.md +0 -64
  70. package/presets/claude/next/agents/ci-investigator.md +0 -62
  71. package/presets/claude/next/agents/code-reviewer.md +0 -62
  72. package/presets/claude/next/agents/debugger.md +0 -63
  73. package/presets/claude/next/agents/feature-developer.md +0 -27
  74. package/presets/claude/next/agents/migration-specialist.md +0 -69
  75. package/presets/claude/next/agents/performance-auditor.md +0 -68
  76. package/presets/claude/next/agents/qa-tester.md +0 -56
  77. package/presets/claude/next/agents/security-reviewer.md +0 -64
  78. package/presets/claude/next/agents/solution-architect.md +0 -70
  79. package/presets/claude/next/agents/task-analyst.md +0 -109
  80. package/presets/claude/next/agents/task-router.md +0 -70
  81. package/presets/claude/next/agents/tech-writer.md +0 -60
  82. package/presets/claude/next/agents/unit-test-generator.md +0 -37
  83. package/presets/claude/next/agents/unit-test-healer.md +0 -38
  84. package/presets/claude/next/agents/unit-test-planner.md +0 -62
  85. package/presets/claude/next/commands/feature-continue.md +0 -51
  86. package/presets/claude/next/commands/feature-start.md +0 -30
  87. package/presets/claude/next/commands/task-continue.md +0 -49
  88. package/presets/claude/next/commands/task.md +0 -50
  89. package/presets/claude/next/commands/technical-retro.md +0 -58
  90. package/presets/claude/next/hooks/chain-team-phases.sh +0 -342
  91. package/presets/claude/next/hooks/guard-shell-command.sh +0 -77
  92. package/presets/claude/next/rules/api-and-data/api-services.md +0 -57
  93. package/presets/claude/next/rules/api-and-data/http-client.md +0 -40
  94. package/presets/claude/next/rules/api-and-data/store-rtk.md +0 -65
  95. package/presets/claude/next/rules/architecture/api-public-imports.md +0 -8
  96. package/presets/claude/next/rules/architecture/architecture-boundaries.md +0 -72
  97. package/presets/claude/next/rules/architecture/feature-delivery-workflow.md +0 -79
  98. package/presets/claude/next/rules/architecture/layer-barrel-exports.md +0 -58
  99. package/presets/claude/next/rules/architecture/public-imports.md +0 -46
  100. package/presets/claude/next/rules/architecture/reference-features.md +0 -37
  101. package/presets/claude/next/rules/architecture/types-public-imports.md +0 -8
  102. package/presets/claude/next/rules/stack/arrow-functions.md +0 -45
  103. package/presets/claude/next/rules/stack/navigation-router.md +0 -61
  104. package/presets/claude/next/rules/stack/next-app-core.md +0 -33
  105. package/presets/claude/next/rules/stack/next-app-router.md +0 -36
  106. package/presets/claude/next/rules/stack/no-type-assertion.md +0 -59
  107. package/presets/claude/next/rules/stack/types-jsdoc.md +0 -37
  108. package/presets/claude/next/rules/testing/playwright-agents.md +0 -74
  109. package/presets/claude/next/rules/testing/tests-e2e-structure.md +0 -52
  110. package/presets/claude/next/rules/testing/tests-unit.md +0 -66
  111. package/presets/claude/next/rules/tooling-and-review/agent-team-intake.md +0 -15
  112. package/presets/claude/next/rules/tooling-and-review/agent-team-orchestrator.md +0 -134
  113. package/presets/claude/next/rules/tooling-and-review/code-quality.md +0 -42
  114. package/presets/claude/next/rules/tooling-and-review/code-review-mr.md +0 -67
  115. package/presets/claude/next/rules/tooling-and-review/package-manager.md +0 -11
  116. package/presets/claude/next/rules/tooling-and-review/post-change-lint.md +0 -33
  117. package/presets/claude/next/rules/ui-and-accessibility/component-styles.md +0 -56
  118. package/presets/claude/next/rules/ui-and-accessibility/css-property-order.md +0 -14
  119. package/presets/claude/next/rules/ui-and-accessibility/no-props-spread.md +0 -57
  120. package/presets/claude/next/rules/ui-and-accessibility/react-a11y-coding.md +0 -37
  121. package/presets/claude/next/rules/ui-and-accessibility/react-ui.md +0 -90
  122. package/presets/claude/next/skills/ci-investigation/SKILL.md +0 -36
  123. package/presets/claude/next/skills/code-review/SKILL.md +0 -26
  124. package/presets/claude/next/skills/debug-investigation/SKILL.md +0 -28
  125. package/presets/claude/next/skills/feature-delivery/SKILL.md +0 -15
  126. package/presets/claude/next/skills/playwright-e2e/SKILL.md +0 -31
  127. package/presets/claude/next/skills/technical-retro/SKILL.md +0 -40
  128. package/presets/claude/next/skills/unit-testing/SKILL.md +0 -32
  129. package/presets/claude/next/team/README.md +0 -46
  130. package/presets/claude/next/team/tasks/.gitkeep +0 -1
  131. package/presets/cursor/next/AGENTS.md +0 -43
  132. package/presets/cursor/next/BUGBOT.md +0 -14
  133. package/presets/cursor/next/agents/README.md +0 -158
  134. package/presets/cursor/next/agents/accessibility-reviewer.md +0 -67
  135. package/presets/cursor/next/agents/api-contract-reviewer.md +0 -71
  136. package/presets/cursor/next/agents/build-verifier.md +0 -66
  137. package/presets/cursor/next/agents/ci-investigator.md +0 -64
  138. package/presets/cursor/next/agents/code-reviewer.md +0 -64
  139. package/presets/cursor/next/agents/debugger.md +0 -64
  140. package/presets/cursor/next/agents/feature-developer.md +0 -27
  141. package/presets/cursor/next/agents/migration-specialist.md +0 -71
  142. package/presets/cursor/next/agents/performance-auditor.md +0 -70
  143. package/presets/cursor/next/agents/playwright-test-generator.md +0 -26
  144. package/presets/cursor/next/agents/playwright-test-healer.md +0 -26
  145. package/presets/cursor/next/agents/playwright-test-planner.md +0 -28
  146. package/presets/cursor/next/agents/qa-tester.md +0 -57
  147. package/presets/cursor/next/agents/security-reviewer.md +0 -66
  148. package/presets/cursor/next/agents/solution-architect.md +0 -71
  149. package/presets/cursor/next/agents/task-analyst.md +0 -112
  150. package/presets/cursor/next/agents/task-router.md +0 -71
  151. package/presets/cursor/next/agents/tech-writer.md +0 -62
  152. package/presets/cursor/next/agents/unit-test-generator.md +0 -39
  153. package/presets/cursor/next/agents/unit-test-healer.md +0 -40
  154. package/presets/cursor/next/agents/unit-test-planner.md +0 -64
  155. package/presets/cursor/next/commands/feature-continue.md +0 -19
  156. package/presets/cursor/next/commands/feature-start.md +0 -33
  157. package/presets/cursor/next/commands/task-continue.md +0 -49
  158. package/presets/cursor/next/commands/task.md +0 -50
  159. package/presets/cursor/next/commands/technical-retro.md +0 -81
  160. package/presets/cursor/next/hooks/README.md +0 -8
  161. package/presets/cursor/next/hooks/chain-team-phases.sh +0 -380
  162. package/presets/cursor/next/hooks/guard-shell-command.sh +0 -77
  163. package/presets/cursor/next/hooks.json +0 -17
  164. package/presets/cursor/next/rules/agent-team-intake.mdc +0 -14
  165. package/presets/cursor/next/rules/agent-team-orchestrator.mdc +0 -139
  166. package/presets/cursor/next/rules/api-public-imports.mdc +0 -8
  167. package/presets/cursor/next/rules/arrow-functions.mdc +0 -46
  168. package/presets/cursor/next/rules/css-property-order-stylelint.mdc +0 -16
  169. package/presets/cursor/next/rules/feature-delivery-workflow.mdc +0 -76
  170. package/presets/cursor/next/rules/http-client.mdc +0 -42
  171. package/presets/cursor/next/rules/layer-barrel-exports.mdc +0 -59
  172. package/presets/cursor/next/rules/navigation-router-stack.mdc +0 -62
  173. package/presets/cursor/next/rules/next-app-router.mdc +0 -36
  174. package/presets/cursor/next/rules/no-cross-component-styles-import.mdc +0 -58
  175. package/presets/cursor/next/rules/no-type-assertion-as-import-export.mdc +0 -60
  176. package/presets/cursor/next/rules/package-manager.mdc +0 -16
  177. package/presets/cursor/next/rules/post-change-lint.mdc +0 -38
  178. package/presets/cursor/next/rules/public-imports.mdc +0 -48
  179. package/presets/cursor/next/rules/react-a11y-coding.mdc +0 -37
  180. package/presets/cursor/next/rules/reference-features.mdc +0 -39
  181. package/presets/cursor/next/rules/technical-retro.mdc +0 -12
  182. package/presets/cursor/next/rules/types-jsdoc.mdc +0 -42
  183. package/presets/cursor/next/rules/types-public-imports.mdc +0 -8
  184. package/presets/cursor/next/skills/README.md +0 -15
  185. package/presets/cursor/next/skills/ci-investigation/SKILL.md +0 -36
  186. package/presets/cursor/next/skills/code-review/SKILL.md +0 -26
  187. package/presets/cursor/next/skills/debug-investigation/SKILL.md +0 -28
  188. package/presets/cursor/next/skills/feature-delivery/SKILL.md +0 -15
  189. package/presets/cursor/next/skills/playwright-e2e/SKILL.md +0 -31
  190. package/presets/cursor/next/skills/technical-retro/SKILL.md +0 -40
  191. package/presets/cursor/next/skills/unit-testing/SKILL.md +0 -32
  192. package/presets/cursor/next/team/README.md +0 -104
  193. package/presets/cursor/next/team/tasks/.gitkeep +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @bonesofspring/ai-rules
2
2
 
3
- Presets for Cursor and Claude Code: rules, commands, plus optional `agents`, `hooks`, `skills`, and team artifacts when the preset includes them. For Claude: `CLAUDE.md`. Install from npm into a project so you don’t have to copy files out of the repo by hand.
3
+ Presets for Cursor and Claude Code: rules, commands, plus for Claude `CLAUDE.md` and `skills` / `agents` / `hooks` when the preset includes them. Install from npm into a project so you don’t have to copy files out of the repo by hand.
4
4
 
5
5
  Node 18+.
6
6
 
@@ -13,18 +13,9 @@ 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 working directory** (or `--cwd <dir>`): dirs are created, matching files are **overwritten**. `--preset` defaults to **`next`**. Supported forms:
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`.
17
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>`.
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.
18
+ `clean cursor` / `clean claude` removes what this CLI installs: for Cursor, the entire `.cursor` directory; for Claude, `.claude/rules`, `.claude/commands`, `skills`, `agents`, `hooks`, and `CLAUDE.md` when present. Anything else under `.claude` is left alone.
28
19
 
29
20
  Where things go on `init`:
30
21
 
@@ -32,13 +23,14 @@ Where things go on `init`:
32
23
  |---|--------|--------|
33
24
  | rules | `.cursor/rules` | `.claude/rules` |
34
25
  | commands | `.cursor/commands` | `.claude/commands` |
35
- | agents | `.cursor/agents` | `.claude/agents` |
36
- | hooks | `.cursor/hooks`, `.cursor/hooks.json` | `.claude/hooks` |
37
- | skills | `.cursor/skills` | `.claude/skills` |
38
- | team artifacts template | `.cursor/team` | `.claude/team` |
39
- | other | root `BUGBOT.md` → `.cursor/BUGBOT.md` | root `CLAUDE.md` → `.claude/CLAUDE.md` |
26
+ | other | | root `CLAUDE.md` → `.claude/CLAUDE.md`; folders `skills`, `agents`, `hooks` → under `.claude/` with the same names |
27
+
28
+ The package ships presets including **`next`** (Next.js stack) and **`ios-swift`** (iOS apps on Swift / SwiftUI). Other preset names: see `presets/` in the [repo](https://github.com/bonesofspring/ai-rules). Name is a single path segment, no `/` or `..`.
40
29
 
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 `..`.
30
+ ```bash
31
+ npx @bonesofspring/ai-rules init cursor --preset ios-swift
32
+ npx @bonesofspring/ai-rules init claude --preset ios-swift
33
+ ```
42
34
 
43
35
  Help: `ai-rules --help`.
44
36
 
package/bin/cli.js CHANGED
@@ -5,86 +5,51 @@
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
 
8
+ const packageRoot = path.join(__dirname, '..');
9
+
8
10
  /** Single path segment: letters, digits, dot, underscore, hyphen; no separators or "..". */
9
11
  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
- optionalCursorDirs: ['agents', 'hooks', 'skills', 'team'],
19
- hooksJsonSrc: 'hooks.json',
20
- toolRootFiles: ['BUGBOT.md', 'AGENTS.md'],
21
18
  },
22
19
  claude: {
23
20
  rulesDir: '.claude/rules',
24
21
  commandsDir: '.claude/commands',
25
22
  relBase: ['presets', 'claude'],
26
- optionalClaudeDirs: ['skills', 'agents', 'hooks', 'team'],
23
+ /** Optional dirs under preset root → under `.claude/` (recursive copy). */
24
+ optionalClaudeDirs: ['skills', 'agents', 'hooks'],
25
+ /** Copied from preset root to `.claude/CLAUDE.md` when present. */
27
26
  claudeMdSrc: 'CLAUDE.md',
28
27
  },
29
28
  };
30
29
 
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
-
39
30
  function printHelp() {
40
31
  console.log(`@bonesofspring/ai-rules
41
32
 
42
33
  Usage:
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)
34
+ ai-rules init <cursor|claude> --preset <name> Copy preset rules and commands into the current directory
35
+ ai-rules clean <cursor|claude> Remove preset target rules and commands directories
50
36
 
51
37
  Notes:
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".
38
+ init merges into existing target folders; same-named files are overwritten.
39
+ clean cursor removes the entire .cursor directory (if present).
40
+ Flags may appear before or after the subcommand (e.g. --preset next init cursor).
56
41
 
57
42
  Examples:
58
43
  npx @bonesofspring/ai-rules init cursor --preset next
59
- npx @bonesofspring/ai-rules init cursor next
60
- npx @bonesofspring/ai-rules init claude
61
- npx @bonesofspring/ai-rules clean cursor
44
+ npx @bonesofspring/ai-rules init claude --preset ios-swift
45
+ npx @bonesofspring/ai-rules clean claude
62
46
  `);
63
47
  }
64
48
 
65
49
  function parseArgs(argv) {
66
- const args = { _: [], cwd: undefined, preset: undefined, presetError: undefined };
50
+ const args = { _: [] };
67
51
  for (let i = 0; i < argv.length; i++) {
68
52
  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
- }
88
53
  if (a === '--preset') {
89
54
  const value = argv[i + 1];
90
55
  if (value === undefined || value.startsWith('-')) {
@@ -105,53 +70,17 @@ function parseArgs(argv) {
105
70
  args.preset = value;
106
71
  continue;
107
72
  }
108
- if (a === '-h' || a === '--help') {
109
- args.help = true;
110
- continue;
111
- }
112
73
  args._.push(a);
113
74
  }
114
75
  return args;
115
76
  }
116
77
 
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
-
149
78
  /**
150
79
  * @returns {string | null} Error message or null if valid.
151
80
  */
152
81
  function validatePresetName(preset) {
153
82
  if (preset === undefined || preset === '') {
154
- return 'Missing preset name (use --preset <name>, e.g. --preset next)';
83
+ return 'Missing --preset <name>';
155
84
  }
156
85
  if (!PRESET_NAME_RE.test(preset) || preset.includes('..')) {
157
86
  return (
@@ -162,6 +91,9 @@ function validatePresetName(preset) {
162
91
  return null;
163
92
  }
164
93
 
94
+ /**
95
+ * Ensures resolved preset dir stays under the tool presets root (defense in depth).
96
+ */
165
97
  function assertPresetDirInsideRoot(presetsRoot, presetBase) {
166
98
  const root = path.resolve(presetsRoot);
167
99
  const resolved = path.resolve(presetBase);
@@ -185,17 +117,10 @@ function copyDir(src, dest) {
185
117
  copyDir(from, to);
186
118
  } else {
187
119
  fs.copyFileSync(from, to);
188
- if (e.name.endsWith('.sh')) {
189
- fs.chmodSync(to, 0o755);
190
- }
191
120
  }
192
121
  }
193
122
  }
194
123
 
195
- function getOptionalDirs(cfg) {
196
- return cfg.optionalCursorDirs || cfg.optionalClaudeDirs || [];
197
- }
198
-
199
124
  function removeDirIfExists(dir) {
200
125
  if (fs.existsSync(dir)) {
201
126
  fs.rmSync(dir, { recursive: true, force: true });
@@ -208,157 +133,76 @@ function removeFileIfExists(filePath) {
208
133
  }
209
134
  }
210
135
 
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
-
222
136
  function presetHasAnySource(base, cfg) {
223
137
  const rulesSrc = path.join(base, 'rules');
224
138
  const commandsSrc = path.join(base, 'commands');
225
139
  if (fs.existsSync(rulesSrc) || fs.existsSync(commandsSrc)) {
226
140
  return true;
227
141
  }
228
- for (const name of getOptionalDirs(cfg)) {
229
- if (fs.existsSync(path.join(base, name))) {
230
- return true;
142
+ if (cfg.optionalClaudeDirs) {
143
+ for (const name of cfg.optionalClaudeDirs) {
144
+ if (fs.existsSync(path.join(base, name))) {
145
+ return true;
146
+ }
231
147
  }
232
148
  }
233
149
  if (cfg.claudeMdSrc && fs.existsSync(path.join(base, cfg.claudeMdSrc))) {
234
150
  return true;
235
151
  }
236
- if (cfg.hooksJsonSrc && fs.existsSync(path.join(base, cfg.hooksJsonSrc))) {
237
- return true;
238
- }
239
- for (const name of cfg.toolRootFiles || []) {
240
- if (fs.existsSync(path.join(base, name))) {
241
- return true;
242
- }
243
- }
244
152
  return false;
245
153
  }
246
154
 
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}`);
251
- process.exit(1);
252
- }
253
- if (!fs.statSync(cwd).isDirectory()) {
254
- console.error(`Target path is not a directory: ${cwd}`);
155
+ function cmdInit(tool, preset) {
156
+ const cfg = PRESETS[tool];
157
+ if (!cfg) {
158
+ console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
255
159
  process.exit(1);
256
160
  }
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);
161
+ const nameErr = validatePresetName(preset);
266
162
  if (nameErr) {
267
163
  console.error(nameErr);
268
164
  process.exit(1);
269
165
  }
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
-
166
+ const presetsRoot = path.join(packageRoot, ...cfg.relBase);
167
+ const base = path.join(presetsRoot, preset);
293
168
  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);
307
169
 
308
170
  const rulesSrc = path.join(base, 'rules');
309
171
  const commandsSrc = path.join(base, 'commands');
310
- const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
311
- const toolRootAbs = path.join(cwd, toolRoot);
312
-
313
- fs.mkdirSync(toolRootAbs, { recursive: true });
172
+ const cwd = process.cwd();
314
173
 
315
- let copiedSteps = 0;
174
+ if (!presetHasAnySource(base, cfg)) {
175
+ console.error(`Preset "${preset}" for ${tool} not found under ${base}`);
176
+ process.exit(1);
177
+ }
316
178
 
317
179
  try {
318
180
  if (tool === 'claude' && cfg.claudeMdSrc) {
319
181
  const mdFrom = path.join(base, cfg.claudeMdSrc);
320
182
  if (fs.existsSync(mdFrom)) {
321
- fs.copyFileSync(mdFrom, path.join(toolRootAbs, 'CLAUDE.md'));
322
- console.log(`Copied CLAUDE.md ${path.join(toolRoot, 'CLAUDE.md')}`);
323
- copiedSteps++;
183
+ const claudeRoot = path.join(cwd, '.claude');
184
+ fs.mkdirSync(claudeRoot, { recursive: true });
185
+ fs.copyFileSync(mdFrom, path.join(claudeRoot, 'CLAUDE.md'));
186
+ console.log('Copied CLAUDE.md → .claude/CLAUDE.md');
324
187
  }
325
188
  }
326
189
 
327
190
  if (fs.existsSync(rulesSrc)) {
328
191
  copyDir(rulesSrc, path.join(cwd, cfg.rulesDir));
329
192
  console.log(`Copied rules → ${cfg.rulesDir}`);
330
- copiedSteps++;
331
193
  }
332
194
  if (fs.existsSync(commandsSrc)) {
333
195
  copyDir(commandsSrc, path.join(cwd, cfg.commandsDir));
334
196
  console.log(`Copied commands → ${cfg.commandsDir}`);
335
- copiedSteps++;
336
- }
337
-
338
- for (const name of getOptionalDirs(cfg)) {
339
- const src = path.join(base, name);
340
- if (fs.existsSync(src)) {
341
- copyDir(src, path.join(toolRootAbs, name));
342
- console.log(`Copied ${name}/ → ${toolRoot}/${name}/`);
343
- copiedSteps++;
344
- }
345
- }
346
-
347
- if (tool === 'cursor' && cfg.hooksJsonSrc) {
348
- const hooksFrom = path.join(base, cfg.hooksJsonSrc);
349
- if (fs.existsSync(hooksFrom)) {
350
- fs.copyFileSync(hooksFrom, path.join(toolRootAbs, 'hooks.json'));
351
- console.log(`Copied hooks.json → ${toolRoot}/hooks.json`);
352
- copiedSteps++;
353
- }
354
197
  }
355
198
 
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++;
199
+ if (tool === 'claude' && cfg.optionalClaudeDirs) {
200
+ for (const name of cfg.optionalClaudeDirs) {
201
+ const src = path.join(base, name);
202
+ if (fs.existsSync(src)) {
203
+ copyDir(src, path.join(cwd, '.claude', name));
204
+ console.log(`Copied ${name}/ → .claude/${name}/`);
205
+ }
362
206
  }
363
207
  }
364
208
  } catch (err) {
@@ -366,95 +210,66 @@ function cmdInit(tool, preset, cwdOption) {
366
210
  console.error(`init failed: ${msg}`);
367
211
  process.exit(1);
368
212
  }
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})`);
381
213
  }
382
214
 
383
- function cmdClean(tool, cwdOption) {
215
+ function cmdClean(tool) {
384
216
  const cfg = PRESETS[tool];
385
217
  if (!cfg) {
386
218
  console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
387
219
  process.exit(1);
388
220
  }
389
- const cwd = resolveTargetCwd(cwdOption);
390
- const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
391
- const removedParts = [cfg.rulesDir, cfg.commandsDir];
392
-
221
+ const cwd = process.cwd();
222
+ if (tool === 'cursor') {
223
+ removeDirIfExists(path.join(cwd, '.cursor'));
224
+ console.log('Removed .cursor (if present)');
225
+ return;
226
+ }
393
227
  removeDirIfExists(path.join(cwd, cfg.rulesDir));
394
228
  removeDirIfExists(path.join(cwd, cfg.commandsDir));
395
-
396
- const optionalDirs = getOptionalDirs(cfg);
397
- for (const name of optionalDirs) {
398
- removeDirIfExists(path.join(cwd, toolRoot, name));
399
- removedParts.push(`${toolRoot}/${name}`);
400
- }
401
-
402
- if (cfg.claudeMdSrc) {
403
- removeFileIfExists(path.join(cwd, toolRoot, 'CLAUDE.md'));
404
- removedParts.push(`${toolRoot}/CLAUDE.md`);
405
- }
406
-
407
- if (cfg.hooksJsonSrc) {
408
- removeFileIfExists(path.join(cwd, toolRoot, 'hooks.json'));
409
- removedParts.push(`${toolRoot}/hooks.json`);
410
- }
411
-
412
- for (const name of cfg.toolRootFiles || []) {
413
- removeFileIfExists(path.join(cwd, toolRoot, name));
414
- removedParts.push(`${toolRoot}/${name}`);
415
- }
416
-
417
- console.log(`Removed ${removedParts.join(', ')} (if present)`);
229
+ if (tool === 'claude' && cfg.optionalClaudeDirs) {
230
+ for (const name of cfg.optionalClaudeDirs) {
231
+ removeDirIfExists(path.join(cwd, '.claude', name));
232
+ }
233
+ removeFileIfExists(path.join(cwd, '.claude', 'CLAUDE.md'));
234
+ }
235
+ console.log(
236
+ `Removed ${cfg.rulesDir} and ${cfg.commandsDir}` +
237
+ (tool === 'claude' && cfg.optionalClaudeDirs
238
+ ? `, optional .claude/{${cfg.optionalClaudeDirs.join(',')}}, and .claude/CLAUDE.md`
239
+ : '') +
240
+ ' (if present)',
241
+ );
418
242
  }
419
243
 
420
244
  function main() {
421
245
  const argv = process.argv.slice(2);
422
- if (argv.length === 0) {
246
+ if (argv.length === 0 || argv.includes('-h') || argv.includes('--help')) {
423
247
  printHelp();
424
248
  process.exit(0);
425
249
  }
426
250
 
427
251
  const parsed = parseArgs(argv);
428
- if (parsed.help) {
429
- printHelp();
430
- process.exit(0);
431
- }
252
+ const [command, tool] = parsed._;
432
253
 
433
254
  if (parsed.presetError) {
434
255
  console.error(parsed.presetError);
435
256
  process.exit(1);
436
257
  }
437
- if (parsed.cwdError) {
438
- console.error(parsed.cwdError);
439
- process.exit(1);
440
- }
441
-
442
- const { command, tool, preset } = normalizeInitClean(parsed);
443
258
 
444
259
  if (command === 'init') {
445
- if (!tool || !TOOLS.has(tool)) {
446
- console.error('Missing or invalid tool. Use: init cursor|claude [--preset next]');
260
+ if (!tool) {
261
+ console.error('Missing <cursor|claude> after init.');
447
262
  process.exit(1);
448
263
  }
449
- cmdInit(tool, preset, parsed.cwd);
264
+ cmdInit(tool, parsed.preset);
450
265
  return;
451
266
  }
452
267
  if (command === 'clean') {
453
- if (!tool || !TOOLS.has(tool)) {
454
- console.error('Missing or invalid tool. Use: clean cursor|claude');
268
+ if (!tool) {
269
+ console.error('Missing <cursor|claude> after clean.');
455
270
  process.exit(1);
456
271
  }
457
- cmdClean(tool, parsed.cwd);
272
+ cmdClean(tool);
458
273
  return;
459
274
  }
460
275
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bonesofspring/ai-rules",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/bonesofspring/ai-rules.git"
9
+ "url": "https://github.com/bonesofspring/ai-rules.git"
10
10
  },
11
11
  "keywords": [
12
12
  "cursor",
@@ -16,11 +16,10 @@
16
16
  "bonesofspring"
17
17
  ],
18
18
  "bin": {
19
- "ai-rules": "bin/cli.js"
19
+ "ai-rules": "./bin/cli.js"
20
20
  },
21
21
  "files": [
22
22
  "README.md",
23
- "CHANGELOG.md",
24
23
  "bin",
25
24
  "presets"
26
25
  ],
@@ -0,0 +1,27 @@
1
+ # iOS Swift stack — project instructions
2
+
3
+ Краткая точка входа для Claude Code. Подробные правила — модульные topic `.md` под `rules/` (derived из Cursor SoT).
4
+
5
+ See @rules/README.md for the full Cursor → Claude mapping and sync checklist.
6
+
7
+ ## Where to look
8
+
9
+ | Area | Folder |
10
+ |------|--------|
11
+ | Stack / conventions | @rules/stack/ |
12
+ | Architecture / feature delivery | @rules/architecture/ |
13
+ | Networking / data | @rules/api-and-data/ |
14
+ | SwiftUI / ViewModels | @rules/ui-and-accessibility/ |
15
+ | Tests | @rules/testing/ |
16
+ | Quality / review / build | @rules/tooling-and-review/ |
17
+
18
+ ## Conventions
19
+
20
+ - Одна тема на файл; path scope — frontmatter `paths:` (из Cursor `globs:`).
21
+ - SoT: `presets/cursor/ios-swift/rules/*.mdc`. Change Cursor → update Claude twin same PR.
22
+ - Slash-команды — в `commands/`; skills — когда появятся в пресете.
23
+
24
+ ## Architecture (postcard)
25
+
26
+ - **Presentation** → **Domain** ← **Data**; DI в `App/`.
27
+ - Views тонкие; ViewModels на `@MainActor`; DTO не покидают Data.
@@ -0,0 +1,29 @@
1
+ # Preset `ios-swift` (Claude Code)
2
+
3
+ Правила для Swift, SwiftUI, слоистой архитектуры и тестов (XCTest, XCUITest).
4
+
5
+ Структура отличается от пресета Cursor: здесь повторяется типичный **layout проекта Claude Code** под `.claude/`, а не плоский список `.mdc` в `rules/`.
6
+
7
+ | В пресете | Куда копирует `ai-rules init claude --preset ios-swift` |
8
+ |-----------|---------------------------------------------------------|
9
+ | `CLAUDE.md` | `.claude/CLAUDE.md` (точка входа, грузится в начале сессии) |
10
+ | `rules/**` | `.claude/rules/**` (рекурсивно: подпапки по темам, см. [документацию](https://code.claude.com/docs/en/memory#organize-rules-with-clauderules)) |
11
+ | `commands/**` | `.claude/commands/**` |
12
+ | `skills/**` | `.claude/skills/**` (если папка есть) |
13
+ | `agents/**` | `.claude/agents/**` (если папка есть) |
14
+ | `hooks/**` | `.claude/hooks/**` (если папка есть) |
15
+
16
+ Файлы правил — обычный Markdown с опциональным YAML frontmatter (`paths:` для скоупа по путям), не формат `.mdc` Cursor.
17
+
18
+ ```bash
19
+ npx @bonesofspring/ai-rules init claude --preset ios-swift
20
+ ```
21
+
22
+ ## Parity with Cursor
23
+
24
+ - **SoT:** `presets/cursor/ios-swift/rules/*.mdc`
25
+ - **Claude twins:** topic `.md` under `rules/**` (bodies derived from Cursor; frontmatter uses `paths:` instead of `globs:` / `alwaysApply`)
26
+ - **Mapping + sync checklist:** [`rules/README.md`](./rules/README.md)
27
+ - **Entry index:** [`CLAUDE.md`](./CLAUDE.md) (thin — no second long-form SoT)
28
+
29
+ When editing rules: change Cursor first, then update the Claude twin in the same PR.
@@ -0,0 +1,3 @@
1
+ # Claude commands (ios-swift preset)
2
+
3
+ Place command definitions for this preset here. They are copied to `.claude/commands` when you run `ai-rules init claude --preset ios-swift`.