@bonesofspring/ai-rules 0.2.1 → 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 (214) hide show
  1. package/README.md +10 -20
  2. package/bin/cli.js +73 -260
  3. package/package.json +4 -13
  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 -33
  27. package/presets/claude/next/README.md +0 -10
  28. package/presets/claude/next/agents/README.md +0 -126
  29. package/presets/claude/next/agents/playwright-test-generator.md +65 -16
  30. package/presets/claude/next/agents/playwright-test-healer.md +51 -16
  31. package/presets/claude/next/agents/playwright-test-planner.md +55 -15
  32. package/presets/claude/next/commands/README.md +2 -10
  33. package/presets/claude/next/hooks/README.md +2 -5
  34. package/presets/claude/next/rules/README.md +11 -29
  35. package/presets/claude/next/rules/api-and-data/README.md +1 -7
  36. package/presets/claude/next/rules/architecture/README.md +2 -10
  37. package/presets/claude/next/rules/stack/README.md +1 -10
  38. package/presets/claude/next/rules/testing/README.md +1 -9
  39. package/presets/claude/next/rules/tooling-and-review/README.md +1 -14
  40. package/presets/claude/next/rules/ui-and-accessibility/README.md +1 -7
  41. package/presets/claude/next/skills/README.md +1 -11
  42. package/presets/cursor/ios-swift/README.md +17 -0
  43. package/presets/cursor/ios-swift/commands/README.md +3 -0
  44. package/presets/cursor/ios-swift/rules/README.md +33 -0
  45. package/presets/cursor/ios-swift/rules/architecture-boundaries.mdc +34 -0
  46. package/presets/cursor/ios-swift/rules/code-quality-and-refactoring.mdc +39 -0
  47. package/presets/cursor/ios-swift/rules/code-review-mr.mdc +42 -0
  48. package/presets/cursor/ios-swift/rules/feature-delivery-workflow.mdc +61 -0
  49. package/presets/cursor/ios-swift/rules/ios-app-core.mdc +43 -0
  50. package/presets/cursor/ios-swift/rules/networking-services.mdc +49 -0
  51. package/presets/cursor/ios-swift/rules/post-change-build.mdc +32 -0
  52. package/presets/cursor/ios-swift/rules/state-and-viewmodels.mdc +43 -0
  53. package/presets/cursor/ios-swift/rules/swift-conventions.mdc +51 -0
  54. package/presets/cursor/ios-swift/rules/swiftui-ui.mdc +61 -0
  55. package/presets/cursor/ios-swift/rules/tests-ui.mdc +41 -0
  56. package/presets/cursor/ios-swift/rules/tests-unit.mdc +41 -0
  57. package/presets/cursor/next/commands/README.md +1 -49
  58. package/presets/cursor/next/rules/README.md +0 -67
  59. package/presets/cursor/next/rules/api-services.mdc +10 -12
  60. package/presets/cursor/next/rules/architecture-boundaries.mdc +12 -31
  61. package/presets/cursor/next/rules/code-quality-and-refactoring.mdc +4 -5
  62. package/presets/cursor/next/rules/code-review-mr.mdc +40 -21
  63. package/presets/cursor/next/rules/next-app-core.mdc +61 -18
  64. package/presets/cursor/next/rules/no-props-spread.mdc +4 -27
  65. package/presets/cursor/next/rules/playwright-agents.mdc +1 -2
  66. package/presets/cursor/next/rules/react-ui.mdc +3 -33
  67. package/presets/cursor/next/rules/store-rtk.mdc +6 -13
  68. package/presets/cursor/next/rules/tests-unit.mdc +10 -30
  69. package/CHANGELOG.md +0 -22
  70. package/CONTRIBUTING.md +0 -102
  71. package/fragments/BUGBOT.md +0 -23
  72. package/presets/claude/next/BUGBOT.md +0 -16
  73. package/presets/claude/next/agents/accessibility-reviewer.md +0 -65
  74. package/presets/claude/next/agents/api-contract-reviewer.md +0 -69
  75. package/presets/claude/next/agents/build-verifier.md +0 -64
  76. package/presets/claude/next/agents/ci-investigator.md +0 -62
  77. package/presets/claude/next/agents/code-reviewer.md +0 -62
  78. package/presets/claude/next/agents/debugger.md +0 -63
  79. package/presets/claude/next/agents/feature-developer.md +0 -27
  80. package/presets/claude/next/agents/migration-specialist.md +0 -69
  81. package/presets/claude/next/agents/performance-auditor.md +0 -68
  82. package/presets/claude/next/agents/qa-tester.md +0 -56
  83. package/presets/claude/next/agents/security-reviewer.md +0 -64
  84. package/presets/claude/next/agents/solution-architect.md +0 -70
  85. package/presets/claude/next/agents/task-analyst.md +0 -109
  86. package/presets/claude/next/agents/task-router.md +0 -94
  87. package/presets/claude/next/agents/tech-writer.md +0 -60
  88. package/presets/claude/next/agents/unit-test-generator.md +0 -37
  89. package/presets/claude/next/agents/unit-test-healer.md +0 -38
  90. package/presets/claude/next/agents/unit-test-planner.md +0 -62
  91. package/presets/claude/next/commands/feature-continue.md +0 -51
  92. package/presets/claude/next/commands/feature-start.md +0 -30
  93. package/presets/claude/next/commands/task-continue.md +0 -49
  94. package/presets/claude/next/commands/task.md +0 -50
  95. package/presets/claude/next/commands/technical-retro.md +0 -58
  96. package/presets/claude/next/hooks/chain-team-phases.sh +0 -342
  97. package/presets/claude/next/hooks/guard-shell-command.sh +0 -77
  98. package/presets/claude/next/hooks.json +0 -17
  99. package/presets/claude/next/rules/api-and-data/api-services.md +0 -57
  100. package/presets/claude/next/rules/api-and-data/http-client.md +0 -40
  101. package/presets/claude/next/rules/api-and-data/store-rtk.md +0 -65
  102. package/presets/claude/next/rules/architecture/api-public-imports.md +0 -8
  103. package/presets/claude/next/rules/architecture/architecture-boundaries-ui.md +0 -15
  104. package/presets/claude/next/rules/architecture/architecture-boundaries.md +0 -75
  105. package/presets/claude/next/rules/architecture/feature-delivery-workflow.md +0 -79
  106. package/presets/claude/next/rules/architecture/layer-barrel-exports.md +0 -58
  107. package/presets/claude/next/rules/architecture/public-imports.md +0 -46
  108. package/presets/claude/next/rules/architecture/reference-features.md +0 -37
  109. package/presets/claude/next/rules/architecture/types-public-imports.md +0 -8
  110. package/presets/claude/next/rules/stack/arrow-functions.md +0 -45
  111. package/presets/claude/next/rules/stack/navigation-router-ui.md +0 -15
  112. package/presets/claude/next/rules/stack/navigation-router.md +0 -62
  113. package/presets/claude/next/rules/stack/next-app-core.md +0 -33
  114. package/presets/claude/next/rules/stack/next-app-router.md +0 -36
  115. package/presets/claude/next/rules/stack/no-type-assertion.md +0 -59
  116. package/presets/claude/next/rules/stack/types-jsdoc.md +0 -37
  117. package/presets/claude/next/rules/testing/playwright-agents.md +0 -74
  118. package/presets/claude/next/rules/testing/tests-e2e-structure.md +0 -52
  119. package/presets/claude/next/rules/testing/tests-unit.md +0 -66
  120. package/presets/claude/next/rules/tooling-and-review/agent-team-intake.md +0 -15
  121. package/presets/claude/next/rules/tooling-and-review/agent-team-orchestrator.md +0 -140
  122. package/presets/claude/next/rules/tooling-and-review/code-quality.md +0 -42
  123. package/presets/claude/next/rules/tooling-and-review/code-review-mr.md +0 -30
  124. package/presets/claude/next/rules/tooling-and-review/package-manager.md +0 -11
  125. package/presets/claude/next/rules/tooling-and-review/post-change-lint.md +0 -33
  126. package/presets/claude/next/rules/ui-and-accessibility/css-property-order.md +0 -14
  127. package/presets/claude/next/rules/ui-and-accessibility/no-props-spread.md +0 -57
  128. package/presets/claude/next/rules/ui-and-accessibility/react-a11y-coding.md +0 -37
  129. package/presets/claude/next/rules/ui-and-accessibility/react-ui.md +0 -91
  130. package/presets/claude/next/skills/ci-investigation/SKILL.md +0 -36
  131. package/presets/claude/next/skills/code-review/SKILL.md +0 -26
  132. package/presets/claude/next/skills/debug-investigation/SKILL.md +0 -28
  133. package/presets/claude/next/skills/feature-delivery/SKILL.md +0 -15
  134. package/presets/claude/next/skills/playwright-e2e/SKILL.md +0 -31
  135. package/presets/claude/next/skills/technical-retro/SKILL.md +0 -40
  136. package/presets/claude/next/skills/unit-testing/SKILL.md +0 -32
  137. package/presets/claude/next/team/README.md +0 -47
  138. package/presets/claude/next/team/fixtures/bugfix-standard.json +0 -15
  139. package/presets/claude/next/team/fixtures/feature-full.json +0 -16
  140. package/presets/claude/next/team/fixtures/feature-light.json +0 -17
  141. package/presets/claude/next/team/tasks/.gitkeep +0 -1
  142. package/presets/cursor/next/AGENTS.md +0 -36
  143. package/presets/cursor/next/BUGBOT.md +0 -16
  144. package/presets/cursor/next/agents/README.md +0 -159
  145. package/presets/cursor/next/agents/accessibility-reviewer.md +0 -67
  146. package/presets/cursor/next/agents/api-contract-reviewer.md +0 -71
  147. package/presets/cursor/next/agents/build-verifier.md +0 -66
  148. package/presets/cursor/next/agents/ci-investigator.md +0 -64
  149. package/presets/cursor/next/agents/code-reviewer.md +0 -64
  150. package/presets/cursor/next/agents/debugger.md +0 -64
  151. package/presets/cursor/next/agents/feature-developer.md +0 -27
  152. package/presets/cursor/next/agents/migration-specialist.md +0 -71
  153. package/presets/cursor/next/agents/performance-auditor.md +0 -70
  154. package/presets/cursor/next/agents/playwright-test-generator.md +0 -26
  155. package/presets/cursor/next/agents/playwright-test-healer.md +0 -26
  156. package/presets/cursor/next/agents/playwright-test-planner.md +0 -28
  157. package/presets/cursor/next/agents/qa-tester.md +0 -57
  158. package/presets/cursor/next/agents/security-reviewer.md +0 -66
  159. package/presets/cursor/next/agents/solution-architect.md +0 -71
  160. package/presets/cursor/next/agents/task-analyst.md +0 -112
  161. package/presets/cursor/next/agents/task-router.md +0 -95
  162. package/presets/cursor/next/agents/tech-writer.md +0 -62
  163. package/presets/cursor/next/agents/unit-test-generator.md +0 -39
  164. package/presets/cursor/next/agents/unit-test-healer.md +0 -40
  165. package/presets/cursor/next/agents/unit-test-planner.md +0 -64
  166. package/presets/cursor/next/commands/feature-continue.md +0 -19
  167. package/presets/cursor/next/commands/feature-start.md +0 -33
  168. package/presets/cursor/next/commands/task-continue.md +0 -49
  169. package/presets/cursor/next/commands/task.md +0 -50
  170. package/presets/cursor/next/commands/technical-retro.md +0 -81
  171. package/presets/cursor/next/hooks/README.md +0 -8
  172. package/presets/cursor/next/hooks/chain-team-phases.sh +0 -380
  173. package/presets/cursor/next/hooks/guard-shell-command.sh +0 -77
  174. package/presets/cursor/next/hooks.json +0 -17
  175. package/presets/cursor/next/rules/agent-team-intake.mdc +0 -14
  176. package/presets/cursor/next/rules/agent-team-orchestrator.mdc +0 -139
  177. package/presets/cursor/next/rules/api-public-imports.mdc +0 -8
  178. package/presets/cursor/next/rules/architecture-boundaries-ui.mdc +0 -15
  179. package/presets/cursor/next/rules/arrow-functions.mdc +0 -46
  180. package/presets/cursor/next/rules/css-property-order-stylelint.mdc +0 -16
  181. package/presets/cursor/next/rules/feature-delivery-workflow.mdc +0 -76
  182. package/presets/cursor/next/rules/http-client.mdc +0 -42
  183. package/presets/cursor/next/rules/layer-barrel-exports.mdc +0 -59
  184. package/presets/cursor/next/rules/navigation-router-stack.mdc +0 -62
  185. package/presets/cursor/next/rules/navigation-router-ui.mdc +0 -16
  186. package/presets/cursor/next/rules/next-app-router.mdc +0 -36
  187. package/presets/cursor/next/rules/no-cross-component-styles-import.mdc +0 -8
  188. package/presets/cursor/next/rules/no-type-assertion-as-import-export.mdc +0 -60
  189. package/presets/cursor/next/rules/package-manager.mdc +0 -16
  190. package/presets/cursor/next/rules/post-change-lint.mdc +0 -38
  191. package/presets/cursor/next/rules/public-imports.mdc +0 -48
  192. package/presets/cursor/next/rules/react-a11y-coding.mdc +0 -37
  193. package/presets/cursor/next/rules/reference-features.mdc +0 -39
  194. package/presets/cursor/next/rules/technical-retro.mdc +0 -12
  195. package/presets/cursor/next/rules/types-jsdoc.mdc +0 -42
  196. package/presets/cursor/next/rules/types-public-imports.mdc +0 -8
  197. package/presets/cursor/next/skills/README.md +0 -15
  198. package/presets/cursor/next/skills/ci-investigation/SKILL.md +0 -36
  199. package/presets/cursor/next/skills/code-review/SKILL.md +0 -26
  200. package/presets/cursor/next/skills/debug-investigation/SKILL.md +0 -28
  201. package/presets/cursor/next/skills/feature-delivery/SKILL.md +0 -15
  202. package/presets/cursor/next/skills/playwright-e2e/SKILL.md +0 -31
  203. package/presets/cursor/next/skills/technical-retro/SKILL.md +0 -40
  204. package/presets/cursor/next/skills/unit-testing/SKILL.md +0 -32
  205. package/presets/cursor/next/team/README.md +0 -105
  206. package/presets/cursor/next/team/fixtures/bugfix-standard.json +0 -15
  207. package/presets/cursor/next/team/fixtures/feature-full.json +0 -16
  208. package/presets/cursor/next/team/fixtures/feature-light.json +0 -17
  209. package/presets/cursor/next/team/tasks/.gitkeep +0 -0
  210. package/scripts/README.md +0 -84
  211. package/scripts/golden-prompts.json +0 -276
  212. package/scripts/preset-manifest.json +0 -72
  213. package/scripts/regression-results/.gitkeep +0 -0
  214. package/scripts/validate-preset.sh +0 -484
package/README.md CHANGED
@@ -1,11 +1,9 @@
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
 
7
- Contributing and Cursor↔Claude sync checklist: **[CONTRIBUTING.md](./CONTRIBUTING.md)**.
8
-
9
7
  ```bash
10
8
  # from the project root
11
9
  npx @bonesofspring/ai-rules init cursor --preset next
@@ -15,18 +13,9 @@ npx @bonesofspring/ai-rules clean cursor
15
13
 
16
14
  Global: `npm i -g @bonesofspring/ai-rules`, then `ai-rules ...`.
17
15
 
18
- `init` merges into the **current working directory** (or `--cwd <dir>`): dirs are created, matching files are **overwritten**. `--preset` defaults to **`next`**. Supported forms:
19
-
20
- ```bash
21
- npx @bonesofspring/ai-rules init cursor --preset next
22
- npx @bonesofspring/ai-rules init cursor next
23
- npx @bonesofspring/ai-rules init cursor # preset next by default
24
- npx @bonesofspring/ai-rules cursor init --preset next
25
- ```
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`.
26
17
 
27
- 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>`.
28
-
29
- `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.
30
19
 
31
20
  Where things go on `init`:
32
21
 
@@ -34,13 +23,14 @@ Where things go on `init`:
34
23
  |---|--------|--------|
35
24
  | rules | `.cursor/rules` | `.claude/rules` |
36
25
  | commands | `.cursor/commands` | `.claude/commands` |
37
- | agents | `.cursor/agents` | `.claude/agents` |
38
- | hooks | `.cursor/hooks`, `.cursor/hooks.json` | `.claude/hooks` |
39
- | skills | `.cursor/skills` | `.claude/skills` |
40
- | team artifacts template | `.cursor/team` | `.claude/team` |
41
- | 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 `..`.
42
29
 
43
- 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
+ ```
44
34
 
45
35
  Help: `ai-rules --help`.
46
36
 
package/bin/cli.js CHANGED
@@ -5,88 +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
- hooksJsonSrc: 'hooks.json',
29
- toolRootFiles: ['BUGBOT.md'],
30
27
  },
31
28
  };
32
29
 
33
- function getPackageRoot() {
34
- try {
35
- return path.dirname(require.resolve('@bonesofspring/ai-rules/package.json'));
36
- } catch {
37
- return path.join(__dirname, '..');
38
- }
39
- }
40
-
41
30
  function printHelp() {
42
31
  console.log(`@bonesofspring/ai-rules
43
32
 
44
33
  Usage:
45
- ai-rules init <cursor|claude> [--preset <name>]
46
- ai-rules init <cursor|claude> <preset-name>
47
- ai-rules clean <cursor|claude> [--cwd <dir>]
48
-
49
- Options:
50
- --preset <name> Preset name (default: ${DEFAULT_PRESET})
51
- --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
52
36
 
53
37
  Notes:
54
- init merges into the target directory; same-named files are overwritten.
55
- Cursor init copies rules, commands, agents, hooks, skills, team, hooks.json, BUGBOT.md, AGENTS.md.
56
- Claude init copies rules, commands, skills, agents, hooks, team, hooks.json, BUGBOT.md, and CLAUDE.md when present.
57
- 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).
58
41
 
59
42
  Examples:
60
43
  npx @bonesofspring/ai-rules init cursor --preset next
61
- npx @bonesofspring/ai-rules init cursor next
62
- npx @bonesofspring/ai-rules init claude
63
- npx @bonesofspring/ai-rules clean cursor
44
+ npx @bonesofspring/ai-rules init claude --preset ios-swift
45
+ npx @bonesofspring/ai-rules clean claude
64
46
  `);
65
47
  }
66
48
 
67
49
  function parseArgs(argv) {
68
- const args = { _: [], cwd: undefined, preset: undefined, presetError: undefined };
50
+ const args = { _: [] };
69
51
  for (let i = 0; i < argv.length; i++) {
70
52
  const a = argv[i];
71
- if (a === '--cwd') {
72
- const value = argv[i + 1];
73
- if (value === undefined || value.startsWith('-')) {
74
- args.cwdError = 'Missing value for --cwd';
75
- continue;
76
- }
77
- args.cwd = value;
78
- i++;
79
- continue;
80
- }
81
- if (a.startsWith('--cwd=')) {
82
- const value = a.slice('--cwd='.length);
83
- if (value === '') {
84
- args.cwdError = 'Missing value after --cwd=';
85
- continue;
86
- }
87
- args.cwd = value;
88
- continue;
89
- }
90
53
  if (a === '--preset') {
91
54
  const value = argv[i + 1];
92
55
  if (value === undefined || value.startsWith('-')) {
@@ -107,53 +70,17 @@ function parseArgs(argv) {
107
70
  args.preset = value;
108
71
  continue;
109
72
  }
110
- if (a === '-h' || a === '--help') {
111
- args.help = true;
112
- continue;
113
- }
114
73
  args._.push(a);
115
74
  }
116
75
  return args;
117
76
  }
118
77
 
119
- /**
120
- * Supports: init cursor, cursor init, init cursor next (positional preset).
121
- */
122
- function normalizeInitClean(parsed) {
123
- const positional = [...parsed._];
124
- let command;
125
- let tool;
126
- let presetFromPositional;
127
-
128
- if (positional.length >= 2 && TOOLS.has(positional[0]) && positional[1] === 'init') {
129
- command = 'init';
130
- tool = positional[0];
131
- presetFromPositional = positional[2];
132
- } else if (positional.length >= 2 && positional[0] === 'init' && TOOLS.has(positional[1])) {
133
- command = 'init';
134
- tool = positional[1];
135
- presetFromPositional = positional[2];
136
- } else if (positional.length >= 2 && positional[0] === 'clean' && TOOLS.has(positional[1])) {
137
- command = 'clean';
138
- tool = positional[1];
139
- } else if (positional.length >= 1) {
140
- command = positional[0];
141
- tool = positional[1];
142
- if (command === 'init' && positional[2] && PRESET_NAME_RE.test(positional[2])) {
143
- presetFromPositional = positional[2];
144
- }
145
- }
146
-
147
- const preset = parsed.preset || presetFromPositional;
148
- return { command, tool, preset };
149
- }
150
-
151
78
  /**
152
79
  * @returns {string | null} Error message or null if valid.
153
80
  */
154
81
  function validatePresetName(preset) {
155
82
  if (preset === undefined || preset === '') {
156
- return 'Missing preset name (use --preset <name>, e.g. --preset next)';
83
+ return 'Missing --preset <name>';
157
84
  }
158
85
  if (!PRESET_NAME_RE.test(preset) || preset.includes('..')) {
159
86
  return (
@@ -164,6 +91,9 @@ function validatePresetName(preset) {
164
91
  return null;
165
92
  }
166
93
 
94
+ /**
95
+ * Ensures resolved preset dir stays under the tool presets root (defense in depth).
96
+ */
167
97
  function assertPresetDirInsideRoot(presetsRoot, presetBase) {
168
98
  const root = path.resolve(presetsRoot);
169
99
  const resolved = path.resolve(presetBase);
@@ -187,17 +117,10 @@ function copyDir(src, dest) {
187
117
  copyDir(from, to);
188
118
  } else {
189
119
  fs.copyFileSync(from, to);
190
- if (e.name.endsWith('.sh')) {
191
- fs.chmodSync(to, 0o755);
192
- }
193
120
  }
194
121
  }
195
122
  }
196
123
 
197
- function getOptionalDirs(cfg) {
198
- return cfg.optionalCursorDirs || cfg.optionalClaudeDirs || [];
199
- }
200
-
201
124
  function removeDirIfExists(dir) {
202
125
  if (fs.existsSync(dir)) {
203
126
  fs.rmSync(dir, { recursive: true, force: true });
@@ -210,157 +133,76 @@ function removeFileIfExists(filePath) {
210
133
  }
211
134
  }
212
135
 
213
- function listPresetNames(presetsRoot) {
214
- if (!fs.existsSync(presetsRoot)) {
215
- return [];
216
- }
217
- return fs
218
- .readdirSync(presetsRoot, { withFileTypes: true })
219
- .filter((e) => e.isDirectory())
220
- .map((e) => e.name)
221
- .filter((name) => PRESET_NAME_RE.test(name));
222
- }
223
-
224
136
  function presetHasAnySource(base, cfg) {
225
137
  const rulesSrc = path.join(base, 'rules');
226
138
  const commandsSrc = path.join(base, 'commands');
227
139
  if (fs.existsSync(rulesSrc) || fs.existsSync(commandsSrc)) {
228
140
  return true;
229
141
  }
230
- for (const name of getOptionalDirs(cfg)) {
231
- if (fs.existsSync(path.join(base, name))) {
232
- 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
+ }
233
147
  }
234
148
  }
235
149
  if (cfg.claudeMdSrc && fs.existsSync(path.join(base, cfg.claudeMdSrc))) {
236
150
  return true;
237
151
  }
238
- if (cfg.hooksJsonSrc && fs.existsSync(path.join(base, cfg.hooksJsonSrc))) {
239
- return true;
240
- }
241
- for (const name of cfg.toolRootFiles || []) {
242
- if (fs.existsSync(path.join(base, name))) {
243
- return true;
244
- }
245
- }
246
152
  return false;
247
153
  }
248
154
 
249
- function resolveTargetCwd(cwdOption) {
250
- const cwd = cwdOption ? path.resolve(cwdOption) : process.cwd();
251
- if (!fs.existsSync(cwd)) {
252
- console.error(`Target directory does not exist: ${cwd}`);
253
- process.exit(1);
254
- }
255
- if (!fs.statSync(cwd).isDirectory()) {
256
- 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.`);
257
159
  process.exit(1);
258
160
  }
259
- return cwd;
260
- }
261
-
262
- function resolvePresetName(tool, preset, packageRoot) {
263
- const cfg = PRESETS[tool];
264
- const presetsRoot = path.join(packageRoot, ...cfg.relBase);
265
- let name = preset || DEFAULT_PRESET;
266
-
267
- const nameErr = validatePresetName(name);
161
+ const nameErr = validatePresetName(preset);
268
162
  if (nameErr) {
269
163
  console.error(nameErr);
270
164
  process.exit(1);
271
165
  }
272
-
273
- let base = path.join(presetsRoot, name);
274
- if (!presetHasAnySource(base, cfg)) {
275
- const available = listPresetNames(presetsRoot);
276
- if (!preset && available.includes(DEFAULT_PRESET)) {
277
- name = DEFAULT_PRESET;
278
- base = path.join(presetsRoot, name);
279
- } else {
280
- console.error(`Preset "${name}" for ${tool} not found.`);
281
- console.error(`Looked in: ${base}`);
282
- console.error(`Package root: ${packageRoot}`);
283
- if (available.length > 0) {
284
- console.error(`Available presets: ${available.join(', ')}`);
285
- } else {
286
- console.error(`No presets directory at: ${presetsRoot}`);
287
- console.error(
288
- 'The installed package may be incomplete. Reinstall @bonesofspring/ai-rules or run from the package source.',
289
- );
290
- }
291
- process.exit(1);
292
- }
293
- }
294
-
166
+ const presetsRoot = path.join(packageRoot, ...cfg.relBase);
167
+ const base = path.join(presetsRoot, preset);
295
168
  assertPresetDirInsideRoot(presetsRoot, base);
296
- return { name, base, presetsRoot };
297
- }
298
-
299
- function cmdInit(tool, preset, cwdOption) {
300
- const cfg = PRESETS[tool];
301
- if (!cfg) {
302
- console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
303
- process.exit(1);
304
- }
305
-
306
- const packageRoot = getPackageRoot();
307
- const cwd = resolveTargetCwd(cwdOption);
308
- const { name: presetName, base } = resolvePresetName(tool, preset, packageRoot);
309
169
 
310
170
  const rulesSrc = path.join(base, 'rules');
311
171
  const commandsSrc = path.join(base, 'commands');
312
- const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
313
- const toolRootAbs = path.join(cwd, toolRoot);
314
-
315
- fs.mkdirSync(toolRootAbs, { recursive: true });
172
+ const cwd = process.cwd();
316
173
 
317
- let copiedSteps = 0;
174
+ if (!presetHasAnySource(base, cfg)) {
175
+ console.error(`Preset "${preset}" for ${tool} not found under ${base}`);
176
+ process.exit(1);
177
+ }
318
178
 
319
179
  try {
320
180
  if (tool === 'claude' && cfg.claudeMdSrc) {
321
181
  const mdFrom = path.join(base, cfg.claudeMdSrc);
322
182
  if (fs.existsSync(mdFrom)) {
323
- fs.copyFileSync(mdFrom, path.join(toolRootAbs, 'CLAUDE.md'));
324
- console.log(`Copied CLAUDE.md ${path.join(toolRoot, 'CLAUDE.md')}`);
325
- 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');
326
187
  }
327
188
  }
328
189
 
329
190
  if (fs.existsSync(rulesSrc)) {
330
191
  copyDir(rulesSrc, path.join(cwd, cfg.rulesDir));
331
192
  console.log(`Copied rules → ${cfg.rulesDir}`);
332
- copiedSteps++;
333
193
  }
334
194
  if (fs.existsSync(commandsSrc)) {
335
195
  copyDir(commandsSrc, path.join(cwd, cfg.commandsDir));
336
196
  console.log(`Copied commands → ${cfg.commandsDir}`);
337
- copiedSteps++;
338
- }
339
-
340
- for (const name of getOptionalDirs(cfg)) {
341
- const src = path.join(base, name);
342
- if (fs.existsSync(src)) {
343
- copyDir(src, path.join(toolRootAbs, name));
344
- console.log(`Copied ${name}/ → ${toolRoot}/${name}/`);
345
- copiedSteps++;
346
- }
347
- }
348
-
349
- if (cfg.hooksJsonSrc) {
350
- const hooksFrom = path.join(base, cfg.hooksJsonSrc);
351
- if (fs.existsSync(hooksFrom)) {
352
- fs.copyFileSync(hooksFrom, path.join(toolRootAbs, 'hooks.json'));
353
- console.log(`Copied hooks.json → ${toolRoot}/hooks.json`);
354
- copiedSteps++;
355
- }
356
197
  }
357
198
 
358
- for (const name of cfg.toolRootFiles || []) {
359
- const from = path.join(base, name);
360
- if (fs.existsSync(from)) {
361
- fs.copyFileSync(from, path.join(toolRootAbs, name));
362
- console.log(`Copied ${name} → ${toolRoot}/${name}`);
363
- 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
+ }
364
206
  }
365
207
  }
366
208
  } catch (err) {
@@ -368,95 +210,66 @@ function cmdInit(tool, preset, cwdOption) {
368
210
  console.error(`init failed: ${msg}`);
369
211
  process.exit(1);
370
212
  }
371
-
372
- if (copiedSteps === 0) {
373
- console.error(`init failed: preset "${presetName}" had no files to copy from ${base}`);
374
- process.exit(1);
375
- }
376
-
377
- if (!fs.existsSync(toolRootAbs)) {
378
- console.error(`init failed: ${toolRootAbs} was not created`);
379
- process.exit(1);
380
- }
381
-
382
- console.log(`Done. ${toolRoot} installed at ${toolRootAbs} (preset: ${presetName})`);
383
213
  }
384
214
 
385
- function cmdClean(tool, cwdOption) {
215
+ function cmdClean(tool) {
386
216
  const cfg = PRESETS[tool];
387
217
  if (!cfg) {
388
218
  console.error(`Unknown tool: ${tool}. Use cursor or claude.`);
389
219
  process.exit(1);
390
220
  }
391
- const cwd = resolveTargetCwd(cwdOption);
392
- const toolRoot = tool === 'cursor' ? '.cursor' : '.claude';
393
- const removedParts = [cfg.rulesDir, cfg.commandsDir];
394
-
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
+ }
395
227
  removeDirIfExists(path.join(cwd, cfg.rulesDir));
396
228
  removeDirIfExists(path.join(cwd, cfg.commandsDir));
397
-
398
- const optionalDirs = getOptionalDirs(cfg);
399
- for (const name of optionalDirs) {
400
- removeDirIfExists(path.join(cwd, toolRoot, name));
401
- removedParts.push(`${toolRoot}/${name}`);
402
- }
403
-
404
- if (cfg.claudeMdSrc) {
405
- removeFileIfExists(path.join(cwd, toolRoot, 'CLAUDE.md'));
406
- removedParts.push(`${toolRoot}/CLAUDE.md`);
407
- }
408
-
409
- if (cfg.hooksJsonSrc) {
410
- removeFileIfExists(path.join(cwd, toolRoot, 'hooks.json'));
411
- removedParts.push(`${toolRoot}/hooks.json`);
412
- }
413
-
414
- for (const name of cfg.toolRootFiles || []) {
415
- removeFileIfExists(path.join(cwd, toolRoot, name));
416
- removedParts.push(`${toolRoot}/${name}`);
417
- }
418
-
419
- 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
+ );
420
242
  }
421
243
 
422
244
  function main() {
423
245
  const argv = process.argv.slice(2);
424
- if (argv.length === 0) {
246
+ if (argv.length === 0 || argv.includes('-h') || argv.includes('--help')) {
425
247
  printHelp();
426
248
  process.exit(0);
427
249
  }
428
250
 
429
251
  const parsed = parseArgs(argv);
430
- if (parsed.help) {
431
- printHelp();
432
- process.exit(0);
433
- }
252
+ const [command, tool] = parsed._;
434
253
 
435
254
  if (parsed.presetError) {
436
255
  console.error(parsed.presetError);
437
256
  process.exit(1);
438
257
  }
439
- if (parsed.cwdError) {
440
- console.error(parsed.cwdError);
441
- process.exit(1);
442
- }
443
-
444
- const { command, tool, preset } = normalizeInitClean(parsed);
445
258
 
446
259
  if (command === 'init') {
447
- if (!tool || !TOOLS.has(tool)) {
448
- console.error('Missing or invalid tool. Use: init cursor|claude [--preset next]');
260
+ if (!tool) {
261
+ console.error('Missing <cursor|claude> after init.');
449
262
  process.exit(1);
450
263
  }
451
- cmdInit(tool, preset, parsed.cwd);
264
+ cmdInit(tool, parsed.preset);
452
265
  return;
453
266
  }
454
267
  if (command === 'clean') {
455
- if (!tool || !TOOLS.has(tool)) {
456
- console.error('Missing or invalid tool. Use: clean cursor|claude');
268
+ if (!tool) {
269
+ console.error('Missing <cursor|claude> after clean.');
457
270
  process.exit(1);
458
271
  }
459
- cmdClean(tool, parsed.cwd);
272
+ cmdClean(tool);
460
273
  return;
461
274
  }
462
275
 
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bonesofspring/ai-rules",
3
- "version": "0.2.1",
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,21 +16,12 @@
16
16
  "bonesofspring"
17
17
  ],
18
18
  "bin": {
19
- "ai-rules": "bin/cli.js"
20
- },
21
- "scripts": {
22
- "validate": "bash scripts/validate-preset.sh",
23
- "validate:init": "bash scripts/validate-preset.sh --init-smoke",
24
- "validate:golden": "bash scripts/validate-preset.sh --check-golden"
19
+ "ai-rules": "./bin/cli.js"
25
20
  },
26
21
  "files": [
27
22
  "README.md",
28
- "CHANGELOG.md",
29
- "CONTRIBUTING.md",
30
23
  "bin",
31
- "fragments",
32
- "presets",
33
- "scripts"
24
+ "presets"
34
25
  ],
35
26
  "engines": {
36
27
  "node": ">=18"
@@ -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`.