@bonesofspring/ai-rules 0.1.42 → 0.2.1

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 (103) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CONTRIBUTING.md +102 -0
  3. package/README.md +2 -0
  4. package/bin/cli.js +4 -2
  5. package/fragments/BUGBOT.md +23 -0
  6. package/package.json +10 -2
  7. package/presets/claude/next/BUGBOT.md +16 -0
  8. package/presets/claude/next/CLAUDE.md +6 -2
  9. package/presets/claude/next/agents/README.md +77 -15
  10. package/presets/claude/next/agents/api-contract-reviewer.md +1 -1
  11. package/presets/claude/next/agents/build-verifier.md +2 -0
  12. package/presets/claude/next/agents/code-reviewer.md +1 -1
  13. package/presets/claude/next/agents/feature-developer.md +8 -21
  14. package/presets/claude/next/agents/playwright-test-generator.md +16 -65
  15. package/presets/claude/next/agents/playwright-test-healer.md +16 -51
  16. package/presets/claude/next/agents/playwright-test-planner.md +15 -55
  17. package/presets/claude/next/agents/task-analyst.md +1 -1
  18. package/presets/claude/next/agents/task-router.md +50 -128
  19. package/presets/claude/next/agents/tech-writer.md +1 -1
  20. package/presets/claude/next/commands/task.md +1 -1
  21. package/presets/claude/next/hooks.json +17 -0
  22. package/presets/claude/next/rules/README.md +7 -3
  23. package/presets/claude/next/rules/api-and-data/api-services.md +3 -3
  24. package/presets/claude/next/rules/api-and-data/http-client.md +2 -2
  25. package/presets/claude/next/rules/api-and-data/store-rtk.md +2 -2
  26. package/presets/claude/next/rules/architecture/README.md +9 -11
  27. package/presets/claude/next/rules/architecture/api-public-imports.md +3 -26
  28. package/presets/claude/next/rules/architecture/architecture-boundaries-ui.md +15 -0
  29. package/presets/claude/next/rules/architecture/architecture-boundaries.md +10 -7
  30. package/presets/claude/next/rules/architecture/feature-delivery-workflow.md +44 -69
  31. package/presets/claude/next/rules/architecture/layer-barrel-exports.md +6 -6
  32. package/presets/claude/next/rules/architecture/public-imports.md +46 -0
  33. package/presets/claude/next/rules/architecture/reference-features.md +4 -1
  34. package/presets/claude/next/rules/architecture/types-public-imports.md +3 -29
  35. package/presets/claude/next/rules/stack/README.md +2 -1
  36. package/presets/claude/next/rules/stack/navigation-router-ui.md +15 -0
  37. package/presets/claude/next/rules/stack/navigation-router.md +2 -1
  38. package/presets/claude/next/rules/stack/next-app-core.md +20 -70
  39. package/presets/claude/next/rules/stack/no-type-assertion.md +3 -2
  40. package/presets/claude/next/rules/stack/types-jsdoc.md +1 -1
  41. package/presets/claude/next/rules/tooling-and-review/agent-team-intake.md +6 -0
  42. package/presets/claude/next/rules/tooling-and-review/agent-team-orchestrator.md +20 -1
  43. package/presets/claude/next/rules/tooling-and-review/code-quality.md +3 -11
  44. package/presets/claude/next/rules/tooling-and-review/code-review-mr.md +22 -59
  45. package/presets/claude/next/rules/tooling-and-review/package-manager.md +6 -15
  46. package/presets/claude/next/rules/tooling-and-review/post-change-lint.md +14 -24
  47. package/presets/claude/next/rules/ui-and-accessibility/README.md +1 -2
  48. package/presets/claude/next/rules/ui-and-accessibility/css-property-order.md +6 -18
  49. package/presets/claude/next/rules/ui-and-accessibility/react-ui.md +5 -4
  50. package/presets/claude/next/skills/feature-delivery/SKILL.md +5 -20
  51. package/presets/claude/next/team/README.md +1 -0
  52. package/presets/claude/next/team/fixtures/bugfix-standard.json +15 -0
  53. package/presets/claude/next/team/fixtures/feature-full.json +16 -0
  54. package/presets/claude/next/team/fixtures/feature-light.json +17 -0
  55. package/presets/cursor/next/AGENTS.md +3 -10
  56. package/presets/cursor/next/BUGBOT.md +2 -0
  57. package/presets/cursor/next/agents/README.md +97 -15
  58. package/presets/cursor/next/agents/api-contract-reviewer.md +1 -1
  59. package/presets/cursor/next/agents/build-verifier.md +2 -0
  60. package/presets/cursor/next/agents/code-reviewer.md +2 -2
  61. package/presets/cursor/next/agents/feature-developer.md +9 -30
  62. package/presets/cursor/next/agents/task-analyst.md +1 -1
  63. package/presets/cursor/next/agents/task-router.md +49 -127
  64. package/presets/cursor/next/agents/tech-writer.md +1 -1
  65. package/presets/cursor/next/commands/task.md +1 -1
  66. package/presets/cursor/next/rules/README.md +27 -8
  67. package/presets/cursor/next/rules/agent-team-intake.mdc +2 -2
  68. package/presets/cursor/next/rules/agent-team-orchestrator.mdc +14 -1
  69. package/presets/cursor/next/rules/api-public-imports.mdc +3 -24
  70. package/presets/cursor/next/rules/api-services.mdc +3 -3
  71. package/presets/cursor/next/rules/architecture-boundaries-ui.mdc +15 -0
  72. package/presets/cursor/next/rules/architecture-boundaries.mdc +7 -7
  73. package/presets/cursor/next/rules/code-quality-and-refactoring.mdc +3 -11
  74. package/presets/cursor/next/rules/code-review-mr.mdc +20 -46
  75. package/presets/cursor/next/rules/css-property-order-stylelint.mdc +6 -18
  76. package/presets/cursor/next/rules/feature-delivery-workflow.mdc +45 -22
  77. package/presets/cursor/next/rules/http-client.mdc +2 -2
  78. package/presets/cursor/next/rules/layer-barrel-exports.mdc +6 -6
  79. package/presets/cursor/next/rules/navigation-router-stack.mdc +1 -1
  80. package/presets/cursor/next/rules/navigation-router-ui.mdc +16 -0
  81. package/presets/cursor/next/rules/next-app-core.mdc +18 -71
  82. package/presets/cursor/next/rules/no-cross-component-styles-import.mdc +3 -53
  83. package/presets/cursor/next/rules/no-type-assertion-as-import-export.mdc +4 -1
  84. package/presets/cursor/next/rules/package-manager.mdc +6 -15
  85. package/presets/cursor/next/rules/post-change-lint.mdc +14 -24
  86. package/presets/cursor/next/rules/public-imports.mdc +48 -0
  87. package/presets/cursor/next/rules/react-ui.mdc +5 -4
  88. package/presets/cursor/next/rules/reference-features.mdc +5 -1
  89. package/presets/cursor/next/rules/store-rtk.mdc +2 -2
  90. package/presets/cursor/next/rules/types-jsdoc.mdc +1 -1
  91. package/presets/cursor/next/rules/types-public-imports.mdc +3 -26
  92. package/presets/cursor/next/skills/feature-delivery/SKILL.md +5 -20
  93. package/presets/cursor/next/team/README.md +1 -0
  94. package/presets/cursor/next/team/fixtures/bugfix-standard.json +15 -0
  95. package/presets/cursor/next/team/fixtures/feature-full.json +16 -0
  96. package/presets/cursor/next/team/fixtures/feature-light.json +17 -0
  97. package/scripts/README.md +84 -0
  98. package/scripts/golden-prompts.json +276 -0
  99. package/scripts/preset-manifest.json +72 -0
  100. package/scripts/regression-results/.gitkeep +0 -0
  101. package/scripts/validate-preset.sh +484 -0
  102. package/presets/claude/next/rules/ui-and-accessibility/component-styles.md +0 -56
  103. package/presets/cursor/next/rules/feature-delivery-flow.mdc +0 -74
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env bash
2
+ # validate-preset.sh — static regression checks for @bonesofspring/ai-rules presets.
3
+ #
4
+ # Usage:
5
+ # ./scripts/validate-preset.sh # full static validation
6
+ # ./scripts/validate-preset.sh --init-smoke # + init cursor/claude into temp dirs
7
+ # ./scripts/validate-preset.sh --check-golden # + validate golden-prompts.json schema/agents
8
+ # ./scripts/validate-preset.sh --print-golden # print manual router regression checklist
9
+ # ./scripts/validate-preset.sh --compare-pipeline <dir> # compare pipeline.json files to golden
10
+ #
11
+ set -euo pipefail
12
+
13
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
+ PKG_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
15
+ CURSOR_PRESET="$PKG_ROOT/presets/cursor/next"
16
+ CLAUDE_PRESET="$PKG_ROOT/presets/claude/next"
17
+ MANIFEST="$SCRIPT_DIR/preset-manifest.json"
18
+ GOLDEN="$SCRIPT_DIR/golden-prompts.json"
19
+
20
+ RUN_INIT_SMOKE=false
21
+ CHECK_GOLDEN=false
22
+ PRINT_GOLDEN=false
23
+ COMPARE_PIPELINE_DIR=""
24
+
25
+ while [[ $# -gt 0 ]]; do
26
+ case "$1" in
27
+ --init-smoke) RUN_INIT_SMOKE=true ;;
28
+ --check-golden) CHECK_GOLDEN=true ;;
29
+ --print-golden) PRINT_GOLDEN=true ;;
30
+ --compare-pipeline)
31
+ shift
32
+ COMPARE_PIPELINE_DIR="${1:-}"
33
+ if [[ -z "$COMPARE_PIPELINE_DIR" ]]; then
34
+ echo "error: --compare-pipeline requires a directory" >&2
35
+ exit 1
36
+ fi
37
+ CHECK_GOLDEN=true
38
+ ;;
39
+ -h | --help)
40
+ sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
41
+ exit 0
42
+ ;;
43
+ *)
44
+ echo "error: unknown argument: $1" >&2
45
+ exit 1
46
+ ;;
47
+ esac
48
+ shift
49
+ done
50
+
51
+ FAILURES=0
52
+ WARNINGS=0
53
+
54
+ pass() { printf ' \033[32m✓\033[0m %s\n' "$1"; }
55
+ fail() { printf ' \033[31m✗\033[0m %s\n' "$1"; FAILURES=$((FAILURES + 1)); }
56
+ warn() { printf ' \033[33m!\033[0m %s\n' "$1"; WARNINGS=$((WARNINGS + 1)); }
57
+ section() { printf '\n\033[1m%s\033[0m\n' "$1"; }
58
+
59
+ require_file() {
60
+ local f="$1"
61
+ if [[ ! -f "$f" ]]; then
62
+ fail "missing file: $f"
63
+ return 1
64
+ fi
65
+ return 0
66
+ }
67
+
68
+ require_dir() {
69
+ local d="$1"
70
+ if [[ ! -d "$d" ]]; then
71
+ fail "missing directory: $d"
72
+ return 1
73
+ fi
74
+ return 0
75
+ }
76
+
77
+ node_json() {
78
+ node -e "$1"
79
+ }
80
+
81
+ # --- print golden checklist (manual LLM regression) ---
82
+ if $PRINT_GOLDEN; then
83
+ section "Golden router regression checklist (manual)"
84
+ node - "$GOLDEN" <<'NODE'
85
+ const fs = require('fs');
86
+ const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
87
+ console.log('Run each prompt via /task in a fresh chat. Save pipeline.json as scripts/regression-results/<id>.json\n');
88
+ console.log('| # | id | prompt | intent | first agent |');
89
+ console.log('|---|-----|--------|--------|-------------|');
90
+ data.prompts.forEach((p, i) => {
91
+ const prompt = p.prompt.replace(/\|/g, '\\|').slice(0, 60);
92
+ const first = p.noPipeline ? '(no pipeline)' : (p.firstAgent || '—');
93
+ console.log(`| ${i + 1} | ${p.id} | ${prompt} | ${p.intent} | ${first} |`);
94
+ });
95
+ console.log('\nAfter run: ./scripts/validate-preset.sh --compare-pipeline scripts/regression-results/');
96
+ NODE
97
+ exit 0
98
+ fi
99
+
100
+ section "CLI syntax"
101
+ if node --check "$PKG_ROOT/bin/cli.js" 2>/dev/null; then
102
+ pass "bin/cli.js parses"
103
+ else
104
+ fail "bin/cli.js syntax error"
105
+ fi
106
+
107
+ section "Manifest"
108
+ require_file "$MANIFEST" || true
109
+ require_file "$GOLDEN" || true
110
+
111
+ section "Cursor always-on rules (session start)"
112
+ ALWAYS_ON_COUNT=0
113
+ ALWAYS_ON_BYTES=0
114
+ while IFS= read -r name; do
115
+ [[ -z "$name" ]] && continue
116
+ f="$CURSOR_PRESET/rules/$name"
117
+ if [[ ! -f "$f" ]]; then
118
+ fail "always-on rule missing: $name"
119
+ continue
120
+ fi
121
+ if ! grep -q 'alwaysApply: true' "$f"; then
122
+ fail "$name exists but alwaysApply: true not set"
123
+ else
124
+ ALWAYS_ON_COUNT=$((ALWAYS_ON_COUNT + 1))
125
+ size=$(wc -c <"$f" | tr -d ' ')
126
+ ALWAYS_ON_BYTES=$((ALWAYS_ON_BYTES + size))
127
+ pass "$name ($size bytes)"
128
+ fi
129
+ done < <(node_json "
130
+ const m = require('$MANIFEST');
131
+ m.alwaysOnCursor.forEach((n) => console.log(n));
132
+ ")
133
+
134
+ EXTRA_ALWAYS=$(grep -l 'alwaysApply: true' "$CURSOR_PRESET"/rules/*.mdc 2>/dev/null | wc -l | tr -d ' ')
135
+ if [[ "$EXTRA_ALWAYS" -ne "$ALWAYS_ON_COUNT" ]]; then
136
+ fail "expected $ALWAYS_ON_COUNT alwaysApply rules, found $EXTRA_ALWAYS"
137
+ grep -l 'alwaysApply: true' "$CURSOR_PRESET"/rules/*.mdc 2>/dev/null | while read -r f; do
138
+ echo " - $(basename "$f")"
139
+ done
140
+ fi
141
+
142
+ MAX_BYTES=$(node_json "console.log(require('$MANIFEST').alwaysOnMaxBytes)")
143
+ if [[ "$ALWAYS_ON_BYTES" -gt "$MAX_BYTES" ]]; then
144
+ fail "always-on bundle $ALWAYS_ON_BYTES bytes exceeds budget $MAX_BYTES"
145
+ else
146
+ pass "always-on bundle: $ALWAYS_ON_BYTES / $MAX_BYTES bytes"
147
+ fi
148
+
149
+ section "Claude session-start rules (no paths:)"
150
+ CLAUDE_SESSION_COUNT=0
151
+ while IFS= read -r rel; do
152
+ [[ -z "$rel" ]] && continue
153
+ f="$CLAUDE_PRESET/rules/$rel"
154
+ if [[ ! -f "$f" ]]; then
155
+ fail "claude session-start rule missing: $rel"
156
+ continue
157
+ fi
158
+ if head -8 "$f" | grep -q '^paths:'; then
159
+ fail "$rel must not have paths: (session start)"
160
+ else
161
+ CLAUDE_SESSION_COUNT=$((CLAUDE_SESSION_COUNT + 1))
162
+ pass "$rel"
163
+ fi
164
+ done < <(node_json "
165
+ const m = require('$MANIFEST');
166
+ m.alwaysOnClaude.forEach((n) => console.log(n));
167
+ ")
168
+
169
+ # Unexpected session-start files
170
+ while IFS= read -r f; do
171
+ [[ "$(basename "$f")" == "README.md" ]] && continue
172
+ rel="${f#"$CLAUDE_PRESET/rules/"}"
173
+ if ! node_json "
174
+ const m = require('$MANIFEST');
175
+ process.exit(m.alwaysOnClaude.includes('$rel') ? 0 : 1);
176
+ " 2>/dev/null; then
177
+ if ! head -8 "$f" | grep -q '^paths:'; then
178
+ fail "unexpected claude session-start file (no paths:): $rel"
179
+ fi
180
+ fi
181
+ done < <(find "$CLAUDE_PRESET/rules" -name '*.md' -type f 2>/dev/null)
182
+
183
+ section "Cursor ↔ Claude rule pairs"
184
+ while IFS=$'\t' read -r cursor claude; do
185
+ cf="$CURSOR_PRESET/rules/$cursor"
186
+ lf="$CLAUDE_PRESET/rules/$claude"
187
+ if [[ ! -f "$cf" ]]; then fail "cursor pair missing: $cursor"; fi
188
+ if [[ ! -f "$lf" ]]; then fail "claude pair missing: $claude"; fi
189
+ if [[ -f "$cf" && -f "$lf" ]]; then pass "$cursor ↔ $claude"; fi
190
+ done < <(node_json "
191
+ const m = require('$MANIFEST');
192
+ m.rulePairs.forEach(([c, l]) => console.log(c + '\t' + l));
193
+ ")
194
+
195
+ section "Parity: agents, skills, fixtures"
196
+ while IFS= read -r dir; do
197
+ [[ -z "$dir" ]] && continue
198
+ cdir="$CURSOR_PRESET/$dir"
199
+ ldir="$CLAUDE_PRESET/$dir"
200
+ require_dir "$cdir" || continue
201
+ require_dir "$ldir" || continue
202
+ # Compare file names (excluding README.md)
203
+ diff_out=$(diff <(find "$cdir" -type f ! -name 'README.md' -exec basename {} \; | sort -u) \
204
+ <(find "$ldir" -type f ! -name 'README.md' -exec basename {} \; | sort -u) 2>/dev/null || true)
205
+ if [[ -n "$diff_out" ]]; then
206
+ fail "$dir file name mismatch"
207
+ echo "$diff_out" | sed 's/^/ /'
208
+ else
209
+ pass "$dir names match"
210
+ fi
211
+ done < <(node_json "
212
+ const m = require('$MANIFEST');
213
+ m.parityDirs.forEach((d) => console.log(d));
214
+ ")
215
+
216
+ section "task-router parity (cursor ↔ claude)"
217
+ CR="$CURSOR_PRESET/agents/task-router.md"
218
+ CL="$CLAUDE_PRESET/agents/task-router.md"
219
+ if [[ -f "$CR" && -f "$CL" ]]; then
220
+ # Strip frontmatter and compare body similarity via line count delta
221
+ c_lines=$(tail -n +8 "$CR" | wc -l | tr -d ' ')
222
+ l_lines=$(tail -n +8 "$CL" | wc -l | tr -d ' ')
223
+ delta=$((c_lines > l_lines ? c_lines - l_lines : l_lines - c_lines))
224
+ if [[ "$delta" -gt 15 ]]; then
225
+ warn "task-router.md body length delta $delta lines (cursor=$c_lines, claude=$l_lines) — review parity"
226
+ else
227
+ pass "task-router.md body length similar (Δ=$delta lines)"
228
+ fi
229
+ else
230
+ fail "task-router.md missing in one or both presets"
231
+ fi
232
+
233
+ section "BUGBOT sync (fragments → presets)"
234
+ while IFS= read -r line; do
235
+ src_rel="${line%%$'\t'*}"
236
+ tgt_rel="${line#*$'\t'}"
237
+ src="$PKG_ROOT/$src_rel"
238
+ tgt="$PKG_ROOT/$tgt_rel"
239
+ if [[ ! -f "$src" || ! -f "$tgt" ]]; then
240
+ fail "BUGBOT sync path missing: $src_rel → $tgt_rel"
241
+ continue
242
+ fi
243
+ # Compare numbered priorities only (preset bodies add platform-specific hints)
244
+ src_prio=$(sed -n '/^## Priorities$/,/^## /p' "$src" | grep -E '^[0-9]+\.' | sed 's/ (`.*`)//g' || true)
245
+ tgt_prio=$(sed -n '/^## Priorities$/,/^## /p' "$tgt" | grep -E '^[0-9]+\.' | sed 's/ (`.*`)//g' || true)
246
+ if [[ "$src_prio" != "$tgt_prio" ]]; then
247
+ fail "BUGBOT priorities differ: $tgt_rel"
248
+ diff <(echo "$src_prio") <(echo "$tgt_prio") | sed 's/^/ /' || true
249
+ else
250
+ pass "$tgt_rel priorities match fragment"
251
+ fi
252
+ done < <(node_json "
253
+ const m = require('$MANIFEST');
254
+ for (const item of m.syncedRootFiles) {
255
+ for (const t of item.targets) console.log(item.source + '\t' + t);
256
+ }
257
+ ")
258
+
259
+ section "UI globs consolidation"
260
+ while IFS=$'\t' read -r id file mustMatch mustNotMatch; do
261
+ f="$CURSOR_PRESET/rules/$file"
262
+ [[ ! -f "$f" ]] && { fail "$id: missing $file"; continue; }
263
+ ok=true
264
+ if [[ -n "$mustMatch" && "$mustMatch" != "null" ]]; then
265
+ if ! grep -qE "$mustMatch" "$f"; then
266
+ fail "$id: expected pattern '$mustMatch' in $file"
267
+ ok=false
268
+ fi
269
+ fi
270
+ if [[ -n "$mustNotMatch" && "$mustNotMatch" != "null" ]]; then
271
+ if grep -qE "$mustNotMatch" "$f"; then
272
+ fail "$id: forbidden pattern '$mustNotMatch' found in $file"
273
+ ok=false
274
+ fi
275
+ fi
276
+ $ok && pass "$id"
277
+ done < <(node_json "
278
+ const m = require('$MANIFEST');
279
+ for (const c of m.uiGlobChecks) {
280
+ console.log([
281
+ c.id,
282
+ c.file,
283
+ c.mustMatch || '',
284
+ c.mustNotMatch || ''
285
+ ].join('\t'));
286
+ }
287
+ ")
288
+
289
+ section "Wide globs denylist (UI edit must not load)"
290
+ while IFS= read -r file; do
291
+ [[ -z "$file" ]] && continue
292
+ f="$CURSOR_PRESET/rules/$file"
293
+ [[ ! -f "$f" ]] && continue
294
+ # Flag app/src/** or app/src/**/* globs; allow narrowed patterns like app/src/**/index.ts
295
+ if grep -E '^globs:' "$f" | grep -E 'app/src/\*\*' | grep -vq 'index\.ts'; then
296
+ fail "$file has wide UI glob without index.ts narrowing"
297
+ else
298
+ pass "$file has no wide UI glob"
299
+ fi
300
+ done < <(node_json "
301
+ const m = require('$MANIFEST');
302
+ m.wideGlobDenylist.forEach((n) => console.log(n));
303
+ ")
304
+
305
+ section "Team fixtures vs golden profiles"
306
+ for fixture in feature-full feature-light bugfix-standard; do
307
+ for tool in cursor claude; do
308
+ f="$PKG_ROOT/presets/$tool/next/team/fixtures/${fixture}.json"
309
+ if [[ ! -f "$f" ]]; then
310
+ fail "missing fixture: $f"
311
+ continue
312
+ fi
313
+ if ! node -e "
314
+ const j = require(process.argv[1]);
315
+ if (!j.intent || !Array.isArray(j.steps) || j.steps.length < 1) process.exit(1);
316
+ " "$f" 2>/dev/null; then
317
+ fail "invalid fixture JSON: $f"
318
+ else
319
+ pass "$tool/$fixture.json"
320
+ fi
321
+ done
322
+ done
323
+
324
+ if $CHECK_GOLDEN; then
325
+ section "Golden prompts schema & agent references"
326
+ node - "$GOLDEN" "$CURSOR_PRESET/agents" "$COMPARE_PIPELINE_DIR" <<'NODE'
327
+ const fs = require('fs');
328
+ const path = require('path');
329
+
330
+ const goldenPath = process.argv[2];
331
+ const agentsDir = process.argv[3];
332
+ const compareDir = process.argv[4] || '';
333
+
334
+ let failures = 0;
335
+ const fail = (msg) => { console.error(` \x1b[31m✗\x1b[0m ${msg}`); failures++; };
336
+ const pass = (msg) => console.log(` \x1b[32m✓\x1b[0m ${msg}`);
337
+ const warn = (msg) => console.log(` \x1b[33m!\x1b[0m ${msg}`);
338
+
339
+ const data = JSON.parse(fs.readFileSync(goldenPath, 'utf8'));
340
+ const agentFiles = fs.readdirSync(agentsDir).filter((f) => f.endsWith('.md') && f !== 'README.md');
341
+ const knownAgents = new Set(agentFiles.map((f) => f.replace(/\.md$/, '')));
342
+
343
+ const requiredFields = ['id', 'prompt', 'intent'];
344
+
345
+ for (const p of data.prompts) {
346
+ for (const field of requiredFields) {
347
+ if (!p[field]) fail(`prompt ${p.id || '?'} missing field: ${field}`);
348
+ }
349
+
350
+ if (p.noPipeline) {
351
+ if (p.intent !== 'retro') fail(`${p.id}: noPipeline only valid for retro`);
352
+ pass(`${p.id}: retro (no pipeline)`);
353
+ continue;
354
+ }
355
+
356
+ if (!p.firstAgent) fail(`${p.id}: missing firstAgent`);
357
+ else if (!knownAgents.has(p.firstAgent)) fail(`${p.id}: unknown firstAgent ${p.firstAgent}`);
358
+ else pass(`${p.id}: firstAgent ${p.firstAgent} exists`);
359
+
360
+ for (const agent of [...(p.requiredSequence || []), ...(p.mustInclude || [])]) {
361
+ if (!knownAgents.has(agent)) fail(`${p.id}: unknown agent ${agent}`);
362
+ }
363
+
364
+ for (const sc of p.stepConstraints || []) {
365
+ if (!knownAgents.has(sc.agent)) fail(`${p.id}: stepConstraints unknown agent ${sc.agent}`);
366
+ }
367
+ }
368
+
369
+ if (compareDir) {
370
+ console.log('');
371
+ console.log('\x1b[1mCompare pipeline.json to golden\x1b[0m');
372
+ for (const p of data.prompts) {
373
+ if (p.noPipeline) continue;
374
+ const pipelinePath = path.join(compareDir, `${p.id}.json`);
375
+ if (!fs.existsSync(pipelinePath)) {
376
+ warn(`${p.id}: no file ${pipelinePath} (skip)`);
377
+ continue;
378
+ }
379
+ const pipeline = JSON.parse(fs.readFileSync(pipelinePath, 'utf8'));
380
+ const agents = (pipeline.steps || []).map((s) => s.agent);
381
+
382
+ if (pipeline.intent && pipeline.intent !== p.intent) {
383
+ fail(`${p.id}: intent got ${pipeline.intent}, want ${p.intent}`);
384
+ }
385
+ if (p.profile && pipeline.profile && pipeline.profile !== p.profile) {
386
+ warn(`${p.id}: profile got ${pipeline.profile}, expected ${p.profile}`);
387
+ }
388
+ if (agents[0] !== p.firstAgent) {
389
+ fail(`${p.id}: first agent got ${agents[0]}, want ${p.firstAgent}`);
390
+ }
391
+
392
+ // requiredSequence — subsequence check
393
+ let idx = 0;
394
+ for (const want of p.requiredSequence || []) {
395
+ while (idx < agents.length && agents[idx] !== want) idx++;
396
+ if (idx >= agents.length) {
397
+ fail(`${p.id}: requiredSequence missing ${want} (got: ${agents.join(' → ')})`);
398
+ break;
399
+ }
400
+ idx++;
401
+ }
402
+ if ((p.requiredSequence || []).length && idx > 0) {
403
+ pass(`${p.id}: requiredSequence satisfied (${agents.join(' → ')})`);
404
+ }
405
+
406
+ for (const forbidden of p.mustNotInclude || []) {
407
+ if (agents.includes(forbidden)) {
408
+ fail(`${p.id}: mustNotInclude violated: ${forbidden}`);
409
+ }
410
+ }
411
+
412
+ for (const sc of p.stepConstraints || []) {
413
+ const step = (pipeline.steps || []).find((s) => s.agent === sc.agent);
414
+ if (!step) {
415
+ warn(`${p.id}: stepConstraints agent ${sc.agent} not in pipeline`);
416
+ continue;
417
+ }
418
+ if (sc.skipIf && step.skipIf !== sc.skipIf) {
419
+ fail(`${p.id}: ${sc.agent}.skipIf got ${step.skipIf}, want ${sc.skipIf}`);
420
+ }
421
+ }
422
+
423
+ if (Array.isArray(p.humanGates) && Array.isArray(pipeline.humanGates)) {
424
+ for (const gate of p.humanGates) {
425
+ if (!pipeline.humanGates.includes(gate)) {
426
+ fail(`${p.id}: missing humanGate ${gate}`);
427
+ }
428
+ }
429
+ if (p.humanGates.length) pass(`${p.id}: humanGates OK`);
430
+ }
431
+ }
432
+ }
433
+
434
+ process.exit(failures > 0 ? 1 : 0);
435
+ NODE
436
+ golden_exit=$?
437
+ if [[ "$golden_exit" -ne 0 ]]; then
438
+ FAILURES=$((FAILURES + golden_exit))
439
+ fi
440
+ fi
441
+
442
+ if $RUN_INIT_SMOKE; then
443
+ section "Init smoke (cursor + claude)"
444
+ TMP_BASE=$(mktemp -d "${TMPDIR:-/tmp}/ai-rules-validate.XXXXXX")
445
+ cleanup() { rm -rf "$TMP_BASE"; }
446
+ trap cleanup EXIT
447
+
448
+ for tool in cursor claude; do
449
+ target="$TMP_BASE/$tool-project"
450
+ mkdir -p "$target"
451
+ if node "$PKG_ROOT/bin/cli.js" init "$tool" --preset next --cwd "$target" >/dev/null 2>&1; then
452
+ pass "init $tool → $target"
453
+ # Spot-check critical paths
454
+ case "$tool" in
455
+ cursor)
456
+ for p in .cursor/rules/next-app-core.mdc .cursor/agents/task-router.md .cursor/team/fixtures/feature-full.json .cursor/BUGBOT.md .cursor/AGENTS.md; do
457
+ [[ -f "$target/$p" ]] && pass " $p" || fail " missing after init: $p"
458
+ done
459
+ ;;
460
+ claude)
461
+ for p in .claude/rules/stack/next-app-core.md .claude/agents/task-router.md .claude/team/fixtures/feature-full.json .claude/CLAUDE.md; do
462
+ [[ -f "$target/$p" ]] && pass " $p" || fail " missing after init: $p"
463
+ done
464
+ ;;
465
+ esac
466
+ else
467
+ fail "init $tool failed"
468
+ fi
469
+ done
470
+ fi
471
+
472
+ section "Summary"
473
+ if [[ "$FAILURES" -eq 0 ]]; then
474
+ printf '\033[32mPASS\033[0m — %s warnings\n' "$WARNINGS"
475
+ echo ""
476
+ echo "Manual router regression:"
477
+ echo " ./scripts/validate-preset.sh --print-golden"
478
+ echo " # save outputs to scripts/regression-results/<id>.json"
479
+ echo " ./scripts/validate-preset.sh --check-golden --compare-pipeline scripts/regression-results/"
480
+ exit 0
481
+ else
482
+ printf '\033[31mFAIL\033[0m — %s error(s), %s warning(s)\n' "$FAILURES" "$WARNINGS"
483
+ exit 1
484
+ fi
@@ -1,56 +0,0 @@
1
- ---
2
- paths:
3
- - app/src/ui/**/*.ts
4
- - app/src/ui/**/*.tsx
5
- ---
6
-
7
- # Стили только рядом с компонентом
8
-
9
- ## Имя корневого styled-элемента: `Root`
10
-
11
- - **Корневой** styled-элемент — **`Root`**.
12
- - В разметке: `<s.Root>...</s.Root>` при `import * as s from './styles'`.
13
- - Вложенные — `Title`, `List`, `Item` и т.д.
14
- - Единственная внешняя styled-обёртка всего JSX — **`Root`**, не `Container` / `Wrapper`.
15
-
16
- ```tsx
17
- // ✅ Хорошо
18
- export const Root = styled.div` ... `
19
- // в компоненте: <s.Root>...</s.Root>
20
-
21
- // ❌ Плохо для единственной обёртки
22
- export const Service = styled.div` ... `
23
- ```
24
-
25
- ## Правило
26
-
27
- **Запрещено** импортировать модули стилей **другого** UI-компонента или блока.
28
-
29
- Под «модулями стилей»:
30
-
31
- - `styles.ts` / `styles.tsx` рядом с компонентом;
32
- - `*.module.css`, `*.module.scss`;
33
- - файлы, экспортирующие styled-примитивы одного компонента.
34
-
35
- ## Разрешено
36
-
37
- - `import * as s from './styles'` — в той же папке.
38
- - Общие примитивы дизайн-системы, токены, `@/ui/components/...`.
39
- - Повторное использование визуала через **сам компонент**, не через его `styles`.
40
-
41
- ## Примеры
42
-
43
- ```tsx
44
- // ✅ Хорошо
45
- import * as s from './styles'
46
-
47
- // ❌ Плохо
48
- import * as s from '../../styles'
49
- import * as s from '../RequestForAnalysisServices/styles'
50
- ```
51
-
52
- ## Почему
53
-
54
- - **`Root`** — быстрая ориентация в разметке.
55
- - Стили и компонент меняются вместе.
56
- - Проще рефакторинг.
@@ -1,74 +0,0 @@
1
- ---
2
- description: Сквозной flow фичи (типы, API, моки, store, UI, тесты) и точки регистрации в репозитории
3
- alwaysApply: false
4
- ---
5
-
6
- # Поставка фичи: порядок слоёв и регистрация
7
-
8
- Плейсхолдеры: `<FeatureName>`, `<ServiceRoot>`, `<Area>` — нейтральные имена; реальные имена брать из соседних фич того же типа.
9
-
10
- ## Инварианты перед кодом
11
-
12
- - Найти в том же слое фичу сопоставимой сложности и **повторить структуру каталогов и паттерн именования**.
13
- - Не добавлять `any`; при необходимости — `unknown` и сужение типа.
14
- - После правок выполнить проверки из **`package.json` каталога `app/`** (типизация, ESLint и связанные скрипты), не оставлять новых ошибок без причины.
15
-
16
- ## Полный flow (слой за слоем)
17
-
18
- ```mermaid
19
- flowchart LR
20
- typesNode["types_domain"]
21
- apiNode["api_services_mappers"]
22
- mocksNode["mocks_msw"]
23
- storeNode["store_slice_thunks"]
24
- mwNode["middleware_optional"]
25
- uiNode["ui_pages_components"]
26
- unitNode["unit_tests"]
27
- e2eNode["e2e_plans_specs"]
28
-
29
- typesNode --> apiNode
30
- apiNode --> mocksNode
31
- apiNode --> storeNode
32
- storeNode --> mwNode
33
- storeNode --> uiNode
34
- apiNode --> unitNode
35
- storeNode --> unitNode
36
- uiNode --> unitNode
37
- uiNode --> e2eNode
38
- mocksNode --> e2eNode
39
- ```
40
-
41
- ## Чеклист по слоям (куда класть и что зарегистрировать)
42
-
43
- 1. **Типы** — `app/src/types/**` (или согласованное имя вроде `<FeatureName>.types.ts`). Доменная модель — источник правды для UI и store.
44
-
45
- 2. **API** — `app/src/api/services/<ServiceRoot>/<Segment>/`: вызовы HTTP, DTO ответов, `*responseMappers.ts`. Новые публичные экспорты (фасады сервисов, типы, **константы путей** для моков) добавить в **`app/src/api/index.ts`** (`@/api`).
46
-
47
- 3. **Моки (MSW)** — `app/src/mocks/data/<feature>/`: `data.ts`, `handlers.ts`; собрать хендлеры в **`app/src/mocks/handlers.ts`**. В хендлерах использовать **те же константы путей**, что и в API (импорт из `@/api`), **не дублировать сырые строки URL**. В репозитории могут быть **два контура** (браузерный worker и Node `setupServer` для тестов): общий список хендлеров должен оставаться согласованным — при добавлении проверять существующие входные точки в `app/src/mocks/**`, чтобы моки были доступны там, где ожидается.
48
-
49
- 4. **Store** — `app/src/store/slices/<FeatureName>/`: slice, thunk’и вызывают методы из **`@/api`**, не HTTP‑клиент. Подключить редюсер в **`app/src/store/reducers.ts`**. Новый **middleware**: реализация в `app/src/store/middleware/**`, регистрация в **`app/src/store/index.ts`** в цепочке `configureStore` (порядок `prepend`/`concat` — как у соседних middleware в этом файле).
50
-
51
- 5. **UI** — `app/src/ui/pages/<FeatureName>Page/**` и/или `app/src/ui/components/**`; стили — как в проекте (см. `package.json` и соседние компоненты). Данные и побочные эффекты загрузки — через store/hooks, без DTO и без прямого HTTP‑клиента.
52
-
53
- 6. **Unit‑тесты** — `*.spec.ts` / `*.spec.tsx` (см. `tests-unit.mdc`); мапперы и нетривиальная логика — обязательно покрыть; e2e Jest не запускает (см. `jest.config.js`).
54
-
55
- 7. **E2E** — план: `app/__tests__/e2e/<Area>/<plan>.cases.md`; реализация: `*.spec.ts` рядом; детали — `tests-e2e-structure.mdc`.
56
-
57
- ## Частичные сценарии (вход с середины)
58
-
59
- | Задача | Минимум действий |
60
- |--------|------------------|
61
- | Только API + типы | Типы в `app/src/types/**`, сервис и мапперы, реэкспорт в `app/src/api/index.ts`; unit на маппер. |
62
- | Только моки | Константа пути уже в `@/api`; `handlers.ts` + данные; регистрация в `app/src/mocks/handlers.ts`; при необходимости убедиться, что хендлеры подхватываются и в браузерном, и в Node‑контуре MSW, если в проекте используются оба. |
63
- | Только store | Thunk на существующий метод `@/api`; slice + `app/src/store/reducers.ts`. |
64
- | Только UI | Читать готовое состояние из store; не добавлять HTTP/DTO; `data-testid` при необходимости для e2e. |
65
- | Только e2e | Синхронизировать `*.cases.md` и спеки; page object и `_shared`. |
66
-
67
- ## Антипаттерны
68
-
69
- - DTO и структуры ответа бэкенда в UI или в нетипизированных кусках store.
70
- - Прямой вызов HTTP‑клиента из компонента или thunk’а в обход сервисного слоя.
71
- - Deep‑импорты в `app/src/api/services/**/…` из UI/store там, где принят импорт из `@/api`.
72
- - Проброс пропсов в компоненты через `{...props}` — см. `no-props-spread.mdc`.
73
-
74
- Подробные границы слоёв: `architecture-boundaries.mdc`, `store-rtk.mdc`, `api-services.mdc`, `react-ui.mdc`.