@bonesofspring/ai-rules 0.2.23 → 0.2.24

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 (285) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +5 -1
  3. package/bin/cli.js +32 -9
  4. package/bin/opencode-install.js +531 -0
  5. package/package.json +10 -3
  6. package/presets/_shared/core/agent-team/agent-team-orchestrator.md +2 -2
  7. package/presets/_shared/core/meta/preset-layering.md +3 -3
  8. package/presets/_shared/core/meta/preset-no-cross-stack-leakage.md +1 -1
  9. package/presets/_shared/core/meta/preset-pr-checklist.md +4 -3
  10. package/presets/_shared/core/meta/preset-token-budget.md +3 -0
  11. package/presets/_shared/core/meta/preset-twin-sync.md +16 -0
  12. package/presets/claude/android-kotlin/rules/tooling-and-review/preset-layering.md +3 -3
  13. package/presets/claude/android-kotlin/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  14. package/presets/claude/android-kotlin/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  15. package/presets/claude/android-kotlin/rules/tooling-and-review/preset-token-budget.md +3 -0
  16. package/presets/claude/android-kotlin/rules/tooling-and-review/preset-twin-sync.md +16 -0
  17. package/presets/claude/go/rules/tooling-and-review/preset-layering.md +3 -3
  18. package/presets/claude/go/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  19. package/presets/claude/go/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  20. package/presets/claude/go/rules/tooling-and-review/preset-token-budget.md +3 -0
  21. package/presets/claude/go/rules/tooling-and-review/preset-twin-sync.md +16 -0
  22. package/presets/claude/ios-swift/rules/tooling-and-review/preset-layering.md +3 -3
  23. package/presets/claude/ios-swift/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  24. package/presets/claude/ios-swift/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  25. package/presets/claude/ios-swift/rules/tooling-and-review/preset-token-budget.md +3 -0
  26. package/presets/claude/ios-swift/rules/tooling-and-review/preset-twin-sync.md +16 -0
  27. package/presets/claude/java/rules/tooling-and-review/preset-layering.md +3 -3
  28. package/presets/claude/java/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  29. package/presets/claude/java/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  30. package/presets/claude/java/rules/tooling-and-review/preset-token-budget.md +3 -0
  31. package/presets/claude/java/rules/tooling-and-review/preset-twin-sync.md +16 -0
  32. package/presets/claude/mcp-ts/rules/tooling-and-review/preset-layering.md +3 -3
  33. package/presets/claude/mcp-ts/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  34. package/presets/claude/mcp-ts/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  35. package/presets/claude/mcp-ts/rules/tooling-and-review/preset-token-budget.md +3 -0
  36. package/presets/claude/mcp-ts/rules/tooling-and-review/preset-twin-sync.md +16 -0
  37. package/presets/claude/next/REPO_AGENTS.md +10 -5
  38. package/presets/claude/next/agents/build-verifier.md +3 -1
  39. package/presets/claude/next/rules/tooling-and-review/ai-local-overlay.md +1 -1
  40. package/presets/claude/next/rules/tooling-and-review/preset-layering.md +3 -3
  41. package/presets/claude/next/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  42. package/presets/claude/next/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  43. package/presets/claude/next/rules/tooling-and-review/preset-token-budget.md +3 -0
  44. package/presets/claude/next/rules/tooling-and-review/preset-twin-sync.md +16 -0
  45. package/presets/claude/nuxt/rules/tooling-and-review/preset-layering.md +3 -3
  46. package/presets/claude/nuxt/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  47. package/presets/claude/nuxt/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  48. package/presets/claude/nuxt/rules/tooling-and-review/preset-token-budget.md +3 -0
  49. package/presets/claude/nuxt/rules/tooling-and-review/preset-twin-sync.md +16 -0
  50. package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-layering.md +3 -3
  51. package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  52. package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  53. package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-token-budget.md +3 -0
  54. package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-twin-sync.md +16 -0
  55. package/presets/claude/php-laravel/rules/tooling-and-review/preset-layering.md +3 -3
  56. package/presets/claude/php-laravel/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  57. package/presets/claude/php-laravel/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  58. package/presets/claude/php-laravel/rules/tooling-and-review/preset-token-budget.md +3 -0
  59. package/presets/claude/php-laravel/rules/tooling-and-review/preset-twin-sync.md +16 -0
  60. package/presets/claude/svelte/rules/tooling-and-review/preset-layering.md +3 -3
  61. package/presets/claude/svelte/rules/tooling-and-review/preset-no-cross-stack-leakage.md +1 -1
  62. package/presets/claude/svelte/rules/tooling-and-review/preset-pr-checklist.md +4 -3
  63. package/presets/claude/svelte/rules/tooling-and-review/preset-token-budget.md +3 -0
  64. package/presets/claude/svelte/rules/tooling-and-review/preset-twin-sync.md +16 -0
  65. package/presets/cursor/android-kotlin/rules/preset-layering.mdc +3 -3
  66. package/presets/cursor/android-kotlin/rules/preset-no-cross-stack-leakage.mdc +1 -1
  67. package/presets/cursor/android-kotlin/rules/preset-pr-checklist.mdc +4 -3
  68. package/presets/cursor/android-kotlin/rules/preset-token-budget.mdc +3 -0
  69. package/presets/cursor/android-kotlin/rules/preset-twin-sync.mdc +16 -0
  70. package/presets/cursor/go/rules/preset-layering.mdc +3 -3
  71. package/presets/cursor/go/rules/preset-no-cross-stack-leakage.mdc +1 -1
  72. package/presets/cursor/go/rules/preset-pr-checklist.mdc +4 -3
  73. package/presets/cursor/go/rules/preset-token-budget.mdc +3 -0
  74. package/presets/cursor/go/rules/preset-twin-sync.mdc +16 -0
  75. package/presets/cursor/ios-swift/rules/preset-layering.mdc +3 -3
  76. package/presets/cursor/ios-swift/rules/preset-no-cross-stack-leakage.mdc +1 -1
  77. package/presets/cursor/ios-swift/rules/preset-pr-checklist.mdc +4 -3
  78. package/presets/cursor/ios-swift/rules/preset-token-budget.mdc +3 -0
  79. package/presets/cursor/ios-swift/rules/preset-twin-sync.mdc +16 -0
  80. package/presets/cursor/java/rules/preset-layering.mdc +3 -3
  81. package/presets/cursor/java/rules/preset-no-cross-stack-leakage.mdc +1 -1
  82. package/presets/cursor/java/rules/preset-pr-checklist.mdc +4 -3
  83. package/presets/cursor/java/rules/preset-token-budget.mdc +3 -0
  84. package/presets/cursor/java/rules/preset-twin-sync.mdc +16 -0
  85. package/presets/cursor/mcp-ts/rules/preset-layering.mdc +3 -3
  86. package/presets/cursor/mcp-ts/rules/preset-no-cross-stack-leakage.mdc +1 -1
  87. package/presets/cursor/mcp-ts/rules/preset-pr-checklist.mdc +4 -3
  88. package/presets/cursor/mcp-ts/rules/preset-token-budget.mdc +3 -0
  89. package/presets/cursor/mcp-ts/rules/preset-twin-sync.mdc +16 -0
  90. package/presets/cursor/next/REPO_AGENTS.md +10 -5
  91. package/presets/cursor/next/agents/build-verifier.md +3 -1
  92. package/presets/cursor/next/rules/ai-local-overlay.mdc +1 -1
  93. package/presets/cursor/next/rules/preset-layering.mdc +3 -3
  94. package/presets/cursor/next/rules/preset-no-cross-stack-leakage.mdc +1 -1
  95. package/presets/cursor/next/rules/preset-pr-checklist.mdc +4 -3
  96. package/presets/cursor/next/rules/preset-token-budget.mdc +3 -0
  97. package/presets/cursor/next/rules/preset-twin-sync.mdc +16 -0
  98. package/presets/cursor/nuxt/rules/preset-layering.mdc +3 -3
  99. package/presets/cursor/nuxt/rules/preset-no-cross-stack-leakage.mdc +1 -1
  100. package/presets/cursor/nuxt/rules/preset-pr-checklist.mdc +4 -3
  101. package/presets/cursor/nuxt/rules/preset-token-budget.mdc +3 -0
  102. package/presets/cursor/nuxt/rules/preset-twin-sync.mdc +16 -0
  103. package/presets/cursor/php-hexagonal/rules/preset-layering.mdc +3 -3
  104. package/presets/cursor/php-hexagonal/rules/preset-no-cross-stack-leakage.mdc +1 -1
  105. package/presets/cursor/php-hexagonal/rules/preset-pr-checklist.mdc +4 -3
  106. package/presets/cursor/php-hexagonal/rules/preset-token-budget.mdc +3 -0
  107. package/presets/cursor/php-hexagonal/rules/preset-twin-sync.mdc +16 -0
  108. package/presets/cursor/php-laravel/rules/preset-layering.mdc +3 -3
  109. package/presets/cursor/php-laravel/rules/preset-no-cross-stack-leakage.mdc +1 -1
  110. package/presets/cursor/php-laravel/rules/preset-pr-checklist.mdc +4 -3
  111. package/presets/cursor/php-laravel/rules/preset-token-budget.mdc +3 -0
  112. package/presets/cursor/php-laravel/rules/preset-twin-sync.mdc +16 -0
  113. package/presets/cursor/svelte/rules/preset-layering.mdc +3 -3
  114. package/presets/cursor/svelte/rules/preset-no-cross-stack-leakage.mdc +1 -1
  115. package/presets/cursor/svelte/rules/preset-pr-checklist.mdc +4 -3
  116. package/presets/cursor/svelte/rules/preset-token-budget.mdc +3 -0
  117. package/presets/cursor/svelte/rules/preset-twin-sync.mdc +16 -0
  118. package/presets/mavis/next/REPO_AGENTS.md +9 -7
  119. package/presets/mavis/next/adapter-map.json +1 -1
  120. package/presets/mavis/next/agents/build-verifier.md +3 -1
  121. package/presets/mavis/next/rules/agent-team-orchestrator.md +2 -2
  122. package/presets/mavis/next/rules/ai-local-overlay.md +1 -1
  123. package/presets/mavis/next/rules/preset-layering.md +3 -3
  124. package/presets/mavis/next/rules/preset-no-cross-stack-leakage.md +1 -1
  125. package/presets/mavis/next/rules/preset-pr-checklist.md +4 -3
  126. package/presets/mavis/next/rules/preset-token-budget.md +3 -0
  127. package/presets/mavis/next/rules/preset-twin-sync.md +16 -0
  128. package/presets/opencode/README.md +61 -0
  129. package/presets/opencode/next/MCP.md +36 -0
  130. package/presets/opencode/next/OPENCODE.md +65 -0
  131. package/presets/opencode/next/README.md +16 -0
  132. package/presets/opencode/next/REPO_AGENTS.md +97 -0
  133. package/presets/opencode/next/adapter-map.json +959 -0
  134. package/presets/opencode/next/agents/README.md +201 -0
  135. package/presets/opencode/next/agents/accessibility-reviewer.md +73 -0
  136. package/presets/opencode/next/agents/api-contract-reviewer.md +69 -0
  137. package/presets/opencode/next/agents/build-verifier.md +124 -0
  138. package/presets/opencode/next/agents/ci-investigator.md +71 -0
  139. package/presets/opencode/next/agents/code-reviewer.md +73 -0
  140. package/presets/opencode/next/agents/codebase-analyzer.md +33 -0
  141. package/presets/opencode/next/agents/debugger.md +88 -0
  142. package/presets/opencode/next/agents/feature-developer.md +109 -0
  143. package/presets/opencode/next/agents/migration-specialist.md +79 -0
  144. package/presets/opencode/next/agents/mockup-reviewer.md +65 -0
  145. package/presets/opencode/next/agents/openspec-author.md +169 -0
  146. package/presets/opencode/next/agents/performance-auditor.md +80 -0
  147. package/presets/opencode/next/agents/playwright-test-generator.md +36 -0
  148. package/presets/opencode/next/agents/playwright-test-healer.md +36 -0
  149. package/presets/opencode/next/agents/playwright-test-planner.md +33 -0
  150. package/presets/opencode/next/agents/qa-tester.md +60 -0
  151. package/presets/opencode/next/agents/security-reviewer.md +66 -0
  152. package/presets/opencode/next/agents/solution-architect.md +80 -0
  153. package/presets/opencode/next/agents/task-analyst.md +178 -0
  154. package/presets/opencode/next/agents/task-router.md +190 -0
  155. package/presets/opencode/next/agents/tech-writer.md +69 -0
  156. package/presets/opencode/next/agents/unit-test-generator.md +39 -0
  157. package/presets/opencode/next/agents/unit-test-healer.md +40 -0
  158. package/presets/opencode/next/agents/unit-test-planner.md +66 -0
  159. package/presets/opencode/next/commands/README.md +49 -0
  160. package/presets/opencode/next/commands/spec-start.md +32 -0
  161. package/presets/opencode/next/commands/task-continue.md +58 -0
  162. package/presets/opencode/next/commands/task.md +58 -0
  163. package/presets/opencode/next/commands/technical-retro.md +88 -0
  164. package/presets/opencode/next/hooks/README.md +22 -0
  165. package/presets/opencode/next/hooks/chain-team-phases.sh +845 -0
  166. package/presets/opencode/next/opencode.json +33 -0
  167. package/presets/opencode/next/rules/README.md +20 -0
  168. package/presets/opencode/next/rules/agent-team-intake.md +11 -0
  169. package/presets/opencode/next/rules/agent-team-orchestrator.md +214 -0
  170. package/presets/opencode/next/rules/ai-local-overlay.md +37 -0
  171. package/presets/opencode/next/rules/anti-sycophancy-discipline.md +23 -0
  172. package/presets/opencode/next/rules/api-services.md +72 -0
  173. package/presets/opencode/next/rules/architecture-boundaries-ui.md +9 -0
  174. package/presets/opencode/next/rules/architecture-boundaries.md +86 -0
  175. package/presets/opencode/next/rules/arrow-functions.md +25 -0
  176. package/presets/opencode/next/rules/code-quality-and-refactoring.md +40 -0
  177. package/presets/opencode/next/rules/code-review-mr.md +40 -0
  178. package/presets/opencode/next/rules/css-property-order-stylelint.md +5 -0
  179. package/presets/opencode/next/rules/design-guidance.md +99 -0
  180. package/presets/opencode/next/rules/feature-delivery-workflow.md +49 -0
  181. package/presets/opencode/next/rules/http-client.md +55 -0
  182. package/presets/opencode/next/rules/layer-barrel-exports.md +68 -0
  183. package/presets/opencode/next/rules/mcp-usage.md +86 -0
  184. package/presets/opencode/next/rules/mockup-review.md +107 -0
  185. package/presets/opencode/next/rules/navigation-router-stack.md +56 -0
  186. package/presets/opencode/next/rules/navigation-router-ui.md +10 -0
  187. package/presets/opencode/next/rules/next-app-core.md +29 -0
  188. package/presets/opencode/next/rules/next-app-router.md +44 -0
  189. package/presets/opencode/next/rules/no-props-spread.md +56 -0
  190. package/presets/opencode/next/rules/no-type-assertion-as-import-export.md +51 -0
  191. package/presets/opencode/next/rules/package-manager.md +11 -0
  192. package/presets/opencode/next/rules/playwright-agents.md +67 -0
  193. package/presets/opencode/next/rules/post-change-lint.md +35 -0
  194. package/presets/opencode/next/rules/preset-layering.md +27 -0
  195. package/presets/opencode/next/rules/preset-no-cross-stack-leakage.md +90 -0
  196. package/presets/opencode/next/rules/preset-pr-checklist.md +36 -0
  197. package/presets/opencode/next/rules/preset-token-budget.md +39 -0
  198. package/presets/opencode/next/rules/preset-twin-sync.md +81 -0
  199. package/presets/opencode/next/rules/product-specs-authoring.md +112 -0
  200. package/presets/opencode/next/rules/product-specs.md +56 -0
  201. package/presets/opencode/next/rules/public-imports.md +50 -0
  202. package/presets/opencode/next/rules/react-a11y-coding.md +31 -0
  203. package/presets/opencode/next/rules/react-next-anti-hallucination.md +60 -0
  204. package/presets/opencode/next/rules/react-ui.md +105 -0
  205. package/presets/opencode/next/rules/reference-features.md +45 -0
  206. package/presets/opencode/next/rules/security-next.md +45 -0
  207. package/presets/opencode/next/rules/store-rtk.md +78 -0
  208. package/presets/opencode/next/rules/technical-retro.md +11 -0
  209. package/presets/opencode/next/rules/tests-e2e-structure.md +72 -0
  210. package/presets/opencode/next/rules/tests-unit.md +72 -0
  211. package/presets/opencode/next/rules/types-jsdoc.md +43 -0
  212. package/presets/opencode/next/rules/ux-design.md +99 -0
  213. package/presets/opencode/next/skills/README.md +21 -0
  214. package/presets/opencode/next/skills/ci-investigation/SKILL.md +44 -0
  215. package/presets/opencode/next/skills/code-review/SKILL.md +44 -0
  216. package/presets/opencode/next/skills/debug-investigation/SKILL.md +59 -0
  217. package/presets/opencode/next/skills/feature-delivery/SKILL.md +39 -0
  218. package/presets/opencode/next/skills/mockup-review/SKILL.md +29 -0
  219. package/presets/opencode/next/skills/playwright-e2e/SKILL.md +33 -0
  220. package/presets/opencode/next/skills/react-performance/SKILL.md +44 -0
  221. package/presets/opencode/next/skills/spec-authoring/SKILL.md +26 -0
  222. package/presets/opencode/next/skills/spec-discovery/SKILL.md +58 -0
  223. package/presets/opencode/next/skills/technical-retro/SKILL.md +54 -0
  224. package/presets/opencode/next/skills/unit-testing/SKILL.md +34 -0
  225. package/presets/opencode/next/skills/ux-design/SKILL.md +36 -0
  226. package/presets/opencode/next/skills/write-adr/SKILL.md +41 -0
  227. package/presets/opencode/next/team/README.md +148 -0
  228. package/presets/opencode/next/team/conventions.md +21 -0
  229. package/presets/opencode/next/team/fixtures/artifact-manifest.example.json +79 -0
  230. package/presets/opencode/next/team/fixtures/bugfix-standard.json +25 -0
  231. package/presets/opencode/next/team/fixtures/feature-full.json +29 -0
  232. package/presets/opencode/next/team/fixtures/feature-light.json +27 -0
  233. package/presets/opencode/next/team/fixtures/feature-with-figma-url.json +17 -0
  234. package/presets/opencode/next/team/fixtures/feature-with-ux-no-figma.json +17 -0
  235. package/presets/opencode/next/team/fixtures/memlog.example.md +9 -0
  236. package/presets/opencode/next/team/fixtures/metrics-scenarios/tasks/human-gate/metrics.json +69 -0
  237. package/presets/opencode/next/team/fixtures/metrics-scenarios/tasks/missing-ledger/status.json +5 -0
  238. package/presets/opencode/next/team/fixtures/metrics-scenarios/tasks/parallel-receipts/metrics.json +79 -0
  239. package/presets/opencode/next/team/fixtures/metrics-scenarios/tasks/serial-retry/metrics.json +124 -0
  240. package/presets/opencode/next/team/fixtures/metrics-scenarios/tasks/validation-failed/metrics.json +124 -0
  241. package/presets/opencode/next/team/fixtures/metrics.example.json +54 -0
  242. package/presets/opencode/next/team/fixtures/mockup-review-fail-embedded-status.json +8 -0
  243. package/presets/opencode/next/team/fixtures/mockup-review-standalone.json +14 -0
  244. package/presets/opencode/next/team/fixtures/ux-design-standalone.json +15 -0
  245. package/presets/opencode/next/team/tasks/.gitkeep +0 -0
  246. package/presets/opencode/platform-contract.json +131 -0
  247. package/scripts/capture-cli-golden.mjs +5 -1
  248. package/scripts/check-chain-hook-coverage.sh +1 -0
  249. package/scripts/check-opencode-adapter-map.mjs +294 -0
  250. package/scripts/check-opencode-local-overlay.mjs +56 -0
  251. package/scripts/check-opencode-platform-contract.mjs +388 -0
  252. package/scripts/check-opencode-token-budget.mjs +48 -0
  253. package/scripts/check-preset-leakage.sh +30 -1
  254. package/scripts/check-preset-structure.sh +45 -0
  255. package/scripts/check-task-router-intents.sh +4 -4
  256. package/scripts/fixtures/cli-golden/claude/next.json +8 -8
  257. package/scripts/fixtures/cli-golden/cursor/next.json +8 -8
  258. package/scripts/fixtures/cli-golden/mavis/next.json +9 -9
  259. package/scripts/fixtures/cli-golden/opencode/next.json +125 -0
  260. package/scripts/fixtures/opencode-adapter-map/invalid-unmapped/cursor/AGENTS.md +1 -0
  261. package/scripts/fixtures/opencode-adapter-map/invalid-unmapped/cursor/rules/extra.mdc +1 -0
  262. package/scripts/fixtures/opencode-adapter-map/invalid-unmapped/cursor/rules/sample.mdc +2 -0
  263. package/scripts/fixtures/opencode-adapter-map/invalid-unmapped/opencode/OPENCODE.md +1 -0
  264. package/scripts/fixtures/opencode-adapter-map/invalid-unmapped/opencode/adapter-map.json +26 -0
  265. package/scripts/fixtures/opencode-adapter-map/invalid-unmapped/opencode/rules/sample.md +2 -0
  266. package/scripts/fixtures/opencode-adapter-map/valid/cursor/AGENTS.md +1 -0
  267. package/scripts/fixtures/opencode-adapter-map/valid/cursor/rules/sample.mdc +2 -0
  268. package/scripts/fixtures/opencode-adapter-map/valid/opencode/OPENCODE.md +1 -0
  269. package/scripts/fixtures/opencode-adapter-map/valid/opencode/adapter-map.json +26 -0
  270. package/scripts/fixtures/opencode-adapter-map/valid/opencode/rules/sample.md +2 -0
  271. package/scripts/fixtures/opencode-platform-contract/invalid-home-destination.json +131 -0
  272. package/scripts/fixtures/opencode-platform-contract/invalid-missing-capability.json +126 -0
  273. package/scripts/fixtures/opencode-platform-contract/invalid-released-unknown-stack.json +133 -0
  274. package/scripts/fixtures/opencode-platform-contract/invalid-unknown-native-path.json +131 -0
  275. package/scripts/fixtures/opencode-platform-contract/invalid-version-pin.json +132 -0
  276. package/scripts/fixtures/opencode-platform-contract/valid.json +131 -0
  277. package/scripts/generate-mavis-next.mjs +9 -7
  278. package/scripts/generate-opencode-next.mjs +831 -0
  279. package/scripts/lib/discover-hooks.mjs +23 -1
  280. package/scripts/sync-dogfood-mavis.sh +90 -0
  281. package/scripts/sync-dogfood-opencode.sh +85 -0
  282. package/scripts/test-agent-task-metrics-hooks.mjs +4 -1
  283. package/scripts/test-chain-team-phases-coverage.mjs +6 -1
  284. package/scripts/test-cli-init-clean.mjs +111 -0
  285. package/scripts/test-opencode-context-budget.mjs +53 -0
@@ -10,6 +10,15 @@ export function loadReleasedMavisStacks(presetsRoot) {
10
10
  return Array.isArray(contract.releasedStacks) ? contract.releasedStacks : [];
11
11
  }
12
12
 
13
+ export function loadReleasedOpencodeStacks(presetsRoot) {
14
+ const contractPath = path.join(presetsRoot, 'opencode', 'platform-contract.json');
15
+ if (!fs.existsSync(contractPath)) {
16
+ return [];
17
+ }
18
+ const contract = JSON.parse(fs.readFileSync(contractPath, 'utf8'));
19
+ return Array.isArray(contract.releasedStacks) ? contract.releasedStacks : [];
20
+ }
21
+
13
22
  export function discoverChainHooks(presetsRoot) {
14
23
  const hooks = [];
15
24
  for (const platform of ['cursor', 'claude']) {
@@ -30,6 +39,12 @@ export function discoverChainHooks(presetsRoot) {
30
39
  hooks.push(hook);
31
40
  }
32
41
  }
42
+ for (const stack of loadReleasedOpencodeStacks(presetsRoot)) {
43
+ const hook = path.join(presetsRoot, 'opencode', stack, 'hooks', 'chain-team-phases.sh');
44
+ if (fs.existsSync(hook)) {
45
+ hooks.push(hook);
46
+ }
47
+ }
33
48
  return hooks;
34
49
  }
35
50
 
@@ -42,6 +57,9 @@ export function platformFromHook(hookPath) {
42
57
  if (hookPath.includes(`${path.sep}mavis${path.sep}`)) {
43
58
  return 'mavis';
44
59
  }
60
+ if (hookPath.includes(`${path.sep}opencode${path.sep}`)) {
61
+ return 'opencode';
62
+ }
45
63
  if (hookPath.includes(`${path.sep}cursor${path.sep}`)) {
46
64
  return 'cursor';
47
65
  }
@@ -66,7 +84,11 @@ export function expectedLegacyHookCount(presetsRoot) {
66
84
  }
67
85
 
68
86
  export function expectedHookCount(presetsRoot) {
69
- return expectedLegacyHookCount(presetsRoot) + loadReleasedMavisStacks(presetsRoot).filter((stack) =>
87
+ const mavisCount = loadReleasedMavisStacks(presetsRoot).filter((stack) =>
70
88
  fs.existsSync(path.join(presetsRoot, 'mavis', stack, 'hooks', 'chain-team-phases.sh')),
71
89
  ).length;
90
+ const opencodeCount = loadReleasedOpencodeStacks(presetsRoot).filter((stack) =>
91
+ fs.existsSync(path.join(presetsRoot, 'opencode', stack, 'hooks', 'chain-team-phases.sh')),
92
+ ).length;
93
+ return expectedLegacyHookCount(presetsRoot) + mavisCount + opencodeCount;
72
94
  }
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ # Sync monorepo dogfood root `.mavis/` from Mavis next preset (packages SoT).
3
+ # Also syncs repo-root AGENTS.md from REPO_AGENTS.md.
4
+ # Usage (from repo root or any cwd):
5
+ # ./packages/ai-rules/scripts/sync-dogfood-mavis.sh
6
+ # AI_RULES_DOGFOOD_PRESET=next ./packages/ai-rules/scripts/sync-dogfood-mavis.sh
7
+ #
8
+ # Note: `.mavis/` is ai-rules-owned, not a MiniMax-native discovery path. Mavis
9
+ # only ships `next` in `releasedStacks` (see presets/mavis/platform-contract.json).
10
+ # Other stacks stay TBD until WP-F human gate.
11
+ set -euo pipefail
12
+
13
+ ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
14
+ PRESET_NAME="${AI_RULES_DOGFOOD_PRESET:-next}"
15
+ SRC="$ROOT/packages/ai-rules/presets/mavis/$PRESET_NAME"
16
+ DST="$ROOT/.mavis"
17
+
18
+ if [[ ! -d "$SRC" ]]; then
19
+ echo "error: preset not found: $SRC" >&2
20
+ exit 1
21
+ fi
22
+
23
+ echo "Dogfood sync: $SRC → $DST (preset=$PRESET_NAME)"
24
+
25
+ mkdir -p "$DST"
26
+
27
+ # Platform files at preset root
28
+ for f in MAVIS.md MCP.md mcp.json README.md; do
29
+ if [[ -f "$SRC/$f" ]]; then
30
+ cp "$SRC/$f" "$DST/$f"
31
+ fi
32
+ done
33
+
34
+ # `.mavis/AGENTS.md` is the Mavis entry point (mirrors `.cursor/AGENTS.md`).
35
+ if [[ -f "$SRC/MAVIS.md" && ! -f "$DST/AGENTS.md" ]]; then
36
+ cp "$SRC/MAVIS.md" "$DST/AGENTS.md"
37
+ echo "Seeded .mavis/AGENTS.md from MAVIS.md"
38
+ fi
39
+
40
+ # Repo-root AGENTS.md (cross-tool) from REPO_AGENTS.md
41
+ if [[ -f "$SRC/REPO_AGENTS.md" ]]; then
42
+ cp "$SRC/REPO_AGENTS.md" "$ROOT/AGENTS.md"
43
+ echo "Copied REPO_AGENTS.md → $ROOT/AGENTS.md"
44
+ fi
45
+
46
+ # Directory trees (preserve local team/tasks)
47
+ for dir in agents commands rules skills; do
48
+ if [[ -d "$SRC/$dir" ]]; then
49
+ mkdir -p "$DST/$dir"
50
+ rsync -a --delete "$SRC/$dir/" "$DST/$dir/"
51
+ fi
52
+ done
53
+
54
+ # Hooks: only chain-team-phases.sh is shipped; examples / hooks.json are
55
+ # intentionally absent (see presets/mavis/next/adapter-map.json).
56
+ if [[ -d "$SRC/hooks" ]]; then
57
+ mkdir -p "$DST/hooks"
58
+ rsync -a --delete "$SRC/hooks/" "$DST/hooks/"
59
+ if [[ -f "$DST/hooks/chain-team-phases.sh" ]]; then
60
+ chmod +x "$DST/hooks/chain-team-phases.sh"
61
+ fi
62
+ fi
63
+
64
+ if [[ -d "$SRC/team" ]]; then
65
+ mkdir -p "$DST/team"
66
+ # Sync fixtures + README; never delete local tasks/
67
+ if [[ -d "$SRC/team/fixtures" ]]; then
68
+ mkdir -p "$DST/team/fixtures"
69
+ rsync -a --delete "$SRC/team/fixtures/" "$DST/team/fixtures/"
70
+ fi
71
+ if [[ -f "$SRC/team/README.md" ]]; then
72
+ cp "$SRC/team/README.md" "$DST/team/README.md"
73
+ fi
74
+ if [[ -f "$SRC/team/.gitkeep" ]]; then
75
+ mkdir -p "$DST/team/tasks"
76
+ cp "$SRC/team/.gitkeep" "$DST/team/tasks/.gitkeep" 2>/dev/null || true
77
+ fi
78
+ # Living conventions: seed if absent; preserve consumer edits
79
+ if [[ -f "$SRC/team/conventions.md" ]]; then
80
+ if [[ ! -f "$DST/team/conventions.md" ]]; then
81
+ cp "$SRC/team/conventions.md" "$DST/team/conventions.md"
82
+ echo "Seeded team/conventions.md"
83
+ else
84
+ echo "Tip: skipped team/conventions.md (already present — living dogfood notes preserved)"
85
+ fi
86
+ fi
87
+ fi
88
+
89
+ echo "Done. Review: git status -- .mavis/ AGENTS.md"
90
+ echo "Tip: run packages/ai-rules/scripts/check-shared-core-drift.sh after core edits."
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env bash
2
+ # Sync monorepo dogfood root `.opencode/` from OpenCode next preset (packages SoT).
3
+ # Also syncs repo-root AGENTS.md from REPO_AGENTS.md.
4
+ # Usage (from repo root or any cwd):
5
+ # ./packages/ai-rules/scripts/sync-dogfood-opencode.sh
6
+ # AI_RULES_DOGFOOD_PRESET=next ./packages/ai-rules/scripts/sync-dogfood-opencode.sh
7
+ #
8
+ # Note: OpenCode only ships `next` in `releasedStacks`.
9
+ set -euo pipefail
10
+
11
+ ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
12
+ PRESET_NAME="${AI_RULES_DOGFOOD_PRESET:-next}"
13
+ SRC="$ROOT/packages/ai-rules/presets/opencode/$PRESET_NAME"
14
+ DST="$ROOT/.opencode"
15
+
16
+ if [[ ! -d "$SRC" ]]; then
17
+ echo "error: preset not found: $SRC" >&2
18
+ exit 1
19
+ fi
20
+
21
+ echo "Dogfood sync: $SRC → $DST (preset=$PRESET_NAME)"
22
+
23
+ mkdir -p "$DST"
24
+
25
+ for f in OPENCODE.md MCP.md opencode.json README.md; do
26
+ if [[ -f "$SRC/$f" ]]; then
27
+ cp "$SRC/$f" "$DST/$f"
28
+ fi
29
+ done
30
+
31
+ if [[ -f "$SRC/OPENCODE.md" && ! -f "$DST/AGENTS.md" ]]; then
32
+ cp "$SRC/OPENCODE.md" "$DST/AGENTS.md"
33
+ echo "Seeded .opencode/AGENTS.md from OPENCODE.md"
34
+ fi
35
+
36
+ if [[ -f "$SRC/REPO_AGENTS.md" ]]; then
37
+ cp "$SRC/REPO_AGENTS.md" "$ROOT/AGENTS.md"
38
+ echo "Copied REPO_AGENTS.md → $ROOT/AGENTS.md"
39
+ fi
40
+
41
+ if [[ -f "$SRC/opencode.json" ]]; then
42
+ cp "$SRC/opencode.json" "$ROOT/opencode.json"
43
+ echo "Copied opencode.json → $ROOT/opencode.json"
44
+ fi
45
+
46
+ for dir in agents commands rules skills; do
47
+ if [[ -d "$SRC/$dir" ]]; then
48
+ mkdir -p "$DST/$dir"
49
+ rsync -a --delete "$SRC/$dir/" "$DST/$dir/"
50
+ fi
51
+ done
52
+
53
+ if [[ -d "$SRC/hooks" ]]; then
54
+ mkdir -p "$DST/hooks"
55
+ rsync -a --delete "$SRC/hooks/" "$DST/hooks/"
56
+ if [[ -f "$DST/hooks/chain-team-phases.sh" ]]; then
57
+ chmod +x "$DST/hooks/chain-team-phases.sh"
58
+ fi
59
+ fi
60
+
61
+ if [[ -d "$SRC/team" ]]; then
62
+ mkdir -p "$DST/team"
63
+ if [[ -d "$SRC/team/fixtures" ]]; then
64
+ mkdir -p "$DST/team/fixtures"
65
+ rsync -a --delete "$SRC/team/fixtures/" "$DST/team/fixtures/"
66
+ fi
67
+ if [[ -f "$SRC/team/README.md" ]]; then
68
+ cp "$SRC/team/README.md" "$DST/team/README.md"
69
+ fi
70
+ if [[ -f "$SRC/team/.gitkeep" ]]; then
71
+ mkdir -p "$DST/team/tasks"
72
+ cp "$SRC/team/.gitkeep" "$DST/team/tasks/.gitkeep" 2>/dev/null || true
73
+ fi
74
+ if [[ -f "$SRC/team/conventions.md" ]]; then
75
+ if [[ ! -f "$DST/team/conventions.md" ]]; then
76
+ cp "$SRC/team/conventions.md" "$DST/team/conventions.md"
77
+ echo "Seeded team/conventions.md"
78
+ else
79
+ echo "Tip: skipped team/conventions.md (already present — living dogfood notes preserved)"
80
+ fi
81
+ fi
82
+ fi
83
+
84
+ echo "Done. Review: git status -- .opencode/ AGENTS.md opencode.json"
85
+ echo "Tip: run packages/ai-rules/scripts/check-shared-core-drift.sh after core edits."
@@ -9,7 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
  const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
10
10
  const packageDirectory = path.resolve(scriptDirectory, '..');
11
11
  const presetsDirectory = path.join(packageDirectory, 'presets');
12
- import { discoverChainHooks, expectedHookCount, loadReleasedMavisStacks, platformFromHook } from './lib/discover-hooks.mjs';
12
+ import { discoverChainHooks, expectedHookCount, loadReleasedMavisStacks, loadReleasedOpencodeStacks, platformFromHook } from './lib/discover-hooks.mjs';
13
13
 
14
14
  const hooks = discoverChainHooks(presetsDirectory);
15
15
 
@@ -424,6 +424,9 @@ for (const command of [
424
424
  ...loadReleasedMavisStacks(presetsDirectory).map((stack) =>
425
425
  path.join(presetsDirectory, 'mavis', stack, 'commands', 'task-continue.md'),
426
426
  ),
427
+ ...loadReleasedOpencodeStacks(presetsDirectory).map((stack) =>
428
+ path.join(presetsDirectory, 'opencode', stack, 'commands', 'task-continue.md'),
429
+ ),
427
430
  ]) {
428
431
  const source = fs.readFileSync(command, 'utf8');
429
432
  assert.match(source, /action":"end_human_gate"/, `${command}: must invoke installed hook end_human_gate action`);
@@ -44,7 +44,12 @@ assert.equal(
44
44
 
45
45
  // mcp-ts does not ship openspec-author (per preset-no-cross-stack-leakage).
46
46
  const HOOKS_WITH_OPENSPEC = hooks.filter((hook) => !hook.includes(`${path.sep}mcp-ts${path.sep}`));
47
- assert.equal(HOOKS_WITH_OPENSPEC.length, 19, 'expected 19 hooks to cover openspec-author');
47
+ const mcpTsHookCount = hooks.filter((hook) => hook.includes(`${path.sep}mcp-ts${path.sep}`)).length;
48
+ assert.equal(
49
+ HOOKS_WITH_OPENSPEC.length,
50
+ hooks.length - mcpTsHookCount,
51
+ `expected ${hooks.length - mcpTsHookCount} hooks to cover openspec-author`,
52
+ );
48
53
 
49
54
  function readJson(filePath) {
50
55
  return JSON.parse(fs.readFileSync(filePath, 'utf8'));
@@ -236,6 +236,111 @@ if (!fs.existsSync(mavisGoldenPath)) {
236
236
  assert.deepEqual(mavisFiles, expectedMavis.files, 'mavis/next golden drifted');
237
237
  }
238
238
 
239
+ const ocFresh = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-opencode-fresh-'));
240
+ const ocInitOut = execFileSync(
241
+ process.execPath,
242
+ [cli, 'init', 'opencode', '--preset', 'next', '--cwd', ocFresh],
243
+ { encoding: 'utf8' },
244
+ );
245
+ assert.match(ocInitOut, /\.opencode installed/);
246
+ assert.equal(fs.existsSync(path.join(ocFresh, 'AGENTS.md')), true);
247
+ assert.equal(fs.existsSync(path.join(ocFresh, '.opencode', 'AGENTS.md')), true);
248
+ assert.equal(fs.existsSync(path.join(ocFresh, '.opencode', 'rules', 'next-app-core.md')), true);
249
+ assert.equal(fs.existsSync(path.join(ocFresh, '.opencode', 'hooks', 'chain-team-phases.sh')), true);
250
+ assert.equal(fs.existsSync(path.join(ocFresh, 'opencode.json')), true);
251
+ assert.equal(fs.existsSync(path.join(ocFresh, '.opencode', 'adapter-map.json')), false);
252
+ assert.equal(fs.existsSync(path.join(ocFresh, 'docs', 'specs', 'INDEX.md')), true);
253
+ const ocManifest = readJson(path.join(ocFresh, '.opencode', '.ai-rules-install.json'));
254
+ assert.equal(ocManifest.schemaVersion, 1);
255
+ assert.equal(ocManifest.platform, 'opencode');
256
+ assert.equal(ocManifest.preset, 'next');
257
+ assert.ok(ocManifest.managedFiles['AGENTS.md']);
258
+ assert.ok(ocManifest.shippedMcpServerIds.includes('context7'));
259
+ assert.equal(ocManifest.docsSeed.seeded, true);
260
+
261
+ const ocAgents = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-opencode-agents-'));
262
+ fs.writeFileSync(path.join(ocAgents, 'AGENTS.md'), '# consumer agents\n');
263
+ runInit(cli, ocAgents, 'opencode', 'next');
264
+ assert.match(fs.readFileSync(path.join(ocAgents, 'AGENTS.md'), 'utf8'), /OpenCode load map/);
265
+
266
+ const ocSpecs = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-opencode-specs-'));
267
+ fs.mkdirSync(path.join(ocSpecs, 'docs', 'specs'), { recursive: true });
268
+ runInit(cli, ocSpecs, 'opencode', 'next');
269
+ assert.equal(fs.existsSync(path.join(ocSpecs, 'docs', 'specs', 'INDEX.md')), false);
270
+ assert.equal(
271
+ readJson(path.join(ocSpecs, '.opencode', '.ai-rules-install.json')).docsSeed.reason,
272
+ 'directory-exists',
273
+ );
274
+
275
+ writeJson(path.join(ocFresh, 'opencode.json'), {
276
+ $schema: 'https://opencode.ai/config.json',
277
+ mcp: {
278
+ context7: {
279
+ type: 'local',
280
+ command: ['npx', '-y', '@upstash/context7-mcp@old'],
281
+ environment: { CONTEXT7_API_KEY: 'keep-me' },
282
+ },
283
+ storybook: { type: 'remote', url: 'http://localhost:6006/mcp' },
284
+ },
285
+ localOnly: true,
286
+ });
287
+ const ocRepeat = runInit(cli, ocFresh, 'opencode', 'next');
288
+ assert.match(ocRepeat, /kept extra servers: storybook/);
289
+ const ocMerged = readJson(path.join(ocFresh, 'opencode.json'));
290
+ assert.equal(ocMerged.localOnly, true);
291
+ assert.ok(ocMerged.mcp.storybook);
292
+ assert.equal(ocMerged.mcp.context7.environment.CONTEXT7_API_KEY, 'keep-me');
293
+
294
+ ocMerged.localNote = true;
295
+ writeJson(path.join(ocFresh, 'opencode.json'), ocMerged);
296
+ const ocModifiedRel = path.join(ocFresh, '.opencode', 'rules', 'next-app-core.md');
297
+ fs.appendFileSync(ocModifiedRel, '\n# local overlay note\n');
298
+ runClean(ocFresh, 'opencode');
299
+ assert.equal(fs.existsSync(ocModifiedRel), true);
300
+ assert.equal(fs.existsSync(path.join(ocFresh, '.opencode', '.ai-rules-install.json')), false);
301
+ assert.equal(fs.existsSync(path.join(ocFresh, 'AGENTS.md')), false);
302
+ assert.ok(readJson(path.join(ocFresh, 'opencode.json')).mcp.storybook);
303
+ assert.equal(readJson(path.join(ocFresh, 'opencode.json')).mcp.context7, undefined);
304
+ assert.equal(readJson(path.join(ocFresh, 'opencode.json')).localNote, true);
305
+
306
+ const ocMissing = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-opencode-missing-'));
307
+ fs.writeFileSync(path.join(ocMissing, 'keep.txt'), 'safe\n');
308
+ const ocMissingClean = runCli(ocMissing, ['clean', 'opencode']);
309
+ assert.equal(ocMissingClean.status, 1);
310
+ assert.match(`${ocMissingClean.stderr}${ocMissingClean.stdout}`, /nothing removed/);
311
+ assert.equal(fs.readFileSync(path.join(ocMissing, 'keep.txt'), 'utf8'), 'safe\n');
312
+
313
+ const ocRollback = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-opencode-rollback-'));
314
+ fs.writeFileSync(path.join(ocRollback, 'AGENTS.md'), '# prior\n');
315
+ const ocFail = runCli(ocRollback, ['init', 'opencode', '--preset', 'next'], {
316
+ AI_RULES_OPENCODE_FAIL_PHASE: 'write',
317
+ });
318
+ assert.equal(ocFail.status, 1);
319
+ assert.equal(fs.readFileSync(path.join(ocRollback, 'AGENTS.md'), 'utf8'), '# prior\n');
320
+ assert.equal(fs.existsSync(path.join(ocRollback, '.opencode')), false);
321
+
322
+ const ocUnreleased = runCli(ocFresh, ['init', 'opencode', '--preset', 'nuxt']);
323
+ assert.equal(ocUnreleased.status, 1);
324
+ assert.match(`${ocUnreleased.stderr}${ocUnreleased.stdout}`, /not released yet/i);
325
+
326
+ const ocGoldenDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-opencode-golden-'));
327
+ runInit(cli, ocGoldenDir, 'opencode', 'next');
328
+ const ocFiles = snapshotInstall(ocGoldenDir);
329
+ delete ocFiles['.opencode/.ai-rules-install.json'];
330
+ const ocGoldenPath = path.join(goldenRoot, 'opencode', 'next.json');
331
+ if (!fs.existsSync(ocGoldenPath)) {
332
+ writeJson(ocGoldenPath, {
333
+ schemaVersion: 1,
334
+ tool: 'opencode',
335
+ preset: 'next',
336
+ files: ocFiles,
337
+ });
338
+ } else {
339
+ const expectedOc = readJson(ocGoldenPath);
340
+ delete expectedOc.files['.opencode/.ai-rules-install.json'];
341
+ assert.deepEqual(ocFiles, expectedOc.files, 'opencode/next golden drifted');
342
+ }
343
+
239
344
  for (const dir of [
240
345
  isolatedHome,
241
346
  fresh,
@@ -249,6 +354,12 @@ for (const dir of [
249
354
  failManifest,
250
355
  toolFirst,
251
356
  mavisGoldenDir,
357
+ ocFresh,
358
+ ocAgents,
359
+ ocSpecs,
360
+ ocMissing,
361
+ ocRollback,
362
+ ocGoldenDir,
252
363
  ]) {
253
364
  fs.rmSync(dir, { recursive: true, force: true });
254
365
  }
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * P1 context budget: root AGENTS + .opencode/AGENTS combined nonempty lines ≤ 170.
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { loadReleasedOpencodeStacks } from './lib/discover-hooks.mjs';
9
+
10
+ const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
11
+ const presetsRoot = path.join(scriptDirectory, '..', 'presets');
12
+ const LINE_CAP = 170;
13
+ const SENTINELS = [
14
+ 'next-app-core',
15
+ 'package-manager',
16
+ 'code-quality-and-refactoring',
17
+ 'ai-local-overlay',
18
+ '.opencode/AGENTS.md',
19
+ 'status.json',
20
+ ];
21
+
22
+ function nonemptyLines(text) {
23
+ return text.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
24
+ }
25
+
26
+ const stacks = loadReleasedOpencodeStacks(presetsRoot);
27
+ if (stacks.length === 0) {
28
+ console.log('ok opencode context-budget: no released stacks');
29
+ process.exit(0);
30
+ }
31
+
32
+ let failed = 0;
33
+ for (const stack of stacks) {
34
+ const root = path.join(presetsRoot, 'opencode', stack);
35
+ const repoAgents = fs.readFileSync(path.join(root, 'REPO_AGENTS.md'), 'utf8');
36
+ const opencodeAgents = fs.readFileSync(path.join(root, 'OPENCODE.md'), 'utf8');
37
+ const combined = `${repoAgents}\n${opencodeAgents}`;
38
+ const count = nonemptyLines(repoAgents) + nonemptyLines(opencodeAgents);
39
+ if (count > LINE_CAP) {
40
+ console.error(`FAIL opencode/${stack}: entry nonempty lines ${count} > ${LINE_CAP}`);
41
+ failed += 1;
42
+ } else {
43
+ console.log(`ok opencode/${stack}: entry nonempty lines ${count} (cap ${LINE_CAP})`);
44
+ }
45
+ for (const sentinel of SENTINELS) {
46
+ if (!combined.includes(sentinel)) {
47
+ console.error(`FAIL opencode/${stack}: missing critical sentinel ${sentinel}`);
48
+ failed += 1;
49
+ }
50
+ }
51
+ }
52
+
53
+ process.exit(failed === 0 ? 0 : 1);