@bonesofspring/ai-rules 0.2.22 → 0.2.23

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 (213) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +6 -2
  3. package/bin/cli.js +32 -8
  4. package/bin/mavis-install.js +441 -0
  5. package/package.json +9 -2
  6. package/presets/_shared/core/meta/preset-pr-checklist.md +1 -0
  7. package/presets/_shared/core/meta/preset-twin-sync.md +16 -0
  8. package/presets/claude/android-kotlin/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  9. package/presets/claude/android-kotlin/rules/tooling-and-review/preset-twin-sync.md +16 -0
  10. package/presets/claude/go/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  11. package/presets/claude/go/rules/tooling-and-review/preset-twin-sync.md +16 -0
  12. package/presets/claude/ios-swift/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  13. package/presets/claude/ios-swift/rules/tooling-and-review/preset-twin-sync.md +16 -0
  14. package/presets/claude/java/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  15. package/presets/claude/java/rules/tooling-and-review/preset-twin-sync.md +16 -0
  16. package/presets/claude/mcp-ts/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  17. package/presets/claude/mcp-ts/rules/tooling-and-review/preset-twin-sync.md +16 -0
  18. package/presets/claude/next/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  19. package/presets/claude/next/rules/tooling-and-review/preset-twin-sync.md +16 -0
  20. package/presets/claude/nuxt/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  21. package/presets/claude/nuxt/rules/tooling-and-review/preset-twin-sync.md +16 -0
  22. package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  23. package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-twin-sync.md +16 -0
  24. package/presets/claude/php-laravel/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  25. package/presets/claude/php-laravel/rules/tooling-and-review/preset-twin-sync.md +16 -0
  26. package/presets/claude/svelte/rules/tooling-and-review/preset-pr-checklist.md +1 -0
  27. package/presets/claude/svelte/rules/tooling-and-review/preset-twin-sync.md +16 -0
  28. package/presets/cursor/android-kotlin/rules/preset-pr-checklist.mdc +1 -0
  29. package/presets/cursor/android-kotlin/rules/preset-twin-sync.mdc +16 -0
  30. package/presets/cursor/go/rules/preset-pr-checklist.mdc +1 -0
  31. package/presets/cursor/go/rules/preset-twin-sync.mdc +16 -0
  32. package/presets/cursor/ios-swift/rules/preset-pr-checklist.mdc +1 -0
  33. package/presets/cursor/ios-swift/rules/preset-twin-sync.mdc +16 -0
  34. package/presets/cursor/java/rules/preset-pr-checklist.mdc +1 -0
  35. package/presets/cursor/java/rules/preset-twin-sync.mdc +16 -0
  36. package/presets/cursor/mcp-ts/rules/preset-pr-checklist.mdc +1 -0
  37. package/presets/cursor/mcp-ts/rules/preset-twin-sync.mdc +16 -0
  38. package/presets/cursor/next/rules/preset-pr-checklist.mdc +1 -0
  39. package/presets/cursor/next/rules/preset-twin-sync.mdc +16 -0
  40. package/presets/cursor/nuxt/rules/preset-pr-checklist.mdc +1 -0
  41. package/presets/cursor/nuxt/rules/preset-twin-sync.mdc +16 -0
  42. package/presets/cursor/php-hexagonal/rules/preset-pr-checklist.mdc +1 -0
  43. package/presets/cursor/php-hexagonal/rules/preset-twin-sync.mdc +16 -0
  44. package/presets/cursor/php-laravel/rules/preset-pr-checklist.mdc +1 -0
  45. package/presets/cursor/php-laravel/rules/preset-twin-sync.mdc +16 -0
  46. package/presets/cursor/svelte/rules/preset-pr-checklist.mdc +1 -0
  47. package/presets/cursor/svelte/rules/preset-twin-sync.mdc +16 -0
  48. package/presets/mavis/README.md +60 -0
  49. package/presets/mavis/next/MAVIS.md +67 -0
  50. package/presets/mavis/next/MCP.md +36 -0
  51. package/presets/mavis/next/README.md +15 -0
  52. package/presets/mavis/next/REPO_AGENTS.md +94 -0
  53. package/presets/mavis/next/adapter-map.json +959 -0
  54. package/presets/mavis/next/agents/README.md +206 -0
  55. package/presets/mavis/next/agents/accessibility-reviewer.md +61 -0
  56. package/presets/mavis/next/agents/api-contract-reviewer.md +57 -0
  57. package/presets/mavis/next/agents/build-verifier.md +110 -0
  58. package/presets/mavis/next/agents/ci-investigator.md +63 -0
  59. package/presets/mavis/next/agents/code-reviewer.md +61 -0
  60. package/presets/mavis/next/agents/codebase-analyzer.md +21 -0
  61. package/presets/mavis/next/agents/debugger.md +80 -0
  62. package/presets/mavis/next/agents/feature-developer.md +101 -0
  63. package/presets/mavis/next/agents/migration-specialist.md +67 -0
  64. package/presets/mavis/next/agents/mockup-reviewer.md +53 -0
  65. package/presets/mavis/next/agents/openspec-author.md +157 -0
  66. package/presets/mavis/next/agents/performance-auditor.md +68 -0
  67. package/presets/mavis/next/agents/playwright-test-generator.md +28 -0
  68. package/presets/mavis/next/agents/playwright-test-healer.md +28 -0
  69. package/presets/mavis/next/agents/playwright-test-planner.md +21 -0
  70. package/presets/mavis/next/agents/qa-tester.md +52 -0
  71. package/presets/mavis/next/agents/security-reviewer.md +54 -0
  72. package/presets/mavis/next/agents/solution-architect.md +68 -0
  73. package/presets/mavis/next/agents/task-analyst.md +166 -0
  74. package/presets/mavis/next/agents/task-router.md +178 -0
  75. package/presets/mavis/next/agents/tech-writer.md +57 -0
  76. package/presets/mavis/next/agents/unit-test-generator.md +31 -0
  77. package/presets/mavis/next/agents/unit-test-healer.md +32 -0
  78. package/presets/mavis/next/agents/unit-test-planner.md +54 -0
  79. package/presets/mavis/next/commands/README.md +49 -0
  80. package/presets/mavis/next/commands/spec-start.md +24 -0
  81. package/presets/mavis/next/commands/task-continue.md +50 -0
  82. package/presets/mavis/next/commands/task.md +50 -0
  83. package/presets/mavis/next/commands/technical-retro.md +80 -0
  84. package/presets/mavis/next/hooks/README.md +22 -0
  85. package/presets/mavis/next/hooks/chain-team-phases.sh +845 -0
  86. package/presets/mavis/next/mcp.json +19 -0
  87. package/presets/mavis/next/rules/README.md +16 -0
  88. package/presets/mavis/next/rules/agent-team-intake.md +11 -0
  89. package/presets/mavis/next/rules/agent-team-orchestrator.md +210 -0
  90. package/presets/mavis/next/rules/ai-local-overlay.md +37 -0
  91. package/presets/mavis/next/rules/anti-sycophancy-discipline.md +23 -0
  92. package/presets/mavis/next/rules/api-services.md +72 -0
  93. package/presets/mavis/next/rules/architecture-boundaries-ui.md +9 -0
  94. package/presets/mavis/next/rules/architecture-boundaries.md +86 -0
  95. package/presets/mavis/next/rules/arrow-functions.md +25 -0
  96. package/presets/mavis/next/rules/code-quality-and-refactoring.md +40 -0
  97. package/presets/mavis/next/rules/code-review-mr.md +40 -0
  98. package/presets/mavis/next/rules/css-property-order-stylelint.md +5 -0
  99. package/presets/mavis/next/rules/design-guidance.md +99 -0
  100. package/presets/mavis/next/rules/feature-delivery-workflow.md +49 -0
  101. package/presets/mavis/next/rules/http-client.md +55 -0
  102. package/presets/mavis/next/rules/layer-barrel-exports.md +68 -0
  103. package/presets/mavis/next/rules/mcp-usage.md +86 -0
  104. package/presets/mavis/next/rules/mockup-review.md +107 -0
  105. package/presets/mavis/next/rules/navigation-router-stack.md +56 -0
  106. package/presets/mavis/next/rules/navigation-router-ui.md +10 -0
  107. package/presets/mavis/next/rules/next-app-core.md +29 -0
  108. package/presets/mavis/next/rules/next-app-router.md +44 -0
  109. package/presets/mavis/next/rules/no-props-spread.md +56 -0
  110. package/presets/mavis/next/rules/no-type-assertion-as-import-export.md +51 -0
  111. package/presets/mavis/next/rules/package-manager.md +11 -0
  112. package/presets/mavis/next/rules/playwright-agents.md +67 -0
  113. package/presets/mavis/next/rules/post-change-lint.md +35 -0
  114. package/presets/mavis/next/rules/preset-layering.md +27 -0
  115. package/presets/mavis/next/rules/preset-no-cross-stack-leakage.md +90 -0
  116. package/presets/mavis/next/rules/preset-pr-checklist.md +35 -0
  117. package/presets/mavis/next/rules/preset-token-budget.md +36 -0
  118. package/presets/mavis/next/rules/preset-twin-sync.md +65 -0
  119. package/presets/mavis/next/rules/product-specs-authoring.md +112 -0
  120. package/presets/mavis/next/rules/product-specs.md +56 -0
  121. package/presets/mavis/next/rules/public-imports.md +50 -0
  122. package/presets/mavis/next/rules/react-a11y-coding.md +31 -0
  123. package/presets/mavis/next/rules/react-next-anti-hallucination.md +60 -0
  124. package/presets/mavis/next/rules/react-ui.md +105 -0
  125. package/presets/mavis/next/rules/reference-features.md +45 -0
  126. package/presets/mavis/next/rules/security-next.md +45 -0
  127. package/presets/mavis/next/rules/store-rtk.md +78 -0
  128. package/presets/mavis/next/rules/technical-retro.md +11 -0
  129. package/presets/mavis/next/rules/tests-e2e-structure.md +72 -0
  130. package/presets/mavis/next/rules/tests-unit.md +72 -0
  131. package/presets/mavis/next/rules/types-jsdoc.md +43 -0
  132. package/presets/mavis/next/rules/ux-design.md +99 -0
  133. package/presets/mavis/next/skills/README.md +21 -0
  134. package/presets/mavis/next/skills/ci-investigation/SKILL.md +39 -0
  135. package/presets/mavis/next/skills/code-review/SKILL.md +39 -0
  136. package/presets/mavis/next/skills/debug-investigation/SKILL.md +26 -0
  137. package/presets/mavis/next/skills/feature-delivery/SKILL.md +34 -0
  138. package/presets/mavis/next/skills/mockup-review/SKILL.md +24 -0
  139. package/presets/mavis/next/skills/playwright-e2e/SKILL.md +28 -0
  140. package/presets/mavis/next/skills/react-performance/SKILL.md +39 -0
  141. package/presets/mavis/next/skills/spec-authoring/SKILL.md +21 -0
  142. package/presets/mavis/next/skills/spec-discovery/SKILL.md +53 -0
  143. package/presets/mavis/next/skills/technical-retro/SKILL.md +49 -0
  144. package/presets/mavis/next/skills/unit-testing/SKILL.md +29 -0
  145. package/presets/mavis/next/skills/ux-design/SKILL.md +31 -0
  146. package/presets/mavis/next/skills/write-adr/SKILL.md +36 -0
  147. package/presets/mavis/next/team/README.md +148 -0
  148. package/presets/mavis/next/team/conventions.md +21 -0
  149. package/presets/mavis/next/team/fixtures/artifact-manifest.example.json +79 -0
  150. package/presets/mavis/next/team/fixtures/bugfix-standard.json +25 -0
  151. package/presets/mavis/next/team/fixtures/feature-full.json +29 -0
  152. package/presets/mavis/next/team/fixtures/feature-light.json +27 -0
  153. package/presets/mavis/next/team/fixtures/feature-with-figma-url.json +17 -0
  154. package/presets/mavis/next/team/fixtures/feature-with-ux-no-figma.json +17 -0
  155. package/presets/mavis/next/team/fixtures/memlog.example.md +9 -0
  156. package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/human-gate/metrics.json +69 -0
  157. package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/missing-ledger/status.json +5 -0
  158. package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/parallel-receipts/metrics.json +79 -0
  159. package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/serial-retry/metrics.json +124 -0
  160. package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/validation-failed/metrics.json +124 -0
  161. package/presets/mavis/next/team/fixtures/metrics.example.json +54 -0
  162. package/presets/mavis/next/team/fixtures/mockup-review-fail-embedded-status.json +8 -0
  163. package/presets/mavis/next/team/fixtures/mockup-review-standalone.json +14 -0
  164. package/presets/mavis/next/team/fixtures/ux-design-standalone.json +15 -0
  165. package/presets/mavis/next/team/tasks/.gitkeep +0 -0
  166. package/presets/mavis/platform-contract.json +131 -0
  167. package/scripts/capture-cli-golden.mjs +44 -0
  168. package/scripts/check-chain-hook-coverage.sh +1 -0
  169. package/scripts/check-mavis-adapter-map.mjs +294 -0
  170. package/scripts/check-mavis-local-overlay.mjs +56 -0
  171. package/scripts/check-mavis-platform-contract.mjs +388 -0
  172. package/scripts/check-mavis-token-budget.mjs +48 -0
  173. package/scripts/check-preset-leakage.sh +35 -3
  174. package/scripts/check-preset-structure.sh +63 -0
  175. package/scripts/check-shared-core-drift.sh +18 -0
  176. package/scripts/check-task-router-intents.sh +15 -2
  177. package/scripts/fixtures/cli-golden/claude/next.json +130 -0
  178. package/scripts/fixtures/cli-golden/cursor/next.json +132 -0
  179. package/scripts/fixtures/cli-golden/mavis/next.json +125 -0
  180. package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/cursor/AGENTS.md +1 -0
  181. package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/cursor/rules/extra.mdc +1 -0
  182. package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/cursor/rules/sample.mdc +2 -0
  183. package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/mavis/MAVIS.md +1 -0
  184. package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/mavis/adapter-map.json +26 -0
  185. package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/mavis/rules/sample.md +2 -0
  186. package/scripts/fixtures/mavis-adapter-map/valid/cursor/AGENTS.md +1 -0
  187. package/scripts/fixtures/mavis-adapter-map/valid/cursor/rules/sample.mdc +2 -0
  188. package/scripts/fixtures/mavis-adapter-map/valid/mavis/MAVIS.md +1 -0
  189. package/scripts/fixtures/mavis-adapter-map/valid/mavis/adapter-map.json +26 -0
  190. package/scripts/fixtures/mavis-adapter-map/valid/mavis/rules/sample.md +2 -0
  191. package/scripts/fixtures/mavis-platform-contract/invalid-minimax-destination.json +131 -0
  192. package/scripts/fixtures/mavis-platform-contract/invalid-missing-capability.json +126 -0
  193. package/scripts/fixtures/mavis-platform-contract/invalid-released-unknown-stack.json +133 -0
  194. package/scripts/fixtures/mavis-platform-contract/invalid-unknown-native-path.json +131 -0
  195. package/scripts/fixtures/mavis-platform-contract/invalid-version-pin.json +132 -0
  196. package/scripts/fixtures/mavis-platform-contract/valid.json +131 -0
  197. package/scripts/fixtures/mavis-probe/AGENTS.md +6 -0
  198. package/scripts/fixtures/mavis-probe/agents/probe-agent.md +6 -0
  199. package/scripts/fixtures/mavis-probe/fake-mcode.mjs +98 -0
  200. package/scripts/fixtures/mavis-probe/output.schema.json +28 -0
  201. package/scripts/fixtures/mavis-probe/rules/probe.md +5 -0
  202. package/scripts/fixtures/mavis-probe/skills/probe/SKILL.md +11 -0
  203. package/scripts/generate-mavis-next.mjs +704 -0
  204. package/scripts/lib/cli-golden.mjs +50 -0
  205. package/scripts/lib/discover-hooks.mjs +72 -0
  206. package/scripts/probe-mavis-capabilities.mjs +480 -0
  207. package/scripts/test-agent-task-metrics-hooks.mjs +22 -16
  208. package/scripts/test-chain-team-phases-coverage.mjs +10 -7
  209. package/scripts/test-cli-init-clean.mjs +256 -0
  210. package/scripts/test-cli-mcp-merge.mjs +23 -0
  211. package/scripts/test-mavis-context-budget.mjs +55 -0
  212. package/scripts/test-mavis-orchestration.mjs +649 -0
  213. package/scripts/test-probe-mavis-capabilities.mjs +205 -0
@@ -0,0 +1,294 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Validate presets/mavis/<stack>/adapter-map.json against architecture §3.5.
4
+ *
5
+ * Usage:
6
+ * node scripts/check-mavis-adapter-map.mjs
7
+ * node scripts/check-mavis-adapter-map.mjs --stack next
8
+ * node scripts/check-mavis-adapter-map.mjs --self-test
9
+ */
10
+ import crypto from 'node:crypto';
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
16
+ const packageRoot = path.join(scriptDirectory, '..');
17
+ const presetsRoot = path.join(packageRoot, 'presets');
18
+ const fixtureDirectory = path.join(scriptDirectory, 'fixtures', 'mavis-adapter-map');
19
+
20
+ const DISPOSITIONS = new Set([
21
+ 'copy',
22
+ 'transform',
23
+ 'embed-entry',
24
+ 'omit',
25
+ 'fallback',
26
+ ]);
27
+ const LOAD_MODES = new Set([
28
+ 'root-entry',
29
+ 'referenced',
30
+ 'native-skill',
31
+ 'native-team',
32
+ 'manual',
33
+ 'not-installed',
34
+ ]);
35
+ const PLATFORM_ONLY = new Set(['adapter-map.json']);
36
+
37
+ function walkFiles(root) {
38
+ if (!fs.existsSync(root)) {
39
+ return [];
40
+ }
41
+ const files = [];
42
+ const visit = (dir) => {
43
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
44
+ const full = path.join(dir, entry.name);
45
+ if (entry.isDirectory()) {
46
+ visit(full);
47
+ } else if (entry.isFile()) {
48
+ files.push(path.relative(root, full).split(path.sep).join('/'));
49
+ }
50
+ }
51
+ };
52
+ visit(root);
53
+ return files.sort();
54
+ }
55
+
56
+ function sha256(content) {
57
+ return crypto.createHash('sha256').update(content).digest('hex');
58
+ }
59
+
60
+ function sourceRevision(cursorRoot, mappedSources) {
61
+ const parts = mappedSources.map((rel) => {
62
+ const content = fs.readFileSync(path.join(cursorRoot, rel), 'utf8');
63
+ return `${rel}\t${sha256(content)}`;
64
+ });
65
+ parts.sort();
66
+ return sha256(parts.join('\n'));
67
+ }
68
+
69
+ export function validateAdapterMap({
70
+ cursorRoot,
71
+ mavisRoot,
72
+ adapterMap,
73
+ allowMissingMavisDir = false,
74
+ }) {
75
+ const failures = [];
76
+ const fail = (message) => {
77
+ failures.push(message);
78
+ };
79
+
80
+ if (adapterMap === null || typeof adapterMap !== 'object' || Array.isArray(adapterMap)) {
81
+ return ['adapter-map must be a JSON object'];
82
+ }
83
+ if (adapterMap.schemaVersion !== 1) {
84
+ fail('schemaVersion must be 1');
85
+ }
86
+ if (adapterMap.platform !== 'mavis') {
87
+ fail('platform must be "mavis"');
88
+ }
89
+ if (typeof adapterMap.stack !== 'string' || adapterMap.stack.length === 0) {
90
+ fail('stack must be a non-empty string');
91
+ }
92
+ if (adapterMap.semanticSource !== `cursor/${adapterMap.stack}`) {
93
+ fail(`semanticSource must be "cursor/${adapterMap.stack}"`);
94
+ }
95
+ if (!Array.isArray(adapterMap.entries)) {
96
+ fail('entries must be an array');
97
+ return failures;
98
+ }
99
+
100
+ for (const [index, item] of adapterMap.entries.entries()) {
101
+ if (!item || typeof item !== 'object') {
102
+ fail(`entries[${index}] must be an object`);
103
+ continue;
104
+ }
105
+ if (typeof item.source !== 'string' || !item.source) {
106
+ fail(`entries[${index}].source is required`);
107
+ }
108
+ if (item.target !== null && (typeof item.target !== 'string' || !item.target)) {
109
+ fail(`entries[${index}].target must be a string or null`);
110
+ }
111
+ if (typeof item.category !== 'string' || !item.category) {
112
+ fail(`entries[${index}].category is required`);
113
+ }
114
+ if (!DISPOSITIONS.has(item.disposition)) {
115
+ fail(`entries[${index}].disposition is invalid`);
116
+ }
117
+ if (!LOAD_MODES.has(item.loadMode)) {
118
+ fail(`entries[${index}].loadMode is invalid`);
119
+ }
120
+ if (typeof item.reason !== 'string' || !item.reason.trim()) {
121
+ fail(`entries[${index}].reason is required`);
122
+ }
123
+ if (item.target === null && item.disposition !== 'omit' && item.disposition !== 'fallback') {
124
+ fail(`entries[${index}] null target requires omit or fallback`);
125
+ }
126
+ if (typeof item.source === 'string' && item.source.split(/[\\/]/).includes('claude')) {
127
+ fail(`entries[${index}] must not derive from Claude`);
128
+ }
129
+ }
130
+
131
+ const cursorFiles = walkFiles(cursorRoot).filter(
132
+ (rel) => path.basename(rel) !== 'README.md',
133
+ );
134
+ const mapped = adapterMap.entries.map((item) => item.source);
135
+ const mappedSet = new Set(mapped);
136
+ if (mapped.length !== mappedSet.size) {
137
+ fail('adapter-map has duplicate source entries');
138
+ }
139
+ for (const source of cursorFiles) {
140
+ if (!mappedSet.has(source)) {
141
+ fail(`unmapped Cursor source: ${source}`);
142
+ }
143
+ }
144
+ for (const source of mappedSet) {
145
+ if (!cursorFiles.includes(source)) {
146
+ fail(`adapter-map source missing from Cursor tree: ${source}`);
147
+ }
148
+ }
149
+
150
+ const expectedRevision = sourceRevision(cursorRoot, cursorFiles);
151
+ if (adapterMap.sourceRevision !== expectedRevision) {
152
+ fail('sourceRevision does not match Cursor next digest');
153
+ }
154
+
155
+ if (!fs.existsSync(mavisRoot)) {
156
+ if (!allowMissingMavisDir) {
157
+ fail(`missing Mavis tree ${mavisRoot}`);
158
+ }
159
+ return failures;
160
+ }
161
+
162
+ const mavisFiles = walkFiles(mavisRoot);
163
+ const targets = new Set(
164
+ adapterMap.entries.filter((item) => item.target).map((item) => item.target),
165
+ );
166
+ for (const item of adapterMap.entries) {
167
+ if (item.target && !mavisFiles.includes(item.target)) {
168
+ fail(`mapped target missing: ${item.target}`);
169
+ }
170
+ if (item.target && item.source) {
171
+ const sourceAbs = path.join(cursorRoot, item.source);
172
+ const targetAbs = path.join(mavisRoot, item.target);
173
+ if (fs.existsSync(sourceAbs) && fs.existsSync(targetAbs)) {
174
+ const sourceText = fs.readFileSync(sourceAbs, 'utf8');
175
+ const targetText = fs.readFileSync(targetAbs, 'utf8');
176
+ const marker = sourceText.match(/<!--\s*shared-core:\s*([^\s>]+)\s*-->/);
177
+ if (marker && !targetText.includes(`<!-- shared-core: ${marker[1]} -->`)) {
178
+ fail(`${item.target} dropped shared-core marker ${marker[1]}`);
179
+ }
180
+ }
181
+ }
182
+ }
183
+
184
+ for (const file of mavisFiles) {
185
+ if (PLATFORM_ONLY.has(file) || path.basename(file) === 'README.md') {
186
+ continue;
187
+ }
188
+ if (!targets.has(file)) {
189
+ fail(`orphan Mavis file: ${file}`);
190
+ }
191
+ }
192
+
193
+ return failures;
194
+ }
195
+
196
+ function readJson(filePath) {
197
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
198
+ }
199
+
200
+ function printFailures(label, failures) {
201
+ for (const failure of failures) {
202
+ console.error(`FAIL ${label}: ${failure}`);
203
+ }
204
+ }
205
+
206
+ function checkLive(stack) {
207
+ const cursorRoot = path.join(presetsRoot, 'cursor', stack);
208
+ const mavisRoot = path.join(presetsRoot, 'mavis', stack);
209
+ const mapPath = path.join(mavisRoot, 'adapter-map.json');
210
+ if (!fs.existsSync(mapPath)) {
211
+ return [`missing ${path.relative(packageRoot, mapPath)}`];
212
+ }
213
+ return validateAdapterMap({
214
+ cursorRoot,
215
+ mavisRoot,
216
+ adapterMap: readJson(mapPath),
217
+ });
218
+ }
219
+
220
+ function runSelfTest() {
221
+ let failed = 0;
222
+ const cases = [
223
+ { name: 'valid', expectFail: false },
224
+ { name: 'invalid-unmapped', expectFail: true },
225
+ ];
226
+ for (const testCase of cases) {
227
+ const root = path.join(fixtureDirectory, testCase.name);
228
+ const failures = validateAdapterMap({
229
+ cursorRoot: path.join(root, 'cursor'),
230
+ mavisRoot: path.join(root, 'mavis'),
231
+ adapterMap: readJson(path.join(root, 'mavis', 'adapter-map.json')),
232
+ });
233
+ const didFail = failures.length > 0;
234
+ if (didFail !== testCase.expectFail) {
235
+ failed += 1;
236
+ console.error(
237
+ `FAIL self-test ${testCase.name}: expected ${
238
+ testCase.expectFail ? 'invalid' : 'valid'
239
+ }`,
240
+ );
241
+ printFailures(testCase.name, failures);
242
+ } else {
243
+ console.log(
244
+ `ok self-test ${testCase.name} (${didFail ? 'invalid' : 'valid'})`,
245
+ );
246
+ }
247
+ }
248
+ return failed;
249
+ }
250
+
251
+ function parseArgs(argv) {
252
+ const options = { stack: 'next', selfTest: false };
253
+ for (let index = 0; index < argv.length; index += 1) {
254
+ const arg = argv[index];
255
+ if (arg === '--self-test') {
256
+ options.selfTest = true;
257
+ } else if (arg === '--stack') {
258
+ options.stack = argv[index + 1];
259
+ index += 1;
260
+ } else {
261
+ throw new Error(`unknown argument: ${arg}`);
262
+ }
263
+ }
264
+ return options;
265
+ }
266
+
267
+ function main() {
268
+ const options = parseArgs(process.argv.slice(2));
269
+ let exitCode = 0;
270
+ if (options.selfTest) {
271
+ if (runSelfTest() !== 0) {
272
+ exitCode = 1;
273
+ }
274
+ }
275
+ if (!options.selfTest || exitCode === 0) {
276
+ const failures = checkLive(options.stack);
277
+ if (failures.length > 0) {
278
+ printFailures(`mavis/${options.stack}`, failures);
279
+ exitCode = 1;
280
+ } else {
281
+ console.log(`ok presets/mavis/${options.stack}/adapter-map.json`);
282
+ }
283
+ }
284
+ process.exit(exitCode);
285
+ }
286
+
287
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
288
+ try {
289
+ main();
290
+ } catch (error) {
291
+ console.error(`FAIL: ${error.message}`);
292
+ process.exit(1);
293
+ }
294
+ }
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Isolated Mavis overlay check. Does not invent alwaysApply / paths: semantics.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { loadReleasedMavisStacks } from './lib/discover-hooks.mjs';
9
+
10
+ const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
11
+ const presetsRoot = path.join(scriptDirectory, '..', 'presets');
12
+ const ACK = 'Using .ai.local: <rule-name>';
13
+ const POINTER = 'docs/LOCAL-OVERLAY.md';
14
+ const PROBE = 'ls -la .ai.local';
15
+
16
+ const stacks = loadReleasedMavisStacks(presetsRoot);
17
+ if (stacks.length === 0) {
18
+ console.log('ok mavis local-overlay: no released stacks');
19
+ process.exit(0);
20
+ }
21
+
22
+ let failed = 0;
23
+ for (const stack of stacks) {
24
+ const root = path.join(presetsRoot, 'mavis', stack);
25
+ const files = [
26
+ path.join(root, 'REPO_AGENTS.md'),
27
+ path.join(root, 'rules', 'ai-local-overlay.md'),
28
+ ];
29
+ for (const filePath of files) {
30
+ if (!fs.existsSync(filePath)) {
31
+ console.error(`FAIL missing ${path.relative(presetsRoot, filePath)}`);
32
+ failed += 1;
33
+ continue;
34
+ }
35
+ const text = fs.readFileSync(filePath, 'utf8');
36
+ const label = path.relative(presetsRoot, filePath);
37
+ if (!text.includes(POINTER)) {
38
+ console.error(`FAIL ${label}: missing ${POINTER}`);
39
+ failed += 1;
40
+ }
41
+ if (filePath.endsWith('ai-local-overlay.md') && !text.includes(ACK)) {
42
+ console.error(`FAIL ${label}: missing acknowledgement format`);
43
+ failed += 1;
44
+ }
45
+ if (filePath.endsWith('ai-local-overlay.md') && !text.includes(PROBE)) {
46
+ console.error(`FAIL ${label}: missing probe command`);
47
+ failed += 1;
48
+ }
49
+ if (filePath.endsWith('REPO_AGENTS.md') && !text.includes('Local overlay')) {
50
+ console.error(`FAIL ${label}: missing Local overlay section`);
51
+ failed += 1;
52
+ }
53
+ }
54
+ }
55
+
56
+ process.exit(failed === 0 ? 0 : 1);