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