@hivehub/rulebook 5.3.3 → 5.4.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 (492) hide show
  1. package/.claude/commands/analysis.md +35 -35
  2. package/.claude/commands/continue.md +33 -33
  3. package/.claude/commands/ralph-config.md +112 -112
  4. package/.claude/commands/ralph-history.md +110 -110
  5. package/.claude/commands/ralph-init.md +72 -72
  6. package/.claude/commands/ralph-pause-resume.md +105 -105
  7. package/.claude/commands/ralph-run.md +101 -101
  8. package/.claude/commands/ralph-status.md +76 -76
  9. package/.claude/commands/rulebook-decision-create.md +55 -55
  10. package/.claude/commands/rulebook-decision-list.md +15 -15
  11. package/.claude/commands/rulebook-knowledge-add.md +41 -41
  12. package/.claude/commands/rulebook-knowledge-list.md +15 -15
  13. package/.claude/commands/rulebook-learn-capture.md +48 -48
  14. package/.claude/commands/rulebook-learn-list.md +13 -13
  15. package/.claude/commands/rulebook-memory-save.md +48 -48
  16. package/.claude/commands/rulebook-memory-search.md +47 -47
  17. package/.claude/commands/rulebook-task-apply.md +67 -67
  18. package/.claude/commands/rulebook-task-archive.md +94 -94
  19. package/.claude/commands/rulebook-task-create.md +93 -93
  20. package/.claude/commands/rulebook-task-list.md +42 -42
  21. package/.claude/commands/rulebook-task-show.md +52 -52
  22. package/.claude/commands/rulebook-task-validate.md +53 -53
  23. package/.claude-plugin/marketplace.json +28 -28
  24. package/.claude-plugin/plugin.json +8 -8
  25. package/LICENSE +191 -191
  26. package/README.md +42 -3
  27. package/dist/cli/commands/compress.d.ts +18 -0
  28. package/dist/cli/commands/compress.d.ts.map +1 -0
  29. package/dist/cli/commands/compress.js +100 -0
  30. package/dist/cli/commands/compress.js.map +1 -0
  31. package/dist/cli/commands/index.d.ts +1 -0
  32. package/dist/cli/commands/index.d.ts.map +1 -1
  33. package/dist/cli/commands/index.js +1 -0
  34. package/dist/cli/commands/index.js.map +1 -1
  35. package/dist/cli/commands/init.d.ts.map +1 -1
  36. package/dist/cli/commands/init.js +2 -0
  37. package/dist/cli/commands/init.js.map +1 -1
  38. package/dist/cli/commands/update.d.ts.map +1 -1
  39. package/dist/cli/commands/update.js +2 -0
  40. package/dist/cli/commands/update.js.map +1 -1
  41. package/dist/core/claude-settings-manager.d.ts +7 -0
  42. package/dist/core/claude-settings-manager.d.ts.map +1 -1
  43. package/dist/core/claude-settings-manager.js +31 -14
  44. package/dist/core/claude-settings-manager.js.map +1 -1
  45. package/dist/core/compress/compressor.d.ts +60 -0
  46. package/dist/core/compress/compressor.d.ts.map +1 -0
  47. package/dist/core/compress/compressor.js +232 -0
  48. package/dist/core/compress/compressor.js.map +1 -0
  49. package/dist/core/compress/discover.d.ts +19 -0
  50. package/dist/core/compress/discover.d.ts.map +1 -0
  51. package/dist/core/compress/discover.js +100 -0
  52. package/dist/core/compress/discover.js.map +1 -0
  53. package/dist/core/compress/validator.d.ts +47 -0
  54. package/dist/core/compress/validator.d.ts.map +1 -0
  55. package/dist/core/compress/validator.js +131 -0
  56. package/dist/core/compress/validator.js.map +1 -0
  57. package/dist/core/doctor.d.ts.map +1 -1
  58. package/dist/core/doctor.js +66 -0
  59. package/dist/core/doctor.js.map +1 -1
  60. package/dist/core/generator.d.ts +16 -0
  61. package/dist/core/generator.d.ts.map +1 -1
  62. package/dist/core/generator.js +36 -11
  63. package/dist/core/generator.js.map +1 -1
  64. package/dist/hooks/safe-flag-io.d.ts +77 -0
  65. package/dist/hooks/safe-flag-io.d.ts.map +1 -0
  66. package/dist/hooks/safe-flag-io.js +169 -0
  67. package/dist/hooks/safe-flag-io.js.map +1 -0
  68. package/dist/index.js +11 -1
  69. package/dist/index.js.map +1 -1
  70. package/dist/mcp/rulebook-server.d.ts.map +1 -1
  71. package/dist/mcp/rulebook-server.js +236 -0
  72. package/dist/mcp/rulebook-server.js.map +1 -1
  73. package/dist/types.d.ts +4 -0
  74. package/dist/types.d.ts.map +1 -1
  75. package/package.json +23 -21
  76. package/templates/agents/accessibility-reviewer.md +43 -43
  77. package/templates/agents/api-designer.md +42 -42
  78. package/templates/agents/architect.md +51 -51
  79. package/templates/agents/build-engineer.md +36 -36
  80. package/templates/agents/code-reviewer.md +47 -47
  81. package/templates/agents/compiler/codegen-debugger.md +34 -34
  82. package/templates/agents/compiler/stdlib-engineer.md +28 -28
  83. package/templates/agents/compiler/test-coverage-guardian.md +31 -31
  84. package/templates/agents/context-intelligence.md +52 -52
  85. package/templates/agents/database-architect.md +41 -41
  86. package/templates/agents/devops-engineer.md +42 -42
  87. package/templates/agents/docs-writer.md +38 -38
  88. package/templates/agents/game-engine/cpp-core-expert.md +35 -35
  89. package/templates/agents/game-engine/render-engineer.md +22 -22
  90. package/templates/agents/game-engine/shader-engineer.md +38 -38
  91. package/templates/agents/game-engine/systems-integration.md +43 -43
  92. package/templates/agents/generic/code-reviewer.md +41 -41
  93. package/templates/agents/generic/docs-writer.md +25 -25
  94. package/templates/agents/generic/project-manager.md +36 -36
  95. package/templates/agents/generic/researcher.md +34 -34
  96. package/templates/agents/generic/test-engineer.md +41 -41
  97. package/templates/agents/i18n-engineer.md +42 -42
  98. package/templates/agents/implementer.md +42 -42
  99. package/templates/agents/migration-engineer.md +42 -42
  100. package/templates/agents/mobile/platform-specialist.md +22 -22
  101. package/templates/agents/mobile/ui-engineer.md +22 -22
  102. package/templates/agents/performance-engineer.md +49 -49
  103. package/templates/agents/refactoring-agent.md +41 -41
  104. package/templates/agents/researcher.md +38 -38
  105. package/templates/agents/security-reviewer.md +40 -40
  106. package/templates/agents/team-lead.md +37 -37
  107. package/templates/agents/tester.md +48 -48
  108. package/templates/agents/ux-reviewer.md +43 -43
  109. package/templates/agents/web-app/api-designer.md +22 -22
  110. package/templates/agents/web-app/backend-engineer.md +30 -30
  111. package/templates/agents/web-app/database-engineer.md +22 -22
  112. package/templates/agents/web-app/frontend-engineer.md +29 -29
  113. package/templates/agents/web-app/security-reviewer.md +32 -32
  114. package/templates/ci/rulebook-review.yml +26 -26
  115. package/templates/cli/AIDER.md +49 -49
  116. package/templates/cli/AMAZON_Q.md +25 -25
  117. package/templates/cli/AUGGIE.md +32 -32
  118. package/templates/cli/CLAUDE.md +117 -117
  119. package/templates/cli/CLINE.md +99 -99
  120. package/templates/cli/CODEBUDDY.md +20 -20
  121. package/templates/cli/CODEIUM.md +20 -20
  122. package/templates/cli/CODEX.md +21 -21
  123. package/templates/cli/CONTINUE.md +34 -34
  124. package/templates/cli/CURSOR_CLI.md +62 -62
  125. package/templates/cli/FACTORY.md +18 -18
  126. package/templates/cli/GEMINI.md +35 -35
  127. package/templates/cli/KILOCODE.md +18 -18
  128. package/templates/cli/OPENCODE.md +18 -18
  129. package/templates/cli/_GENERIC_TEMPLATE.md +29 -29
  130. package/templates/cli/gemini-extension.json +77 -77
  131. package/templates/commands/rulebook-decision-create.md +55 -55
  132. package/templates/commands/rulebook-decision-list.md +15 -15
  133. package/templates/commands/rulebook-knowledge-add.md +41 -41
  134. package/templates/commands/rulebook-knowledge-list.md +15 -15
  135. package/templates/commands/rulebook-learn-capture.md +48 -48
  136. package/templates/commands/rulebook-learn-list.md +13 -13
  137. package/templates/commands/rulebook-memory-save.md +48 -48
  138. package/templates/commands/rulebook-memory-search.md +47 -47
  139. package/templates/commands/rulebook-task-apply.md +67 -67
  140. package/templates/commands/rulebook-task-archive.md +94 -94
  141. package/templates/commands/rulebook-task-create.md +93 -93
  142. package/templates/commands/rulebook-task-list.md +42 -42
  143. package/templates/commands/rulebook-task-show.md +52 -52
  144. package/templates/commands/rulebook-task-validate.md +53 -53
  145. package/templates/compact-context/_default.md +23 -23
  146. package/templates/compact-context/cpp.md +26 -26
  147. package/templates/compact-context/go.md +26 -26
  148. package/templates/compact-context/python.md +26 -26
  149. package/templates/compact-context/rust.md +28 -28
  150. package/templates/compact-context/typescript.md +29 -29
  151. package/templates/core/AGENTS_OVERRIDE.md +16 -16
  152. package/templates/core/AGENT_AUTOMATION.md +296 -296
  153. package/templates/core/CLAUDE_MD_v2.md +71 -71
  154. package/templates/core/DAG.md +304 -304
  155. package/templates/core/DECISIONS.md +38 -38
  156. package/templates/core/DOCUMENTATION_RULES.md +36 -36
  157. package/templates/core/KNOWLEDGE.md +49 -49
  158. package/templates/core/MULTI_AGENT.md +74 -74
  159. package/templates/core/PLANS.md +28 -28
  160. package/templates/core/QUALITY_ENFORCEMENT.md +68 -68
  161. package/templates/core/RALPH.md +471 -471
  162. package/templates/core/RULEBOOK.md +1947 -1947
  163. package/templates/core/TIER1_PROHIBITIONS.md +154 -154
  164. package/templates/core/TOKEN_OPTIMIZATION.md +49 -49
  165. package/templates/core/WORKSPACE.md +69 -69
  166. package/templates/frameworks/ANGULAR.md +36 -36
  167. package/templates/frameworks/DJANGO.md +83 -83
  168. package/templates/frameworks/ELECTRON.md +147 -147
  169. package/templates/frameworks/FLASK.md +38 -38
  170. package/templates/frameworks/FLUTTER.md +55 -55
  171. package/templates/frameworks/JQUERY.md +32 -32
  172. package/templates/frameworks/LARAVEL.md +38 -38
  173. package/templates/frameworks/NESTJS.md +43 -43
  174. package/templates/frameworks/NEXTJS.md +127 -127
  175. package/templates/frameworks/NUXT.md +40 -40
  176. package/templates/frameworks/RAILS.md +66 -66
  177. package/templates/frameworks/REACT.md +38 -38
  178. package/templates/frameworks/REACT_NATIVE.md +47 -47
  179. package/templates/frameworks/SPRING.md +39 -39
  180. package/templates/frameworks/SYMFONY.md +36 -36
  181. package/templates/frameworks/VUE.md +36 -36
  182. package/templates/frameworks/ZEND.md +35 -35
  183. package/templates/git/CI_CD_PATTERNS.md +661 -661
  184. package/templates/git/GITHUB_ACTIONS.md +728 -728
  185. package/templates/git/GITLAB_CI.md +730 -730
  186. package/templates/git/GIT_WORKFLOW.md +1192 -1192
  187. package/templates/git/SECRETS_MANAGEMENT.md +585 -585
  188. package/templates/hooks/COMMIT_MSG.md +530 -530
  189. package/templates/hooks/POST_CHECKOUT.md +546 -546
  190. package/templates/hooks/PREPARE_COMMIT_MSG.md +619 -619
  191. package/templates/hooks/PRE_COMMIT.md +414 -414
  192. package/templates/hooks/PRE_PUSH.md +601 -601
  193. package/templates/hooks/check-context-and-handoff.ps1 +58 -58
  194. package/templates/hooks/check-context-and-handoff.sh +76 -76
  195. package/templates/hooks/enforce-team-for-background-agents.ps1 +63 -63
  196. package/templates/hooks/enforce-team-for-background-agents.sh +55 -55
  197. package/templates/hooks/on-compact-reinject.sh +34 -34
  198. package/templates/hooks/resume-from-handoff.ps1 +40 -40
  199. package/templates/hooks/resume-from-handoff.sh +61 -61
  200. package/templates/hooks/terse-activate.ps1 +143 -0
  201. package/templates/hooks/terse-activate.sh +197 -0
  202. package/templates/hooks/terse-mode-tracker.ps1 +153 -0
  203. package/templates/hooks/terse-mode-tracker.sh +187 -0
  204. package/templates/ides/CONTINUE_RULES.md +16 -16
  205. package/templates/ides/COPILOT.md +37 -37
  206. package/templates/ides/COPILOT_INSTRUCTIONS.md +23 -23
  207. package/templates/ides/CURSOR.md +43 -43
  208. package/templates/ides/GEMINI_RULES.md +17 -17
  209. package/templates/ides/JETBRAINS_AI.md +35 -35
  210. package/templates/ides/REPLIT.md +36 -36
  211. package/templates/ides/TABNINE.md +29 -29
  212. package/templates/ides/VSCODE.md +40 -40
  213. package/templates/ides/WINDSURF.md +36 -36
  214. package/templates/ides/WINDSURF_RULES.md +14 -14
  215. package/templates/ides/ZED.md +32 -32
  216. package/templates/ides/cursor-mdc/go.mdc +24 -24
  217. package/templates/ides/cursor-mdc/python.mdc +24 -24
  218. package/templates/ides/cursor-mdc/quality.mdc +25 -25
  219. package/templates/ides/cursor-mdc/ralph.mdc +39 -39
  220. package/templates/ides/cursor-mdc/rulebook.mdc +38 -38
  221. package/templates/ides/cursor-mdc/rust.mdc +24 -24
  222. package/templates/ides/cursor-mdc/typescript.mdc +25 -25
  223. package/templates/languages/C.md +333 -333
  224. package/templates/languages/CPP.md +743 -743
  225. package/templates/languages/CSHARP.md +417 -417
  226. package/templates/languages/ELIXIR.md +454 -454
  227. package/templates/languages/ERLANG.md +361 -361
  228. package/templates/languages/GO.md +645 -645
  229. package/templates/languages/HASKELL.md +177 -177
  230. package/templates/languages/JAVA.md +607 -607
  231. package/templates/languages/JAVASCRIPT.md +631 -631
  232. package/templates/languages/JULIA.md +97 -97
  233. package/templates/languages/KOTLIN.md +511 -511
  234. package/templates/languages/LISP.md +100 -100
  235. package/templates/languages/LUA.md +74 -74
  236. package/templates/languages/OBJECTIVEC.md +90 -90
  237. package/templates/languages/PHP.md +416 -416
  238. package/templates/languages/PYTHON.md +682 -682
  239. package/templates/languages/RUBY.md +421 -421
  240. package/templates/languages/RUST.md +477 -477
  241. package/templates/languages/SAS.md +73 -73
  242. package/templates/languages/SCALA.md +348 -348
  243. package/templates/languages/SOLIDITY.md +580 -580
  244. package/templates/languages/SQL.md +137 -137
  245. package/templates/languages/SWIFT.md +466 -466
  246. package/templates/languages/TYPESCRIPT.md +591 -591
  247. package/templates/languages/ZIG.md +265 -265
  248. package/templates/modules/ATLASSIAN.md +255 -255
  249. package/templates/modules/CONTEXT7.md +54 -54
  250. package/templates/modules/FIGMA.md +267 -267
  251. package/templates/modules/GITHUB_MCP.md +64 -64
  252. package/templates/modules/GRAFANA.md +328 -328
  253. package/templates/modules/MEMORY.md +126 -126
  254. package/templates/modules/NOTION.md +247 -247
  255. package/templates/modules/PLAYWRIGHT.md +90 -90
  256. package/templates/modules/RULEBOOK_MCP.md +208 -156
  257. package/templates/modules/SERENA.md +337 -337
  258. package/templates/modules/SUPABASE.md +223 -223
  259. package/templates/modules/SYNAP.md +69 -69
  260. package/templates/modules/VECTORIZER.md +63 -63
  261. package/templates/modules/sequential-thinking.md +42 -42
  262. package/templates/ralph/ralph-history.bat +4 -4
  263. package/templates/ralph/ralph-history.sh +5 -5
  264. package/templates/ralph/ralph-init.bat +5 -5
  265. package/templates/ralph/ralph-init.sh +5 -5
  266. package/templates/ralph/ralph-pause.bat +5 -5
  267. package/templates/ralph/ralph-pause.sh +5 -5
  268. package/templates/ralph/ralph-run.bat +5 -5
  269. package/templates/ralph/ralph-run.sh +5 -5
  270. package/templates/ralph/ralph-status.bat +4 -4
  271. package/templates/ralph/ralph-status.sh +5 -5
  272. package/templates/rules/consult-analysis-before-implementing.md +23 -23
  273. package/templates/rules/cpp.md +46 -46
  274. package/templates/rules/csharp.md +44 -44
  275. package/templates/rules/diagnostic-first.md +39 -39
  276. package/templates/rules/fail-twice-escalate.md +46 -46
  277. package/templates/rules/follow-task-sequence.md +36 -36
  278. package/templates/rules/git-safety.md +29 -29
  279. package/templates/rules/go.md +40 -40
  280. package/templates/rules/incremental-implementation.md +56 -56
  281. package/templates/rules/incremental-tests.md +29 -29
  282. package/templates/rules/java.md +43 -43
  283. package/templates/rules/javascript.md +39 -39
  284. package/templates/rules/knowledge-base-usage.md +41 -41
  285. package/templates/rules/multi-agent-teams.md +75 -75
  286. package/templates/rules/no-deferred.md +31 -31
  287. package/templates/rules/no-shortcuts.md +30 -30
  288. package/templates/rules/python.md +43 -43
  289. package/templates/rules/research-first.md +30 -30
  290. package/templates/rules/respect-handoff-trigger.md +41 -41
  291. package/templates/rules/rust.md +40 -40
  292. package/templates/rules/sequential-editing.md +21 -21
  293. package/templates/rules/session-workflow.md +24 -24
  294. package/templates/rules/task-decomposition.md +32 -32
  295. package/templates/rules/typescript.md +40 -40
  296. package/templates/services/AZURE_BLOB.md +184 -184
  297. package/templates/services/CASSANDRA.md +239 -239
  298. package/templates/services/DATADOG.md +26 -26
  299. package/templates/services/DOCKER.md +124 -124
  300. package/templates/services/DOCKER_COMPOSE.md +168 -168
  301. package/templates/services/DYNAMODB.md +308 -308
  302. package/templates/services/ELASTICSEARCH.md +347 -347
  303. package/templates/services/GCS.md +178 -178
  304. package/templates/services/HELM.md +194 -194
  305. package/templates/services/INFLUXDB.md +265 -265
  306. package/templates/services/KAFKA.md +341 -341
  307. package/templates/services/KUBERNETES.md +208 -208
  308. package/templates/services/MARIADB.md +183 -183
  309. package/templates/services/MEMCACHED.md +242 -242
  310. package/templates/services/MINIO.md +201 -201
  311. package/templates/services/MONGODB.md +268 -268
  312. package/templates/services/MYSQL.md +358 -358
  313. package/templates/services/NEO4J.md +247 -247
  314. package/templates/services/OPENTELEMETRY.md +25 -25
  315. package/templates/services/ORACLE.md +290 -290
  316. package/templates/services/PINO.md +24 -24
  317. package/templates/services/POSTGRESQL.md +326 -326
  318. package/templates/services/PROMETHEUS.md +33 -33
  319. package/templates/services/RABBITMQ.md +286 -286
  320. package/templates/services/REDIS.md +292 -292
  321. package/templates/services/S3.md +298 -298
  322. package/templates/services/SENTRY.md +23 -23
  323. package/templates/services/SQLITE.md +294 -294
  324. package/templates/services/SQLSERVER.md +294 -294
  325. package/templates/services/WINSTON.md +30 -30
  326. package/templates/skills/cli/aider/SKILL.md +59 -59
  327. package/templates/skills/cli/amazon-q/SKILL.md +35 -35
  328. package/templates/skills/cli/auggie/SKILL.md +42 -42
  329. package/templates/skills/cli/claude/SKILL.md +42 -42
  330. package/templates/skills/cli/cline/SKILL.md +42 -42
  331. package/templates/skills/cli/codebuddy/SKILL.md +30 -30
  332. package/templates/skills/cli/codeium/SKILL.md +30 -30
  333. package/templates/skills/cli/codex/SKILL.md +31 -31
  334. package/templates/skills/cli/continue/SKILL.md +44 -44
  335. package/templates/skills/cli/cursor-cli/SKILL.md +38 -38
  336. package/templates/skills/cli/factory/SKILL.md +28 -28
  337. package/templates/skills/cli/gemini/SKILL.md +45 -45
  338. package/templates/skills/cli/kilocode/SKILL.md +28 -28
  339. package/templates/skills/cli/opencode/SKILL.md +28 -28
  340. package/templates/skills/core/agent-automation/SKILL.md +194 -194
  341. package/templates/skills/core/dag/SKILL.md +314 -314
  342. package/templates/skills/core/documentation-rules/SKILL.md +46 -46
  343. package/templates/skills/core/quality-enforcement/SKILL.md +78 -78
  344. package/templates/skills/core/rulebook/SKILL.md +176 -176
  345. package/templates/skills/core/rulebook-terse/SKILL.md +116 -0
  346. package/templates/skills/core/rulebook-terse-commit/SKILL.md +96 -0
  347. package/templates/skills/core/rulebook-terse-review/SKILL.md +112 -0
  348. package/templates/skills/dev/accessibility/SKILL.md +17 -17
  349. package/templates/skills/dev/analysis/SKILL.md +19 -19
  350. package/templates/skills/dev/api-design/SKILL.md +15 -15
  351. package/templates/skills/dev/architect/SKILL.md +17 -17
  352. package/templates/skills/dev/build-fix/SKILL.md +17 -17
  353. package/templates/skills/dev/db-design/SKILL.md +15 -15
  354. package/templates/skills/dev/debug/SKILL.md +16 -16
  355. package/templates/skills/dev/deploy/SKILL.md +17 -17
  356. package/templates/skills/dev/docs/SKILL.md +17 -17
  357. package/templates/skills/dev/handoff/SKILL.md +27 -27
  358. package/templates/skills/dev/migrate/SKILL.md +15 -15
  359. package/templates/skills/dev/perf/SKILL.md +17 -17
  360. package/templates/skills/dev/refactor/SKILL.md +17 -17
  361. package/templates/skills/dev/research/SKILL.md +14 -14
  362. package/templates/skills/dev/review/SKILL.md +18 -18
  363. package/templates/skills/dev/security-audit/SKILL.md +17 -17
  364. package/templates/skills/frameworks/angular/SKILL.md +46 -46
  365. package/templates/skills/frameworks/django/SKILL.md +93 -93
  366. package/templates/skills/frameworks/electron/SKILL.md +157 -157
  367. package/templates/skills/frameworks/flask/SKILL.md +48 -48
  368. package/templates/skills/frameworks/flutter/SKILL.md +65 -65
  369. package/templates/skills/frameworks/jquery/SKILL.md +42 -42
  370. package/templates/skills/frameworks/laravel/SKILL.md +48 -48
  371. package/templates/skills/frameworks/nestjs/SKILL.md +53 -53
  372. package/templates/skills/frameworks/nextjs/SKILL.md +137 -137
  373. package/templates/skills/frameworks/nuxt/SKILL.md +50 -50
  374. package/templates/skills/frameworks/rails/SKILL.md +76 -76
  375. package/templates/skills/frameworks/react/SKILL.md +48 -48
  376. package/templates/skills/frameworks/react-native/SKILL.md +57 -57
  377. package/templates/skills/frameworks/spring/SKILL.md +49 -49
  378. package/templates/skills/frameworks/symfony/SKILL.md +46 -46
  379. package/templates/skills/frameworks/vue/SKILL.md +46 -46
  380. package/templates/skills/frameworks/zend/SKILL.md +45 -45
  381. package/templates/skills/ides/copilot/SKILL.md +47 -47
  382. package/templates/skills/ides/cursor/SKILL.md +53 -53
  383. package/templates/skills/ides/jetbrains-ai/SKILL.md +45 -45
  384. package/templates/skills/ides/replit/SKILL.md +46 -46
  385. package/templates/skills/ides/tabnine/SKILL.md +39 -39
  386. package/templates/skills/ides/vscode/SKILL.md +50 -50
  387. package/templates/skills/ides/windsurf/SKILL.md +46 -46
  388. package/templates/skills/ides/zed/SKILL.md +42 -42
  389. package/templates/skills/languages/c/SKILL.md +343 -343
  390. package/templates/skills/languages/cpp/SKILL.md +753 -753
  391. package/templates/skills/languages/csharp/SKILL.md +427 -427
  392. package/templates/skills/languages/elixir/SKILL.md +464 -464
  393. package/templates/skills/languages/erlang/SKILL.md +371 -371
  394. package/templates/skills/languages/go/SKILL.md +655 -655
  395. package/templates/skills/languages/haskell/SKILL.md +187 -187
  396. package/templates/skills/languages/java/SKILL.md +617 -617
  397. package/templates/skills/languages/javascript/SKILL.md +641 -641
  398. package/templates/skills/languages/julia/SKILL.md +107 -107
  399. package/templates/skills/languages/kotlin/SKILL.md +521 -521
  400. package/templates/skills/languages/lisp/SKILL.md +110 -110
  401. package/templates/skills/languages/lua/SKILL.md +84 -84
  402. package/templates/skills/languages/objectivec/SKILL.md +100 -100
  403. package/templates/skills/languages/php/SKILL.md +426 -426
  404. package/templates/skills/languages/python/SKILL.md +692 -692
  405. package/templates/skills/languages/ruby/SKILL.md +431 -431
  406. package/templates/skills/languages/rust/SKILL.md +487 -487
  407. package/templates/skills/languages/sas/SKILL.md +83 -83
  408. package/templates/skills/languages/scala/SKILL.md +358 -358
  409. package/templates/skills/languages/solidity/SKILL.md +590 -590
  410. package/templates/skills/languages/sql/SKILL.md +147 -147
  411. package/templates/skills/languages/swift/SKILL.md +476 -476
  412. package/templates/skills/languages/typescript/SKILL.md +302 -302
  413. package/templates/skills/languages/zig/SKILL.md +275 -275
  414. package/templates/skills/modules/atlassian/SKILL.md +265 -265
  415. package/templates/skills/modules/context7/SKILL.md +64 -64
  416. package/templates/skills/modules/figma/SKILL.md +277 -277
  417. package/templates/skills/modules/github-mcp/SKILL.md +74 -74
  418. package/templates/skills/modules/grafana/SKILL.md +338 -338
  419. package/templates/skills/modules/memory/SKILL.md +73 -73
  420. package/templates/skills/modules/notion/SKILL.md +257 -257
  421. package/templates/skills/modules/playwright/SKILL.md +100 -100
  422. package/templates/skills/modules/rulebook-mcp/SKILL.md +166 -166
  423. package/templates/skills/modules/serena/SKILL.md +347 -347
  424. package/templates/skills/modules/supabase/SKILL.md +233 -233
  425. package/templates/skills/modules/synap/SKILL.md +79 -79
  426. package/templates/skills/modules/vectorizer/SKILL.md +73 -73
  427. package/templates/skills/services/azure-blob/SKILL.md +194 -194
  428. package/templates/skills/services/cassandra/SKILL.md +249 -249
  429. package/templates/skills/services/dynamodb/SKILL.md +318 -318
  430. package/templates/skills/services/elasticsearch/SKILL.md +357 -357
  431. package/templates/skills/services/gcs/SKILL.md +188 -188
  432. package/templates/skills/services/influxdb/SKILL.md +275 -275
  433. package/templates/skills/services/kafka/SKILL.md +351 -351
  434. package/templates/skills/services/mariadb/SKILL.md +193 -193
  435. package/templates/skills/services/memcached/SKILL.md +252 -252
  436. package/templates/skills/services/minio/SKILL.md +211 -211
  437. package/templates/skills/services/mongodb/SKILL.md +278 -278
  438. package/templates/skills/services/mysql/SKILL.md +368 -368
  439. package/templates/skills/services/neo4j/SKILL.md +257 -257
  440. package/templates/skills/services/oracle/SKILL.md +300 -300
  441. package/templates/skills/services/postgresql/SKILL.md +336 -336
  442. package/templates/skills/services/rabbitmq/SKILL.md +296 -296
  443. package/templates/skills/services/redis/SKILL.md +302 -302
  444. package/templates/skills/services/s3/SKILL.md +308 -308
  445. package/templates/skills/services/sqlite/SKILL.md +304 -304
  446. package/templates/skills/services/sqlserver/SKILL.md +304 -304
  447. package/templates/skills/workflows/ralph/SKILL.md +309 -309
  448. package/templates/skills/workflows/ralph/install.sh +87 -87
  449. package/templates/skills/workflows/ralph/manifest.json +158 -158
  450. package/templates/workflows/codespell.yml +31 -31
  451. package/templates/workflows/cpp-lint.yml +47 -47
  452. package/templates/workflows/cpp-publish.yml +119 -119
  453. package/templates/workflows/cpp-test.yml +77 -77
  454. package/templates/workflows/dotnet-lint.yml +29 -29
  455. package/templates/workflows/dotnet-publish.yml +40 -40
  456. package/templates/workflows/dotnet-test.yml +41 -41
  457. package/templates/workflows/elixir-lint.yml +45 -45
  458. package/templates/workflows/elixir-publish.yml +49 -49
  459. package/templates/workflows/elixir-test.yml +54 -54
  460. package/templates/workflows/erlang-lint.yml +47 -47
  461. package/templates/workflows/erlang-test.yml +62 -62
  462. package/templates/workflows/go-lint.yml +39 -39
  463. package/templates/workflows/go-publish.yml +95 -95
  464. package/templates/workflows/go-test.yml +59 -59
  465. package/templates/workflows/java-lint.yml +60 -60
  466. package/templates/workflows/java-publish.yml +120 -120
  467. package/templates/workflows/java-test.yml +85 -85
  468. package/templates/workflows/kotlin-lint.yml +34 -34
  469. package/templates/workflows/kotlin-publish.yml +56 -56
  470. package/templates/workflows/kotlin-test.yml +48 -48
  471. package/templates/workflows/php-lint.yml +39 -39
  472. package/templates/workflows/php-publish.yml +50 -50
  473. package/templates/workflows/php-test.yml +54 -54
  474. package/templates/workflows/python-lint.yml +47 -47
  475. package/templates/workflows/python-publish.yml +91 -91
  476. package/templates/workflows/python-test.yml +59 -59
  477. package/templates/workflows/rust-lint.yml +54 -54
  478. package/templates/workflows/rust-publish.yml +66 -66
  479. package/templates/workflows/rust-test.yml +75 -75
  480. package/templates/workflows/solidity-lint.yml +41 -41
  481. package/templates/workflows/solidity-test.yml +47 -47
  482. package/templates/workflows/swift-lint.yml +32 -32
  483. package/templates/workflows/swift-publish.yml +58 -58
  484. package/templates/workflows/swift-test.yml +44 -44
  485. package/templates/workflows/typescript-publish.yml +60 -60
  486. package/templates/workflows/typescript-test.yml +73 -73
  487. package/templates/workflows/zig-lint.yml +27 -27
  488. package/templates/workflows/zig-test.yml +40 -40
  489. package/dist/cli/commands.d.ts +0 -225
  490. package/dist/cli/commands.d.ts.map +0 -1
  491. package/dist/cli/commands.js +0 -3984
  492. package/dist/cli/commands.js.map +0 -1
@@ -1,3984 +0,0 @@
1
- import chalk from 'chalk';
2
- import ora from 'ora';
3
- import { detectProject } from '../core/detector.js';
4
- import { generateFullAgents } from '../core/generator.js';
5
- import { mergeFullAgents } from '../core/merger.js';
6
- import { generateWorkflows, generateIDEFiles, generateAICLIFiles, } from '../core/workflow-generator.js';
7
- import { writeFile, createBackup, ensureDir } from '../utils/file-system.js';
8
- import { existsSync } from 'fs';
9
- import { parseRulesIgnore } from '../utils/rulesignore.js';
10
- import { installGitHooks } from '../utils/git-hooks.js';
11
- import { scaffoldMinimalProject } from '../core/minimal-scaffolder.js';
12
- import path from 'path';
13
- import { readFileSync, writeFileSync } from 'fs';
14
- import { fileURLToPath } from 'url';
15
- import { SkillsManager, getDefaultTemplatesPath } from '../core/skills-manager.js';
16
- import { WorkspaceManager } from '../core/workspace/workspace-manager.js';
17
- import { migrateLegacyMcpConfigs } from '../core/workspace/legacy-migrator.js';
18
- const FRAMEWORK_LABELS = {
19
- nestjs: 'NestJS',
20
- spring: 'Spring Boot',
21
- laravel: 'Laravel',
22
- angular: 'Angular',
23
- react: 'React',
24
- vue: 'Vue.js',
25
- nuxt: 'Nuxt',
26
- nextjs: 'Next.js',
27
- django: 'Django',
28
- rails: 'Ruby on Rails',
29
- flask: 'Flask',
30
- symfony: 'Symfony',
31
- zend: 'Zend Framework',
32
- jquery: 'jQuery',
33
- reactnative: 'React Native',
34
- flutter: 'Flutter',
35
- electron: 'Electron',
36
- };
37
- // Get version from package.json
38
- function getRulebookVersion() {
39
- try {
40
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
41
- const packagePath = path.join(__dirname, '..', '..', 'package.json');
42
- const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
43
- return packageJson.version;
44
- }
45
- catch {
46
- return '0.12.1'; // Fallback version
47
- }
48
- }
49
- export async function initCommand(options) {
50
- try {
51
- const cwd = process.cwd();
52
- console.log(chalk.bold.blue('\n🔍 Rulebook Project Initializer\n'));
53
- // Detect project
54
- const spinner = ora('Detecting project structure...').start();
55
- const detection = await detectProject(cwd);
56
- spinner.succeed('Project detection complete');
57
- // Assess project complexity for calibrated generation (v5)
58
- const { assessComplexity } = await import('../core/complexity-detector.js');
59
- const complexity = assessComplexity(cwd);
60
- console.log(chalk.gray(` Complexity: ${complexity.tier.toUpperCase()} (${complexity.metrics.estimatedLoc.toLocaleString()} LOC, ${complexity.metrics.languageCount} languages)`));
61
- // Show detection results
62
- if (detection.languages.length > 0) {
63
- console.log(chalk.green('\n✓ Detected languages:'));
64
- for (const lang of detection.languages) {
65
- console.log(` - ${lang.language} (${(lang.confidence * 100).toFixed(0)}% confidence)`);
66
- console.log(` Indicators: ${lang.indicators.join(', ')}`);
67
- }
68
- }
69
- if (detection.modules.filter((m) => m.detected).length > 0) {
70
- console.log(chalk.green('\n✓ Detected modules:'));
71
- for (const module of detection.modules.filter((m) => m.detected)) {
72
- console.log(` - ${module.module} (${module.source})`);
73
- }
74
- }
75
- // Show monorepo detection
76
- if (detection.monorepo?.detected) {
77
- const mono = detection.monorepo;
78
- console.log(chalk.green(`\n✓ Monorepo detected: ${chalk.bold(mono.tool ?? 'manual')}`));
79
- if (mono.packages.length > 0) {
80
- console.log(` Packages (${mono.packages.length}): ${mono.packages.slice(0, 5).join(', ')}${mono.packages.length > 5 ? ` +${mono.packages.length - 5} more` : ''}`);
81
- }
82
- if (options.package) {
83
- console.log(chalk.cyan(` → Initializing package: ${options.package}`));
84
- }
85
- }
86
- // Recommend sequential-thinking MCP if not detected
87
- const seqThinking = detection.modules.find((m) => m.module === 'sequential_thinking');
88
- if (seqThinking && !seqThinking.detected) {
89
- console.log(chalk.yellow('\n💡 Tip: Install sequential-thinking MCP for structured problem solving:\n' +
90
- ' npx @modelcontextprotocol/create-server sequential-thinking\n' +
91
- ' or add to .mcp.json: { "mcpServers": { "sequential-thinking": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"] } } }'));
92
- }
93
- const detectedFrameworks = detection.frameworks.filter((f) => f.detected);
94
- if (detectedFrameworks.length > 0) {
95
- console.log(chalk.green('\n✓ Detected frameworks:'));
96
- for (const framework of detectedFrameworks) {
97
- const languagesLabel = framework.languages.map((lang) => lang.toUpperCase()).join(', ');
98
- const indicators = framework.indicators.join(', ');
99
- const label = FRAMEWORK_LABELS[framework.framework] || framework.framework;
100
- console.log(` - ${label} (${languagesLabel})${indicators ? ` [${indicators}]` : ''}`);
101
- }
102
- }
103
- if (detection.existingAgents) {
104
- console.log(chalk.yellow(`\n⚠ Found existing AGENTS.md with ${detection.existingAgents.blocks.length} blocks`));
105
- for (const block of detection.existingAgents.blocks) {
106
- console.log(` - ${block.name}`);
107
- }
108
- }
109
- // Get project configuration — auto-setup from detection, no prompts
110
- const cliMinimal = Boolean(options.minimal);
111
- const cliLight = Boolean(options.light);
112
- const cliLean = Boolean(options.lean);
113
- const config = {
114
- languages: detection.languages.map((l) => l.language),
115
- modules: cliMinimal ? [] : detection.modules.filter((m) => m.detected).map((m) => m.module),
116
- frameworks: detection.frameworks.filter((f) => f.detected).map((f) => f.framework),
117
- ides: cliMinimal ? [] : ['cursor'],
118
- projectType: 'application',
119
- coverageThreshold: 95,
120
- strictDocs: true,
121
- generateWorkflows: true,
122
- includeGitWorkflow: true,
123
- gitPushMode: 'manual',
124
- installGitHooks: false,
125
- minimal: cliMinimal,
126
- lightMode: cliLight,
127
- modular: true,
128
- };
129
- console.log(chalk.blue('\nAuto-configuring from detection results...'));
130
- const minimalMode = config.minimal ?? cliMinimal;
131
- config.minimal = minimalMode;
132
- config.modules = minimalMode ? [] : config.modules || [];
133
- config.frameworks = config.frameworks || [];
134
- config.ides = minimalMode ? [] : config.ides || ['cursor'];
135
- config.includeGitWorkflow = config.includeGitWorkflow ?? true;
136
- config.generateWorkflows = config.generateWorkflows ?? true;
137
- config.modular = config.modular ?? true; // Enable modular by default
138
- if (cliLean) {
139
- config.agentsMode = 'lean';
140
- }
141
- let minimalArtifacts = [];
142
- if (minimalMode) {
143
- minimalArtifacts = await scaffoldMinimalProject(cwd, {
144
- projectName: path.basename(cwd),
145
- description: 'Essential project scaffolding generated by Rulebook minimal mode.',
146
- license: 'MIT',
147
- });
148
- }
149
- const detectedHookStatus = {
150
- preCommit: detection.gitHooks?.preCommitExists ?? false,
151
- prePush: detection.gitHooks?.prePushExists ?? false,
152
- };
153
- const hookLanguages = detection.languages.length > 0
154
- ? detection.languages
155
- : config.languages.map((language) => ({
156
- language: language,
157
- confidence: 1,
158
- indicators: [],
159
- }));
160
- let hooksInstalled = false;
161
- if (config.installGitHooks) {
162
- const hookSpinner = ora('Installing Git hooks (pre-commit & pre-push)...').start();
163
- try {
164
- await installGitHooks({ languages: hookLanguages, cwd });
165
- hookSpinner.succeed('Git hooks installed successfully');
166
- hooksInstalled = true;
167
- }
168
- catch (error) {
169
- hookSpinner.fail('Failed to install Git hooks');
170
- console.error(chalk.red(' ➤'), error instanceof Error ? error.message : error);
171
- console.log(chalk.yellow(' ⚠ Skipping automatic hook installation. You can rerun "rulebook init" later to retry or install manually.'));
172
- }
173
- }
174
- else if (!detectedHookStatus.preCommit || !detectedHookStatus.prePush) {
175
- console.log(chalk.gray('\nℹ Git hooks were not installed automatically. Run "rulebook init" again if you want to add them later.'));
176
- }
177
- const gitHooksActive = hooksInstalled || (detectedHookStatus.preCommit && detectedHookStatus.prePush);
178
- config.installGitHooks = gitHooksActive;
179
- // Check .rulesignore
180
- const rulesIgnore = await parseRulesIgnore(cwd);
181
- if (rulesIgnore.patterns.length > 0) {
182
- console.log(chalk.yellow('\n📋 Found .rulesignore with patterns:'));
183
- for (const pattern of rulesIgnore.patterns) {
184
- console.log(` - ${pattern}`);
185
- }
186
- }
187
- // Save project configuration to .rulebook
188
- const { createConfigManager } = await import('../core/config-manager.js');
189
- const configManager = createConfigManager(cwd);
190
- // Migrate old directory structure to new consolidated structure
191
- const dirMigrationSpinner = ora('Migrating directory structure...').start();
192
- await configManager.migrateDirectoryStructure(cwd);
193
- dirMigrationSpinner.succeed('Directory structure migrated');
194
- // Ensure .rulebook/memory/ directory exists for per-project memory persistence
195
- await ensureDir(path.join(cwd, '.rulebook', 'memory'));
196
- // Ensure .gitignore has .rulebook entries (keep specs/ and tasks/ tracked)
197
- await configManager.ensureGitignore();
198
- // Auto-detect and enable skills based on project detection (v2.0)
199
- let enabledSkills = [];
200
- try {
201
- const { SkillsManager, getDefaultTemplatesPath } = await import('../core/skills-manager.js');
202
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
203
- // Build a RulebookConfig-like object for skill detection
204
- const rulebookConfigForSkills = {
205
- languages: config.languages,
206
- frameworks: config.frameworks,
207
- modules: config.modules,
208
- services: config.services,
209
- };
210
- enabledSkills = await skillsManager.autoDetectSkills(rulebookConfigForSkills);
211
- if (enabledSkills.length > 0) {
212
- console.log(chalk.green('\n✓ Auto-detected skills:'));
213
- for (const skillId of enabledSkills) {
214
- console.log(chalk.gray(` - ${skillId}`));
215
- }
216
- }
217
- }
218
- catch {
219
- // Skills system not available or error - continue without skills
220
- }
221
- // Load existing config to preserve ralph and memory settings
222
- const existingConfig = await configManager.loadConfig();
223
- await configManager.updateConfig({
224
- languages: config.languages,
225
- frameworks: config.frameworks,
226
- modules: config.modules,
227
- services: config.services,
228
- modular: config.modular ?? true,
229
- rulebookDir: config.rulebookDir || '.rulebook',
230
- ...(config.agentsMode ? { agentsMode: config.agentsMode } : {}),
231
- skills: enabledSkills.length > 0 ? { enabled: enabledSkills } : undefined,
232
- ralph: existingConfig.ralph,
233
- memory: existingConfig.memory,
234
- });
235
- // --package: generate only the specified package's AGENTS.md and exit
236
- if (options.package) {
237
- const packageRoot = path.join(cwd, options.package);
238
- const { generatePackageAgentsMd } = await import('../core/generator.js');
239
- await generatePackageAgentsMd(packageRoot, config, cwd);
240
- console.log(chalk.green(`\n✅ AGENTS.md generated for package: ${options.package}`));
241
- return;
242
- }
243
- // Generate or merge AGENTS.md
244
- const agentsPath = path.join(cwd, 'AGENTS.md');
245
- let finalContent;
246
- if (detection.existingAgents) {
247
- // Migrate flat layout to specs/ subdirectory if needed
248
- {
249
- const { hasFlatLayout, migrateFlatToSpecs } = await import('../core/migrator.js');
250
- const rulebookDirForMigration = config.rulebookDir || '.rulebook';
251
- if (await hasFlatLayout(cwd, rulebookDirForMigration)) {
252
- const { migratedFiles } = await migrateFlatToSpecs(cwd, rulebookDirForMigration);
253
- if (migratedFiles.length > 0) {
254
- console.log(chalk.gray(` Migrated ${migratedFiles.length} file(s) to /${rulebookDirForMigration}/specs/`));
255
- }
256
- }
257
- }
258
- // Always merge — preserve existing customizations
259
- const mergeSpinner = ora('Merging with existing AGENTS.md...').start();
260
- finalContent = await mergeFullAgents(detection.existingAgents, config, cwd);
261
- const backupPath = await createBackup(agentsPath);
262
- mergeSpinner.succeed(`Backup created: ${path.basename(backupPath)}`);
263
- }
264
- else {
265
- const genSpinner = ora('Generating AGENTS.md...').start();
266
- finalContent = await generateFullAgents(config, cwd);
267
- genSpinner.succeed('AGENTS.md generated');
268
- }
269
- // Write AGENTS.md
270
- await writeFile(agentsPath, finalContent);
271
- console.log(chalk.green(`\n✅ AGENTS.md written to ${agentsPath}`));
272
- // Install canonical rules based on complexity tier (v5)
273
- {
274
- const { installRule, projectRules } = await import('../core/rule-engine.js');
275
- const { getTemplatesDir } = await import('../core/generator.js');
276
- const templatesDir = getTemplatesDir();
277
- // Tier 1 rules — always installed
278
- const tier1Rules = [
279
- 'no-shortcuts',
280
- 'git-safety',
281
- 'sequential-editing',
282
- 'research-first',
283
- 'follow-task-sequence',
284
- 'incremental-implementation',
285
- ];
286
- // Tier 2 rules — installed for medium+ complexity
287
- const tier2Rules = [
288
- 'task-decomposition',
289
- 'incremental-tests',
290
- 'no-deferred',
291
- 'session-workflow',
292
- ];
293
- const rulesToInstall = [...tier1Rules];
294
- if (complexity.recommendations.tier2Rules) {
295
- rulesToInstall.push(...tier2Rules);
296
- }
297
- let installedCount = 0;
298
- for (const name of rulesToInstall) {
299
- const result = await installRule(cwd, name, templatesDir);
300
- if (result)
301
- installedCount++;
302
- }
303
- if (installedCount > 0) {
304
- console.log(chalk.gray(` • Installed ${installedCount} canonical rules to .rulebook/rules/`));
305
- }
306
- // Project rules to detected tools
307
- const ruleResult = await projectRules(cwd, {
308
- claudeCode: existsSync(path.join(cwd, '.claude')) || existsSync(path.join(cwd, 'CLAUDE.md')),
309
- cursor: detection.cursor?.detected,
310
- gemini: detection.geminiCli?.detected,
311
- windsurf: detection.windsurf?.detected,
312
- copilot: detection.githubCopilot?.detected,
313
- continueDev: detection.continueDev?.detected,
314
- });
315
- const totalProjected = ruleResult.claudeCode.length +
316
- ruleResult.cursor.length +
317
- ruleResult.gemini.length +
318
- ruleResult.copilot.length +
319
- ruleResult.windsurf.length +
320
- ruleResult.continueDev.length;
321
- if (totalProjected > 0) {
322
- console.log(chalk.gray(` • Projected rules to ${totalProjected} tool-specific files`));
323
- }
324
- }
325
- // Show Cursor MDC feedback
326
- if (detection.cursor?.detected) {
327
- if (detection.cursor.hasMdcRules) {
328
- console.log(chalk.gray(' • Cursor .mdc rules updated in .cursor/rules/'));
329
- }
330
- else {
331
- console.log(chalk.gray(' • Cursor .mdc rules generated in .cursor/rules/'));
332
- }
333
- }
334
- // Show multi-tool config feedback
335
- if (detection.geminiCli?.detected) {
336
- console.log(chalk.gray(' • Gemini CLI config generated: GEMINI.md'));
337
- }
338
- if (detection.continueDev?.detected) {
339
- console.log(chalk.gray(' • Continue.dev rules generated in .continue/rules/'));
340
- }
341
- if (detection.windsurf?.detected) {
342
- console.log(chalk.gray(' • Windsurf rules generated: .windsurfrules'));
343
- }
344
- if (detection.githubCopilot?.detected) {
345
- console.log(chalk.gray(' • GitHub Copilot instructions generated in .github/'));
346
- }
347
- // Generate workflows if requested
348
- if (config.generateWorkflows) {
349
- const workflowSpinner = ora('Generating GitHub Actions workflows...').start();
350
- const workflows = await generateWorkflows(config, cwd, {
351
- mode: minimalMode ? 'minimal' : 'full',
352
- });
353
- workflowSpinner.succeed(`Generated ${workflows.length} workflow files`);
354
- for (const workflow of workflows) {
355
- console.log(chalk.gray(` - ${path.relative(cwd, workflow)}`));
356
- }
357
- }
358
- // Generate or update .gitignore
359
- const gitignoreSpinner = ora('Generating/updating .gitignore...').start();
360
- const { generateGitignore } = await import('../core/gitignore-generator.js');
361
- const gitignoreResult = await generateGitignore(cwd, detection.languages);
362
- if (gitignoreResult.created) {
363
- gitignoreSpinner.succeed('.gitignore created');
364
- }
365
- else if (gitignoreResult.updated) {
366
- gitignoreSpinner.succeed('.gitignore updated with missing patterns');
367
- }
368
- else {
369
- gitignoreSpinner.info('.gitignore already contains all necessary patterns');
370
- }
371
- // Generate IDE-specific files
372
- if (!minimalMode && config.ides.length > 0) {
373
- const ideSpinner = ora('Generating IDE-specific files...').start();
374
- const ideFiles = await generateIDEFiles(config, cwd);
375
- if (ideFiles.length > 0) {
376
- ideSpinner.succeed(`Generated ${ideFiles.length} IDE configuration files`);
377
- for (const file of ideFiles) {
378
- console.log(chalk.gray(` - ${path.relative(cwd, file)}`));
379
- }
380
- }
381
- else {
382
- ideSpinner.info('IDE files already exist (skipped)');
383
- }
384
- }
385
- // Generate AI CLI configuration files (CLAUDE.md, CODEX.md, GEMINI.md)
386
- if (!minimalMode) {
387
- const cliSpinner = ora('Generating AI CLI configuration files...').start();
388
- const cliFiles = await generateAICLIFiles(config, cwd);
389
- if (cliFiles.length > 0) {
390
- cliSpinner.succeed(`Generated ${cliFiles.length} AI CLI configuration files`);
391
- for (const file of cliFiles) {
392
- console.log(chalk.gray(` - ${path.relative(cwd, file)}`));
393
- }
394
- }
395
- else {
396
- cliSpinner.info('AI CLI files already exist (skipped)');
397
- }
398
- }
399
- // Auto-setup Claude Code integration (MCP + skills)
400
- if (!minimalMode) {
401
- const claudeSpinner = ora('Checking Claude Code integration...').start();
402
- try {
403
- const { setupClaudeCodeIntegration } = await import('../core/claude-mcp.js');
404
- const result = await setupClaudeCodeIntegration(cwd);
405
- if (result.detected) {
406
- claudeSpinner.succeed('Claude Code integration configured');
407
- if (result.mcpConfigured) {
408
- console.log(chalk.gray(' • MCP server added to .mcp.json'));
409
- }
410
- if (result.skillsInstalled.length > 0) {
411
- console.log(chalk.gray(` • ${result.skillsInstalled.length} skills installed to .claude/commands/`));
412
- }
413
- if (result.agentTeamsEnabled) {
414
- console.log(chalk.gray(' • Multi-agent teams enabled in .claude/settings.json'));
415
- }
416
- if (result.agentDefinitionsInstalled.length > 0) {
417
- console.log(chalk.gray(` • ${result.agentDefinitionsInstalled.length} agent definitions installed to .claude/agents/`));
418
- }
419
- }
420
- else {
421
- claudeSpinner.info('Claude Code not detected (skipped)');
422
- }
423
- }
424
- catch {
425
- claudeSpinner.info('Claude Code integration skipped');
426
- }
427
- }
428
- // Install Ralph shell scripts
429
- try {
430
- const { installRalphScripts } = await import('../core/ralph-scripts.js');
431
- const scripts = await installRalphScripts(cwd);
432
- if (scripts.length > 0) {
433
- console.log(chalk.gray(` • ${scripts.length} Ralph scripts installed to .rulebook/scripts/`));
434
- }
435
- }
436
- catch {
437
- // Skip if Ralph scripts installation fails
438
- }
439
- // Create PLANS.md for session continuity
440
- try {
441
- const { initPlans } = await import('../core/plans-manager.js');
442
- const created = await initPlans(cwd);
443
- if (created) {
444
- console.log(chalk.gray(' • PLANS.md created for session continuity'));
445
- }
446
- }
447
- catch {
448
- // Non-blocking
449
- }
450
- // Create AGENTS.override.md (never overwrites existing)
451
- try {
452
- const { initOverride } = await import('../core/override-manager.js');
453
- const created = await initOverride(cwd);
454
- if (created) {
455
- console.log(chalk.gray(' • AGENTS.override.md created (add project-specific rules here)'));
456
- }
457
- }
458
- catch {
459
- // Non-blocking
460
- }
461
- // --add-sequential-thinking: inject into mcp.json if not already present
462
- if (options.addSequentialThinking) {
463
- try {
464
- await addSequentialThinkingMcp(cwd);
465
- console.log(chalk.gray(' • sequential-thinking MCP added to mcp.json'));
466
- }
467
- catch {
468
- console.log(chalk.yellow(' ⚠ Could not add sequential-thinking MCP'));
469
- }
470
- }
471
- if (minimalMode && minimalArtifacts.length > 0) {
472
- console.log(chalk.green('\n✅ Essentials created:'));
473
- for (const artifact of minimalArtifacts) {
474
- console.log(chalk.gray(` - ${path.relative(cwd, artifact)}`));
475
- }
476
- }
477
- // Clean up any accidental nested .rulebook/.rulebook directory
478
- try {
479
- const fsPromises = await import('fs/promises');
480
- const accidentalDir = path.join(cwd, '.rulebook', '.rulebook');
481
- if (existsSync(accidentalDir)) {
482
- await fsPromises.rm(accidentalDir, { recursive: true, force: true });
483
- }
484
- }
485
- catch {
486
- // Ignore cleanup errors
487
- }
488
- // Detect if this looks like a workspace root and hint the user
489
- const wsConfig = WorkspaceManager.findWorkspaceConfig(cwd);
490
- if (wsConfig && wsConfig.projects.length > 1) {
491
- console.log(chalk.yellow(`\n⚠ Workspace detected: "${wsConfig.name}" with ${wsConfig.projects.length} projects.`));
492
- console.log(chalk.yellow(' The MCP server will auto-detect workspace mode. For explicit setup, run:'));
493
- console.log(chalk.yellow(' rulebook workspace init'));
494
- console.log(chalk.yellow(' Then run `rulebook init` inside each sub-project individually for best results.\n'));
495
- }
496
- console.log(chalk.bold.green('\n✨ Rulebook initialization complete!\n'));
497
- console.log(chalk.gray('Next steps:'));
498
- console.log(chalk.gray(' 1. Review AGENTS.md'));
499
- console.log(chalk.gray(' 2. Review generated workflows in .github/workflows/'));
500
- console.log(chalk.gray(' 3. Create .rulesignore if needed'));
501
- console.log(chalk.gray(' 4. Set up /docs structure'));
502
- console.log(chalk.gray(' 5. Run your AI assistant with the new rules\n'));
503
- }
504
- catch (error) {
505
- console.error(chalk.red('\n❌ Error:'), error);
506
- process.exit(1);
507
- }
508
- }
509
- export async function validateCommand() {
510
- try {
511
- const cwd = process.cwd();
512
- console.log(chalk.bold.blue('\n🔍 Rulebook Project Validator\n'));
513
- const spinner = ora('Validating project structure...').start();
514
- // Import validator
515
- const { validateProject } = await import('../core/validator.js');
516
- const result = await validateProject(cwd);
517
- spinner.stop();
518
- // Display results
519
- if (result.valid) {
520
- console.log(chalk.green(`\n✅ Validation passed! Score: ${result.score}/100\n`));
521
- }
522
- else {
523
- console.log(chalk.red(`\n❌ Validation failed. Score: ${result.score}/100\n`));
524
- }
525
- // Show errors
526
- if (result.errors.length > 0) {
527
- console.log(chalk.red.bold('Errors:'));
528
- for (const error of result.errors) {
529
- console.log(chalk.red(` ❌ ${error.category}: ${error.message}`));
530
- if (error.file) {
531
- console.log(chalk.gray(` File: ${error.file}`));
532
- }
533
- }
534
- console.log('');
535
- }
536
- // Show warnings
537
- if (result.warnings.length > 0) {
538
- console.log(chalk.yellow.bold('Warnings:'));
539
- for (const warning of result.warnings) {
540
- console.log(chalk.yellow(` ⚠️ ${warning.category}: ${warning.message}`));
541
- if (warning.file) {
542
- console.log(chalk.gray(` File: ${warning.file}`));
543
- }
544
- }
545
- console.log('');
546
- }
547
- // Summary
548
- if (result.valid && result.warnings.length === 0) {
549
- console.log(chalk.green('🎉 Perfect! Your project follows all rulebook standards.\n'));
550
- }
551
- else if (result.valid) {
552
- console.log(chalk.yellow(`✅ Project is valid but has ${result.warnings.length} warnings to address.\n`));
553
- }
554
- else {
555
- console.log(chalk.red(`❌ Project has ${result.errors.length} errors that must be fixed.\n`));
556
- console.log(chalk.gray('Run "rulebook init" to set up missing standards.\n'));
557
- }
558
- process.exit(result.valid ? 0 : 1);
559
- }
560
- catch (error) {
561
- console.error(chalk.red('\n❌ Validation error:'), error);
562
- process.exit(1);
563
- }
564
- }
565
- export async function workflowsCommand() {
566
- try {
567
- const cwd = process.cwd();
568
- console.log(chalk.bold.blue('\n📦 Generating GitHub Actions Workflows\n'));
569
- // Detect project
570
- const spinner = ora('Detecting project languages...').start();
571
- const detection = await detectProject(cwd);
572
- spinner.succeed('Detection complete');
573
- if (detection.languages.length === 0) {
574
- console.log(chalk.yellow('\n⚠ No languages detected. Cannot generate workflows.'));
575
- console.log(chalk.gray('Ensure you have project files (Cargo.toml, package.json, etc.)\n'));
576
- return;
577
- }
578
- const config = {
579
- languages: detection.languages.map((l) => l.language),
580
- modules: [],
581
- ides: [],
582
- projectType: 'application',
583
- coverageThreshold: 95,
584
- strictDocs: true,
585
- generateWorkflows: true,
586
- };
587
- const workflowSpinner = ora('Generating workflows...').start();
588
- const workflows = await generateWorkflows(config, cwd);
589
- workflowSpinner.succeed(`Generated ${workflows.length} workflow files`);
590
- console.log(chalk.green('\n✅ Workflows generated:\n'));
591
- for (const workflow of workflows) {
592
- console.log(chalk.gray(` - ${path.relative(cwd, workflow)}`));
593
- }
594
- console.log(chalk.bold.green('\n✨ Done!\n'));
595
- }
596
- catch (error) {
597
- console.error(chalk.red('\n❌ Error:'), error);
598
- process.exit(1);
599
- }
600
- }
601
- export async function checkDepsCommand() {
602
- try {
603
- const cwd = process.cwd();
604
- console.log(chalk.bold.blue('\n🔍 Checking Dependencies\n'));
605
- const spinner = ora('Analyzing dependencies...').start();
606
- const { checkDependencies } = await import('../core/dependency-checker.js');
607
- const result = await checkDependencies(cwd);
608
- spinner.succeed('Analysis complete');
609
- console.log('');
610
- console.log(chalk.bold('Dependency Summary:'));
611
- console.log(chalk.gray(` Total: ${result.total}`));
612
- console.log(chalk.green(` Up-to-date: ${result.upToDate}`));
613
- if (result.outdated.length > 0) {
614
- console.log(chalk.yellow(` Outdated: ${result.outdated.length}`));
615
- }
616
- if (result.vulnerable.length > 0) {
617
- console.log(chalk.red(` Vulnerable: ${result.vulnerable.length}`));
618
- }
619
- // Show outdated
620
- if (result.outdated.length > 0) {
621
- console.log('');
622
- console.log(chalk.yellow.bold('Outdated Dependencies:'));
623
- for (const dep of result.outdated.slice(0, 10)) {
624
- console.log(chalk.yellow(` ↑ ${dep.name}: ${dep.current} → ${dep.latest}`));
625
- }
626
- if (result.outdated.length > 10) {
627
- console.log(chalk.gray(` ... and ${result.outdated.length - 10} more`));
628
- }
629
- }
630
- // Show vulnerable
631
- if (result.vulnerable.length > 0) {
632
- console.log('');
633
- console.log(chalk.red.bold('Vulnerable Dependencies:'));
634
- for (const vuln of result.vulnerable.slice(0, 5)) {
635
- console.log(chalk.red(` ❌ ${vuln.name} (${vuln.severity}): ${vuln.title}`));
636
- }
637
- if (result.vulnerable.length > 5) {
638
- console.log(chalk.gray(` ... and ${result.vulnerable.length - 5} more`));
639
- }
640
- console.log('');
641
- console.log(chalk.red.bold('⚠️ SECURITY: Update vulnerable dependencies immediately!'));
642
- }
643
- console.log('');
644
- if (result.outdated.length === 0 && result.vulnerable.length === 0) {
645
- console.log(chalk.green('✅ All dependencies are up-to-date and secure!\n'));
646
- }
647
- }
648
- catch (error) {
649
- console.error(chalk.red('\n❌ Error checking dependencies:'), error);
650
- process.exit(1);
651
- }
652
- }
653
- export async function checkCoverageCommand(options) {
654
- try {
655
- const cwd = process.cwd();
656
- const threshold = options.threshold || 95;
657
- console.log(chalk.bold.blue('\n📊 Checking Test Coverage\n'));
658
- const spinner = ora('Running coverage analysis...').start();
659
- const { checkCoverage } = await import('../core/coverage-checker.js');
660
- const result = await checkCoverage(cwd, threshold);
661
- spinner.succeed('Coverage analysis complete');
662
- console.log('');
663
- console.log(chalk.bold('Coverage Summary:'));
664
- console.log(chalk.gray(` Threshold: ${threshold}%`));
665
- console.log(result.meetsThreshold
666
- ? chalk.green(` Actual: ${result.percentage.toFixed(2)}% ✅`)
667
- : chalk.red(` Actual: ${result.percentage.toFixed(2)}% ❌`));
668
- if (result.details) {
669
- console.log('');
670
- console.log(chalk.bold('Details:'));
671
- if (result.details.lines) {
672
- console.log(chalk.gray(` Lines: ${result.details.lines.toFixed(2)}%`));
673
- }
674
- if (result.details.statements) {
675
- console.log(chalk.gray(` Statements: ${result.details.statements.toFixed(2)}%`));
676
- }
677
- if (result.details.functions) {
678
- console.log(chalk.gray(` Functions: ${result.details.functions.toFixed(2)}%`));
679
- }
680
- if (result.details.branches) {
681
- console.log(chalk.gray(` Branches: ${result.details.branches.toFixed(2)}%`));
682
- }
683
- }
684
- console.log('');
685
- if (result.meetsThreshold) {
686
- console.log(chalk.green(`✅ Coverage meets threshold of ${threshold}%!\n`));
687
- }
688
- else {
689
- console.log(chalk.red(`❌ Coverage ${result.percentage.toFixed(2)}% is below threshold ${threshold}%\n`));
690
- console.log(chalk.gray('Add more tests to increase coverage.\n'));
691
- process.exit(1);
692
- }
693
- }
694
- catch (error) {
695
- console.error(chalk.red('\n❌ Error checking coverage:'), error);
696
- process.exit(1);
697
- }
698
- }
699
- export async function generateDocsCommand(options) {
700
- try {
701
- const cwd = process.cwd();
702
- console.log(chalk.bold.blue('\n📚 Generate Documentation Structure\n'));
703
- let config;
704
- if (options.yes) {
705
- config = {
706
- projectName: path.basename(cwd),
707
- description: 'A modern software project',
708
- author: 'Your Name',
709
- email: '',
710
- license: 'MIT',
711
- };
712
- console.log(chalk.blue('Using defaults...\n'));
713
- }
714
- else {
715
- const { promptDocsConfig } = await import('./docs-prompts.js');
716
- config = await promptDocsConfig();
717
- }
718
- const spinner = ora('Generating documentation structure...').start();
719
- const { generateDocsStructure } = await import('../core/docs-generator.js');
720
- const generatedFiles = await generateDocsStructure(config, cwd);
721
- spinner.succeed(`Generated ${generatedFiles.length} files`);
722
- console.log('');
723
- console.log(chalk.green('✅ Files created:\n'));
724
- for (const file of generatedFiles) {
725
- console.log(chalk.gray(` - ${path.relative(cwd, file)}`));
726
- }
727
- console.log('');
728
- console.log(chalk.bold.green('✨ Documentation structure ready!\n'));
729
- console.log(chalk.gray('Next steps:'));
730
- console.log(chalk.gray(' 1. Review and customize generated files'));
731
- console.log(chalk.gray(' 2. Add your project-specific content'));
732
- console.log(chalk.gray(' 3. Update ROADMAP.md with your milestones'));
733
- console.log(chalk.gray(' 4. Document architecture in ARCHITECTURE.md\n'));
734
- }
735
- catch (error) {
736
- console.error(chalk.red('\n❌ Error generating docs:'), error);
737
- process.exit(1);
738
- }
739
- }
740
- export async function versionCommand(options) {
741
- try {
742
- const cwd = process.cwd();
743
- console.log(chalk.bold.blue('\n📦 Version Bump\n'));
744
- const { bumpProjectVersion } = await import('../core/version-bumper.js');
745
- const spinner = ora('Bumping version...').start();
746
- const result = await bumpProjectVersion(cwd, options.type);
747
- spinner.succeed(`Version bumped: ${result.oldVersion} → ${result.newVersion}`);
748
- console.log('');
749
- console.log(chalk.green('✅ Files updated:\n'));
750
- result.filesUpdated.forEach((file) => {
751
- console.log(chalk.gray(` - ${file}`));
752
- });
753
- console.log('');
754
- console.log(chalk.yellow('Next steps:'));
755
- console.log(chalk.gray(' 1. Review changes'));
756
- console.log(chalk.gray(' 2. Update CHANGELOG.md (or run: rulebook changelog)'));
757
- console.log(chalk.gray(` 3. Commit: git add . && git commit -m "chore: Release version ${result.newVersion}"`));
758
- console.log(chalk.gray(` 4. Tag: git tag -a v${result.newVersion} -m "Release v${result.newVersion}"`));
759
- }
760
- catch (error) {
761
- console.error(chalk.red('\n❌ Error:'), error.message);
762
- process.exit(1);
763
- }
764
- }
765
- export async function changelogCommand(options) {
766
- try {
767
- const cwd = process.cwd();
768
- console.log(chalk.bold.blue('\n📝 Changelog Generation\n'));
769
- const { generateChangelog, getCurrentVersion } = await import('../core/changelog-generator.js');
770
- const version = options.version || (await getCurrentVersion(cwd));
771
- if (!version) {
772
- console.error(chalk.red('❌ Could not determine version'));
773
- console.log(chalk.gray(' Specify version with --version flag'));
774
- process.exit(1);
775
- }
776
- const spinner = ora('Generating changelog from commits...').start();
777
- const section = await generateChangelog(cwd, version);
778
- spinner.succeed(`Changelog generated for version ${version}`);
779
- console.log('');
780
- console.log(chalk.green('✅ Changelog sections:\n'));
781
- if (section.breaking.length > 0) {
782
- console.log(chalk.red(' Breaking Changes: ') + section.breaking.length);
783
- }
784
- if (section.added.length > 0) {
785
- console.log(chalk.green(' Added: ') + section.added.length);
786
- }
787
- if (section.changed.length > 0) {
788
- console.log(chalk.blue(' Changed: ') + section.changed.length);
789
- }
790
- if (section.fixed.length > 0) {
791
- console.log(chalk.yellow(' Fixed: ') + section.fixed.length);
792
- }
793
- console.log('');
794
- console.log(chalk.gray('CHANGELOG.md has been updated'));
795
- console.log(chalk.gray('Review and edit as needed before committing'));
796
- }
797
- catch (error) {
798
- console.error(chalk.red('\n❌ Error:'), error.message);
799
- process.exit(1);
800
- }
801
- }
802
- export async function healthCommand() {
803
- try {
804
- const cwd = process.cwd();
805
- console.log(chalk.bold.blue('\n🏥 Project Health Check\n'));
806
- const { calculateHealthScore } = await import('../core/health-scorer.js');
807
- const spinner = ora('Analyzing project health...').start();
808
- const health = await calculateHealthScore(cwd);
809
- spinner.succeed('Health analysis complete');
810
- console.log('');
811
- console.log(chalk.bold(`Overall Health Score: ${health.overall}/100 (${health.grade})`));
812
- console.log('');
813
- console.log(chalk.bold('Category Scores:\n'));
814
- console.log(` 📝 Documentation: ${health.categories.documentation}/100`);
815
- console.log(` 🧪 Testing: ${health.categories.testing}/100`);
816
- console.log(` 🎨 Code Quality: ${health.categories.quality}/100`);
817
- console.log(` 🔒 Security: ${health.categories.security}/100`);
818
- console.log(` 🔄 CI/CD: ${health.categories.cicd}/100`);
819
- console.log(` 📦 Dependencies: ${health.categories.dependencies}/100`);
820
- console.log(` 🤖 AGENTS.md: ${health.categories.agentsMd}/100`);
821
- console.log(` 🔁 Ralph: ${health.categories.ralph}/100`);
822
- console.log(` 🧠 Memory: ${health.categories.memory}/100`);
823
- console.log('');
824
- if (health.recommendations.length > 0) {
825
- console.log(chalk.bold.yellow('Recommendations:\n'));
826
- health.recommendations.forEach((rec) => {
827
- console.log(chalk.yellow(` ${rec}`));
828
- });
829
- console.log('');
830
- }
831
- if (health.overall >= 90) {
832
- console.log(chalk.green('🌟 Excellent! Your project is in great shape!'));
833
- }
834
- else if (health.overall >= 70) {
835
- console.log(chalk.blue('👍 Good project health. A few improvements suggested.'));
836
- }
837
- else {
838
- console.log(chalk.yellow('⚠️ Project health needs improvement.'));
839
- console.log(chalk.gray(' Run: rulebook fix'));
840
- console.log(chalk.gray(' To auto-fix common issues.'));
841
- }
842
- }
843
- catch (error) {
844
- console.error(chalk.red('\n❌ Error:'), error.message);
845
- process.exit(1);
846
- }
847
- }
848
- export async function fixCommand() {
849
- try {
850
- const cwd = process.cwd();
851
- console.log(chalk.bold.blue('\n🔧 Auto-Fix Common Issues\n'));
852
- const { autoFixProject, autoFixLint } = await import('../core/auto-fixer.js');
853
- const spinner = ora('Analyzing and fixing issues...').start();
854
- const result = await autoFixProject(cwd);
855
- spinner.succeed('Auto-fix complete');
856
- console.log('');
857
- if (result.applied.length > 0) {
858
- console.log(chalk.green('✅ Fixed:\n'));
859
- result.applied.forEach((fix) => {
860
- console.log(chalk.gray(` - ${fix}`));
861
- });
862
- console.log('');
863
- }
864
- if (result.skipped.length > 0) {
865
- console.log(chalk.blue('ℹ️ Skipped:\n'));
866
- result.skipped.forEach((skip) => {
867
- console.log(chalk.gray(` - ${skip}`));
868
- });
869
- console.log('');
870
- }
871
- if (result.failed.length > 0) {
872
- console.log(chalk.yellow('⚠️ Failed:\n'));
873
- result.failed.forEach((fail) => {
874
- console.log(chalk.gray(` - ${fail}`));
875
- });
876
- console.log('');
877
- }
878
- // Try to fix lint errors
879
- console.log(chalk.blue('🎨 Attempting to fix lint errors...\n'));
880
- const lintFixed = await autoFixLint(cwd);
881
- if (lintFixed) {
882
- console.log(chalk.green('✅ Lint errors fixed'));
883
- }
884
- else {
885
- console.log(chalk.gray('ℹ️ No lint auto-fix available'));
886
- }
887
- console.log('');
888
- console.log(chalk.bold.green('✨ Auto-fix complete!\n'));
889
- console.log(chalk.gray('Run: rulebook health'));
890
- console.log(chalk.gray('To check updated health score.'));
891
- }
892
- catch (error) {
893
- console.error(chalk.red('\n❌ Error:'), error.message);
894
- process.exit(1);
895
- }
896
- }
897
- export async function watcherCommand() {
898
- try {
899
- const cwd = process.cwd();
900
- const { startWatcher } = await import('../core/watcher.js');
901
- console.log(chalk.bold.blue('\n🚀 Starting Modern Console Watcher\n'));
902
- console.log(chalk.gray('Full-screen interface with system monitoring'));
903
- console.log(chalk.gray('Press Ctrl+C or F10 to exit\n'));
904
- await startWatcher(cwd);
905
- }
906
- catch (error) {
907
- console.error(chalk.red('\n❌ Watcher error:'), error);
908
- process.exit(1);
909
- }
910
- }
911
- export async function agentCommand(options) {
912
- try {
913
- const cwd = process.cwd();
914
- const { startAgent } = await import('../core/agent-manager.js');
915
- console.log(chalk.bold.blue('\n🤖 Starting Rulebook Agent\n'));
916
- const agentOptions = {
917
- dryRun: options.dryRun || false,
918
- tool: options.tool,
919
- maxIterations: options.iterations || 10,
920
- watchMode: options.watch || false,
921
- };
922
- if (agentOptions.dryRun) {
923
- console.log(chalk.yellow('🔍 DRY RUN MODE - No actual changes will be made\n'));
924
- }
925
- await startAgent(cwd, agentOptions);
926
- }
927
- catch (error) {
928
- console.error(chalk.red('\n❌ Agent error:'), error);
929
- process.exit(1);
930
- }
931
- }
932
- export async function configCommand(options) {
933
- try {
934
- const cwd = process.cwd();
935
- const { createConfigManager } = await import('../core/config-manager.js');
936
- const configManager = createConfigManager(cwd);
937
- if (options.show) {
938
- const summary = await configManager.getConfigSummary();
939
- console.log(chalk.bold.blue('\n⚙️ Rulebook Configuration\n'));
940
- console.log(chalk.white(`Version: ${summary.version}`));
941
- console.log(chalk.white(`Project ID: ${summary.projectId}`));
942
- console.log(chalk.white(`Coverage Threshold: ${summary.coverageThreshold}%`));
943
- console.log(chalk.white(`CLI Tools: ${summary.cliTools.join(', ') || 'None detected'}`));
944
- console.log(chalk.white(`Enabled Features: ${summary.enabledFeatures.join(', ')}`));
945
- }
946
- else if (options.feature && typeof options.enable === 'boolean') {
947
- await configManager.toggleFeature(options.feature, options.enable);
948
- console.log(chalk.green(`✅ Feature '${options.feature}' ${options.enable ? 'enabled' : 'disabled'}`));
949
- }
950
- else if (options.set) {
951
- const [key, value] = options.set.split('=');
952
- if (!key || !value) {
953
- console.error(chalk.red('Invalid set format. Use: --set key=value'));
954
- process.exit(1);
955
- }
956
- // Handle different value types
957
- let parsedValue = value;
958
- if (value === 'true')
959
- parsedValue = true;
960
- else if (value === 'false')
961
- parsedValue = false;
962
- else if (!isNaN(Number(value)))
963
- parsedValue = Number(value);
964
- await configManager.updateConfig({ [key]: parsedValue });
965
- console.log(chalk.green(`✅ Configuration updated: ${key} = ${value}`));
966
- }
967
- else {
968
- console.log(chalk.bold.blue('\n⚙️ Rulebook Configuration\n'));
969
- console.log(chalk.gray('Available commands:'));
970
- console.log(chalk.gray(' --show Show current configuration'));
971
- console.log(chalk.gray(' --set key=value Set configuration value'));
972
- console.log(chalk.gray(' --feature name --enable Enable a feature'));
973
- console.log(chalk.gray(' --feature name --disable Disable a feature'));
974
- }
975
- }
976
- catch (error) {
977
- console.error(chalk.red('\n❌ Config error:'), error);
978
- process.exit(1);
979
- }
980
- }
981
- /**
982
- * Resolve a TaskManager for a specific project.
983
- *
984
- * Resolution order:
985
- * 1. Explicit --project flag → find workspace config from cwd, route to named project
986
- * 2. Auto-detect → walk up from cwd to find workspace, match cwd to a project
987
- * 3. Fallback → single-project from cwd (no workspace)
988
- */
989
- async function resolveTaskManager(cwd, options) {
990
- const { createTaskManager } = await import('../core/task-manager.js');
991
- const { createConfigManager } = await import('../core/config-manager.js');
992
- const { isAbsolute, resolve } = await import('path');
993
- // Helper: build TaskManager from a resolved project root
994
- const buildFromProjectRoot = async (projectRoot, label) => {
995
- const configManager = createConfigManager(projectRoot);
996
- const config = await configManager.loadConfig();
997
- const rulebookDir = config.rulebookDir || '.rulebook';
998
- return { taskManager: createTaskManager(projectRoot, rulebookDir), projectLabel: label };
999
- };
1000
- // 1. Explicit --project flag
1001
- if (options?.project) {
1002
- const ws = WorkspaceManager.findWorkspaceFromCwd(cwd);
1003
- if (!ws) {
1004
- console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
1005
- process.exit(1);
1006
- }
1007
- const project = ws.config.projects.find((p) => p.name === options.project);
1008
- if (!project) {
1009
- console.error(chalk.red(`Project "${options.project}" not found in workspace.`));
1010
- console.error(chalk.gray(`Available: ${ws.config.projects.map((p) => p.name).join(', ')}`));
1011
- process.exit(1);
1012
- }
1013
- const projectRoot = isAbsolute(project.path) ? project.path : resolve(ws.root, project.path);
1014
- return buildFromProjectRoot(projectRoot, project.name);
1015
- }
1016
- // 2. Auto-detect: walk up from cwd to find workspace and match project
1017
- const resolved = WorkspaceManager.resolveProjectFromCwd(cwd);
1018
- if (resolved) {
1019
- const project = resolved.config.projects.find((p) => p.name === resolved.projectName);
1020
- if (project) {
1021
- const projectRoot = isAbsolute(project.path)
1022
- ? project.path
1023
- : resolve(resolved.root, project.path);
1024
- return buildFromProjectRoot(projectRoot, project.name);
1025
- }
1026
- }
1027
- // 3. Fallback: single-project from cwd
1028
- const configManager = createConfigManager(cwd);
1029
- const config = await configManager.loadConfig();
1030
- const rulebookDir = config.rulebookDir || '.rulebook';
1031
- return { taskManager: createTaskManager(cwd, rulebookDir) };
1032
- }
1033
- // Task management commands using Rulebook task system
1034
- export async function taskCreateCommand(taskId, wsOptions) {
1035
- try {
1036
- const cwd = process.cwd();
1037
- const { taskManager, projectLabel } = await resolveTaskManager(cwd, wsOptions);
1038
- await taskManager.createTask(taskId);
1039
- const prefix = projectLabel ? `[${projectLabel}] ` : '';
1040
- console.log(chalk.green(`✅ ${prefix}Task ${taskId} created successfully`));
1041
- console.log(chalk.yellow('\n⚠️ Remember to:'));
1042
- console.log(chalk.gray(' 1. Fill in proposal.md (minimum 20 characters in "Why" section)'));
1043
- console.log(chalk.gray(' 3. Add tasks to tasks.md'));
1044
- console.log(chalk.gray(' 4. Create spec deltas in specs/*/spec.md'));
1045
- console.log(chalk.gray(' 5. Validate with: rulebook task validate ' + taskId));
1046
- }
1047
- catch (error) {
1048
- console.error(chalk.red(`❌ Failed to create task: ${error.message}`));
1049
- process.exit(1);
1050
- }
1051
- }
1052
- export async function taskListCommand(includeArchived = false, wsOptions) {
1053
- try {
1054
- const cwd = process.cwd();
1055
- // --all-projects: list tasks from every workspace project
1056
- if (wsOptions?.allProjects) {
1057
- const ws = WorkspaceManager.findWorkspaceFromCwd(cwd);
1058
- if (!ws) {
1059
- console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
1060
- process.exit(1);
1061
- return;
1062
- }
1063
- console.log(chalk.bold.blue(`\n📋 Workspace Tasks (${ws.config.name})\n`));
1064
- const { createTaskManager } = await import('../core/task-manager.js');
1065
- const { createConfigManager } = await import('../core/config-manager.js');
1066
- const { isAbsolute, resolve } = await import('path');
1067
- let totalTasks = 0;
1068
- for (const project of ws.config.projects) {
1069
- const projectRoot = isAbsolute(project.path)
1070
- ? project.path
1071
- : resolve(ws.root, project.path);
1072
- try {
1073
- const configManager = createConfigManager(projectRoot);
1074
- const config = await configManager.loadConfig();
1075
- const rulebookDir = config.rulebookDir || '.rulebook';
1076
- const taskManager = createTaskManager(projectRoot, rulebookDir);
1077
- const tasks = await taskManager.listTasks(includeArchived);
1078
- if (tasks.length > 0) {
1079
- console.log(chalk.bold.cyan(` [${project.name}]`));
1080
- for (const task of tasks) {
1081
- const statusColor = task.status === 'completed'
1082
- ? chalk.green
1083
- : task.status === 'in-progress'
1084
- ? chalk.yellow
1085
- : task.status === 'blocked'
1086
- ? chalk.red
1087
- : chalk.gray;
1088
- console.log(` ${statusColor(task.status.padEnd(12))} ${chalk.white(task.id)} - ${chalk.gray(task.title)}`);
1089
- }
1090
- console.log('');
1091
- totalTasks += tasks.length;
1092
- }
1093
- }
1094
- catch {
1095
- console.log(chalk.bold.cyan(` [${project.name}]`));
1096
- console.log(chalk.gray(' No tasks or no .rulebook config'));
1097
- console.log('');
1098
- }
1099
- }
1100
- console.log(chalk.gray(`${totalTasks} task(s) across ${ws.config.projects.length} project(s)`));
1101
- return;
1102
- }
1103
- // Single project (optionally targeted via --project)
1104
- const { taskManager, projectLabel } = await resolveTaskManager(cwd, wsOptions);
1105
- const tasks = await taskManager.listTasks(includeArchived);
1106
- if (tasks.length === 0) {
1107
- console.log(chalk.gray('No tasks found'));
1108
- return;
1109
- }
1110
- const header = projectLabel
1111
- ? `\n📋 Rulebook Tasks [${projectLabel}]\n`
1112
- : '\n📋 Rulebook Tasks\n';
1113
- console.log(chalk.bold.blue(header));
1114
- const activeTasks = tasks.filter((t) => !t.archivedAt);
1115
- const archivedTasks = tasks.filter((t) => t.archivedAt);
1116
- if (activeTasks.length > 0) {
1117
- console.log(chalk.bold('Active Tasks:'));
1118
- for (const task of activeTasks) {
1119
- const statusColor = task.status === 'completed'
1120
- ? chalk.green
1121
- : task.status === 'in-progress'
1122
- ? chalk.yellow
1123
- : task.status === 'blocked'
1124
- ? chalk.red
1125
- : chalk.gray;
1126
- console.log(` ${statusColor(task.status.padEnd(12))} ${chalk.white(task.id)} - ${chalk.gray(task.title)}`);
1127
- }
1128
- console.log('');
1129
- }
1130
- if (includeArchived && archivedTasks.length > 0) {
1131
- console.log(chalk.bold('Archived Tasks:'));
1132
- for (const task of archivedTasks) {
1133
- console.log(` ${chalk.gray('archived'.padEnd(12))} ${chalk.white(task.id)} - ${chalk.gray(task.title)} ${chalk.dim(`(${task.archivedAt})`)}`);
1134
- }
1135
- console.log('');
1136
- }
1137
- }
1138
- catch (error) {
1139
- console.error(chalk.red(`❌ Failed to list tasks: ${error.message}`));
1140
- process.exit(1);
1141
- }
1142
- }
1143
- export async function taskShowCommand(taskId, wsOptions) {
1144
- try {
1145
- const cwd = process.cwd();
1146
- const { taskManager } = await resolveTaskManager(cwd, wsOptions);
1147
- const task = await taskManager.showTask(taskId);
1148
- if (!task) {
1149
- console.error(chalk.red(`❌ Task ${taskId} not found`));
1150
- process.exit(1);
1151
- return;
1152
- }
1153
- console.log(chalk.bold.blue(`\n📋 Task: ${task.id}\n`));
1154
- console.log(chalk.white(`Title: ${task.title}`));
1155
- console.log(chalk.gray(`Status: ${task.status}`));
1156
- console.log(chalk.gray(`Created: ${task.createdAt}`));
1157
- console.log(chalk.gray(`Updated: ${task.updatedAt}`));
1158
- if (task.archivedAt) {
1159
- console.log(chalk.gray(`Archived: ${task.archivedAt}`));
1160
- }
1161
- console.log('');
1162
- if (task.proposal) {
1163
- console.log(chalk.bold('Proposal:'));
1164
- console.log(chalk.gray(task.proposal.substring(0, 500) + (task.proposal.length > 500 ? '...' : '')));
1165
- console.log('');
1166
- }
1167
- if (task.specs && Object.keys(task.specs).length > 0) {
1168
- console.log(chalk.bold('Specs:'));
1169
- for (const [module, spec] of Object.entries(task.specs)) {
1170
- console.log(chalk.gray(` ${module}/spec.md (${spec.length} chars)`));
1171
- }
1172
- console.log('');
1173
- }
1174
- }
1175
- catch (error) {
1176
- console.error(chalk.red(`❌ Failed to show task: ${error.message}`));
1177
- process.exit(1);
1178
- }
1179
- }
1180
- export async function taskValidateCommand(taskId, wsOptions) {
1181
- try {
1182
- const cwd = process.cwd();
1183
- const { taskManager } = await resolveTaskManager(cwd, wsOptions);
1184
- const validation = await taskManager.validateTask(taskId);
1185
- if (validation.valid) {
1186
- console.log(chalk.green(`✅ Task ${taskId} is valid`));
1187
- if (validation.warnings.length > 0) {
1188
- console.log(chalk.yellow('\n⚠️ Warnings:'));
1189
- for (const warning of validation.warnings) {
1190
- console.log(chalk.yellow(` - ${warning}`));
1191
- }
1192
- }
1193
- }
1194
- else {
1195
- console.log(chalk.red(`❌ Task ${taskId} validation failed\n`));
1196
- console.log(chalk.red('Errors:'));
1197
- for (const error of validation.errors) {
1198
- console.log(chalk.red(` - ${error}`));
1199
- }
1200
- if (validation.warnings.length > 0) {
1201
- console.log(chalk.yellow('\n⚠️ Warnings:'));
1202
- for (const warning of validation.warnings) {
1203
- console.log(chalk.yellow(` - ${warning}`));
1204
- }
1205
- }
1206
- process.exit(1);
1207
- }
1208
- }
1209
- catch (error) {
1210
- console.error(chalk.red(`❌ Failed to validate task: ${error.message}`));
1211
- process.exit(1);
1212
- }
1213
- }
1214
- export async function taskArchiveCommand(taskId, skipValidation = false, wsOptions) {
1215
- try {
1216
- const cwd = process.cwd();
1217
- const { taskManager, projectLabel } = await resolveTaskManager(cwd, wsOptions);
1218
- await taskManager.archiveTask(taskId, skipValidation);
1219
- const prefix = projectLabel ? `[${projectLabel}] ` : '';
1220
- console.log(chalk.green(`✅ ${prefix}Task ${taskId} archived successfully`));
1221
- }
1222
- catch (error) {
1223
- console.error(chalk.red(`❌ Failed to archive task: ${error.message}`));
1224
- process.exit(1);
1225
- }
1226
- }
1227
- /**
1228
- * Initialize MCP configuration in .rulebook file
1229
- * Adds mcp block to .rulebook and creates/updates .cursor/mcp.json
1230
- */
1231
- export async function mcpInitCommand(options) {
1232
- const { findRulebookConfig } = await import('../mcp/rulebook-server.js');
1233
- const { existsSync, readFileSync, writeFileSync, statSync } = await import('fs');
1234
- const { join, dirname } = await import('path');
1235
- const { createConfigManager } = await import('../core/config-manager.js');
1236
- try {
1237
- const cwd = process.cwd();
1238
- // --- Workspace mode ---
1239
- if (options?.workspace) {
1240
- const wsConfig = WorkspaceManager.findWorkspaceConfig(cwd);
1241
- if (!wsConfig) {
1242
- console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
1243
- process.exit(1);
1244
- }
1245
- const mcpArgs = ['-y', '@hivehub/rulebook@latest', 'mcp-server', '--workspace'];
1246
- const mcpEntry = { command: 'npx', args: mcpArgs };
1247
- // Write to .cursor/mcp.json if .cursor exists
1248
- const cursorDir = join(cwd, '.cursor');
1249
- if (existsSync(cursorDir)) {
1250
- const mcpJsonPath = join(cursorDir, 'mcp.json');
1251
- let mcpConfig = { mcpServers: {} };
1252
- if (existsSync(mcpJsonPath)) {
1253
- mcpConfig = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
1254
- }
1255
- mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
1256
- mcpConfig.mcpServers.rulebook = mcpEntry;
1257
- writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
1258
- console.log(chalk.green('✓ Workspace MCP initialized'));
1259
- console.log(chalk.gray(` • Updated .cursor/mcp.json with --workspace flag`));
1260
- }
1261
- // Write to .mcp.json (Claude Code format)
1262
- const mcpJsonPath = join(cwd, '.mcp.json');
1263
- let mcpConfig = { mcpServers: {} };
1264
- if (existsSync(mcpJsonPath)) {
1265
- mcpConfig = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
1266
- }
1267
- mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
1268
- mcpConfig.mcpServers.rulebook = mcpEntry;
1269
- writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
1270
- console.log(chalk.green('✓ Workspace MCP initialized'));
1271
- console.log(chalk.gray(` • Workspace: ${wsConfig.name} (${wsConfig.projects.length} projects)`));
1272
- console.log(chalk.gray(` • Updated .mcp.json with --workspace flag`));
1273
- console.log(chalk.gray(` • MCP server will manage all projects automatically`));
1274
- return;
1275
- }
1276
- // --- Single-project mode (original behavior) ---
1277
- let rulebookPath = findRulebookConfig(cwd);
1278
- if (!rulebookPath) {
1279
- rulebookPath = join(cwd, '.rulebook');
1280
- const configManager = createConfigManager(cwd);
1281
- await configManager.initializeConfig();
1282
- }
1283
- const projectRoot = dirname(rulebookPath);
1284
- let configFilePath = rulebookPath;
1285
- if (existsSync(rulebookPath)) {
1286
- const stats = statSync(rulebookPath);
1287
- if (stats.isDirectory()) {
1288
- configFilePath = join(rulebookPath, 'rulebook.json');
1289
- }
1290
- }
1291
- let config = {};
1292
- if (existsSync(configFilePath)) {
1293
- const raw = readFileSync(configFilePath, 'utf8');
1294
- config = JSON.parse(raw);
1295
- }
1296
- config.mcp = config.mcp ?? {};
1297
- if (config.mcp.enabled === undefined)
1298
- config.mcp.enabled = true;
1299
- if (!config.mcp.tasksDir)
1300
- config.mcp.tasksDir = '.rulebook/tasks';
1301
- if (!config.mcp.archiveDir)
1302
- config.mcp.archiveDir = '.rulebook/archive';
1303
- writeFileSync(configFilePath, JSON.stringify(config, null, 2) + '\n');
1304
- const cursorDir = join(projectRoot, '.cursor');
1305
- if (existsSync(cursorDir)) {
1306
- const mcpJsonPath = join(cursorDir, 'mcp.json');
1307
- let mcpConfig = { mcpServers: {} };
1308
- if (existsSync(mcpJsonPath)) {
1309
- const raw = readFileSync(mcpJsonPath, 'utf8');
1310
- mcpConfig = JSON.parse(raw);
1311
- }
1312
- mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
1313
- mcpConfig.mcpServers.rulebook = {
1314
- command: 'npx',
1315
- args: ['-y', '@hivehub/rulebook@latest', 'mcp-server'],
1316
- };
1317
- writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
1318
- console.log(chalk.green('✓ Rulebook MCP initialized'));
1319
- console.log(chalk.gray(` • Updated .rulebook with MCP configuration`));
1320
- console.log(chalk.gray(` • Updated .cursor/mcp.json`));
1321
- console.log(chalk.gray(` • MCP server will find .rulebook automatically`));
1322
- }
1323
- else {
1324
- console.log(chalk.green('✓ Rulebook MCP initialized'));
1325
- console.log(chalk.gray(` • Updated .rulebook with MCP configuration`));
1326
- console.log(chalk.gray(` • To use with Cursor, create .cursor/mcp.json manually`));
1327
- }
1328
- }
1329
- catch (error) {
1330
- console.error(chalk.red(`\n❌ Failed to initialize MCP: ${error.message}`));
1331
- console.error(error.stack);
1332
- process.exit(1);
1333
- }
1334
- }
1335
- export async function mcpServerCommand() {
1336
- try {
1337
- const { startRulebookMcpServer } = await import('../mcp/rulebook-server.js');
1338
- // CRITICAL: In stdio mode, stdout MUST contain ONLY JSON-RPC 2.0 messages
1339
- // stdout must contain ONLY JSON-RPC 2.0 messages for MCP protocol
1340
- // All logs must go to stderr
1341
- // Use environment variable for debug: RULEBOOK_MCP_DEBUG=1
1342
- if (process.env.RULEBOOK_MCP_DEBUG === '1') {
1343
- console.error(chalk.gray('[rulebook-mcp] Starting MCP server with stdio transport'));
1344
- console.error(chalk.gray('[rulebook-mcp] Server will find .rulebook automatically'));
1345
- }
1346
- await startRulebookMcpServer();
1347
- }
1348
- catch (error) {
1349
- // Errors always go to stderr
1350
- console.error(chalk.red(`\n❌ Failed to start MCP server: ${error.message}`));
1351
- console.error(error.stack);
1352
- process.exit(1);
1353
- }
1354
- }
1355
- // Legacy tasks command (deprecated - use task commands instead)
1356
- export async function tasksCommand(options) {
1357
- console.log(chalk.yellow('⚠️ The `tasks` command is deprecated. Use `rulebook task` commands instead.'));
1358
- console.log(chalk.gray(' - rulebook task list'));
1359
- console.log(chalk.gray(' - rulebook task show <task-id>'));
1360
- console.log(chalk.gray(' - rulebook task create <task-id>'));
1361
- console.log(chalk.gray(' - rulebook task validate <task-id>'));
1362
- console.log(chalk.gray(' - rulebook task archive <task-id>'));
1363
- if (options.tree || options.current || options.status) {
1364
- console.log(chalk.red('\n❌ Legacy commands are no longer supported.'));
1365
- console.log(chalk.yellow('Please use the Rulebook task system.'));
1366
- process.exit(1);
1367
- }
1368
- // Fallback to list tasks
1369
- await taskListCommand(false);
1370
- }
1371
- export async function updateCommand(options) {
1372
- try {
1373
- const cwd = process.cwd();
1374
- // Auto-detect workspace: if inside a workspace, update ALL projects
1375
- const ws = WorkspaceManager.findWorkspaceFromCwd(cwd);
1376
- if (ws && ws.config.projects.length > 1) {
1377
- console.log(chalk.bold.blue('\n🔄 Rulebook Workspace Update\n'));
1378
- console.log(chalk.gray(`Workspace "${ws.config.name}" detected — updating ${ws.config.projects.length} projects\n`));
1379
- const { isAbsolute, resolve, join } = await import('path');
1380
- const fsPromises = await import('fs/promises');
1381
- let updatedCount = 0;
1382
- // Build workspace project list for the template
1383
- const projectListMd = ws.config.projects
1384
- .map((p) => {
1385
- const root = isAbsolute(p.path) ? p.path : resolve(ws.root, p.path);
1386
- return `- **${p.name}** → \`${root}\``;
1387
- })
1388
- .join('\n');
1389
- // Load WORKSPACE.md template
1390
- const { getDefaultTemplatesPath } = await import('../core/skills-manager.js');
1391
- let workspaceTplContent = '';
1392
- try {
1393
- const tplPath = join(getDefaultTemplatesPath(), 'core', 'WORKSPACE.md');
1394
- workspaceTplContent = await fsPromises.readFile(tplPath, 'utf-8');
1395
- }
1396
- catch {
1397
- // Template not available — skip
1398
- }
1399
- for (const project of ws.config.projects) {
1400
- const projectRoot = isAbsolute(project.path)
1401
- ? project.path
1402
- : resolve(ws.root, project.path);
1403
- console.log(chalk.bold.cyan(`\n━━━ [${project.name}] ${projectRoot} ━━━\n`));
1404
- try {
1405
- await updateSingleProject(projectRoot, options);
1406
- // Inject WORKSPACE.md spec into this project's .rulebook/specs/
1407
- if (workspaceTplContent) {
1408
- const specsDir = join(projectRoot, '.rulebook', 'specs');
1409
- await fsPromises.mkdir(specsDir, { recursive: true });
1410
- const rendered = workspaceTplContent
1411
- .replace('{{DEFAULT_PROJECT}}', ws.config.defaultProject ?? ws.config.projects[0]?.name ?? '')
1412
- .replace('{{WORKSPACE_PROJECTS}}', projectListMd);
1413
- await fsPromises.writeFile(join(specsDir, 'WORKSPACE.md'), rendered, 'utf-8');
1414
- }
1415
- updatedCount++;
1416
- }
1417
- catch (error) {
1418
- console.error(chalk.red(` ❌ Failed to update ${project.name}: ${error.message}`));
1419
- }
1420
- }
1421
- console.log(chalk.bold.green(`\n✅ Workspace update complete — ${updatedCount}/${ws.config.projects.length} projects updated\n`));
1422
- return;
1423
- }
1424
- // Single project update
1425
- console.log(chalk.bold.blue('\n🔄 Rulebook Update Tool\n'));
1426
- console.log(chalk.gray('This will update your AGENTS.md and .rulebook to the latest version\n'));
1427
- await updateSingleProject(cwd, options);
1428
- }
1429
- catch (error) {
1430
- console.error(chalk.red('\n❌ Update failed:'), error);
1431
- process.exit(1);
1432
- }
1433
- }
1434
- /** Update a single project at the given root directory. */
1435
- async function updateSingleProject(cwd, options) {
1436
- // Detect project
1437
- const spinner = ora('Detecting project structure...').start();
1438
- const detection = await detectProject(cwd);
1439
- spinner.succeed('Project detection complete');
1440
- // Show detected languages
1441
- if (detection.languages.length > 0) {
1442
- console.log(chalk.green('\n✓ Detected languages:'));
1443
- for (const lang of detection.languages) {
1444
- console.log(` - ${lang.language} (${(lang.confidence * 100).toFixed(0)}% confidence)`);
1445
- }
1446
- }
1447
- // Check for existing AGENTS.md
1448
- if (!detection.existingAgents) {
1449
- console.log(chalk.yellow('\n⚠ No AGENTS.md found. Use "rulebook init" instead.'));
1450
- process.exit(0);
1451
- }
1452
- console.log(chalk.green(`\n✓ Found existing AGENTS.md with ${detection.existingAgents.blocks.length} blocks`));
1453
- // Get existing blocks to preserve user customizations
1454
- const existingBlocks = detection.existingAgents.blocks.map((b) => b.name);
1455
- console.log(chalk.gray(` Existing blocks: ${existingBlocks.join(', ')}`));
1456
- let inquirerModule = null;
1457
- if (!options.yes) {
1458
- inquirerModule = (await import('inquirer')).default;
1459
- const { confirm } = await inquirerModule.prompt([
1460
- {
1461
- type: 'confirm',
1462
- name: 'confirm',
1463
- message: 'Update AGENTS.md and .rulebook with latest templates?',
1464
- default: true,
1465
- },
1466
- ]);
1467
- if (!confirm) {
1468
- console.log(chalk.yellow('\nUpdate cancelled'));
1469
- process.exit(0);
1470
- }
1471
- }
1472
- const hasPreCommit = detection.gitHooks?.preCommitExists ?? false;
1473
- const hasPrePush = detection.gitHooks?.prePushExists ?? false;
1474
- const missingHooks = !hasPreCommit || !hasPrePush;
1475
- let installHooksOnUpdate = false;
1476
- let hooksInstalledOnUpdate = false;
1477
- if (missingHooks) {
1478
- if (options.yes) {
1479
- console.log(chalk.yellow('\n⚠ Git hooks are missing. Re-run "rulebook update" without --yes to install automated hooks or install them manually.'));
1480
- }
1481
- else {
1482
- if (!inquirerModule) {
1483
- inquirerModule = (await import('inquirer')).default;
1484
- }
1485
- const { installHooks } = await inquirerModule.prompt([
1486
- {
1487
- type: 'confirm',
1488
- name: 'installHooks',
1489
- message: `Install Git hooks for automated quality checks? Missing: ${hasPreCommit ? '' : 'pre-commit '}${hasPrePush ? '' : 'pre-push'}`.trim(),
1490
- default: true,
1491
- },
1492
- ]);
1493
- installHooksOnUpdate = installHooks;
1494
- }
1495
- }
1496
- if (missingHooks && !installHooksOnUpdate && !options.yes) {
1497
- console.log(chalk.gray('\nℹ Git hooks were not installed during update. Re-run "rulebook update" later or install them manually if you change your mind.'));
1498
- }
1499
- const agentsPath = path.join(cwd, 'AGENTS.md');
1500
- // Load existing config using ConfigManager
1501
- const { createConfigManager } = await import('../core/config-manager.js');
1502
- const configManager = createConfigManager(cwd);
1503
- const existingConfig = await configManager.loadConfig();
1504
- let existingMode;
1505
- let existingLightMode;
1506
- if (existingConfig) {
1507
- if (existingConfig && (existingConfig.mode === 'minimal' || existingConfig.mode === 'full')) {
1508
- existingMode = existingConfig.mode;
1509
- }
1510
- if (existingConfig && existingConfig.lightMode !== undefined) {
1511
- existingLightMode = existingConfig.lightMode;
1512
- }
1513
- }
1514
- const minimalMode = options.minimal ?? existingMode === 'minimal';
1515
- const lightMode = options.light !== undefined ? options.light : (existingLightMode ?? false);
1516
- const leanMode = options.lean ?? existingConfig?.agentsMode === 'lean';
1517
- // Build config from detected project
1518
- const config = {
1519
- languages: detection.languages.map((l) => l.language),
1520
- modules: minimalMode ? [] : detection.modules.filter((m) => m.detected).map((m) => m.module),
1521
- frameworks: detection.frameworks.filter((f) => f.detected).map((f) => f.framework),
1522
- ides: [], // Preserve existing IDE choices
1523
- projectType: 'application',
1524
- coverageThreshold: 95,
1525
- strictDocs: true,
1526
- generateWorkflows: false, // Don't regenerate workflows on update
1527
- includeGitWorkflow: true,
1528
- gitPushMode: 'manual',
1529
- installGitHooks: installHooksOnUpdate,
1530
- minimal: minimalMode,
1531
- lightMode: lightMode,
1532
- ...(leanMode ? { agentsMode: 'lean' } : {}),
1533
- };
1534
- if (minimalMode) {
1535
- config.ides = [];
1536
- config.generateWorkflows = true;
1537
- }
1538
- let minimalArtifacts = [];
1539
- if (minimalMode) {
1540
- minimalArtifacts = await scaffoldMinimalProject(cwd, {
1541
- projectName: path.basename(cwd),
1542
- description: 'Essential project scaffolding refreshed via Rulebook minimal mode.',
1543
- license: 'MIT',
1544
- });
1545
- }
1546
- // Generate Rulebook commands if Cursor is detected
1547
- // This ensures commands are available for all Cursor projects
1548
- const cursorRulesPath = path.join(cwd, '.cursorrules');
1549
- const cursorCommandsDir = path.join(cwd, '.cursor', 'commands');
1550
- const usesCursor = existsSync(cursorRulesPath) || existsSync(cursorCommandsDir);
1551
- // Deprecated notice: .cursorrules is superseded by .cursor/rules/*.mdc in Cursor v0.45+
1552
- if (existsSync(cursorRulesPath)) {
1553
- console.log(chalk.yellow(' ⚠ .cursorrules is deprecated as of Cursor v0.45. Use .cursor/rules/*.mdc instead.'));
1554
- }
1555
- if (usesCursor) {
1556
- // Check if commands already exist to avoid duplicate generation
1557
- const existingCommandsDir = path.join(cwd, '.cursor', 'commands');
1558
- if (existsSync(existingCommandsDir)) {
1559
- const { readdir } = await import('fs/promises');
1560
- const existingFiles = await readdir(existingCommandsDir);
1561
- const hasRulebookCommands = existingFiles.some((file) => file.startsWith('rulebook-task-'));
1562
- if (!hasRulebookCommands) {
1563
- const { generateCursorCommands } = await import('../core/workflow-generator.js');
1564
- const generatedCommands = await generateCursorCommands(cwd);
1565
- if (generatedCommands.length > 0) {
1566
- console.log(chalk.green(` Generated ${generatedCommands.length} Rulebook command(s) in .cursor/commands/`));
1567
- }
1568
- }
1569
- }
1570
- else {
1571
- // Directory doesn't exist, create it and generate commands
1572
- const { generateCursorCommands } = await import('../core/workflow-generator.js');
1573
- const generatedCommands = await generateCursorCommands(cwd);
1574
- if (generatedCommands.length > 0) {
1575
- console.log(chalk.green(` Generated ${generatedCommands.length} Rulebook command(s) in .cursor/commands/`));
1576
- }
1577
- }
1578
- }
1579
- // Migration already done via configManager.loadConfig() -> migrateConfig() -> migrateDirectoryStructure()
1580
- // No need to call it again here
1581
- // Load existing config to preserve skills and ralph settings (already loaded above)
1582
- const existingSkills = existingConfig.skills?.enabled || [];
1583
- const existingRalph = existingConfig.ralph;
1584
- // Auto-detect skills based on project detection (v2.0)
1585
- let detectedSkills = [];
1586
- try {
1587
- const { SkillsManager, getDefaultTemplatesPath } = await import('../core/skills-manager.js');
1588
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
1589
- // Build a RulebookConfig-like object for skill detection
1590
- const rulebookConfigForSkills = {
1591
- languages: config.languages,
1592
- frameworks: config.frameworks,
1593
- modules: config.modules,
1594
- services: config.services,
1595
- };
1596
- detectedSkills = await skillsManager.autoDetectSkills(rulebookConfigForSkills);
1597
- // Merge with existing skills (keep existing, add new detected)
1598
- const mergedSkills = [...new Set([...existingSkills, ...detectedSkills])];
1599
- if (detectedSkills.length > existingSkills.length) {
1600
- const newSkills = detectedSkills.filter((s) => !existingSkills.includes(s));
1601
- if (newSkills.length > 0) {
1602
- console.log(chalk.green('\n✓ New skills detected:'));
1603
- for (const skillId of newSkills) {
1604
- console.log(chalk.gray(` - ${skillId}`));
1605
- }
1606
- }
1607
- }
1608
- detectedSkills = mergedSkills;
1609
- }
1610
- catch {
1611
- // Skills system not available or error - preserve existing skills
1612
- detectedSkills = existingSkills;
1613
- }
1614
- await configManager.updateConfig({
1615
- languages: config.languages,
1616
- frameworks: config.frameworks,
1617
- modules: config.modules,
1618
- services: config.services,
1619
- modular: config.modular ?? true,
1620
- rulebookDir: config.rulebookDir || '.rulebook',
1621
- skills: detectedSkills.length > 0 ? { enabled: detectedSkills } : undefined,
1622
- ralph: existingRalph,
1623
- memory: existingConfig.memory,
1624
- });
1625
- // Ensure .rulebook is in .gitignore with exceptions for specs/tasks
1626
- await configManager.ensureGitignore();
1627
- // Migrate flat layout to specs/ subdirectory if needed
1628
- {
1629
- const { hasFlatLayout, migrateFlatToSpecs } = await import('../core/migrator.js');
1630
- const rulebookDirForMigration = config.rulebookDir || '.rulebook';
1631
- if (await hasFlatLayout(cwd, rulebookDirForMigration)) {
1632
- const migrationSpinner = ora('Migrating rulebook files to specs/ subdirectory...').start();
1633
- const { migratedFiles } = await migrateFlatToSpecs(cwd, rulebookDirForMigration);
1634
- if (migratedFiles.length > 0) {
1635
- migrationSpinner.succeed(`Migrated ${migratedFiles.length} file(s) to /${rulebookDirForMigration}/specs/`);
1636
- }
1637
- else {
1638
- migrationSpinner.info('No files to migrate');
1639
- }
1640
- }
1641
- }
1642
- // Migrate legacy archive directory from tasks/archive to .rulebook/archive
1643
- {
1644
- const { createTaskManager } = await import('../core/task-manager.js');
1645
- const rulebookDirForArchive = config.rulebookDir || '.rulebook';
1646
- const tm = createTaskManager(cwd, rulebookDirForArchive);
1647
- const migrated = await tm.migrateArchive();
1648
- if (migrated) {
1649
- console.log(chalk.gray(' • Migrated task archive to .rulebook/archive/'));
1650
- }
1651
- }
1652
- // Merge with existing AGENTS.md (with migration support)
1653
- const mergeSpinner = ora('Updating AGENTS.md with latest templates...').start();
1654
- config.modular = config.modular ?? true; // Enable modular by default
1655
- const mergedContent = await mergeFullAgents(detection.existingAgents, config, cwd);
1656
- await writeFile(agentsPath, mergedContent);
1657
- mergeSpinner.succeed('AGENTS.md updated');
1658
- // Install + project canonical rules to all detected tools (v5 rule engine)
1659
- // On update: if .rulebook/rules/ is empty (v4 project), auto-install based on complexity
1660
- {
1661
- const { projectRules, installRule, loadCanonicalRules } = await import('../core/rule-engine.js');
1662
- const existingRules = await loadCanonicalRules(cwd);
1663
- if (existingRules.length === 0) {
1664
- // v4 project upgrading to v5 — install canonical rules based on complexity
1665
- const { assessComplexity } = await import('../core/complexity-detector.js');
1666
- const { getTemplatesDir } = await import('../core/generator.js');
1667
- const complexity = assessComplexity(cwd);
1668
- const templatesDir = getTemplatesDir();
1669
- const tier1 = [
1670
- 'no-shortcuts',
1671
- 'git-safety',
1672
- 'sequential-editing',
1673
- 'research-first',
1674
- 'follow-task-sequence',
1675
- 'incremental-implementation',
1676
- 'knowledge-base-usage',
1677
- ];
1678
- const tier2 = ['task-decomposition', 'incremental-tests', 'no-deferred', 'session-workflow'];
1679
- const toInstall = [...tier1];
1680
- if (complexity.recommendations.tier2Rules) {
1681
- toInstall.push(...tier2);
1682
- }
1683
- let installed = 0;
1684
- for (const name of toInstall) {
1685
- const result = await installRule(cwd, name, templatesDir);
1686
- if (result)
1687
- installed++;
1688
- }
1689
- if (installed > 0) {
1690
- console.log(chalk.gray(` • Installed ${installed} v5 canonical rules (${complexity.tier} project, ${complexity.metrics.estimatedLoc.toLocaleString()} LOC)`));
1691
- }
1692
- }
1693
- const ruleResult = await projectRules(cwd, {
1694
- claudeCode: existsSync(path.join(cwd, '.claude')) || existsSync(path.join(cwd, 'CLAUDE.md')),
1695
- cursor: detection.cursor?.detected,
1696
- gemini: detection.geminiCli?.detected,
1697
- windsurf: detection.windsurf?.detected,
1698
- copilot: detection.githubCopilot?.detected,
1699
- continueDev: detection.continueDev?.detected,
1700
- });
1701
- const totalProjected = ruleResult.claudeCode.length +
1702
- ruleResult.cursor.length +
1703
- ruleResult.gemini.length +
1704
- ruleResult.copilot.length +
1705
- ruleResult.windsurf.length +
1706
- ruleResult.continueDev.length;
1707
- if (totalProjected > 0) {
1708
- console.log(chalk.gray(` • Projected ${totalProjected} canonical rules to detected tools`));
1709
- }
1710
- }
1711
- // Show multi-tool config feedback (update command)
1712
- if (detection.geminiCli?.detected) {
1713
- console.log(chalk.gray(' • Gemini CLI config updated: GEMINI.md'));
1714
- }
1715
- if (detection.continueDev?.detected) {
1716
- console.log(chalk.gray(' • Continue.dev rules updated in .continue/rules/'));
1717
- }
1718
- if (detection.windsurf?.detected) {
1719
- console.log(chalk.gray(' • Windsurf rules updated: .windsurfrules'));
1720
- }
1721
- if (detection.githubCopilot?.detected) {
1722
- console.log(chalk.gray(' • GitHub Copilot instructions updated in .github/'));
1723
- }
1724
- if (installHooksOnUpdate) {
1725
- const hookLanguages = detection.languages.length > 0
1726
- ? detection.languages
1727
- : config.languages.map((language) => ({
1728
- language: language,
1729
- confidence: 1,
1730
- indicators: [],
1731
- }));
1732
- const hookSpinner = ora('Installing Git hooks (pre-commit & pre-push)...').start();
1733
- try {
1734
- await installGitHooks({ languages: hookLanguages, cwd });
1735
- hookSpinner.succeed('Git hooks installed successfully');
1736
- hooksInstalledOnUpdate = true;
1737
- }
1738
- catch (error) {
1739
- hookSpinner.fail('Failed to install Git hooks');
1740
- console.error(chalk.red(' ➤'), error instanceof Error ? error.message : error);
1741
- console.log(chalk.yellow(' ⚠ Skipping automatic hook installation. You can rerun "rulebook update" later to retry or install manually.'));
1742
- }
1743
- }
1744
- const gitHooksActiveAfterUpdate = hooksInstalledOnUpdate || (hasPreCommit && hasPrePush);
1745
- config.installGitHooks = gitHooksActiveAfterUpdate;
1746
- // Update .rulebook config
1747
- const configSpinner = ora('Updating .rulebook configuration...').start();
1748
- const rulebookFeatures = {
1749
- watcher: false,
1750
- agent: false,
1751
- logging: true,
1752
- telemetry: false,
1753
- notifications: false,
1754
- dryRun: false,
1755
- gitHooks: gitHooksActiveAfterUpdate,
1756
- repl: false,
1757
- templates: true,
1758
- context: minimalMode ? false : true,
1759
- health: true,
1760
- plugins: false,
1761
- parallel: minimalMode ? false : true,
1762
- smartContinue: minimalMode ? false : true,
1763
- };
1764
- const rulebookConfig = {
1765
- version: getRulebookVersion(),
1766
- installedAt: detection.existingAgents.content?.match(/Generated at: (.+)/)?.[1] ||
1767
- new Date().toISOString(),
1768
- updatedAt: new Date().toISOString(),
1769
- projectId: path.basename(cwd),
1770
- mode: minimalMode ? 'minimal' : 'full',
1771
- features: rulebookFeatures,
1772
- coverageThreshold: existingConfig.coverageThreshold ?? 95,
1773
- language: existingConfig.language ?? 'en',
1774
- outputLanguage: existingConfig.outputLanguage ?? 'en',
1775
- cliTools: existingConfig.cliTools ?? [],
1776
- maxParallelTasks: existingConfig.maxParallelTasks ?? 5,
1777
- timeouts: existingConfig.timeouts ?? {
1778
- taskExecution: 3600000,
1779
- cliResponse: 180000,
1780
- testRun: 600000,
1781
- },
1782
- ...(existingConfig.memory ? { memory: existingConfig.memory } : {}),
1783
- ...(existingConfig.ralph ? { ralph: existingConfig.ralph } : {}),
1784
- ...(existingConfig.skills ? { skills: existingConfig.skills } : {}),
1785
- ...(leanMode
1786
- ? { agentsMode: 'lean' }
1787
- : existingConfig.agentsMode
1788
- ? { agentsMode: existingConfig.agentsMode }
1789
- : {}),
1790
- };
1791
- await configManager.saveConfig(rulebookConfig);
1792
- configSpinner.succeed('.rulebook configuration updated');
1793
- // Auto-setup Claude Code integration (MCP + skills)
1794
- const claudeSpinner = ora('Checking Claude Code integration...').start();
1795
- try {
1796
- const { setupClaudeCodeIntegration } = await import('../core/claude-mcp.js');
1797
- const result = await setupClaudeCodeIntegration(cwd);
1798
- if (result.detected) {
1799
- claudeSpinner.succeed('Claude Code integration updated');
1800
- if (result.mcpConfigured) {
1801
- console.log(chalk.gray(' • MCP server added to .mcp.json'));
1802
- }
1803
- if (result.skillsInstalled.length > 0) {
1804
- console.log(chalk.gray(` • ${result.skillsInstalled.length} skills updated in .claude/commands/`));
1805
- }
1806
- if (result.agentTeamsEnabled) {
1807
- console.log(chalk.gray(' • Multi-agent teams enabled in .claude/settings.json'));
1808
- }
1809
- if (result.agentDefinitionsInstalled.length > 0) {
1810
- console.log(chalk.gray(` • ${result.agentDefinitionsInstalled.length} agent definitions updated in .claude/agents/`));
1811
- }
1812
- }
1813
- else {
1814
- claudeSpinner.info('Claude Code not detected (skipped)');
1815
- }
1816
- }
1817
- catch {
1818
- claudeSpinner.info('Claude Code integration skipped');
1819
- }
1820
- // Install/update Ralph shell scripts
1821
- try {
1822
- const { installRalphScripts } = await import('../core/ralph-scripts.js');
1823
- const scripts = await installRalphScripts(cwd);
1824
- if (scripts.length > 0) {
1825
- console.log(chalk.gray(` • ${scripts.length} Ralph scripts updated in .rulebook/scripts/`));
1826
- }
1827
- }
1828
- catch {
1829
- // Skip if Ralph scripts installation fails
1830
- }
1831
- // Ensure PLANS.md exists (create if missing, never overwrite)
1832
- try {
1833
- const { initPlans } = await import('../core/plans-manager.js');
1834
- await initPlans(cwd);
1835
- }
1836
- catch {
1837
- // Non-blocking
1838
- }
1839
- // Migrate memory directory if old structure exists
1840
- try {
1841
- await migrateMemoryDirectory();
1842
- }
1843
- catch {
1844
- // Silently skip if migration fails
1845
- }
1846
- // Install plugin in Claude Code
1847
- try {
1848
- await setupClaudeCodePlugin();
1849
- }
1850
- catch {
1851
- // Silently skip if plugin installation fails
1852
- }
1853
- // Clean up any accidental duplicate directories
1854
- try {
1855
- const fsPromises = await import('fs/promises');
1856
- const accidentalDir = path.join(cwd, '.rulebook', '.rulebook');
1857
- if (existsSync(accidentalDir)) {
1858
- await fsPromises.rm(accidentalDir, { recursive: true, force: true });
1859
- }
1860
- }
1861
- catch {
1862
- // Ignore cleanup errors
1863
- }
1864
- // Success message
1865
- console.log(chalk.bold.green('\n✅ Update complete!\n'));
1866
- console.log(chalk.white('Updated components:'));
1867
- console.log(chalk.green(' ✓ AGENTS.md - Merged with latest templates'));
1868
- console.log(chalk.green(` ✓ .rulebook - Updated to v${getRulebookVersion()}`));
1869
- console.log(chalk.white('\nWhat was updated:'));
1870
- console.log(chalk.gray(` - ${detection.languages.length} language templates`));
1871
- console.log(chalk.gray(` - ${detection.modules.filter((m) => m.detected).length} MCP modules`));
1872
- console.log(chalk.gray(' - Git workflow rules'));
1873
- console.log(chalk.gray(' - Rulebook task management'));
1874
- console.log(chalk.gray(' - Pre-commit command standardization'));
1875
- console.log(chalk.yellow('\n⚠ Review the updated AGENTS.md to ensure your custom rules are preserved'));
1876
- console.log(chalk.white('\nNext steps:'));
1877
- console.log(chalk.gray(' 1. Review AGENTS.md changes'));
1878
- console.log(chalk.gray(' 2. Test that your project still builds'));
1879
- console.log(chalk.gray(' 3. Run quality checks (lint, test, build)'));
1880
- console.log(chalk.gray(' 4. Commit the updated files\n'));
1881
- if (minimalMode && minimalArtifacts.length > 0) {
1882
- console.log(chalk.green('Essentials ensured:'));
1883
- for (const artifact of minimalArtifacts) {
1884
- console.log(chalk.gray(` - ${path.relative(cwd, artifact)}`));
1885
- }
1886
- console.log('');
1887
- }
1888
- }
1889
- // ============================================
1890
- // Skills Commands (v2.0)
1891
- // ============================================
1892
- /**
1893
- * List all available skills
1894
- */
1895
- export async function skillListCommand(options) {
1896
- try {
1897
- const cwd = process.cwd();
1898
- const spinner = ora('Discovering skills...').start();
1899
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
1900
- const { createConfigManager } = await import('../core/config-manager.js');
1901
- const configManager = createConfigManager(cwd);
1902
- let skills;
1903
- if (options.category) {
1904
- skills = await skillsManager.getSkillsByCategory(options.category);
1905
- }
1906
- else {
1907
- skills = await skillsManager.getSkills();
1908
- }
1909
- // Get enabled status from config
1910
- let enabledIds = new Set();
1911
- try {
1912
- const config = await configManager.loadConfig();
1913
- enabledIds = new Set(config.skills?.enabled || []);
1914
- }
1915
- catch {
1916
- // No config file, all skills disabled
1917
- }
1918
- // Filter by enabled status if requested
1919
- if (options.enabled) {
1920
- skills = skills.filter((s) => enabledIds.has(s.id));
1921
- }
1922
- spinner.succeed(`Found ${skills.length} skill(s)`);
1923
- if (skills.length === 0) {
1924
- console.log(chalk.yellow('\nNo skills found matching criteria.'));
1925
- return;
1926
- }
1927
- // Group by category
1928
- const byCategory = new Map();
1929
- for (const skill of skills) {
1930
- const cat = skill.category;
1931
- if (!byCategory.has(cat)) {
1932
- byCategory.set(cat, []);
1933
- }
1934
- byCategory.get(cat).push(skill);
1935
- }
1936
- console.log(chalk.bold.blue('\n📦 Available Skills\n'));
1937
- for (const [category, categorySkills] of byCategory) {
1938
- console.log(chalk.bold.white(`${category.toUpperCase()}`));
1939
- for (const skill of categorySkills) {
1940
- const enabled = enabledIds.has(skill.id);
1941
- const status = enabled ? chalk.green('✓') : chalk.gray('○');
1942
- const name = enabled ? chalk.green(skill.metadata.name) : chalk.white(skill.metadata.name);
1943
- console.log(` ${status} ${name}`);
1944
- console.log(chalk.gray(` ${skill.metadata.description}`));
1945
- console.log(chalk.gray(` ID: ${skill.id}`));
1946
- }
1947
- console.log('');
1948
- }
1949
- console.log(chalk.gray('Use "rulebook skill add <skill-id>" to enable a skill'));
1950
- console.log(chalk.gray('Use "rulebook skill remove <skill-id>" to disable a skill'));
1951
- }
1952
- catch (error) {
1953
- console.error(chalk.red('\n❌ Failed to list skills:'), error);
1954
- process.exit(1);
1955
- }
1956
- }
1957
- /**
1958
- * Add (enable) a skill
1959
- */
1960
- export async function skillAddCommand(skillId) {
1961
- try {
1962
- const cwd = process.cwd();
1963
- const spinner = ora(`Adding skill: ${skillId}...`).start();
1964
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
1965
- const { createConfigManager } = await import('../core/config-manager.js');
1966
- const configManager = createConfigManager(cwd);
1967
- // Check if skill exists
1968
- const skill = await skillsManager.getSkillById(skillId);
1969
- if (!skill) {
1970
- spinner.fail(`Skill not found: ${skillId}`);
1971
- // Search for similar skills
1972
- const allSkills = await skillsManager.getSkills();
1973
- const similar = allSkills.filter((s) => s.id.includes(skillId.toLowerCase()) ||
1974
- s.metadata.name.toLowerCase().includes(skillId.toLowerCase()));
1975
- if (similar.length > 0) {
1976
- console.log(chalk.yellow('\nDid you mean one of these?'));
1977
- for (const s of similar.slice(0, 5)) {
1978
- console.log(chalk.gray(` - ${s.id} (${s.metadata.name})`));
1979
- }
1980
- }
1981
- console.log(chalk.gray('\nUse "rulebook skill list" to see all available skills'));
1982
- process.exit(1);
1983
- }
1984
- // Load config and enable skill
1985
- let config = await configManager.loadConfig();
1986
- config = await skillsManager.enableSkill(skillId, config);
1987
- // Validate for conflicts
1988
- const validation = await skillsManager.validateSkills(config);
1989
- if (validation.conflicts.length > 0) {
1990
- spinner.warn(`Skill enabled with conflicts`);
1991
- console.log(chalk.yellow('\n⚠️ Conflicts detected:'));
1992
- for (const conflict of validation.conflicts) {
1993
- console.log(chalk.yellow(` - ${conflict.skillA} conflicts with ${conflict.skillB}`));
1994
- console.log(chalk.gray(` ${conflict.reason}`));
1995
- }
1996
- }
1997
- else {
1998
- spinner.succeed(`Skill added: ${skill.metadata.name}`);
1999
- }
2000
- // Save config
2001
- await configManager.saveConfig(config);
2002
- console.log(chalk.green(`\n✓ Skill "${skill.metadata.name}" is now enabled`));
2003
- console.log(chalk.gray(` Category: ${skill.category}`));
2004
- console.log(chalk.gray(` Description: ${skill.metadata.description}`));
2005
- if (validation.warnings.length > 0) {
2006
- console.log(chalk.yellow('\n⚠️ Warnings:'));
2007
- for (const warning of validation.warnings) {
2008
- console.log(chalk.yellow(` - ${warning}`));
2009
- }
2010
- }
2011
- console.log(chalk.gray('\nRun "rulebook update" to regenerate AGENTS.md with the new skill'));
2012
- }
2013
- catch (error) {
2014
- console.error(chalk.red('\n❌ Failed to add skill:'), error);
2015
- process.exit(1);
2016
- }
2017
- }
2018
- /**
2019
- * Remove (disable) a skill
2020
- */
2021
- export async function skillRemoveCommand(skillId) {
2022
- try {
2023
- const cwd = process.cwd();
2024
- const spinner = ora(`Removing skill: ${skillId}...`).start();
2025
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
2026
- const { createConfigManager } = await import('../core/config-manager.js');
2027
- const configManager = createConfigManager(cwd);
2028
- // Check if skill exists
2029
- const skill = await skillsManager.getSkillById(skillId);
2030
- if (!skill) {
2031
- spinner.fail(`Skill not found: ${skillId}`);
2032
- console.log(chalk.gray('Use "rulebook skill list" to see all available skills'));
2033
- process.exit(1);
2034
- }
2035
- // Load config
2036
- let config = await configManager.loadConfig();
2037
- // Check if skill is enabled
2038
- if (!config.skills?.enabled?.includes(skillId)) {
2039
- spinner.fail(`Skill "${skillId}" is not currently enabled`);
2040
- process.exit(1);
2041
- }
2042
- // Disable skill
2043
- config = await skillsManager.disableSkill(skillId, config);
2044
- await configManager.saveConfig(config);
2045
- spinner.succeed(`Skill removed: ${skill.metadata.name}`);
2046
- console.log(chalk.green(`\n✓ Skill "${skill.metadata.name}" is now disabled`));
2047
- console.log(chalk.gray('\nRun "rulebook update" to regenerate AGENTS.md without this skill'));
2048
- }
2049
- catch (error) {
2050
- console.error(chalk.red('\n❌ Failed to remove skill:'), error);
2051
- process.exit(1);
2052
- }
2053
- }
2054
- /**
2055
- * Show skill details
2056
- */
2057
- export async function skillShowCommand(skillId) {
2058
- try {
2059
- const cwd = process.cwd();
2060
- const spinner = ora(`Loading skill: ${skillId}...`).start();
2061
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
2062
- const { createConfigManager } = await import('../core/config-manager.js');
2063
- const configManager = createConfigManager(cwd);
2064
- const skill = await skillsManager.getSkillById(skillId);
2065
- if (!skill) {
2066
- spinner.fail(`Skill not found: ${skillId}`);
2067
- // Search for similar skills
2068
- const allSkills = await skillsManager.getSkills();
2069
- const similar = allSkills.filter((s) => s.id.includes(skillId.toLowerCase()) ||
2070
- s.metadata.name.toLowerCase().includes(skillId.toLowerCase()));
2071
- if (similar.length > 0) {
2072
- console.log(chalk.yellow('\nDid you mean one of these?'));
2073
- for (const s of similar.slice(0, 5)) {
2074
- console.log(chalk.gray(` - ${s.id} (${s.metadata.name})`));
2075
- }
2076
- }
2077
- process.exit(1);
2078
- }
2079
- spinner.stop();
2080
- // Check if enabled
2081
- let enabled = false;
2082
- try {
2083
- const config = await configManager.loadConfig();
2084
- enabled = config.skills?.enabled?.includes(skillId) || false;
2085
- }
2086
- catch {
2087
- // No config
2088
- }
2089
- console.log(chalk.bold.blue(`\n📦 ${skill.metadata.name}\n`));
2090
- console.log(chalk.white(`ID: ${skill.id}`));
2091
- console.log(chalk.white(`Category: ${skill.category}`));
2092
- console.log(chalk.white(`Status: ${enabled ? chalk.green('Enabled') : chalk.gray('Disabled')}`));
2093
- if (skill.metadata.version) {
2094
- console.log(chalk.white(`Version: ${skill.metadata.version}`));
2095
- }
2096
- if (skill.metadata.author) {
2097
- console.log(chalk.white(`Author: ${skill.metadata.author}`));
2098
- }
2099
- console.log(chalk.white(`\nDescription:`));
2100
- console.log(chalk.gray(` ${skill.metadata.description}`));
2101
- if (skill.metadata.tags && skill.metadata.tags.length > 0) {
2102
- console.log(chalk.white(`\nTags: ${skill.metadata.tags.join(', ')}`));
2103
- }
2104
- if (skill.metadata.dependencies && skill.metadata.dependencies.length > 0) {
2105
- console.log(chalk.white(`\nDependencies:`));
2106
- for (const dep of skill.metadata.dependencies) {
2107
- console.log(chalk.gray(` - ${dep}`));
2108
- }
2109
- }
2110
- if (skill.metadata.conflicts && skill.metadata.conflicts.length > 0) {
2111
- console.log(chalk.yellow(`\nConflicts with:`));
2112
- for (const conflict of skill.metadata.conflicts) {
2113
- console.log(chalk.yellow(` - ${conflict}`));
2114
- }
2115
- }
2116
- // Show preview of content
2117
- console.log(chalk.white(`\nContent Preview:`));
2118
- const preview = skill.content.slice(0, 500);
2119
- console.log(chalk.gray(preview + (skill.content.length > 500 ? '...' : '')));
2120
- console.log(chalk.gray(`\nPath: ${skill.path}`));
2121
- }
2122
- catch (error) {
2123
- console.error(chalk.red('\n❌ Failed to show skill:'), error);
2124
- process.exit(1);
2125
- }
2126
- }
2127
- /**
2128
- * Search for skills
2129
- */
2130
- export async function skillSearchCommand(query) {
2131
- try {
2132
- const cwd = process.cwd();
2133
- const spinner = ora(`Searching for: ${query}...`).start();
2134
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
2135
- const { createConfigManager } = await import('../core/config-manager.js');
2136
- const configManager = createConfigManager(cwd);
2137
- const skills = await skillsManager.searchSkills(query);
2138
- spinner.succeed(`Found ${skills.length} result(s)`);
2139
- if (skills.length === 0) {
2140
- console.log(chalk.yellow(`\nNo skills found matching "${query}"`));
2141
- console.log(chalk.gray('Try a different search term or use "rulebook skill list"'));
2142
- return;
2143
- }
2144
- // Get enabled status
2145
- let enabledIds = new Set();
2146
- try {
2147
- const config = await configManager.loadConfig();
2148
- enabledIds = new Set(config.skills?.enabled || []);
2149
- }
2150
- catch {
2151
- // No config
2152
- }
2153
- console.log(chalk.bold.blue(`\n🔍 Search Results for "${query}"\n`));
2154
- for (const skill of skills) {
2155
- const enabled = enabledIds.has(skill.id);
2156
- const status = enabled ? chalk.green('✓') : chalk.gray('○');
2157
- const name = enabled ? chalk.green(skill.metadata.name) : chalk.white(skill.metadata.name);
2158
- console.log(`${status} ${name} (${skill.category})`);
2159
- console.log(chalk.gray(` ${skill.metadata.description}`));
2160
- console.log(chalk.gray(` ID: ${skill.id}\n`));
2161
- }
2162
- }
2163
- catch (error) {
2164
- console.error(chalk.red('\n❌ Search failed:'), error);
2165
- process.exit(1);
2166
- }
2167
- }
2168
- // ============================================
2169
- // Memory Commands (v2.2)
2170
- // ============================================
2171
- export async function memorySearchCommand(query, options) {
2172
- const ora = (await import('ora')).default;
2173
- const chalk = (await import('chalk')).default;
2174
- const spinner = ora('Searching memories...').start();
2175
- try {
2176
- const { createConfigManager } = await import('../core/config-manager.js');
2177
- const cwd = process.cwd();
2178
- const configManager = createConfigManager(cwd);
2179
- const config = await configManager.loadConfig();
2180
- if (!config.memory?.enabled) {
2181
- spinner.fail('Memory system is not enabled. Run: rulebook config --set memory.enabled=true');
2182
- return;
2183
- }
2184
- const { createMemoryManager } = await import('../memory/memory-manager.js');
2185
- const manager = createMemoryManager(cwd, config.memory);
2186
- const results = await manager.searchMemories({
2187
- query,
2188
- mode: options.mode || 'hybrid',
2189
- type: options.type,
2190
- limit: options.limit ? parseInt(options.limit) : 20,
2191
- });
2192
- spinner.succeed(`Found ${results.length} memories`);
2193
- if (results.length === 0) {
2194
- console.log(chalk.yellow('\nNo memories found for that query.'));
2195
- }
2196
- else {
2197
- console.log('');
2198
- for (const r of results) {
2199
- const typeColor = r.type === 'bugfix' ? chalk.red : r.type === 'feature' ? chalk.green : chalk.blue;
2200
- console.log(` ${typeColor(r.type.padEnd(12))} ${chalk.white(r.title)} ${chalk.gray(`[${r.matchType}] ${r.score.toFixed(3)}`)}`);
2201
- }
2202
- }
2203
- await manager.close();
2204
- }
2205
- catch (error) {
2206
- spinner.fail('Search failed');
2207
- console.error(chalk.red(String(error)));
2208
- process.exit(1);
2209
- }
2210
- }
2211
- export async function memorySaveCommand(text, options) {
2212
- const ora = (await import('ora')).default;
2213
- const chalk = (await import('chalk')).default;
2214
- const spinner = ora('Saving memory...').start();
2215
- try {
2216
- const { createConfigManager } = await import('../core/config-manager.js');
2217
- const cwd = process.cwd();
2218
- const configManager = createConfigManager(cwd);
2219
- const config = await configManager.loadConfig();
2220
- if (!config.memory?.enabled) {
2221
- spinner.fail('Memory system is not enabled. Run: rulebook config --set memory.enabled=true');
2222
- return;
2223
- }
2224
- const { createMemoryManager } = await import('../memory/memory-manager.js');
2225
- const { classifyMemory } = await import('../memory/memory-hooks.js');
2226
- const manager = createMemoryManager(cwd, config.memory);
2227
- const type = (options.type || classifyMemory(text));
2228
- const title = options.title || text.slice(0, 80);
2229
- const tags = options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
2230
- const memory = await manager.saveMemory({ type, title, content: text, tags });
2231
- spinner.succeed(`Memory saved: ${chalk.cyan(memory.id)}`);
2232
- console.log(chalk.gray(` Type: ${memory.type} | Title: ${memory.title}`));
2233
- await manager.close();
2234
- }
2235
- catch (error) {
2236
- spinner.fail('Save failed');
2237
- console.error(chalk.red(String(error)));
2238
- process.exit(1);
2239
- }
2240
- }
2241
- export async function memoryListCommand(options) {
2242
- const ora = (await import('ora')).default;
2243
- const chalk = (await import('chalk')).default;
2244
- const spinner = ora('Loading memories...').start();
2245
- try {
2246
- const { createConfigManager } = await import('../core/config-manager.js');
2247
- const cwd = process.cwd();
2248
- const configManager = createConfigManager(cwd);
2249
- const config = await configManager.loadConfig();
2250
- if (!config.memory?.enabled) {
2251
- spinner.fail('Memory system is not enabled.');
2252
- return;
2253
- }
2254
- const { createMemoryManager } = await import('../memory/memory-manager.js');
2255
- const manager = createMemoryManager(cwd, config.memory);
2256
- const stats = await manager.getStats();
2257
- const exported = await manager.exportMemories('json');
2258
- const memories = JSON.parse(exported).slice(0, options.limit ? parseInt(options.limit) : 20);
2259
- spinner.succeed(`${stats.memoryCount} memories total`);
2260
- if (memories.length === 0) {
2261
- console.log(chalk.yellow('\nNo memories stored yet.'));
2262
- }
2263
- else {
2264
- console.log('');
2265
- for (const m of memories) {
2266
- const date = new Date(m.createdAt).toLocaleDateString();
2267
- const typeColor = m.type === 'bugfix' ? chalk.red : m.type === 'feature' ? chalk.green : chalk.blue;
2268
- console.log(` ${chalk.gray(date)} ${typeColor(m.type.padEnd(12))} ${chalk.white(m.title)}`);
2269
- }
2270
- }
2271
- await manager.close();
2272
- }
2273
- catch (error) {
2274
- spinner.fail('Failed to list memories');
2275
- console.error(chalk.red(String(error)));
2276
- process.exit(1);
2277
- }
2278
- }
2279
- export async function memoryStatsCommand() {
2280
- const ora = (await import('ora')).default;
2281
- const chalk = (await import('chalk')).default;
2282
- const spinner = ora('Loading stats...').start();
2283
- try {
2284
- const { createConfigManager } = await import('../core/config-manager.js');
2285
- const cwd = process.cwd();
2286
- const configManager = createConfigManager(cwd);
2287
- const config = await configManager.loadConfig();
2288
- if (!config.memory?.enabled) {
2289
- spinner.fail('Memory system is not enabled.');
2290
- return;
2291
- }
2292
- const { createMemoryManager } = await import('../memory/memory-manager.js');
2293
- const manager = createMemoryManager(cwd, config.memory);
2294
- const stats = await manager.getStats();
2295
- spinner.succeed('Memory statistics');
2296
- const sizeMB = (stats.dbSizeBytes / 1024 / 1024).toFixed(2);
2297
- const maxMB = (stats.maxSizeBytes / 1024 / 1024).toFixed(0);
2298
- const usage = stats.usagePercent.toFixed(1);
2299
- const bar = '█'.repeat(Math.floor(stats.usagePercent / 5)) +
2300
- '░'.repeat(20 - Math.floor(stats.usagePercent / 5));
2301
- console.log(`\n Memories: ${chalk.cyan(stats.memoryCount)}`);
2302
- console.log(` Sessions: ${chalk.cyan(stats.sessionCount)}`);
2303
- console.log(` DB Size: ${chalk.cyan(sizeMB + ' MB')} / ${maxMB} MB`);
2304
- console.log(` Usage: [${stats.usagePercent > 80 ? chalk.red(bar) : chalk.green(bar)}] ${usage}%`);
2305
- console.log(` Health: ${stats.indexHealth === 'good' ? chalk.green(stats.indexHealth) : chalk.yellow(stats.indexHealth)}`);
2306
- await manager.close();
2307
- }
2308
- catch (error) {
2309
- spinner.fail('Failed to load stats');
2310
- console.error(chalk.red(String(error)));
2311
- process.exit(1);
2312
- }
2313
- }
2314
- export async function memoryVerifyCommand() {
2315
- const ora = (await import('ora')).default;
2316
- const chalk = (await import('chalk')).default;
2317
- const spinner = ora('Verifying memory system...').start();
2318
- try {
2319
- const { createConfigManager } = await import('../core/config-manager.js');
2320
- const cwd = process.cwd();
2321
- const configManager = createConfigManager(cwd);
2322
- const config = await configManager.loadConfig();
2323
- const memoryEnabled = config.memory?.enabled ?? false;
2324
- const dbPathRelative = config.memory?.dbPath ?? '.rulebook/memory/memory.db';
2325
- const dbPathAbsolute = path.join(cwd, dbPathRelative);
2326
- spinner.succeed('Memory verification');
2327
- // Check enabled status
2328
- console.log(`\n ${memoryEnabled ? chalk.green('✓') : chalk.red('✗')} Memory enabled: ${memoryEnabled}`);
2329
- // Check DB path
2330
- console.log(` ${chalk.green('✓')} DB path: ${dbPathRelative}`);
2331
- // Check if file exists on disk
2332
- const fileExists = existsSync(dbPathAbsolute);
2333
- if (fileExists) {
2334
- const { statSync } = await import('fs');
2335
- const fileStat = statSync(dbPathAbsolute);
2336
- const sizeKB = (fileStat.size / 1024).toFixed(1);
2337
- console.log(` ${chalk.green('✓')} File exists: YES (${sizeKB} KB)`);
2338
- }
2339
- else {
2340
- console.log(` ${chalk.red('✗')} File exists: NO`);
2341
- }
2342
- // If memory is enabled and file exists, show record count
2343
- if (memoryEnabled && fileExists) {
2344
- try {
2345
- const { createMemoryManager } = await import('../memory/memory-manager.js');
2346
- const manager = createMemoryManager(cwd, config.memory);
2347
- const stats = await manager.getStats();
2348
- console.log(` ${chalk.green('✓')} Record count: ${stats.memoryCount} memories`);
2349
- await manager.close();
2350
- }
2351
- catch (error) {
2352
- console.log(` ${chalk.yellow('!')} Record count: unable to read (${String(error)})`);
2353
- }
2354
- }
2355
- else if (!memoryEnabled) {
2356
- console.log(` ${chalk.yellow('!')} Enable memory with: ${chalk.bold('rulebook config --feature memory --enable')}`);
2357
- }
2358
- console.log('');
2359
- }
2360
- catch (error) {
2361
- spinner.fail('Memory verification failed');
2362
- console.error(chalk.red(String(error)));
2363
- process.exit(1);
2364
- }
2365
- }
2366
- export async function memoryCleanupCommand(options) {
2367
- const ora = (await import('ora')).default;
2368
- const chalk = (await import('chalk')).default;
2369
- const spinner = ora('Running cleanup...').start();
2370
- try {
2371
- const { createConfigManager } = await import('../core/config-manager.js');
2372
- const cwd = process.cwd();
2373
- const configManager = createConfigManager(cwd);
2374
- const config = await configManager.loadConfig();
2375
- if (!config.memory?.enabled) {
2376
- spinner.fail('Memory system is not enabled.');
2377
- return;
2378
- }
2379
- const { createMemoryManager } = await import('../memory/memory-manager.js');
2380
- const manager = createMemoryManager(cwd, config.memory);
2381
- const result = await manager.cleanup(options.force || false);
2382
- if (result.evictedCount > 0) {
2383
- const freedMB = (result.freedBytes / 1024 / 1024).toFixed(2);
2384
- spinner.succeed(`Cleaned up ${result.evictedCount} memories (freed ${freedMB} MB)`);
2385
- }
2386
- else {
2387
- spinner.succeed('No cleanup needed');
2388
- }
2389
- await manager.close();
2390
- }
2391
- catch (error) {
2392
- spinner.fail('Cleanup failed');
2393
- console.error(chalk.red(String(error)));
2394
- process.exit(1);
2395
- }
2396
- }
2397
- export async function memoryExportCommand(options) {
2398
- const ora = (await import('ora')).default;
2399
- const chalk = (await import('chalk')).default;
2400
- const spinner = ora('Exporting memories...').start();
2401
- try {
2402
- const { createConfigManager } = await import('../core/config-manager.js');
2403
- const cwd = process.cwd();
2404
- const configManager = createConfigManager(cwd);
2405
- const config = await configManager.loadConfig();
2406
- if (!config.memory?.enabled) {
2407
- spinner.fail('Memory system is not enabled.');
2408
- return;
2409
- }
2410
- const { createMemoryManager } = await import('../memory/memory-manager.js');
2411
- const manager = createMemoryManager(cwd, config.memory);
2412
- const format = (options.format || 'json');
2413
- const exported = await manager.exportMemories(format);
2414
- const count = format === 'json' ? JSON.parse(exported).length : exported.split('\n').length - 1;
2415
- if (options.output) {
2416
- const { writeFile } = await import('fs/promises');
2417
- await writeFile(options.output, exported);
2418
- spinner.succeed(`Exported ${count} memories to ${chalk.cyan(options.output)}`);
2419
- }
2420
- else {
2421
- spinner.succeed(`Exported ${count} memories`);
2422
- console.log(exported);
2423
- }
2424
- await manager.close();
2425
- }
2426
- catch (error) {
2427
- spinner.fail('Export failed');
2428
- console.error(chalk.red(String(error)));
2429
- process.exit(1);
2430
- }
2431
- }
2432
- // Ralph Autonomous Loop Commands (v3.0)
2433
- export async function ralphInitCommand() {
2434
- const oraModule = await import('ora');
2435
- const ora = oraModule.default;
2436
- const spinner = ora('Initializing Ralph autonomous loop...').start();
2437
- try {
2438
- const cwd = process.cwd();
2439
- const { Logger } = await import('../core/logger.js');
2440
- const { RalphManager } = await import('../core/ralph-manager.js');
2441
- const { PRDGenerator } = await import('../core/prd-generator.js');
2442
- const { createConfigManager } = await import('../core/config-manager.js');
2443
- const logger = new Logger(cwd);
2444
- const configManager = createConfigManager(cwd);
2445
- const config = await configManager.loadConfig();
2446
- // Create managers
2447
- const ralphManager = new RalphManager(cwd, logger);
2448
- const prdGenerator = new PRDGenerator(cwd, logger);
2449
- // Initialize Ralph
2450
- const maxIterations = config.ralph?.maxIterations || 10;
2451
- const tool = (config.ralph?.tool || 'claude');
2452
- await ralphManager.initialize(maxIterations, tool);
2453
- // Generate PRD from rulebook tasks
2454
- const prd = await prdGenerator.generatePRD(path.basename(cwd));
2455
- // Save PRD
2456
- const prdPath = path.join(cwd, '.rulebook', 'ralph', 'prd.json');
2457
- await writeFile(prdPath, JSON.stringify(prd, null, 2));
2458
- spinner.succeed(`Ralph initialized: ${prd.userStories.length} user stories loaded`);
2459
- console.log(`\n 📋 PRD: ${prdPath}`);
2460
- console.log(` 🔄 Max iterations: ${maxIterations}`);
2461
- console.log(` 🤖 AI Tool: ${tool}`);
2462
- console.log(`\n Run: ${chalk.bold('rulebook ralph run')}\n`);
2463
- }
2464
- catch (error) {
2465
- spinner.fail('Ralph initialization failed');
2466
- console.error(chalk.red(String(error)));
2467
- process.exit(1);
2468
- }
2469
- }
2470
- export async function ralphRunCommand(options) {
2471
- const oraModule = await import('ora');
2472
- const ora = oraModule.default;
2473
- const spinner = ora('Starting Ralph autonomous loop...').start();
2474
- try {
2475
- const cwd = process.cwd();
2476
- const { Logger } = await import('../core/logger.js');
2477
- const { RalphManager } = await import('../core/ralph-manager.js');
2478
- const { RalphParser } = await import('../agents/ralph-parser.js');
2479
- const { createConfigManager } = await import('../core/config-manager.js');
2480
- const { IterationTracker } = await import('../core/iteration-tracker.js');
2481
- const childProcess = await import('child_process');
2482
- const logger = new Logger(cwd);
2483
- const configManager = createConfigManager(cwd);
2484
- const config = await configManager.loadConfig();
2485
- const ralphManager = new RalphManager(cwd, logger);
2486
- const maxIterations = options.maxIterations || config.ralph?.maxIterations || 10;
2487
- const tool = options.tool || config.ralph?.tool || 'claude';
2488
- // Resolve parallel mode — CLI flag takes precedence over config
2489
- const parallelWorkers = options.parallel ??
2490
- (config.ralph?.parallel?.enabled ? config.ralph.parallel.maxWorkers : undefined);
2491
- // Resolve plan checkpoint config — --plan-first CLI flag takes precedence
2492
- const planCheckpointConfig = {
2493
- enabled: options.planFirst ?? config.ralph?.planCheckpoint?.enabled ?? false,
2494
- autoApproveAfterSeconds: config.ralph?.planCheckpoint?.autoApproveAfterSeconds ?? 0,
2495
- requireApprovalForStories: config.ralph?.planCheckpoint?.requireApprovalForStories ?? 'all',
2496
- };
2497
- // Context compression config
2498
- const compressionConfig = config.ralph?.contextCompression;
2499
- const compressionEnabled = compressionConfig?.enabled !== false;
2500
- const compressionRecentCount = compressionConfig?.recentCount ?? 3;
2501
- const compressionThreshold = compressionConfig?.threshold ?? 5;
2502
- const iterationTracker = new IterationTracker(cwd, logger);
2503
- await iterationTracker.initialize();
2504
- await ralphManager.initialize(maxIterations, tool);
2505
- // Create git branch from PRD
2506
- const prd = await ralphManager.loadPRD();
2507
- if (prd?.branchName) {
2508
- await ralphCreateBranch(cwd, prd.branchName);
2509
- }
2510
- // Handle Ctrl+C for graceful pause
2511
- let interrupted = false;
2512
- const handleInterrupt = async () => {
2513
- interrupted = true;
2514
- spinner.warn('Pausing after current iteration...');
2515
- await ralphManager.pause();
2516
- };
2517
- process.on('SIGINT', handleInterrupt);
2518
- // Sync task count from PRD (may have been saved after initialize)
2519
- await ralphManager.refreshTaskCount();
2520
- // ─── Parallel execution mode ───
2521
- if (parallelWorkers && parallelWorkers > 1) {
2522
- spinner.text = `Ralph parallel mode (${parallelWorkers} workers)...`;
2523
- const batches = await ralphManager.getParallelBatches(parallelWorkers);
2524
- spinner.stop();
2525
- console.log(chalk.bold.cyan(`\n Parallel mode: ${batches.length} batch(es), max ${parallelWorkers} workers\n`));
2526
- let iterationCount = 0;
2527
- for (const batch of batches) {
2528
- if (interrupted)
2529
- break;
2530
- console.log(chalk.bold(` ── Batch: ${batch.map((s) => s.id).join(', ')} (${batch.length} stories) ──`));
2531
- // Run all stories in the batch concurrently
2532
- const batchResults = await Promise.allSettled(batch.map(async (task) => {
2533
- iterationCount++;
2534
- const localIteration = iterationCount;
2535
- const startTime = Date.now();
2536
- // Build context (shared — read-only)
2537
- let contextHistory = '';
2538
- if (compressionEnabled) {
2539
- contextHistory = await iterationTracker.buildCompressedContext(compressionRecentCount, compressionThreshold);
2540
- }
2541
- let plansContext = '';
2542
- try {
2543
- const { readPlans, plansExists } = await import('../core/plans-manager.js');
2544
- if (plansExists(cwd)) {
2545
- const plans = await readPlans(cwd);
2546
- if (plans?.context && plans.context.trim()) {
2547
- plansContext = plans.context.trim();
2548
- }
2549
- }
2550
- }
2551
- catch {
2552
- // PLANS.md injection is optional
2553
- }
2554
- const prompt = ralphBuildPrompt(task, prd, contextHistory, plansContext);
2555
- let agentOutput = '';
2556
- try {
2557
- agentOutput = await ralphExecuteAgent(tool, prompt, cwd, childProcess.spawn);
2558
- }
2559
- catch (agentError) {
2560
- agentOutput = `Error executing agent: ${agentError.message || agentError}`;
2561
- }
2562
- const qualityResults = await ralphRunQualityGates(cwd, childProcess.spawn);
2563
- const executionTime = Date.now() - startTime;
2564
- const parsed = RalphParser.parseAgentOutput(agentOutput, localIteration, task.id, task.title, tool);
2565
- const allGatesPass = qualityResults.type_check &&
2566
- qualityResults.lint &&
2567
- qualityResults.tests &&
2568
- qualityResults.coverage_met;
2569
- const passCount = Object.values(qualityResults).filter(Boolean).length;
2570
- const status = allGatesPass
2571
- ? 'success'
2572
- : passCount >= 2
2573
- ? 'partial'
2574
- : 'failed';
2575
- let gitCommit;
2576
- if (allGatesPass) {
2577
- gitCommit = await ralphGitCommit(cwd, task, localIteration, childProcess.spawn);
2578
- await ralphManager.markStoryComplete(task.id);
2579
- console.log(chalk.green(` [parallel] Story ${task.id} completed`));
2580
- }
2581
- else {
2582
- console.log(chalk.yellow(` [parallel] Story ${task.id} not completed (quality gates failed)`));
2583
- }
2584
- const result = {
2585
- iteration: localIteration,
2586
- timestamp: new Date().toISOString(),
2587
- task_id: task.id,
2588
- task_title: task.title,
2589
- status,
2590
- ai_tool: tool,
2591
- execution_time_ms: executionTime,
2592
- quality_checks: qualityResults,
2593
- output_summary: parsed.output_summary || `Iteration ${localIteration}: ${task.title}`,
2594
- git_commit: gitCommit,
2595
- learnings: parsed.learnings,
2596
- errors: parsed.errors,
2597
- metadata: {
2598
- context_loss_count: parsed.metadata.context_loss_count,
2599
- parsed_completion: parsed.metadata.parsed_completion,
2600
- },
2601
- };
2602
- await ralphManager.recordIteration(result);
2603
- return result;
2604
- }));
2605
- // Log rejected promises
2606
- for (const [i, result] of batchResults.entries()) {
2607
- if (result.status === 'rejected') {
2608
- const story = batch[i];
2609
- console.log(chalk.red(` [parallel] Story ${story.id} threw: ${result.reason}`));
2610
- }
2611
- }
2612
- // Check for pause
2613
- const currentStatus = await ralphManager.getStatus();
2614
- if (currentStatus?.paused)
2615
- break;
2616
- }
2617
- // Cleanup and summary for parallel mode
2618
- process.removeListener('SIGINT', handleInterrupt);
2619
- const stats = await ralphManager.getTaskStats();
2620
- console.log(`\n Parallel run complete: ${stats.completed}/${stats.total} tasks completed`);
2621
- console.log(` Iterations: ${iterationCount}`);
2622
- if (interrupted) {
2623
- console.log(chalk.yellow(` Paused by user. Resume: ${chalk.bold('rulebook ralph resume')}`));
2624
- }
2625
- console.log(`\n View history: ${chalk.bold('rulebook ralph history')}\n`);
2626
- return;
2627
- }
2628
- // ─── Sequential execution (default) ───
2629
- spinner.text = 'Ralph loop running (Ctrl+C to pause)...';
2630
- let iterationCount = 0;
2631
- while (ralphManager.canContinue() && !interrupted) {
2632
- iterationCount++;
2633
- const task = await ralphManager.getNextTask();
2634
- if (!task) {
2635
- break;
2636
- }
2637
- spinner.stop();
2638
- console.log(chalk.bold.cyan(`\n ── Iteration ${iterationCount}: ${task.title} ──\n`));
2639
- const startTime = Date.now();
2640
- // 0. Plan checkpoint — require approval before implementation
2641
- if (planCheckpointConfig.enabled) {
2642
- const checkpoint = await ralphManager.runCheckpoint(task, tool, planCheckpointConfig);
2643
- if (!checkpoint.proceed) {
2644
- console.log(chalk.yellow(` Plan rejected for ${task.id}. Skipping implementation.`));
2645
- if (checkpoint.feedback) {
2646
- console.log(chalk.gray(` Feedback: ${checkpoint.feedback}`));
2647
- }
2648
- spinner.start('Preparing next iteration...');
2649
- continue;
2650
- }
2651
- }
2652
- // 1. Execute AI agent with task context
2653
- let contextHistory = '';
2654
- if (compressionEnabled) {
2655
- contextHistory = await iterationTracker.buildCompressedContext(compressionRecentCount, compressionThreshold);
2656
- }
2657
- // Read PLANS.md context for session scratchpad injection
2658
- let plansContext = '';
2659
- try {
2660
- const { readPlans, plansExists } = await import('../core/plans-manager.js');
2661
- if (plansExists(cwd)) {
2662
- const plans = await readPlans(cwd);
2663
- if (plans?.context && plans.context.trim()) {
2664
- plansContext = plans.context.trim();
2665
- }
2666
- }
2667
- }
2668
- catch {
2669
- // PLANS.md injection is optional — skip on error
2670
- }
2671
- const prompt = ralphBuildPrompt(task, prd, contextHistory, plansContext);
2672
- let agentOutput = '';
2673
- try {
2674
- agentOutput = await ralphExecuteAgent(tool, prompt, cwd, childProcess.spawn);
2675
- }
2676
- catch (agentError) {
2677
- agentOutput = `Error executing agent: ${agentError.message || agentError}`;
2678
- console.log(chalk.red(` Agent error: ${agentError.message || agentError}`));
2679
- }
2680
- // 2. Run quality gates
2681
- spinner.start('Running quality gates...');
2682
- const qualityResults = await ralphRunQualityGates(cwd, childProcess.spawn);
2683
- spinner.stop();
2684
- // Print quality gate results
2685
- const gateIcon = (pass) => (pass ? chalk.green('✓') : chalk.red('✗'));
2686
- console.log(` ${gateIcon(qualityResults.type_check)} type-check`);
2687
- console.log(` ${gateIcon(qualityResults.lint)} lint`);
2688
- console.log(` ${gateIcon(qualityResults.tests)} tests`);
2689
- console.log(` ${gateIcon(qualityResults.coverage_met)} coverage`);
2690
- const executionTime = Date.now() - startTime;
2691
- // 3. Parse agent output for learnings/errors
2692
- const parsed = RalphParser.parseAgentOutput(agentOutput, iterationCount, task.id, task.title, tool);
2693
- // 4. Determine status from real quality gates
2694
- const allGatesPass = qualityResults.type_check &&
2695
- qualityResults.lint &&
2696
- qualityResults.tests &&
2697
- qualityResults.coverage_met;
2698
- const passCount = Object.values(qualityResults).filter(Boolean).length;
2699
- const status = allGatesPass
2700
- ? 'success'
2701
- : passCount >= 2
2702
- ? 'partial'
2703
- : 'failed';
2704
- // 5. Git commit if successful
2705
- let gitCommit;
2706
- if (allGatesPass) {
2707
- gitCommit = await ralphGitCommit(cwd, task, iterationCount, childProcess.spawn);
2708
- await ralphManager.markStoryComplete(task.id);
2709
- console.log(chalk.green(`\n ✅ Story ${task.id} completed`));
2710
- }
2711
- else {
2712
- console.log(chalk.yellow(`\n ⚠ Story ${task.id} not completed (quality gates failed)`));
2713
- }
2714
- // 6. Record iteration
2715
- const result = {
2716
- iteration: iterationCount,
2717
- timestamp: new Date().toISOString(),
2718
- task_id: task.id,
2719
- task_title: task.title,
2720
- status,
2721
- ai_tool: tool,
2722
- execution_time_ms: executionTime,
2723
- quality_checks: qualityResults,
2724
- output_summary: parsed.output_summary || `Iteration ${iterationCount}: ${task.title}`,
2725
- git_commit: gitCommit,
2726
- learnings: parsed.learnings,
2727
- errors: parsed.errors,
2728
- metadata: {
2729
- context_loss_count: parsed.metadata.context_loss_count,
2730
- parsed_completion: parsed.metadata.parsed_completion,
2731
- },
2732
- };
2733
- await ralphManager.recordIteration(result);
2734
- spinner.start('Preparing next iteration...');
2735
- }
2736
- // Cleanup
2737
- process.removeListener('SIGINT', handleInterrupt);
2738
- const stats = await ralphManager.getTaskStats();
2739
- spinner.succeed(`Ralph loop complete: ${stats.completed}/${stats.total} tasks completed`);
2740
- console.log(`\n ✅ Iterations: ${iterationCount}`);
2741
- console.log(` 📊 Completed: ${stats.completed}/${stats.total}`);
2742
- if (interrupted) {
2743
- console.log(chalk.yellow(` ⏸ Paused by user. Resume: ${chalk.bold('rulebook ralph resume')}`));
2744
- }
2745
- console.log(`\n View history: ${chalk.bold('rulebook ralph history')}\n`);
2746
- }
2747
- catch (error) {
2748
- spinner.fail('Ralph loop failed');
2749
- console.error(chalk.red(String(error)));
2750
- process.exit(1);
2751
- }
2752
- }
2753
- /**
2754
- * Build prompt for AI agent from user story context
2755
- */
2756
- function ralphBuildPrompt(task, prd, contextHistory, plansContext) {
2757
- const criteria = (task.acceptanceCriteria || []).map((c) => `- ${c}`).join('\n');
2758
- return [
2759
- `You are working on project: ${prd?.project || 'unknown'}`,
2760
- ``,
2761
- plansContext ? `## Session Context (PLANS.md)\n${plansContext}\n` : '',
2762
- contextHistory && contextHistory !== 'No iteration history available.'
2763
- ? `## Iteration History\n${contextHistory}\n`
2764
- : '',
2765
- `## Current Task: ${task.title}`,
2766
- `ID: ${task.id}`,
2767
- ``,
2768
- `## Description`,
2769
- task.description,
2770
- ``,
2771
- `## Acceptance Criteria`,
2772
- criteria,
2773
- ``,
2774
- task.notes ? `## Notes\n${task.notes}\n` : '',
2775
- `## Instructions`,
2776
- `1. Implement the changes described above`,
2777
- `2. Ensure all acceptance criteria are met`,
2778
- `3. Run quality checks: type-check, lint, tests`,
2779
- `4. Fix any issues found by quality checks`,
2780
- `5. When done, summarize what was changed`,
2781
- ]
2782
- .filter(Boolean)
2783
- .join('\n');
2784
- }
2785
- /**
2786
- * Execute AI agent and capture output
2787
- */
2788
- async function ralphExecuteAgent(tool, prompt, cwd, spawn) {
2789
- // Claude: use -p (print mode) with --dangerously-skip-permissions to allow file edits
2790
- // Prompt is passed via stdin to avoid shell escaping issues and arg length limits
2791
- const toolCommands = {
2792
- claude: {
2793
- cmd: 'claude',
2794
- args: ['-p', '--dangerously-skip-permissions', '--verbose'],
2795
- stdinPrompt: true,
2796
- },
2797
- amp: { cmd: 'amp', args: ['-p', prompt], stdinPrompt: false },
2798
- gemini: { cmd: 'gemini', args: ['-p', prompt], stdinPrompt: false },
2799
- };
2800
- const config = toolCommands[tool] || toolCommands.claude;
2801
- return new Promise((resolve, reject) => {
2802
- let output = '';
2803
- let errorOutput = '';
2804
- const proc = spawn(config.cmd, config.args, {
2805
- cwd,
2806
- shell: true,
2807
- stdio: ['pipe', 'pipe', 'pipe'],
2808
- env: { ...process.env },
2809
- });
2810
- // For Claude, write prompt to stdin then close it
2811
- if (config.stdinPrompt && proc.stdin) {
2812
- proc.stdin.write(prompt);
2813
- proc.stdin.end();
2814
- }
2815
- proc.stdout?.on('data', (data) => {
2816
- const text = data.toString();
2817
- output += text;
2818
- process.stdout.write(text);
2819
- });
2820
- proc.stderr?.on('data', (data) => {
2821
- errorOutput += data.toString();
2822
- });
2823
- proc.on('close', (code) => {
2824
- if (code === 0 || output.length > 0) {
2825
- resolve(output || errorOutput);
2826
- }
2827
- else {
2828
- reject(new Error(`Agent ${tool} exited with code ${code}: ${errorOutput.slice(0, 500)}`));
2829
- }
2830
- });
2831
- proc.on('error', (err) => {
2832
- reject(new Error(`Failed to start ${tool}: ${err.message}`));
2833
- });
2834
- // 10 minute timeout per iteration
2835
- setTimeout(() => {
2836
- proc.kill('SIGTERM');
2837
- resolve(output || 'Agent execution timed out after 10 minutes');
2838
- }, 600000);
2839
- });
2840
- }
2841
- /**
2842
- * Run quality gates and return results
2843
- */
2844
- async function ralphRunQualityGates(cwd, spawn) {
2845
- const runGate = (cmd, args) => {
2846
- return new Promise((resolve) => {
2847
- const proc = spawn(cmd, args, {
2848
- cwd,
2849
- shell: true,
2850
- stdio: ['pipe', 'pipe', 'pipe'],
2851
- });
2852
- proc.on('close', (code) => {
2853
- resolve(code === 0);
2854
- });
2855
- proc.on('error', () => {
2856
- resolve(false);
2857
- });
2858
- // 2 minute timeout per gate
2859
- setTimeout(() => {
2860
- proc.kill('SIGTERM');
2861
- resolve(false);
2862
- }, 120000);
2863
- });
2864
- };
2865
- // Detect monorepo to choose the right test command
2866
- const { detectMonorepo } = await import('../core/detector.js');
2867
- const monorepo = await detectMonorepo(cwd).catch(() => ({
2868
- detected: false,
2869
- tool: null,
2870
- packages: [],
2871
- }));
2872
- let testCmd = ['npm', ['test']];
2873
- if (monorepo.detected) {
2874
- if (monorepo.tool === 'turborepo')
2875
- testCmd = ['turbo', ['run', 'test']];
2876
- else if (monorepo.tool === 'nx')
2877
- testCmd = ['nx', ['run-many', '--target=test']];
2878
- }
2879
- // Run gates in parallel
2880
- const [typeCheck, lint, tests] = await Promise.all([
2881
- runGate('npm', ['run', 'type-check']),
2882
- runGate('npm', ['run', 'lint']),
2883
- runGate(testCmd[0], testCmd[1]),
2884
- ]);
2885
- return {
2886
- type_check: typeCheck,
2887
- lint: lint,
2888
- tests: tests,
2889
- coverage_met: tests,
2890
- };
2891
- }
2892
- /**
2893
- * Create git branch from PRD branchName
2894
- */
2895
- async function ralphCreateBranch(cwd, branchName) {
2896
- const { readFileSync } = await import('fs');
2897
- const { spawn } = await import('child_process');
2898
- // Check if already on the branch
2899
- try {
2900
- const gitHeadPath = path.join(cwd, '.git', 'HEAD');
2901
- const head = readFileSync(gitHeadPath, 'utf8').trim();
2902
- const currentBranch = head.replace('ref: refs/heads/', '');
2903
- if (currentBranch === branchName) {
2904
- return;
2905
- }
2906
- }
2907
- catch {
2908
- return;
2909
- }
2910
- // Create or checkout branch
2911
- await new Promise((resolve) => {
2912
- const proc = spawn('git', ['checkout', '-B', branchName], {
2913
- cwd,
2914
- shell: true,
2915
- stdio: 'pipe',
2916
- });
2917
- proc.on('close', () => resolve());
2918
- proc.on('error', () => resolve());
2919
- });
2920
- }
2921
- /**
2922
- * Commit changes after successful iteration
2923
- */
2924
- async function ralphGitCommit(cwd, task, iteration, spawn) {
2925
- // Stage all changes
2926
- await new Promise((resolve) => {
2927
- const proc = spawn('git', ['add', '-A'], { cwd, shell: true, stdio: 'pipe' });
2928
- proc.on('close', () => resolve());
2929
- proc.on('error', () => resolve());
2930
- });
2931
- // Check if there are staged changes
2932
- const hasChanges = await new Promise((resolve) => {
2933
- let output = '';
2934
- const proc = spawn('git', ['diff', '--cached', '--stat'], {
2935
- cwd,
2936
- shell: true,
2937
- stdio: ['pipe', 'pipe', 'pipe'],
2938
- });
2939
- proc.stdout?.on('data', (data) => {
2940
- output += data.toString();
2941
- });
2942
- proc.on('close', () => resolve(output.trim().length > 0));
2943
- proc.on('error', () => resolve(false));
2944
- });
2945
- if (!hasChanges) {
2946
- return undefined;
2947
- }
2948
- // Commit with Ralph message
2949
- const commitMsg = `ralph(${task.id}): ${task.title}\n\nIteration ${iteration} - Ralph autonomous loop`;
2950
- const commitHash = await new Promise((resolve) => {
2951
- let output = '';
2952
- const proc = spawn('git', ['commit', '-m', commitMsg], {
2953
- cwd,
2954
- shell: true,
2955
- stdio: ['pipe', 'pipe', 'pipe'],
2956
- });
2957
- proc.stdout?.on('data', (data) => {
2958
- output += data.toString();
2959
- });
2960
- proc.on('close', (code) => {
2961
- if (code === 0) {
2962
- const hashMatch = output.match(/\[[\w/.-]+ ([a-f0-9]+)\]/);
2963
- resolve(hashMatch ? hashMatch[1] : undefined);
2964
- }
2965
- else {
2966
- resolve(undefined);
2967
- }
2968
- });
2969
- proc.on('error', () => resolve(undefined));
2970
- });
2971
- return commitHash;
2972
- }
2973
- export async function ralphStatusCommand() {
2974
- const oraModule = await import('ora');
2975
- const ora = oraModule.default;
2976
- const spinner = ora('Loading Ralph status...').start();
2977
- try {
2978
- const cwd = process.cwd();
2979
- const { Logger } = await import('../core/logger.js');
2980
- const { RalphManager } = await import('../core/ralph-manager.js');
2981
- const logger = new Logger(cwd);
2982
- const ralphManager = new RalphManager(cwd, logger);
2983
- const status = await ralphManager.getStatus();
2984
- if (!status) {
2985
- spinner.fail('Ralph not initialized');
2986
- console.log(`\n Run: ${chalk.bold('rulebook ralph init')}\n`);
2987
- return;
2988
- }
2989
- spinner.stop();
2990
- // Show agentsMode from config
2991
- const { createConfigManager } = await import('../core/config-manager.js');
2992
- const configManager = createConfigManager(cwd);
2993
- const cfg = await configManager.loadConfig();
2994
- const agentsMode = cfg.agentsMode ?? 'full';
2995
- console.log(`\n ${chalk.bold('Ralph Loop Status')}`);
2996
- console.log(` Iteration: ${status.current_iteration}/${status.max_iterations}`);
2997
- console.log(` Tasks: ${status.completed_tasks}/${status.total_tasks}`);
2998
- console.log(` Status: ${status.paused ? chalk.yellow('PAUSED') : chalk.green('RUNNING')}`);
2999
- console.log(` AI Tool: ${status.tool}`);
3000
- console.log(` Started: ${new Date(status.started_at).toLocaleString()}`);
3001
- console.log(` Agents Mode: ${agentsMode === 'lean' ? chalk.cyan('lean') : chalk.gray('full')} (rulebook mode set lean|full)`);
3002
- console.log();
3003
- }
3004
- catch (error) {
3005
- spinner.fail('Failed to load status');
3006
- console.error(chalk.red(String(error)));
3007
- process.exit(1);
3008
- }
3009
- }
3010
- export async function ralphHistoryCommand(options) {
3011
- const oraModule = await import('ora');
3012
- const ora = oraModule.default;
3013
- const spinner = ora('Loading iteration history...').start();
3014
- try {
3015
- const cwd = process.cwd();
3016
- const { Logger } = await import('../core/logger.js');
3017
- const { IterationTracker } = await import('../core/iteration-tracker.js');
3018
- const logger = new Logger(cwd);
3019
- const tracker = new IterationTracker(cwd, logger);
3020
- const limit = options.limit || 10;
3021
- const history = await tracker.getHistory(limit);
3022
- if (history.length === 0) {
3023
- spinner.fail('No iteration history found');
3024
- return;
3025
- }
3026
- spinner.stop();
3027
- console.log(`\n ${chalk.bold('Recent Iterations')} (${history.length})\n`);
3028
- for (const iter of history) {
3029
- const statusIcon = iter.status === 'success'
3030
- ? chalk.green('✓')
3031
- : iter.status === 'partial'
3032
- ? chalk.yellow('◐')
3033
- : chalk.red('✗');
3034
- console.log(` ${statusIcon} Iteration ${iter.iteration}: ${iter.task_title}`);
3035
- console.log(` Status: ${iter.status} | Duration: ${(iter.duration_ms || 0) / 1000}s`);
3036
- console.log(` Checks: type=${iter.quality_checks.type_check ? '✓' : '✗'} lint=${iter.quality_checks.lint ? '✓' : '✗'} tests=${iter.quality_checks.tests ? '✓' : '✗'}`);
3037
- if (iter.git_commit) {
3038
- console.log(` Commit: ${iter.git_commit}`);
3039
- }
3040
- console.log();
3041
- }
3042
- // Show statistics
3043
- const stats = await tracker.getStatistics();
3044
- console.log(` ${chalk.bold('Statistics')}`);
3045
- console.log(` Total: ${stats.total_iterations} | Success: ${stats.successful_iterations} | Failed: ${stats.failed_iterations}`);
3046
- console.log(` Success rate: ${(stats.success_rate * 100).toFixed(1)}%`);
3047
- console.log(` Avg duration: ${stats.average_duration_ms}ms\n`);
3048
- }
3049
- catch (error) {
3050
- spinner.fail('Failed to load history');
3051
- console.error(chalk.red(String(error)));
3052
- process.exit(1);
3053
- }
3054
- }
3055
- export async function ralphPauseCommand() {
3056
- const oraModule = await import('ora');
3057
- const ora = oraModule.default;
3058
- const spinner = ora('Pausing Ralph loop...').start();
3059
- try {
3060
- const cwd = process.cwd();
3061
- const { Logger } = await import('../core/logger.js');
3062
- const { RalphManager } = await import('../core/ralph-manager.js');
3063
- const logger = new Logger(cwd);
3064
- const ralphManager = new RalphManager(cwd, logger);
3065
- const status = await ralphManager.getStatus();
3066
- if (!status) {
3067
- spinner.fail('Ralph not initialized');
3068
- console.log(`\n Run: ${chalk.bold('rulebook ralph init')}\n`);
3069
- return;
3070
- }
3071
- await ralphManager.pause();
3072
- spinner.succeed('Ralph loop paused');
3073
- console.log(`\n Resume with: ${chalk.bold('rulebook ralph resume')}\n`);
3074
- }
3075
- catch (error) {
3076
- spinner.fail('Failed to pause');
3077
- console.error(chalk.red(String(error)));
3078
- process.exit(1);
3079
- }
3080
- }
3081
- export async function ralphResumeCommand() {
3082
- const oraModule = await import('ora');
3083
- const ora = oraModule.default;
3084
- const spinner = ora('Resuming Ralph loop...').start();
3085
- try {
3086
- const cwd = process.cwd();
3087
- const { Logger } = await import('../core/logger.js');
3088
- const { RalphManager } = await import('../core/ralph-manager.js');
3089
- const logger = new Logger(cwd);
3090
- const ralphManager = new RalphManager(cwd, logger);
3091
- const status = await ralphManager.getStatus();
3092
- if (!status) {
3093
- spinner.fail('Ralph not initialized');
3094
- console.log(`\n Run: ${chalk.bold('rulebook ralph init')}\n`);
3095
- return;
3096
- }
3097
- await ralphManager.resume();
3098
- spinner.succeed('Ralph loop resumed');
3099
- console.log(`\n Continue loop: ${chalk.bold('rulebook ralph run')}\n`);
3100
- }
3101
- catch (error) {
3102
- spinner.fail('Failed to resume');
3103
- console.error(chalk.red(String(error)));
3104
- process.exit(1);
3105
- }
3106
- }
3107
- export async function setupClaudeCodePlugin() {
3108
- const oraModule = await import('ora');
3109
- const ora = oraModule.default;
3110
- const spinner = ora('Setting up Claude Code plugin...').start();
3111
- try {
3112
- const { readFile } = await import('../utils/file-system.js');
3113
- const os = await import('os');
3114
- const fs = await import('fs/promises');
3115
- const url = await import('url');
3116
- // Get plugin info from .claude-plugin/plugin.json relative to this module
3117
- const packageDir = path.dirname(url.fileURLToPath(import.meta.url));
3118
- const pluginJsonPath = path.join(packageDir, '..', '..', '.claude-plugin', 'plugin.json');
3119
- const pluginJson = JSON.parse(await readFile(pluginJsonPath));
3120
- // Get Claude Code plugins directory
3121
- const homeDir = os.homedir();
3122
- const pluginsDir = path.join(homeDir, '.claude', 'plugins');
3123
- const installedPluginsPath = path.join(pluginsDir, 'installed_plugins.json');
3124
- // Create plugins directory if it doesn't exist
3125
- await fs.mkdir(pluginsDir, { recursive: true });
3126
- // Load or create installed_plugins.json
3127
- let installedPlugins = {
3128
- version: 2,
3129
- plugins: {},
3130
- };
3131
- if (existsSync(installedPluginsPath)) {
3132
- const content = await readFile(installedPluginsPath);
3133
- installedPlugins = JSON.parse(content);
3134
- }
3135
- // Add rulebook plugin — keep exactly ONE entry per plugin key
3136
- const pluginKey = `rulebook@hivehub`;
3137
- const version = pluginJson.version || '3.2.1';
3138
- const installPath = path.join(pluginsDir, 'cache', 'hivehub', 'rulebook', version);
3139
- if (!installedPlugins.plugins[pluginKey]) {
3140
- installedPlugins.plugins[pluginKey] = [];
3141
- }
3142
- const entries = installedPlugins.plugins[pluginKey];
3143
- // Deduplicate: keep only the single latest entry, update it in-place
3144
- if (entries.length > 0) {
3145
- // Preserve original installedAt from the first entry
3146
- const originalInstalledAt = entries[0].installedAt;
3147
- // Replace all entries with a single updated one
3148
- installedPlugins.plugins[pluginKey] = [
3149
- {
3150
- scope: 'user',
3151
- installPath: installPath,
3152
- version: version,
3153
- installedAt: originalInstalledAt || new Date().toISOString(),
3154
- lastUpdated: new Date().toISOString(),
3155
- },
3156
- ];
3157
- }
3158
- else {
3159
- // First install
3160
- entries.push({
3161
- scope: 'user',
3162
- installPath: installPath,
3163
- version: version,
3164
- installedAt: new Date().toISOString(),
3165
- lastUpdated: new Date().toISOString(),
3166
- });
3167
- }
3168
- // Save installed_plugins.json
3169
- await fs.writeFile(installedPluginsPath, JSON.stringify(installedPlugins, null, 2));
3170
- spinner.succeed('Claude Code plugin installed');
3171
- console.log(`\n ${chalk.green('✓')} Plugin: ${pluginKey} v${version}`);
3172
- console.log(` ${chalk.gray('Installed to:')} ${installPath}`);
3173
- console.log(`\n ${chalk.blue('Note:')} Restart Claude Code to load the plugin.\n`);
3174
- }
3175
- catch (error) {
3176
- spinner.fail('Failed to install plugin');
3177
- console.error(chalk.red(String(error)));
3178
- process.exit(1);
3179
- }
3180
- }
3181
- // ─── Plans Commands ────────────────────────────────────────────────────────
3182
- /**
3183
- * Add sequential-thinking MCP server entry to mcp.json (or .cursor/mcp.json).
3184
- * Non-destructive: skips if already present.
3185
- */
3186
- async function addSequentialThinkingMcp(cwd) {
3187
- const { readFileSync, writeFileSync, existsSync } = await import('fs');
3188
- const { mkdirSync } = await import('fs');
3189
- const seqEntry = {
3190
- command: 'npx',
3191
- args: ['-y', '@modelcontextprotocol/server-sequential-thinking'],
3192
- };
3193
- const candidates = [
3194
- path.join(cwd, 'mcp.json'),
3195
- path.join(cwd, '.cursor', 'mcp.json'),
3196
- path.join(cwd, '.mcp.json'),
3197
- ];
3198
- // Find existing mcp.json or default to mcp.json
3199
- let mcpPath = candidates.find((p) => existsSync(p)) ?? path.join(cwd, 'mcp.json');
3200
- let mcpConfig = {};
3201
- if (existsSync(mcpPath)) {
3202
- try {
3203
- mcpConfig = JSON.parse(readFileSync(mcpPath, 'utf8'));
3204
- }
3205
- catch {
3206
- mcpConfig = {};
3207
- }
3208
- }
3209
- mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
3210
- // Already configured under any key variant
3211
- const keys = Object.keys(mcpConfig.mcpServers);
3212
- const alreadyPresent = keys.some((k) => ['sequential-thinking', 'sequential_thinking', 'sequentialThinking'].includes(k));
3213
- if (alreadyPresent)
3214
- return;
3215
- mcpConfig.mcpServers['sequential-thinking'] = seqEntry;
3216
- // Ensure directory exists
3217
- const dir = path.dirname(mcpPath);
3218
- if (!existsSync(dir))
3219
- mkdirSync(dir, { recursive: true });
3220
- writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n');
3221
- }
3222
- /**
3223
- * Show contents of AGENTS.override.md.
3224
- */
3225
- export async function overrideShowCommand() {
3226
- const cwd = process.cwd();
3227
- const { overrideExists, getOverridePath, readOverrideContent } = await import('../core/override-manager.js');
3228
- if (!overrideExists(cwd)) {
3229
- console.log(chalk.yellow('AGENTS.override.md does not exist. Run `rulebook override edit` or `rulebook init` to create it.'));
3230
- return;
3231
- }
3232
- const content = await readOverrideContent(cwd);
3233
- if (!content) {
3234
- console.log(chalk.gray('AGENTS.override.md exists but has no custom content yet.'));
3235
- console.log(chalk.gray(` Path: ${getOverridePath(cwd)}`));
3236
- return;
3237
- }
3238
- console.log(chalk.bold('\n📄 AGENTS.override.md\n'));
3239
- console.log(content);
3240
- console.log();
3241
- }
3242
- /**
3243
- * Open AGENTS.override.md in $EDITOR, or print path if no EDITOR.
3244
- */
3245
- export async function overrideEditCommand() {
3246
- const cwd = process.cwd();
3247
- const { initOverride, getOverridePath } = await import('../core/override-manager.js');
3248
- await initOverride(cwd); // create if missing
3249
- const overridePath = getOverridePath(cwd);
3250
- const editor = process.env.EDITOR || process.env.VISUAL;
3251
- if (editor) {
3252
- const { spawn } = await import('child_process');
3253
- const proc = spawn(editor, [overridePath], { stdio: 'inherit', shell: true });
3254
- await new Promise((resolve) => proc.on('close', () => resolve()));
3255
- }
3256
- else {
3257
- console.log(chalk.gray(`No $EDITOR set. Edit the file directly:`));
3258
- console.log(chalk.cyan(` ${overridePath}`));
3259
- }
3260
- }
3261
- /**
3262
- * Reset AGENTS.override.md to empty template.
3263
- */
3264
- export async function overrideClearCommand() {
3265
- const cwd = process.cwd();
3266
- const { clearOverride } = await import('../core/override-manager.js');
3267
- await clearOverride(cwd);
3268
- console.log(chalk.green('✓ AGENTS.override.md reset to empty template'));
3269
- }
3270
- /**
3271
- * Set the AGENTS.md generation mode (lean or full).
3272
- */
3273
- export async function modeSetCommand(mode) {
3274
- const cwd = process.cwd();
3275
- const { createConfigManager } = await import('../core/config-manager.js');
3276
- const configManager = createConfigManager(cwd);
3277
- const config = await configManager.loadConfig();
3278
- config.agentsMode = mode;
3279
- await configManager.saveConfig(config);
3280
- console.log(chalk.green(`✓ AGENTS.md mode set to: ${chalk.bold(mode)}`));
3281
- if (mode === 'lean') {
3282
- console.log(chalk.gray(' Lean mode: AGENTS.md will be a lightweight index (<3KB).\n' +
3283
- ' Run `rulebook update` to regenerate AGENTS.md.'));
3284
- }
3285
- else {
3286
- console.log(chalk.gray(' Full mode: AGENTS.md will include all rules inline.\n' +
3287
- ' Run `rulebook update` to regenerate AGENTS.md.'));
3288
- }
3289
- }
3290
- /**
3291
- * Show current PLANS.md content.
3292
- */
3293
- export async function plansShowCommand() {
3294
- const { readPlans, getPlansPath } = await import('../core/plans-manager.js');
3295
- const cwd = process.cwd();
3296
- const plans = await readPlans(cwd);
3297
- if (!plans) {
3298
- console.log(chalk.yellow(`No PLANS.md found at ${getPlansPath(cwd)}`));
3299
- console.log(chalk.gray('Run `rulebook plans init` to create one.'));
3300
- return;
3301
- }
3302
- console.log(chalk.bold.blue('\n📋 PLANS.md — Session Scratchpad\n'));
3303
- if (plans.context &&
3304
- plans.context !== '_No active context. Start a session to populate this section._') {
3305
- console.log(chalk.bold('Active Context:'));
3306
- console.log(chalk.white(plans.context));
3307
- }
3308
- if (plans.currentTask && plans.currentTask !== '_No task in progress._') {
3309
- console.log(chalk.bold('\nCurrent Task:'));
3310
- console.log(chalk.cyan(plans.currentTask));
3311
- }
3312
- if (plans.history) {
3313
- console.log(chalk.bold('\nSession History:'));
3314
- console.log(chalk.gray(plans.history));
3315
- }
3316
- console.log('');
3317
- }
3318
- /**
3319
- * Initialize PLANS.md in project root.
3320
- */
3321
- export async function plansInitCommand() {
3322
- const { initPlans, getPlansPath } = await import('../core/plans-manager.js');
3323
- const cwd = process.cwd();
3324
- const created = await initPlans(cwd);
3325
- if (created) {
3326
- console.log(chalk.green(`✓ Created ${getPlansPath(cwd)}`));
3327
- console.log(chalk.gray(' AI agents will use this file for session continuity.'));
3328
- }
3329
- else {
3330
- console.log(chalk.yellow(`PLANS.md already exists at ${getPlansPath(cwd)}`));
3331
- }
3332
- }
3333
- /**
3334
- * Reset PLANS.md to the empty template.
3335
- */
3336
- export async function plansClearCommand() {
3337
- const { clearPlans, getPlansPath } = await import('../core/plans-manager.js');
3338
- const cwd = process.cwd();
3339
- await clearPlans(cwd);
3340
- console.log(chalk.green(`✓ Cleared ${getPlansPath(cwd)}`));
3341
- console.log(chalk.gray(' Session history and context have been reset.'));
3342
- }
3343
- // ─── Continue Command ───────────────────────────────────────────────────────
3344
- /**
3345
- * `rulebook continue` — Print orientations to resume work in a new AI session.
3346
- *
3347
- * Aggregates context from:
3348
- * 1. PLANS.md (session scratchpad)
3349
- * 2. Active rulebook tasks (pending items in tasks.md)
3350
- * 3. Recent git commits (last 5)
3351
- * 4. Ralph status (if running)
3352
- *
3353
- * Outputs a structured prompt that the AI agent can paste at the start of a session.
3354
- */
3355
- export async function continueCommand() {
3356
- const cwd = process.cwd();
3357
- const { readPlans } = await import('../core/plans-manager.js');
3358
- const { exec } = await import('child_process');
3359
- const { promisify } = await import('util');
3360
- const execAsync = promisify(exec);
3361
- const fs = await import('fs/promises');
3362
- console.log(chalk.bold.blue('\n🔄 Generating session continuity context...\n'));
3363
- const sections = [];
3364
- // ── 1. PLANS.md context ──
3365
- const plans = await readPlans(cwd);
3366
- if (plans && (plans.context || plans.currentTask)) {
3367
- const plansParts = ['## Active Plans'];
3368
- if (plans.context && !plans.context.includes('_No active context')) {
3369
- plansParts.push(plans.context);
3370
- }
3371
- if (plans.currentTask && !plans.currentTask.includes('_No task')) {
3372
- plansParts.push(`**Current Task:** ${plans.currentTask}`);
3373
- }
3374
- sections.push(plansParts.join('\n'));
3375
- }
3376
- // ── 2. Active tasks (pending checklist items) ──
3377
- const tasksDir = path.join(cwd, '.rulebook', 'tasks');
3378
- if (existsSync(tasksDir)) {
3379
- const taskSummaries = [];
3380
- try {
3381
- const entries = await fs.readdir(tasksDir, { withFileTypes: true });
3382
- for (const entry of entries) {
3383
- if (!entry.isDirectory() || entry.name === 'archive')
3384
- continue;
3385
- const tasksPath = path.join(tasksDir, entry.name, 'tasks.md');
3386
- if (!existsSync(tasksPath))
3387
- continue;
3388
- const content = await fs.readFile(tasksPath, 'utf-8');
3389
- const pending = content
3390
- .split('\n')
3391
- .filter((l) => l.match(/^- \[ \]/))
3392
- .map((l) => l.replace(/^- \[ \]\s*/, '').trim())
3393
- .slice(0, 3);
3394
- if (pending.length > 0) {
3395
- taskSummaries.push(`**${entry.name}** (${pending.length}+ pending):`);
3396
- pending.forEach((p) => taskSummaries.push(` - ${p}`));
3397
- }
3398
- }
3399
- }
3400
- catch {
3401
- // ignore
3402
- }
3403
- if (taskSummaries.length > 0) {
3404
- sections.push('## Pending Tasks\n' + taskSummaries.join('\n'));
3405
- }
3406
- }
3407
- // ── 3. Recent git commits ──
3408
- try {
3409
- const { stdout } = await execAsync('git log --oneline -8', { cwd });
3410
- if (stdout.trim()) {
3411
- sections.push('## Recent Commits\n```\n' + stdout.trim() + '\n```');
3412
- }
3413
- }
3414
- catch {
3415
- // not a git repo or git not available
3416
- }
3417
- // ── 4. Ralph status ──
3418
- const ralphStatePath = path.join(cwd, '.rulebook', 'ralph', 'state.json');
3419
- if (existsSync(ralphStatePath)) {
3420
- try {
3421
- const state = JSON.parse(await fs.readFile(ralphStatePath, 'utf-8'));
3422
- if (state.enabled) {
3423
- const prdPath = path.join(cwd, '.rulebook', 'ralph', 'prd.json');
3424
- let prdInfo = '';
3425
- if (existsSync(prdPath)) {
3426
- const prd = JSON.parse(await fs.readFile(prdPath, 'utf-8'));
3427
- const pending = (prd.userStories ?? []).filter((s) => !s.passes).length;
3428
- const total = (prd.userStories ?? []).length;
3429
- prdInfo = ` | ${total - pending}/${total} stories complete`;
3430
- }
3431
- sections.push(`## Ralph Status\n` +
3432
- `Iteration ${state.current_iteration}/${state.max_iterations}${prdInfo} | Tool: ${state.tool} | Paused: ${state.paused}`);
3433
- }
3434
- }
3435
- catch {
3436
- // ignore
3437
- }
3438
- }
3439
- // ── 5. Current branch ──
3440
- try {
3441
- const { stdout } = await execAsync('git rev-parse --abbrev-ref HEAD', { cwd });
3442
- const branch = stdout.trim();
3443
- if (branch) {
3444
- sections.unshift(`## Branch\n\`${branch}\``);
3445
- }
3446
- }
3447
- catch {
3448
- // ignore
3449
- }
3450
- // ── Render ──
3451
- if (sections.length === 0) {
3452
- console.log(chalk.yellow('No session context found. Create tasks, a PLANS.md, or make some commits.'));
3453
- return;
3454
- }
3455
- const output = [
3456
- '─'.repeat(60),
3457
- chalk.bold('📋 SESSION CONTINUITY CONTEXT'),
3458
- chalk.gray('Paste this at the start of a new AI session:'),
3459
- '─'.repeat(60),
3460
- '',
3461
- sections.join('\n\n'),
3462
- '',
3463
- '─'.repeat(60),
3464
- ].join('\n');
3465
- console.log(output);
3466
- // Also write to PLANS.md if it exists
3467
- if (plans !== null) {
3468
- const { appendPlansHistory } = await import('../core/plans-manager.js');
3469
- try {
3470
- await appendPlansHistory(cwd, `Session context generated. Branch: current. Pending tasks summarized.`);
3471
- }
3472
- catch {
3473
- // non-critical
3474
- }
3475
- }
3476
- }
3477
- export async function migrateMemoryDirectory() {
3478
- const oraModule = await import('ora');
3479
- const ora = oraModule.default;
3480
- const spinner = ora('Migrating memory directory structure...').start();
3481
- try {
3482
- const { createConfigManager } = await import('../core/config-manager.js');
3483
- const fs = await import('fs');
3484
- const fsPromises = fs.promises;
3485
- const cwd = process.cwd();
3486
- const oldDir = path.join(cwd, '.rulebook-memory');
3487
- const rulebookDir = path.join(cwd, '.rulebook');
3488
- const newDir = path.join(rulebookDir, 'memory');
3489
- // Check if old directory exists
3490
- if (!existsSync(oldDir)) {
3491
- spinner.info('No old memory directory found (.rulebook-memory)');
3492
- return;
3493
- }
3494
- // Create .rulebook directory if it doesn't exist
3495
- if (!existsSync(rulebookDir)) {
3496
- await fsPromises.mkdir(rulebookDir, { recursive: true });
3497
- }
3498
- // Create memory subdirectory
3499
- await fsPromises.mkdir(newDir, { recursive: true });
3500
- // Copy files from old to new
3501
- const files = await fsPromises.readdir(oldDir);
3502
- for (const file of files) {
3503
- const oldPath = path.join(oldDir, file);
3504
- const newPath = path.join(newDir, file);
3505
- await fsPromises.copyFile(oldPath, newPath);
3506
- }
3507
- // Remove old directory
3508
- await fsPromises.rm(oldDir, { recursive: true, force: true });
3509
- // Update config to use new path
3510
- const configManager = createConfigManager(cwd);
3511
- const existingConfig = await configManager.loadConfig();
3512
- if (existingConfig.memory) {
3513
- existingConfig.memory.dbPath = '.rulebook/memory/memory.db';
3514
- }
3515
- await configManager.updateConfig(existingConfig);
3516
- spinner.succeed('Memory directory migrated');
3517
- console.log(`\n ${chalk.green('✓')} Migrated to: ${newDir}`);
3518
- console.log(` ${chalk.gray('Old directory removed: .rulebook-memory')}\n`);
3519
- }
3520
- catch (error) {
3521
- const oraModule = await import('ora');
3522
- const ora = oraModule.default;
3523
- const spinner2 = ora();
3524
- spinner2.fail('Failed to migrate memory directory');
3525
- console.error(chalk.red(String(error)));
3526
- process.exit(1);
3527
- }
3528
- }
3529
- // ─── Review Command ─────────────────────────────────────────────────────────
3530
- /**
3531
- * `rulebook review` — Run AI-powered code review on changes vs a base branch.
3532
- *
3533
- * Retrieves the git diff, builds a structured prompt, sends it to an AI tool,
3534
- * parses the result, and outputs in the requested format.
3535
- */
3536
- export async function reviewCommand(options) {
3537
- const cwd = process.cwd();
3538
- const baseBranch = options.baseBranch ?? 'main';
3539
- const outputFormat = options.output ?? 'terminal';
3540
- const tool = (options.tool ?? 'claude');
3541
- const { getDiffContext, buildReviewPrompt, runAIReview, parseReviewOutput, formatReviewTerminal, postGitHubComment, readAgentsMd, hasFailingIssues, } = await import('../core/review-manager.js');
3542
- // 1. Get diff
3543
- const diff = await getDiffContext(cwd, baseBranch);
3544
- if (!diff) {
3545
- console.log(chalk.yellow(`No changes detected vs ${baseBranch}`));
3546
- return;
3547
- }
3548
- // 2. Read AGENTS.md (optional context)
3549
- const agentsMdContent = await readAgentsMd(cwd);
3550
- // 3. Build prompt
3551
- const projectName = path.basename(cwd);
3552
- const prompt = buildReviewPrompt(diff, { agentsMdContent, projectName });
3553
- // 4. Run AI review
3554
- const spinner = ora('Running AI review...').start();
3555
- const rawOutput = await runAIReview(prompt, tool);
3556
- if (!rawOutput) {
3557
- spinner.fail('AI review returned no output. Is the AI tool installed and configured?');
3558
- process.exit(1);
3559
- }
3560
- spinner.succeed('AI review complete');
3561
- // 5. Parse result
3562
- const result = parseReviewOutput(rawOutput);
3563
- // 6. Output
3564
- switch (outputFormat) {
3565
- case 'terminal':
3566
- console.log(formatReviewTerminal(result));
3567
- break;
3568
- case 'json':
3569
- console.log(JSON.stringify(result, null, 2));
3570
- break;
3571
- case 'github-comment':
3572
- try {
3573
- await postGitHubComment(result);
3574
- console.log(chalk.green('Review posted as PR comment'));
3575
- }
3576
- catch (error) {
3577
- console.error(chalk.red(`Failed to post comment: ${error}`));
3578
- process.exit(1);
3579
- }
3580
- break;
3581
- }
3582
- // 7. Exit code
3583
- if (options.failOn && hasFailingIssues(result.issues, options.failOn)) {
3584
- console.log(chalk.red(`\nFailing: found issues at or above "${options.failOn}" severity`));
3585
- process.exit(1);
3586
- }
3587
- }
3588
- // ─── Ralph Import Issues Command ───
3589
- export async function ralphImportIssuesCommand(options) {
3590
- const oraModule = await import('ora');
3591
- const ora = oraModule.default;
3592
- try {
3593
- const { checkGhCliAvailable, fetchGithubIssues, convertIssueToStory, mergeStoriesIntoExistingPrd, } = await import('../core/github-issues-importer.js');
3594
- // 1. Check gh CLI availability
3595
- const ghAvailable = await checkGhCliAvailable();
3596
- if (!ghAvailable) {
3597
- console.error(chalk.red('GitHub CLI (gh) is not installed. Install it from: https://cli.github.com'));
3598
- return;
3599
- }
3600
- // 2. Fetch issues
3601
- const spinner = ora('Fetching GitHub issues...').start();
3602
- const issues = await fetchGithubIssues({
3603
- label: options.label,
3604
- milestone: options.milestone,
3605
- limit: options.limit ?? 20,
3606
- });
3607
- if (issues.length === 0) {
3608
- spinner.info('No open issues found matching the given filters.');
3609
- return;
3610
- }
3611
- spinner.text = `Converting ${issues.length} issues to Ralph stories...`;
3612
- // 3. Load existing PRD (may not exist yet)
3613
- const cwd = process.cwd();
3614
- let existingPrd = null;
3615
- try {
3616
- const { RalphManager } = await import('../core/ralph-manager.js');
3617
- const { Logger } = await import('../core/logger.js');
3618
- const logger = new Logger(cwd);
3619
- const manager = new RalphManager(cwd, logger);
3620
- existingPrd = await manager.loadPRD();
3621
- }
3622
- catch {
3623
- // PRD not initialized — will create a new one
3624
- }
3625
- // 4. Convert issues to stories
3626
- const newStories = issues.map((issue) => convertIssueToStory(issue));
3627
- // 5. Merge into existing PRD
3628
- const { prd: mergedPrd, result } = mergeStoriesIntoExistingPrd(existingPrd, newStories);
3629
- // 6. Dry run — preview only
3630
- if (options.dryRun) {
3631
- spinner.stop();
3632
- console.log(chalk.yellow(`Dry run — would import ${result.imported} stories, update ${result.updated}, skip ${result.skipped}`));
3633
- console.log('');
3634
- for (const story of mergedPrd.userStories) {
3635
- const marker = story.passes ? chalk.green('[PASS]') : chalk.gray('[ ]');
3636
- console.log(` ${marker} ${story.id}: ${story.title}`);
3637
- }
3638
- return;
3639
- }
3640
- // 7. Save PRD
3641
- const prdPath = path.join(cwd, '.rulebook', 'ralph', 'prd.json');
3642
- await ensureDir(path.join(cwd, '.rulebook', 'ralph'));
3643
- await writeFile(prdPath, JSON.stringify(mergedPrd, null, 2));
3644
- spinner.succeed(`Imported ${result.imported} new stories, updated ${result.updated} existing, ${result.skipped} skipped`);
3645
- console.log(`\n PRD saved to: ${prdPath}`);
3646
- console.log(` Total stories: ${mergedPrd.userStories.length}\n`);
3647
- }
3648
- catch (error) {
3649
- console.error(chalk.red(`Failed to import GitHub issues: ${String(error)}`));
3650
- process.exit(1);
3651
- }
3652
- }
3653
- // ============================================
3654
- // Workspace Commands (v4.2)
3655
- // ============================================
3656
- /** Resolve the workspace config path inside .rulebook/ */
3657
- function getWorkspaceConfigPath(cwd) {
3658
- return path.join(cwd, '.rulebook', 'workspace.json');
3659
- }
3660
- export async function workspaceInitCommand() {
3661
- const cwd = process.cwd();
3662
- const configPath = getWorkspaceConfigPath(cwd);
3663
- if (existsSync(configPath)) {
3664
- console.log(chalk.yellow('Workspace already initialized at .rulebook/workspace.json'));
3665
- return;
3666
- }
3667
- const spinner = ora('Detecting workspace structure...').start();
3668
- // Try auto-discovery first
3669
- let config = WorkspaceManager.findWorkspaceConfig(cwd);
3670
- if (config) {
3671
- spinner.succeed(`Detected workspace: ${config.name} (${config.projects.length} projects)`);
3672
- console.log('\n Projects found:');
3673
- for (const p of config.projects) {
3674
- console.log(` - ${chalk.cyan(p.name)} → ${p.path}`);
3675
- }
3676
- }
3677
- else {
3678
- spinner.info('No workspace structure detected. Creating empty workspace config.');
3679
- config = {
3680
- name: path.basename(cwd),
3681
- version: '1.0.0',
3682
- projects: [],
3683
- };
3684
- }
3685
- // Ensure .rulebook/ directory exists
3686
- const rulebookDir = path.join(cwd, '.rulebook');
3687
- if (!existsSync(rulebookDir)) {
3688
- const { mkdirSync } = await import('fs');
3689
- mkdirSync(rulebookDir, { recursive: true });
3690
- }
3691
- // Write config
3692
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3693
- console.log(chalk.green(`\n Created: .rulebook/workspace.json`));
3694
- // Check for legacy .mcp.json files
3695
- const migration = await migrateLegacyMcpConfigs(cwd);
3696
- if (migration.migratedFiles.length > 0) {
3697
- console.log(chalk.yellow(`\n Migrated ${migration.migratedFiles.length} legacy .mcp.json files (backups at *.mcp.json.bak)`));
3698
- }
3699
- console.log(chalk.dim('\n Use `rulebook workspace add <path>` to add more projects'));
3700
- console.log(chalk.dim(' Use `rulebook mcp init --workspace` to configure MCP for workspace'));
3701
- }
3702
- export async function workspaceAddCommand(projectPath) {
3703
- const cwd = process.cwd();
3704
- const configPath = getWorkspaceConfigPath(cwd);
3705
- if (!existsSync(configPath)) {
3706
- console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
3707
- process.exit(1);
3708
- }
3709
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
3710
- const resolvedPath = path.resolve(cwd, projectPath);
3711
- const name = path.basename(resolvedPath);
3712
- // Check for duplicates
3713
- if (config.projects.some((p) => p.name === name)) {
3714
- console.error(chalk.red(`Project "${name}" already exists in workspace.`));
3715
- process.exit(1);
3716
- }
3717
- // Use relative path if within workspace, absolute otherwise
3718
- const isSubpath = resolvedPath.startsWith(cwd);
3719
- const storedPath = isSubpath ? path.relative(cwd, resolvedPath) : resolvedPath;
3720
- const project = {
3721
- name,
3722
- path: storedPath.startsWith('.') ? storedPath : `./${storedPath}`,
3723
- };
3724
- config.projects.push(project);
3725
- if (!config.defaultProject) {
3726
- config.defaultProject = name;
3727
- }
3728
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3729
- console.log(chalk.green(`Added project "${name}" → ${project.path}`));
3730
- }
3731
- export async function workspaceRemoveCommand(projectName) {
3732
- const cwd = process.cwd();
3733
- const configPath = getWorkspaceConfigPath(cwd);
3734
- if (!existsSync(configPath)) {
3735
- console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
3736
- process.exit(1);
3737
- }
3738
- const config = JSON.parse(readFileSync(configPath, 'utf-8'));
3739
- const idx = config.projects.findIndex((p) => p.name === projectName);
3740
- if (idx === -1) {
3741
- console.error(chalk.red(`Project "${projectName}" not found in workspace.`));
3742
- process.exit(1);
3743
- }
3744
- config.projects.splice(idx, 1);
3745
- if (config.defaultProject === projectName) {
3746
- config.defaultProject = config.projects[0]?.name;
3747
- }
3748
- writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3749
- console.log(chalk.green(`Removed project "${projectName}" from workspace.`));
3750
- }
3751
- export async function workspaceListCommand() {
3752
- const cwd = process.cwd();
3753
- const config = WorkspaceManager.findWorkspaceConfig(cwd);
3754
- if (!config) {
3755
- console.log(chalk.yellow('No workspace found. Run `rulebook workspace init` to create one.'));
3756
- return;
3757
- }
3758
- console.log(chalk.bold(`\nWorkspace: ${config.name}`));
3759
- console.log(chalk.dim(` Version: ${config.version}`));
3760
- if (config.defaultProject) {
3761
- console.log(chalk.dim(` Default: ${config.defaultProject}`));
3762
- }
3763
- console.log();
3764
- for (const p of config.projects) {
3765
- const isDefault = p.name === config.defaultProject;
3766
- const marker = isDefault ? chalk.green(' (default)') : '';
3767
- const disabled = p.enabled === false ? chalk.red(' [disabled]') : '';
3768
- console.log(` ${chalk.cyan(p.name)}${marker}${disabled}`);
3769
- console.log(` ${chalk.dim(p.path)}`);
3770
- }
3771
- console.log(chalk.dim(`\n ${config.projects.length} project(s) total`));
3772
- }
3773
- export async function workspaceStatusCommand() {
3774
- const cwd = process.cwd();
3775
- const config = WorkspaceManager.findWorkspaceConfig(cwd);
3776
- if (!config) {
3777
- console.log(chalk.yellow('No workspace found. Run `rulebook workspace init` to create one.'));
3778
- return;
3779
- }
3780
- const manager = new WorkspaceManager(config, cwd);
3781
- const spinner = ora('Checking workspace status...').start();
3782
- try {
3783
- const status = await manager.getStatus();
3784
- spinner.stop();
3785
- console.log(chalk.bold(`\nWorkspace: ${status.name}`));
3786
- console.log(` Projects: ${status.totalProjects} | Active workers: ${status.activeWorkers}`);
3787
- console.log();
3788
- for (const p of status.projects) {
3789
- const configBadge = p.hasRulebookConfig ? chalk.green('.rulebook') : chalk.dim('no config');
3790
- const memBadge = p.memoryEnabled ? chalk.blue('memory') : '';
3791
- const taskBadge = p.taskCount > 0 ? chalk.yellow(`${p.taskCount} tasks`) : '';
3792
- const badges = [configBadge, memBadge, taskBadge].filter(Boolean).join(' ');
3793
- console.log(` ${chalk.cyan(p.name)} ${badges}`);
3794
- console.log(` ${chalk.dim(p.path)}`);
3795
- }
3796
- console.log();
3797
- }
3798
- catch (error) {
3799
- spinner.fail(`Failed: ${String(error)}`);
3800
- }
3801
- finally {
3802
- await manager.shutdownAll();
3803
- }
3804
- }
3805
- // Context Intelligence Layer commands (v4.4)
3806
- export async function decisionCreateCommand(title, options) {
3807
- const { DecisionManager } = await import('../core/decision-manager.js');
3808
- const mgr = new DecisionManager(process.cwd());
3809
- const spinner = ora('Creating decision...').start();
3810
- try {
3811
- const d = await mgr.create(title, {
3812
- context: options.context,
3813
- relatedTasks: options.relatedTask ? [options.relatedTask] : undefined,
3814
- });
3815
- spinner.succeed(`Decision ADR-${String(d.id).padStart(3, '0')}: ${d.title} (${d.status})`);
3816
- }
3817
- catch (error) {
3818
- spinner.fail(`Failed: ${String(error)}`);
3819
- }
3820
- }
3821
- export async function decisionListCommand(options) {
3822
- const { DecisionManager } = await import('../core/decision-manager.js');
3823
- const mgr = new DecisionManager(process.cwd());
3824
- const decisions = await mgr.list(options.status);
3825
- if (decisions.length === 0) {
3826
- console.log(chalk.dim('No decisions found.'));
3827
- return;
3828
- }
3829
- for (const d of decisions) {
3830
- const badge = d.status === 'accepted'
3831
- ? chalk.green(d.status)
3832
- : d.status === 'superseded'
3833
- ? chalk.dim(d.status)
3834
- : chalk.yellow(d.status);
3835
- console.log(` ADR-${String(d.id).padStart(3, '0')} ${d.title} ${badge}`);
3836
- }
3837
- }
3838
- export async function decisionShowCommand(id) {
3839
- const { DecisionManager } = await import('../core/decision-manager.js');
3840
- const mgr = new DecisionManager(process.cwd());
3841
- const result = await mgr.show(parseInt(id));
3842
- if (!result) {
3843
- console.log(chalk.red(`Decision ${id} not found.`));
3844
- return;
3845
- }
3846
- console.log(result.content);
3847
- }
3848
- export async function decisionSupersedeCommand(oldId, newId) {
3849
- const { DecisionManager } = await import('../core/decision-manager.js');
3850
- const mgr = new DecisionManager(process.cwd());
3851
- const ok = await mgr.supersede(parseInt(oldId), parseInt(newId));
3852
- if (ok) {
3853
- console.log(chalk.green(`Decision ${oldId} superseded by ${newId}.`));
3854
- }
3855
- else {
3856
- console.log(chalk.red(`Decision ${oldId} not found.`));
3857
- }
3858
- }
3859
- export async function knowledgeAddCommand(type, title, options) {
3860
- const { KnowledgeManager } = await import('../core/knowledge-manager.js');
3861
- const mgr = new KnowledgeManager(process.cwd());
3862
- const spinner = ora('Adding knowledge entry...').start();
3863
- try {
3864
- const entry = await mgr.add(type, title, {
3865
- category: (options.category ?? 'code'),
3866
- description: options.description ?? '',
3867
- });
3868
- spinner.succeed(`${entry.type}: ${entry.title} (${entry.category})`);
3869
- }
3870
- catch (error) {
3871
- spinner.fail(`Failed: ${String(error)}`);
3872
- }
3873
- }
3874
- export async function knowledgeListCommand(options) {
3875
- const { KnowledgeManager } = await import('../core/knowledge-manager.js');
3876
- const mgr = new KnowledgeManager(process.cwd());
3877
- const entries = await mgr.list(options.type, options.category);
3878
- if (entries.length === 0) {
3879
- console.log(chalk.dim('No knowledge entries found.'));
3880
- return;
3881
- }
3882
- for (const e of entries) {
3883
- const badge = e.type === 'pattern' ? chalk.green('pattern') : chalk.red('anti-pattern');
3884
- console.log(` ${badge} ${e.title} ${chalk.dim(e.category)}`);
3885
- }
3886
- }
3887
- export async function knowledgeShowCommand(id) {
3888
- const { KnowledgeManager } = await import('../core/knowledge-manager.js');
3889
- const mgr = new KnowledgeManager(process.cwd());
3890
- const result = await mgr.show(id);
3891
- if (!result) {
3892
- console.log(chalk.red(`Knowledge entry "${id}" not found.`));
3893
- return;
3894
- }
3895
- console.log(result.content);
3896
- }
3897
- export async function knowledgeRemoveCommand(id) {
3898
- const { KnowledgeManager } = await import('../core/knowledge-manager.js');
3899
- const mgr = new KnowledgeManager(process.cwd());
3900
- const ok = await mgr.remove(id);
3901
- if (ok) {
3902
- console.log(chalk.green(`Knowledge entry "${id}" removed.`));
3903
- }
3904
- else {
3905
- console.log(chalk.red(`Knowledge entry "${id}" not found.`));
3906
- }
3907
- }
3908
- export async function learnCaptureCommand(options) {
3909
- const { LearnManager } = await import('../core/learn-manager.js');
3910
- const mgr = new LearnManager(process.cwd());
3911
- // If no title/content provided, this would be interactive (placeholder for prompts)
3912
- const title = options.title ?? 'Untitled learning';
3913
- const content = options.content ?? '';
3914
- const tags = options.tags ? options.tags.split(',').map((t) => t.trim()) : [];
3915
- const spinner = ora('Capturing learning...').start();
3916
- try {
3917
- const learning = await mgr.capture(title, content, {
3918
- source: 'manual',
3919
- relatedTask: options.relatedTask,
3920
- tags,
3921
- });
3922
- spinner.succeed(`Learning captured: ${learning.title}`);
3923
- }
3924
- catch (error) {
3925
- spinner.fail(`Failed: ${String(error)}`);
3926
- }
3927
- }
3928
- export async function learnFromRalphCommand() {
3929
- const { LearnManager } = await import('../core/learn-manager.js');
3930
- const mgr = new LearnManager(process.cwd());
3931
- const spinner = ora('Extracting learnings from Ralph history...').start();
3932
- try {
3933
- const learnings = await mgr.fromRalph();
3934
- if (learnings.length === 0) {
3935
- spinner.info('No new learnings found in Ralph history.');
3936
- }
3937
- else {
3938
- spinner.succeed(`Extracted ${learnings.length} learning(s) from Ralph history.`);
3939
- }
3940
- }
3941
- catch (error) {
3942
- spinner.fail(`Failed: ${String(error)}`);
3943
- }
3944
- }
3945
- export async function learnListCommand(options) {
3946
- const { LearnManager } = await import('../core/learn-manager.js');
3947
- const mgr = new LearnManager(process.cwd());
3948
- const limit = options.limit ? parseInt(options.limit) : undefined;
3949
- const learnings = await mgr.list(limit);
3950
- if (learnings.length === 0) {
3951
- console.log(chalk.dim('No learnings found.'));
3952
- return;
3953
- }
3954
- for (const l of learnings) {
3955
- const badge = l.source === 'ralph'
3956
- ? chalk.blue('ralph')
3957
- : l.source === 'task-archive'
3958
- ? chalk.yellow('archive')
3959
- : chalk.dim('manual');
3960
- const promoted = l.promotedTo ? chalk.green(` → ${l.promotedTo.type}`) : '';
3961
- console.log(` ${badge} ${l.title}${promoted}`);
3962
- }
3963
- }
3964
- export async function learnPromoteCommand(id, target, options) {
3965
- const { LearnManager } = await import('../core/learn-manager.js');
3966
- const mgr = new LearnManager(process.cwd());
3967
- if (target !== 'knowledge' && target !== 'decision') {
3968
- console.log(chalk.red('Target must be "knowledge" or "decision".'));
3969
- return;
3970
- }
3971
- const spinner = ora(`Promoting learning to ${target}...`).start();
3972
- try {
3973
- const result = await mgr.promote(id, target, { title: options.title });
3974
- if (!result) {
3975
- spinner.fail(`Learning "${id}" not found.`);
3976
- return;
3977
- }
3978
- spinner.succeed(`Learning promoted to ${result.type} (id: ${result.id}).`);
3979
- }
3980
- catch (error) {
3981
- spinner.fail(`Failed: ${String(error)}`);
3982
- }
3983
- }
3984
- //# sourceMappingURL=commands.js.map