@hivehub/rulebook 4.1.0 → 4.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/.claude/commands/continue.md +33 -33
  2. package/.claude/commands/ralph-config.md +112 -112
  3. package/.claude/commands/ralph-history.md +110 -110
  4. package/.claude/commands/ralph-init.md +72 -72
  5. package/.claude/commands/ralph-pause-resume.md +105 -105
  6. package/.claude/commands/ralph-run.md +101 -101
  7. package/.claude/commands/ralph-status.md +76 -76
  8. package/.claude/commands/rulebook-memory-save.md +48 -48
  9. package/.claude/commands/rulebook-memory-search.md +47 -47
  10. package/.claude/commands/rulebook-task-apply.md +67 -67
  11. package/.claude/commands/rulebook-task-archive.md +70 -70
  12. package/.claude/commands/rulebook-task-create.md +93 -93
  13. package/.claude/commands/rulebook-task-list.md +42 -42
  14. package/.claude/commands/rulebook-task-show.md +52 -52
  15. package/.claude/commands/rulebook-task-validate.md +53 -53
  16. package/.claude-plugin/marketplace.json +28 -28
  17. package/.claude-plugin/plugin.json +8 -8
  18. package/README.md +137 -1
  19. package/dist/cli/commands.d.ts +18 -6
  20. package/dist/cli/commands.d.ts.map +1 -1
  21. package/dist/cli/commands.js +727 -406
  22. package/dist/cli/commands.js.map +1 -1
  23. package/dist/core/claude-mcp.d.ts +4 -2
  24. package/dist/core/claude-mcp.d.ts.map +1 -1
  25. package/dist/core/claude-mcp.js +14 -9
  26. package/dist/core/claude-mcp.js.map +1 -1
  27. package/dist/core/generator.d.ts.map +1 -1
  28. package/dist/core/generator.js +13 -0
  29. package/dist/core/generator.js.map +1 -1
  30. package/dist/core/indexer/background-indexer.d.ts.map +1 -1
  31. package/dist/core/indexer/background-indexer.js +26 -5
  32. package/dist/core/indexer/background-indexer.js.map +1 -1
  33. package/dist/core/indexer/file-parser.d.ts.map +1 -1
  34. package/dist/core/indexer/file-parser.js +1 -1
  35. package/dist/core/indexer/file-parser.js.map +1 -1
  36. package/dist/core/indexer/indexer-types.d.ts.map +1 -1
  37. package/dist/core/workspace/legacy-migrator.d.ts +29 -0
  38. package/dist/core/workspace/legacy-migrator.d.ts.map +1 -0
  39. package/dist/core/workspace/legacy-migrator.js +142 -0
  40. package/dist/core/workspace/legacy-migrator.js.map +1 -0
  41. package/dist/core/workspace/project-worker.d.ts +49 -0
  42. package/dist/core/workspace/project-worker.d.ts.map +1 -0
  43. package/dist/core/workspace/project-worker.js +108 -0
  44. package/dist/core/workspace/project-worker.js.map +1 -0
  45. package/dist/core/workspace/workspace-manager.d.ts +90 -0
  46. package/dist/core/workspace/workspace-manager.d.ts.map +1 -0
  47. package/dist/core/workspace/workspace-manager.js +347 -0
  48. package/dist/core/workspace/workspace-manager.js.map +1 -0
  49. package/dist/core/workspace/workspace-types.d.ts +37 -0
  50. package/dist/core/workspace/workspace-types.d.ts.map +1 -0
  51. package/dist/core/workspace/workspace-types.js +8 -0
  52. package/dist/core/workspace/workspace-types.js.map +1 -0
  53. package/dist/index.js +43 -7
  54. package/dist/index.js.map +1 -1
  55. package/dist/mcp/rulebook-server.d.ts.map +1 -1
  56. package/dist/mcp/rulebook-server.js +367 -100
  57. package/dist/mcp/rulebook-server.js.map +1 -1
  58. package/dist/memory/memory-manager.js +2 -2
  59. package/dist/memory/memory-manager.js.map +1 -1
  60. package/dist/memory/memory-search.js.map +1 -1
  61. package/dist/memory/memory-store.d.ts.map +1 -1
  62. package/dist/memory/memory-store.js +1 -1
  63. package/dist/memory/memory-store.js.map +1 -1
  64. package/dist/types.d.ts +1 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +22 -21
  67. package/templates/agents/implementer.md +35 -35
  68. package/templates/agents/researcher.md +34 -34
  69. package/templates/agents/team-lead.md +34 -34
  70. package/templates/agents/tester.md +42 -42
  71. package/templates/ci/rulebook-review.yml +26 -26
  72. package/templates/cli/AIDER.md +49 -49
  73. package/templates/cli/AMAZON_Q.md +25 -25
  74. package/templates/cli/AUGGIE.md +32 -32
  75. package/templates/cli/CLAUDE.md +117 -117
  76. package/templates/cli/CLINE.md +99 -99
  77. package/templates/cli/CODEBUDDY.md +20 -20
  78. package/templates/cli/CODEIUM.md +20 -20
  79. package/templates/cli/CODEX.md +21 -21
  80. package/templates/cli/CONTINUE.md +34 -34
  81. package/templates/cli/CURSOR_CLI.md +62 -62
  82. package/templates/cli/FACTORY.md +18 -18
  83. package/templates/cli/GEMINI.md +35 -35
  84. package/templates/cli/KILOCODE.md +18 -18
  85. package/templates/cli/OPENCODE.md +18 -18
  86. package/templates/cli/_GENERIC_TEMPLATE.md +29 -29
  87. package/templates/commands/rulebook-memory-save.md +48 -48
  88. package/templates/commands/rulebook-memory-search.md +47 -47
  89. package/templates/commands/rulebook-task-apply.md +67 -67
  90. package/templates/commands/rulebook-task-archive.md +94 -94
  91. package/templates/commands/rulebook-task-create.md +93 -93
  92. package/templates/commands/rulebook-task-list.md +42 -42
  93. package/templates/commands/rulebook-task-show.md +52 -52
  94. package/templates/commands/rulebook-task-validate.md +53 -53
  95. package/templates/core/AGENTS_LEAN.md +25 -25
  96. package/templates/core/AGENTS_OVERRIDE.md +16 -16
  97. package/templates/core/AGENT_AUTOMATION.md +288 -288
  98. package/templates/core/DAG.md +304 -304
  99. package/templates/core/DOCUMENTATION_RULES.md +36 -36
  100. package/templates/core/MULTI_AGENT.md +74 -74
  101. package/templates/core/PLANS.md +28 -28
  102. package/templates/core/QUALITY_ENFORCEMENT.md +68 -68
  103. package/templates/core/RALPH.md +471 -471
  104. package/templates/core/RULEBOOK.md +1935 -1935
  105. package/templates/core/WORKSPACE.md +69 -0
  106. package/templates/frameworks/ANGULAR.md +36 -36
  107. package/templates/frameworks/DJANGO.md +83 -83
  108. package/templates/frameworks/ELECTRON.md +147 -147
  109. package/templates/frameworks/FLASK.md +38 -38
  110. package/templates/frameworks/FLUTTER.md +55 -55
  111. package/templates/frameworks/JQUERY.md +32 -32
  112. package/templates/frameworks/LARAVEL.md +38 -38
  113. package/templates/frameworks/NESTJS.md +43 -43
  114. package/templates/frameworks/NEXTJS.md +127 -127
  115. package/templates/frameworks/NUXT.md +40 -40
  116. package/templates/frameworks/RAILS.md +66 -66
  117. package/templates/frameworks/REACT.md +38 -38
  118. package/templates/frameworks/REACT_NATIVE.md +47 -47
  119. package/templates/frameworks/SPRING.md +39 -39
  120. package/templates/frameworks/SYMFONY.md +36 -36
  121. package/templates/frameworks/VUE.md +36 -36
  122. package/templates/frameworks/ZEND.md +35 -35
  123. package/templates/git/CI_CD_PATTERNS.md +661 -661
  124. package/templates/git/GITHUB_ACTIONS.md +728 -728
  125. package/templates/git/GITLAB_CI.md +730 -730
  126. package/templates/git/GIT_WORKFLOW.md +1157 -1157
  127. package/templates/git/SECRETS_MANAGEMENT.md +585 -585
  128. package/templates/hooks/COMMIT_MSG.md +530 -530
  129. package/templates/hooks/POST_CHECKOUT.md +546 -546
  130. package/templates/hooks/PREPARE_COMMIT_MSG.md +619 -619
  131. package/templates/hooks/PRE_COMMIT.md +414 -414
  132. package/templates/hooks/PRE_PUSH.md +601 -601
  133. package/templates/ides/CONTINUE_RULES.md +16 -16
  134. package/templates/ides/COPILOT.md +37 -37
  135. package/templates/ides/COPILOT_INSTRUCTIONS.md +23 -23
  136. package/templates/ides/CURSOR.md +43 -43
  137. package/templates/ides/GEMINI_RULES.md +17 -17
  138. package/templates/ides/JETBRAINS_AI.md +35 -35
  139. package/templates/ides/REPLIT.md +36 -36
  140. package/templates/ides/TABNINE.md +29 -29
  141. package/templates/ides/VSCODE.md +40 -40
  142. package/templates/ides/WINDSURF.md +36 -36
  143. package/templates/ides/WINDSURF_RULES.md +14 -14
  144. package/templates/ides/ZED.md +32 -32
  145. package/templates/ides/cursor-mdc/go.mdc +24 -24
  146. package/templates/ides/cursor-mdc/python.mdc +24 -24
  147. package/templates/ides/cursor-mdc/quality.mdc +25 -25
  148. package/templates/ides/cursor-mdc/ralph.mdc +39 -39
  149. package/templates/ides/cursor-mdc/rulebook.mdc +38 -38
  150. package/templates/ides/cursor-mdc/rust.mdc +24 -24
  151. package/templates/ides/cursor-mdc/typescript.mdc +25 -25
  152. package/templates/languages/C.md +333 -333
  153. package/templates/languages/CPP.md +743 -743
  154. package/templates/languages/CSHARP.md +417 -417
  155. package/templates/languages/ELIXIR.md +454 -454
  156. package/templates/languages/ERLANG.md +361 -361
  157. package/templates/languages/GO.md +645 -645
  158. package/templates/languages/HASKELL.md +177 -177
  159. package/templates/languages/JAVA.md +607 -607
  160. package/templates/languages/JAVASCRIPT.md +631 -631
  161. package/templates/languages/JULIA.md +97 -97
  162. package/templates/languages/KOTLIN.md +511 -511
  163. package/templates/languages/LISP.md +100 -100
  164. package/templates/languages/LUA.md +74 -74
  165. package/templates/languages/OBJECTIVEC.md +90 -90
  166. package/templates/languages/PHP.md +416 -416
  167. package/templates/languages/PYTHON.md +682 -682
  168. package/templates/languages/RUBY.md +421 -421
  169. package/templates/languages/RUST.md +477 -477
  170. package/templates/languages/SAS.md +73 -73
  171. package/templates/languages/SCALA.md +348 -348
  172. package/templates/languages/SOLIDITY.md +580 -580
  173. package/templates/languages/SQL.md +137 -137
  174. package/templates/languages/SWIFT.md +466 -466
  175. package/templates/languages/TYPESCRIPT.md +591 -591
  176. package/templates/languages/ZIG.md +265 -265
  177. package/templates/modules/ATLASSIAN.md +255 -255
  178. package/templates/modules/CONTEXT7.md +54 -54
  179. package/templates/modules/FIGMA.md +267 -267
  180. package/templates/modules/GITHUB_MCP.md +64 -64
  181. package/templates/modules/GRAFANA.md +328 -328
  182. package/templates/modules/MEMORY.md +126 -126
  183. package/templates/modules/NOTION.md +247 -247
  184. package/templates/modules/PLAYWRIGHT.md +90 -90
  185. package/templates/modules/RULEBOOK_MCP.md +156 -156
  186. package/templates/modules/SERENA.md +337 -337
  187. package/templates/modules/SUPABASE.md +223 -223
  188. package/templates/modules/SYNAP.md +69 -69
  189. package/templates/modules/VECTORIZER.md +63 -63
  190. package/templates/modules/sequential-thinking.md +42 -42
  191. package/templates/ralph/ralph-history.bat +4 -4
  192. package/templates/ralph/ralph-history.sh +5 -5
  193. package/templates/ralph/ralph-init.bat +5 -5
  194. package/templates/ralph/ralph-init.sh +5 -5
  195. package/templates/ralph/ralph-pause.bat +5 -5
  196. package/templates/ralph/ralph-pause.sh +5 -5
  197. package/templates/ralph/ralph-run.bat +5 -5
  198. package/templates/ralph/ralph-run.sh +5 -5
  199. package/templates/ralph/ralph-status.bat +4 -4
  200. package/templates/ralph/ralph-status.sh +5 -5
  201. package/templates/services/AZURE_BLOB.md +184 -184
  202. package/templates/services/CASSANDRA.md +239 -239
  203. package/templates/services/DATADOG.md +26 -26
  204. package/templates/services/DOCKER.md +124 -124
  205. package/templates/services/DOCKER_COMPOSE.md +168 -168
  206. package/templates/services/DYNAMODB.md +308 -308
  207. package/templates/services/ELASTICSEARCH.md +347 -347
  208. package/templates/services/GCS.md +178 -178
  209. package/templates/services/HELM.md +194 -194
  210. package/templates/services/INFLUXDB.md +265 -265
  211. package/templates/services/KAFKA.md +341 -341
  212. package/templates/services/KUBERNETES.md +208 -208
  213. package/templates/services/MARIADB.md +183 -183
  214. package/templates/services/MEMCACHED.md +242 -242
  215. package/templates/services/MINIO.md +201 -201
  216. package/templates/services/MONGODB.md +268 -268
  217. package/templates/services/MYSQL.md +358 -358
  218. package/templates/services/NEO4J.md +247 -247
  219. package/templates/services/OPENTELEMETRY.md +25 -25
  220. package/templates/services/ORACLE.md +290 -290
  221. package/templates/services/PINO.md +24 -24
  222. package/templates/services/POSTGRESQL.md +326 -326
  223. package/templates/services/PROMETHEUS.md +33 -33
  224. package/templates/services/RABBITMQ.md +286 -286
  225. package/templates/services/REDIS.md +292 -292
  226. package/templates/services/S3.md +298 -298
  227. package/templates/services/SENTRY.md +23 -23
  228. package/templates/services/SQLITE.md +294 -294
  229. package/templates/services/SQLSERVER.md +294 -294
  230. package/templates/services/WINSTON.md +30 -30
  231. package/templates/skills/cli/aider/SKILL.md +59 -59
  232. package/templates/skills/cli/amazon-q/SKILL.md +35 -35
  233. package/templates/skills/cli/auggie/SKILL.md +42 -42
  234. package/templates/skills/cli/claude/SKILL.md +42 -42
  235. package/templates/skills/cli/cline/SKILL.md +42 -42
  236. package/templates/skills/cli/codebuddy/SKILL.md +30 -30
  237. package/templates/skills/cli/codeium/SKILL.md +30 -30
  238. package/templates/skills/cli/codex/SKILL.md +31 -31
  239. package/templates/skills/cli/continue/SKILL.md +44 -44
  240. package/templates/skills/cli/cursor-cli/SKILL.md +38 -38
  241. package/templates/skills/cli/factory/SKILL.md +28 -28
  242. package/templates/skills/cli/gemini/SKILL.md +45 -45
  243. package/templates/skills/cli/kilocode/SKILL.md +28 -28
  244. package/templates/skills/cli/opencode/SKILL.md +28 -28
  245. package/templates/skills/core/agent-automation/SKILL.md +194 -194
  246. package/templates/skills/core/dag/SKILL.md +314 -314
  247. package/templates/skills/core/documentation-rules/SKILL.md +46 -46
  248. package/templates/skills/core/quality-enforcement/SKILL.md +78 -78
  249. package/templates/skills/core/rulebook/SKILL.md +176 -176
  250. package/templates/skills/frameworks/angular/SKILL.md +46 -46
  251. package/templates/skills/frameworks/django/SKILL.md +93 -93
  252. package/templates/skills/frameworks/electron/SKILL.md +157 -157
  253. package/templates/skills/frameworks/flask/SKILL.md +48 -48
  254. package/templates/skills/frameworks/flutter/SKILL.md +65 -65
  255. package/templates/skills/frameworks/jquery/SKILL.md +42 -42
  256. package/templates/skills/frameworks/laravel/SKILL.md +48 -48
  257. package/templates/skills/frameworks/nestjs/SKILL.md +53 -53
  258. package/templates/skills/frameworks/nextjs/SKILL.md +137 -137
  259. package/templates/skills/frameworks/nuxt/SKILL.md +50 -50
  260. package/templates/skills/frameworks/rails/SKILL.md +76 -76
  261. package/templates/skills/frameworks/react/SKILL.md +48 -48
  262. package/templates/skills/frameworks/react-native/SKILL.md +57 -57
  263. package/templates/skills/frameworks/spring/SKILL.md +49 -49
  264. package/templates/skills/frameworks/symfony/SKILL.md +46 -46
  265. package/templates/skills/frameworks/vue/SKILL.md +46 -46
  266. package/templates/skills/frameworks/zend/SKILL.md +45 -45
  267. package/templates/skills/ides/copilot/SKILL.md +47 -47
  268. package/templates/skills/ides/cursor/SKILL.md +53 -53
  269. package/templates/skills/ides/jetbrains-ai/SKILL.md +45 -45
  270. package/templates/skills/ides/replit/SKILL.md +46 -46
  271. package/templates/skills/ides/tabnine/SKILL.md +39 -39
  272. package/templates/skills/ides/vscode/SKILL.md +50 -50
  273. package/templates/skills/ides/windsurf/SKILL.md +46 -46
  274. package/templates/skills/ides/zed/SKILL.md +42 -42
  275. package/templates/skills/languages/c/SKILL.md +343 -343
  276. package/templates/skills/languages/cpp/SKILL.md +753 -753
  277. package/templates/skills/languages/csharp/SKILL.md +427 -427
  278. package/templates/skills/languages/elixir/SKILL.md +464 -464
  279. package/templates/skills/languages/erlang/SKILL.md +371 -371
  280. package/templates/skills/languages/go/SKILL.md +655 -655
  281. package/templates/skills/languages/haskell/SKILL.md +187 -187
  282. package/templates/skills/languages/java/SKILL.md +617 -617
  283. package/templates/skills/languages/javascript/SKILL.md +641 -641
  284. package/templates/skills/languages/julia/SKILL.md +107 -107
  285. package/templates/skills/languages/kotlin/SKILL.md +521 -521
  286. package/templates/skills/languages/lisp/SKILL.md +110 -110
  287. package/templates/skills/languages/lua/SKILL.md +84 -84
  288. package/templates/skills/languages/objectivec/SKILL.md +100 -100
  289. package/templates/skills/languages/php/SKILL.md +426 -426
  290. package/templates/skills/languages/python/SKILL.md +692 -692
  291. package/templates/skills/languages/ruby/SKILL.md +431 -431
  292. package/templates/skills/languages/rust/SKILL.md +487 -487
  293. package/templates/skills/languages/sas/SKILL.md +83 -83
  294. package/templates/skills/languages/scala/SKILL.md +358 -358
  295. package/templates/skills/languages/solidity/SKILL.md +590 -590
  296. package/templates/skills/languages/sql/SKILL.md +147 -147
  297. package/templates/skills/languages/swift/SKILL.md +476 -476
  298. package/templates/skills/languages/typescript/SKILL.md +302 -302
  299. package/templates/skills/languages/zig/SKILL.md +275 -275
  300. package/templates/skills/modules/atlassian/SKILL.md +265 -265
  301. package/templates/skills/modules/context7/SKILL.md +64 -64
  302. package/templates/skills/modules/figma/SKILL.md +277 -277
  303. package/templates/skills/modules/github-mcp/SKILL.md +74 -74
  304. package/templates/skills/modules/grafana/SKILL.md +338 -338
  305. package/templates/skills/modules/memory/SKILL.md +73 -73
  306. package/templates/skills/modules/notion/SKILL.md +257 -257
  307. package/templates/skills/modules/playwright/SKILL.md +100 -100
  308. package/templates/skills/modules/rulebook-mcp/SKILL.md +166 -166
  309. package/templates/skills/modules/serena/SKILL.md +347 -347
  310. package/templates/skills/modules/supabase/SKILL.md +233 -233
  311. package/templates/skills/modules/synap/SKILL.md +79 -79
  312. package/templates/skills/modules/vectorizer/SKILL.md +73 -73
  313. package/templates/skills/services/azure-blob/SKILL.md +194 -194
  314. package/templates/skills/services/cassandra/SKILL.md +249 -249
  315. package/templates/skills/services/dynamodb/SKILL.md +318 -318
  316. package/templates/skills/services/elasticsearch/SKILL.md +357 -357
  317. package/templates/skills/services/gcs/SKILL.md +188 -188
  318. package/templates/skills/services/influxdb/SKILL.md +275 -275
  319. package/templates/skills/services/kafka/SKILL.md +351 -351
  320. package/templates/skills/services/mariadb/SKILL.md +193 -193
  321. package/templates/skills/services/memcached/SKILL.md +252 -252
  322. package/templates/skills/services/minio/SKILL.md +211 -211
  323. package/templates/skills/services/mongodb/SKILL.md +278 -278
  324. package/templates/skills/services/mysql/SKILL.md +368 -368
  325. package/templates/skills/services/neo4j/SKILL.md +257 -257
  326. package/templates/skills/services/oracle/SKILL.md +300 -300
  327. package/templates/skills/services/postgresql/SKILL.md +336 -336
  328. package/templates/skills/services/rabbitmq/SKILL.md +296 -296
  329. package/templates/skills/services/redis/SKILL.md +302 -302
  330. package/templates/skills/services/s3/SKILL.md +308 -308
  331. package/templates/skills/services/sqlite/SKILL.md +304 -304
  332. package/templates/skills/services/sqlserver/SKILL.md +304 -304
  333. package/templates/skills/workflows/ralph/SKILL.md +309 -309
  334. package/templates/skills/workflows/ralph/install.sh +87 -87
  335. package/templates/skills/workflows/ralph/manifest.json +158 -158
@@ -10,9 +10,11 @@ import { parseRulesIgnore } from '../utils/rulesignore.js';
10
10
  import { installGitHooks } from '../utils/git-hooks.js';
11
11
  import { scaffoldMinimalProject } from '../core/minimal-scaffolder.js';
12
12
  import path from 'path';
13
- import { readFileSync } from 'fs';
13
+ import { readFileSync, writeFileSync } from 'fs';
14
14
  import { fileURLToPath } from 'url';
15
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';
16
18
  const FRAMEWORK_LABELS = {
17
19
  nestjs: 'NestJS',
18
20
  spring: 'Spring Boot',
@@ -900,19 +902,68 @@ export async function configCommand(options) {
900
902
  process.exit(1);
901
903
  }
902
904
  }
905
+ /**
906
+ * Resolve a TaskManager for a specific project.
907
+ *
908
+ * Resolution order:
909
+ * 1. Explicit --project flag → find workspace config from cwd, route to named project
910
+ * 2. Auto-detect → walk up from cwd to find workspace, match cwd to a project
911
+ * 3. Fallback → single-project from cwd (no workspace)
912
+ */
913
+ async function resolveTaskManager(cwd, options) {
914
+ const { createTaskManager } = await import('../core/task-manager.js');
915
+ const { createConfigManager } = await import('../core/config-manager.js');
916
+ const { isAbsolute, resolve } = await import('path');
917
+ // Helper: build TaskManager from a resolved project root
918
+ const buildFromProjectRoot = async (projectRoot, label) => {
919
+ const configManager = createConfigManager(projectRoot);
920
+ const config = await configManager.loadConfig();
921
+ const rulebookDir = config.rulebookDir || '.rulebook';
922
+ return { taskManager: createTaskManager(projectRoot, rulebookDir), projectLabel: label };
923
+ };
924
+ // 1. Explicit --project flag
925
+ if (options?.project) {
926
+ const ws = WorkspaceManager.findWorkspaceFromCwd(cwd);
927
+ if (!ws) {
928
+ console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
929
+ process.exit(1);
930
+ }
931
+ const project = ws.config.projects.find((p) => p.name === options.project);
932
+ if (!project) {
933
+ console.error(chalk.red(`Project "${options.project}" not found in workspace.`));
934
+ console.error(chalk.gray(`Available: ${ws.config.projects.map((p) => p.name).join(', ')}`));
935
+ process.exit(1);
936
+ }
937
+ const projectRoot = isAbsolute(project.path)
938
+ ? project.path
939
+ : resolve(ws.root, project.path);
940
+ return buildFromProjectRoot(projectRoot, project.name);
941
+ }
942
+ // 2. Auto-detect: walk up from cwd to find workspace and match project
943
+ const resolved = WorkspaceManager.resolveProjectFromCwd(cwd);
944
+ if (resolved) {
945
+ const project = resolved.config.projects.find((p) => p.name === resolved.projectName);
946
+ if (project) {
947
+ const projectRoot = isAbsolute(project.path)
948
+ ? project.path
949
+ : resolve(resolved.root, project.path);
950
+ return buildFromProjectRoot(projectRoot, project.name);
951
+ }
952
+ }
953
+ // 3. Fallback: single-project from cwd
954
+ const configManager = createConfigManager(cwd);
955
+ const config = await configManager.loadConfig();
956
+ const rulebookDir = config.rulebookDir || '.rulebook';
957
+ return { taskManager: createTaskManager(cwd, rulebookDir) };
958
+ }
903
959
  // Task management commands using Rulebook task system
904
- export async function taskCreateCommand(taskId) {
960
+ export async function taskCreateCommand(taskId, wsOptions) {
905
961
  try {
906
962
  const cwd = process.cwd();
907
- const { createTaskManager } = await import('../core/task-manager.js');
908
- const { createConfigManager } = await import('../core/config-manager.js');
909
- const configManager = createConfigManager(cwd);
910
- const config = await configManager.loadConfig();
911
- const rulebookDir = config.rulebookDir || '.rulebook';
912
- const taskManager = createTaskManager(cwd, rulebookDir);
963
+ const { taskManager, projectLabel } = await resolveTaskManager(cwd, wsOptions);
913
964
  await taskManager.createTask(taskId);
914
- console.log(chalk.green(`✅ Task ${taskId} created successfully`));
915
- console.log(chalk.gray(`Location: ${rulebookDir}/tasks/${taskId}/`));
965
+ const prefix = projectLabel ? `[${projectLabel}] ` : '';
966
+ console.log(chalk.green(`✅ ${prefix}Task ${taskId} created successfully`));
916
967
  console.log(chalk.yellow('\n⚠️ Remember to:'));
917
968
  console.log(chalk.gray(' 1. Fill in proposal.md (minimum 20 characters in "Why" section)'));
918
969
  console.log(chalk.gray(' 3. Add tasks to tasks.md'));
@@ -924,21 +975,66 @@ export async function taskCreateCommand(taskId) {
924
975
  process.exit(1);
925
976
  }
926
977
  }
927
- export async function taskListCommand(includeArchived = false) {
978
+ export async function taskListCommand(includeArchived = false, wsOptions) {
928
979
  try {
929
980
  const cwd = process.cwd();
930
- const { createTaskManager } = await import('../core/task-manager.js');
931
- const { createConfigManager } = await import('../core/config-manager.js');
932
- const configManager = createConfigManager(cwd);
933
- const config = await configManager.loadConfig();
934
- const rulebookDir = config.rulebookDir || '.rulebook';
935
- const taskManager = createTaskManager(cwd, rulebookDir);
981
+ // --all-projects: list tasks from every workspace project
982
+ if (wsOptions?.allProjects) {
983
+ const ws = WorkspaceManager.findWorkspaceFromCwd(cwd);
984
+ if (!ws) {
985
+ console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
986
+ process.exit(1);
987
+ return;
988
+ }
989
+ console.log(chalk.bold.blue(`\n📋 Workspace Tasks (${ws.config.name})\n`));
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
+ let totalTasks = 0;
994
+ for (const project of ws.config.projects) {
995
+ const projectRoot = isAbsolute(project.path) ? project.path : resolve(ws.root, project.path);
996
+ try {
997
+ const configManager = createConfigManager(projectRoot);
998
+ const config = await configManager.loadConfig();
999
+ const rulebookDir = config.rulebookDir || '.rulebook';
1000
+ const taskManager = createTaskManager(projectRoot, rulebookDir);
1001
+ const tasks = await taskManager.listTasks(includeArchived);
1002
+ if (tasks.length > 0) {
1003
+ console.log(chalk.bold.cyan(` [${project.name}]`));
1004
+ for (const task of tasks) {
1005
+ const statusColor = task.status === 'completed'
1006
+ ? chalk.green
1007
+ : task.status === 'in-progress'
1008
+ ? chalk.yellow
1009
+ : task.status === 'blocked'
1010
+ ? chalk.red
1011
+ : chalk.gray;
1012
+ console.log(` ${statusColor(task.status.padEnd(12))} ${chalk.white(task.id)} - ${chalk.gray(task.title)}`);
1013
+ }
1014
+ console.log('');
1015
+ totalTasks += tasks.length;
1016
+ }
1017
+ }
1018
+ catch {
1019
+ console.log(chalk.bold.cyan(` [${project.name}]`));
1020
+ console.log(chalk.gray(' No tasks or no .rulebook config'));
1021
+ console.log('');
1022
+ }
1023
+ }
1024
+ console.log(chalk.gray(`${totalTasks} task(s) across ${ws.config.projects.length} project(s)`));
1025
+ return;
1026
+ }
1027
+ // Single project (optionally targeted via --project)
1028
+ const { taskManager, projectLabel } = await resolveTaskManager(cwd, wsOptions);
936
1029
  const tasks = await taskManager.listTasks(includeArchived);
937
1030
  if (tasks.length === 0) {
938
1031
  console.log(chalk.gray('No tasks found'));
939
1032
  return;
940
1033
  }
941
- console.log(chalk.bold.blue('\n📋 Rulebook Tasks\n'));
1034
+ const header = projectLabel
1035
+ ? `\n📋 Rulebook Tasks [${projectLabel}]\n`
1036
+ : '\n📋 Rulebook Tasks\n';
1037
+ console.log(chalk.bold.blue(header));
942
1038
  const activeTasks = tasks.filter((t) => !t.archivedAt);
943
1039
  const archivedTasks = tasks.filter((t) => t.archivedAt);
944
1040
  if (activeTasks.length > 0) {
@@ -968,15 +1064,10 @@ export async function taskListCommand(includeArchived = false) {
968
1064
  process.exit(1);
969
1065
  }
970
1066
  }
971
- export async function taskShowCommand(taskId) {
1067
+ export async function taskShowCommand(taskId, wsOptions) {
972
1068
  try {
973
1069
  const cwd = process.cwd();
974
- const { createTaskManager } = await import('../core/task-manager.js');
975
- const { createConfigManager } = await import('../core/config-manager.js');
976
- const configManager = createConfigManager(cwd);
977
- const config = await configManager.loadConfig();
978
- const rulebookDir = config.rulebookDir || '.rulebook';
979
- const taskManager = createTaskManager(cwd, rulebookDir);
1070
+ const { taskManager } = await resolveTaskManager(cwd, wsOptions);
980
1071
  const task = await taskManager.showTask(taskId);
981
1072
  if (!task) {
982
1073
  console.error(chalk.red(`❌ Task ${taskId} not found`));
@@ -1010,15 +1101,10 @@ export async function taskShowCommand(taskId) {
1010
1101
  process.exit(1);
1011
1102
  }
1012
1103
  }
1013
- export async function taskValidateCommand(taskId) {
1104
+ export async function taskValidateCommand(taskId, wsOptions) {
1014
1105
  try {
1015
1106
  const cwd = process.cwd();
1016
- const { createTaskManager } = await import('../core/task-manager.js');
1017
- const { createConfigManager } = await import('../core/config-manager.js');
1018
- const configManager = createConfigManager(cwd);
1019
- const config = await configManager.loadConfig();
1020
- const rulebookDir = config.rulebookDir || '.rulebook';
1021
- const taskManager = createTaskManager(cwd, rulebookDir);
1107
+ const { taskManager } = await resolveTaskManager(cwd, wsOptions);
1022
1108
  const validation = await taskManager.validateTask(taskId);
1023
1109
  if (validation.valid) {
1024
1110
  console.log(chalk.green(`✅ Task ${taskId} is valid`));
@@ -1049,17 +1135,13 @@ export async function taskValidateCommand(taskId) {
1049
1135
  process.exit(1);
1050
1136
  }
1051
1137
  }
1052
- export async function taskArchiveCommand(taskId, skipValidation = false) {
1138
+ export async function taskArchiveCommand(taskId, skipValidation = false, wsOptions) {
1053
1139
  try {
1054
1140
  const cwd = process.cwd();
1055
- const { createTaskManager } = await import('../core/task-manager.js');
1056
- const { createConfigManager } = await import('../core/config-manager.js');
1057
- const configManager = createConfigManager(cwd);
1058
- const config = await configManager.loadConfig();
1059
- const rulebookDir = config.rulebookDir || '.rulebook';
1060
- const taskManager = createTaskManager(cwd, rulebookDir);
1141
+ const { taskManager, projectLabel } = await resolveTaskManager(cwd, wsOptions);
1061
1142
  await taskManager.archiveTask(taskId, skipValidation);
1062
- console.log(chalk.green(`✅ Task ${taskId} archived successfully`));
1143
+ const prefix = projectLabel ? `[${projectLabel}] ` : '';
1144
+ console.log(chalk.green(`✅ ${prefix}Task ${taskId} archived successfully`));
1063
1145
  }
1064
1146
  catch (error) {
1065
1147
  console.error(chalk.red(`❌ Failed to archive task: ${error.message}`));
@@ -1070,23 +1152,59 @@ export async function taskArchiveCommand(taskId, skipValidation = false) {
1070
1152
  * Initialize MCP configuration in .rulebook file
1071
1153
  * Adds mcp block to .rulebook and creates/updates .cursor/mcp.json
1072
1154
  */
1073
- export async function mcpInitCommand() {
1155
+ export async function mcpInitCommand(options) {
1074
1156
  const { findRulebookConfig } = await import('../mcp/rulebook-server.js');
1075
1157
  const { existsSync, readFileSync, writeFileSync, statSync } = await import('fs');
1076
1158
  const { join, dirname } = await import('path');
1077
1159
  const { createConfigManager } = await import('../core/config-manager.js');
1078
1160
  try {
1079
- // Find or create .rulebook file/directory
1080
1161
  const cwd = process.cwd();
1162
+ // --- Workspace mode ---
1163
+ if (options?.workspace) {
1164
+ const wsConfig = WorkspaceManager.findWorkspaceConfig(cwd);
1165
+ if (!wsConfig) {
1166
+ console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
1167
+ process.exit(1);
1168
+ }
1169
+ const mcpArgs = ['-y', '@hivehub/rulebook@latest', 'mcp-server', '--workspace'];
1170
+ const mcpEntry = { command: 'npx', args: mcpArgs };
1171
+ // Write to .cursor/mcp.json if .cursor exists
1172
+ const cursorDir = join(cwd, '.cursor');
1173
+ if (existsSync(cursorDir)) {
1174
+ const mcpJsonPath = join(cursorDir, 'mcp.json');
1175
+ let mcpConfig = { mcpServers: {} };
1176
+ if (existsSync(mcpJsonPath)) {
1177
+ mcpConfig = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
1178
+ }
1179
+ mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
1180
+ mcpConfig.mcpServers.rulebook = mcpEntry;
1181
+ writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
1182
+ console.log(chalk.green('✓ Workspace MCP initialized'));
1183
+ console.log(chalk.gray(` • Updated .cursor/mcp.json with --workspace flag`));
1184
+ }
1185
+ // Write to .mcp.json (Claude Code format)
1186
+ const mcpJsonPath = join(cwd, '.mcp.json');
1187
+ let mcpConfig = { mcpServers: {} };
1188
+ if (existsSync(mcpJsonPath)) {
1189
+ mcpConfig = JSON.parse(readFileSync(mcpJsonPath, 'utf8'));
1190
+ }
1191
+ mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
1192
+ mcpConfig.mcpServers.rulebook = mcpEntry;
1193
+ writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2) + '\n');
1194
+ console.log(chalk.green('✓ Workspace MCP initialized'));
1195
+ console.log(chalk.gray(` • Workspace: ${wsConfig.name} (${wsConfig.projects.length} projects)`));
1196
+ console.log(chalk.gray(` • Updated .mcp.json with --workspace flag`));
1197
+ console.log(chalk.gray(` • MCP server will manage all projects automatically`));
1198
+ return;
1199
+ }
1200
+ // --- Single-project mode (original behavior) ---
1081
1201
  let rulebookPath = findRulebookConfig(cwd);
1082
1202
  if (!rulebookPath) {
1083
- // Create new .rulebook directory via ConfigManager
1084
1203
  rulebookPath = join(cwd, '.rulebook');
1085
1204
  const configManager = createConfigManager(cwd);
1086
1205
  await configManager.initializeConfig();
1087
1206
  }
1088
1207
  const projectRoot = dirname(rulebookPath);
1089
- // Resolve config file path (handle .rulebook as directory or file)
1090
1208
  let configFilePath = rulebookPath;
1091
1209
  if (existsSync(rulebookPath)) {
1092
1210
  const stats = statSync(rulebookPath);
@@ -1094,13 +1212,11 @@ export async function mcpInitCommand() {
1094
1212
  configFilePath = join(rulebookPath, 'rulebook.json');
1095
1213
  }
1096
1214
  }
1097
- // Load existing config
1098
1215
  let config = {};
1099
1216
  if (existsSync(configFilePath)) {
1100
1217
  const raw = readFileSync(configFilePath, 'utf8');
1101
1218
  config = JSON.parse(raw);
1102
1219
  }
1103
- // Add/update mcp block
1104
1220
  config.mcp = config.mcp ?? {};
1105
1221
  if (config.mcp.enabled === undefined)
1106
1222
  config.mcp.enabled = true;
@@ -1108,9 +1224,7 @@ export async function mcpInitCommand() {
1108
1224
  config.mcp.tasksDir = '.rulebook/tasks';
1109
1225
  if (!config.mcp.archiveDir)
1110
1226
  config.mcp.archiveDir = '.rulebook/archive';
1111
- // Save updated config
1112
1227
  writeFileSync(configFilePath, JSON.stringify(config, null, 2) + '\n');
1113
- // Create/update .cursor/mcp.json if .cursor directory exists
1114
1228
  const cursorDir = join(projectRoot, '.cursor');
1115
1229
  if (existsSync(cursorDir)) {
1116
1230
  const mcpJsonPath = join(cursorDir, 'mcp.json');
@@ -1181,144 +1295,195 @@ export async function tasksCommand(options) {
1181
1295
  export async function updateCommand(options) {
1182
1296
  try {
1183
1297
  const cwd = process.cwd();
1184
- console.log(chalk.bold.blue('\n🔄 Rulebook Update Tool\n'));
1185
- console.log(chalk.gray('This will update your AGENTS.md and .rulebook to the latest version\n'));
1186
- // Detect project
1187
- const spinner = ora('Detecting project structure...').start();
1188
- const detection = await detectProject(cwd);
1189
- spinner.succeed('Project detection complete');
1190
- // Show detected languages
1191
- if (detection.languages.length > 0) {
1192
- console.log(chalk.green('\n✓ Detected languages:'));
1193
- for (const lang of detection.languages) {
1194
- console.log(` - ${lang.language} (${(lang.confidence * 100).toFixed(0)}% confidence)`);
1298
+ // Auto-detect workspace: if inside a workspace, update ALL projects
1299
+ const ws = WorkspaceManager.findWorkspaceFromCwd(cwd);
1300
+ if (ws && ws.config.projects.length > 1) {
1301
+ console.log(chalk.bold.blue('\n🔄 Rulebook Workspace Update\n'));
1302
+ console.log(chalk.gray(`Workspace "${ws.config.name}" detected updating ${ws.config.projects.length} projects\n`));
1303
+ const { isAbsolute, resolve, join } = await import('path');
1304
+ const fsPromises = await import('fs/promises');
1305
+ let updatedCount = 0;
1306
+ // Build workspace project list for the template
1307
+ const projectListMd = ws.config.projects
1308
+ .map((p) => {
1309
+ const root = isAbsolute(p.path) ? p.path : resolve(ws.root, p.path);
1310
+ return `- **${p.name}** → \`${root}\``;
1311
+ })
1312
+ .join('\n');
1313
+ // Load WORKSPACE.md template
1314
+ const { getDefaultTemplatesPath } = await import('../core/skills-manager.js');
1315
+ let workspaceTplContent = '';
1316
+ try {
1317
+ const tplPath = join(getDefaultTemplatesPath(), 'core', 'WORKSPACE.md');
1318
+ workspaceTplContent = await fsPromises.readFile(tplPath, 'utf-8');
1195
1319
  }
1320
+ catch {
1321
+ // Template not available — skip
1322
+ }
1323
+ for (const project of ws.config.projects) {
1324
+ const projectRoot = isAbsolute(project.path)
1325
+ ? project.path
1326
+ : resolve(ws.root, project.path);
1327
+ console.log(chalk.bold.cyan(`\n━━━ [${project.name}] ${projectRoot} ━━━\n`));
1328
+ try {
1329
+ await updateSingleProject(projectRoot, options);
1330
+ // Inject WORKSPACE.md spec into this project's .rulebook/specs/
1331
+ if (workspaceTplContent) {
1332
+ const specsDir = join(projectRoot, '.rulebook', 'specs');
1333
+ await fsPromises.mkdir(specsDir, { recursive: true });
1334
+ const rendered = workspaceTplContent
1335
+ .replace('{{DEFAULT_PROJECT}}', ws.config.defaultProject ?? ws.config.projects[0]?.name ?? '')
1336
+ .replace('{{WORKSPACE_PROJECTS}}', projectListMd);
1337
+ await fsPromises.writeFile(join(specsDir, 'WORKSPACE.md'), rendered, 'utf-8');
1338
+ }
1339
+ updatedCount++;
1340
+ }
1341
+ catch (error) {
1342
+ console.error(chalk.red(` ❌ Failed to update ${project.name}: ${error.message}`));
1343
+ }
1344
+ }
1345
+ console.log(chalk.bold.green(`\n✅ Workspace update complete — ${updatedCount}/${ws.config.projects.length} projects updated\n`));
1346
+ return;
1196
1347
  }
1197
- // Check for existing AGENTS.md
1198
- if (!detection.existingAgents) {
1199
- console.log(chalk.yellow('\n⚠ No AGENTS.md found. Use "rulebook init" instead.'));
1348
+ // Single project update
1349
+ console.log(chalk.bold.blue('\n🔄 Rulebook Update Tool\n'));
1350
+ console.log(chalk.gray('This will update your AGENTS.md and .rulebook to the latest version\n'));
1351
+ await updateSingleProject(cwd, options);
1352
+ }
1353
+ catch (error) {
1354
+ console.error(chalk.red('\n❌ Update failed:'), error);
1355
+ process.exit(1);
1356
+ }
1357
+ }
1358
+ /** Update a single project at the given root directory. */
1359
+ async function updateSingleProject(cwd, options) {
1360
+ // Detect project
1361
+ const spinner = ora('Detecting project structure...').start();
1362
+ const detection = await detectProject(cwd);
1363
+ spinner.succeed('Project detection complete');
1364
+ // Show detected languages
1365
+ if (detection.languages.length > 0) {
1366
+ console.log(chalk.green('\n✓ Detected languages:'));
1367
+ for (const lang of detection.languages) {
1368
+ console.log(` - ${lang.language} (${(lang.confidence * 100).toFixed(0)}% confidence)`);
1369
+ }
1370
+ }
1371
+ // Check for existing AGENTS.md
1372
+ if (!detection.existingAgents) {
1373
+ console.log(chalk.yellow('\n⚠ No AGENTS.md found. Use "rulebook init" instead.'));
1374
+ process.exit(0);
1375
+ }
1376
+ console.log(chalk.green(`\n✓ Found existing AGENTS.md with ${detection.existingAgents.blocks.length} blocks`));
1377
+ // Get existing blocks to preserve user customizations
1378
+ const existingBlocks = detection.existingAgents.blocks.map((b) => b.name);
1379
+ console.log(chalk.gray(` Existing blocks: ${existingBlocks.join(', ')}`));
1380
+ let inquirerModule = null;
1381
+ if (!options.yes) {
1382
+ inquirerModule = (await import('inquirer')).default;
1383
+ const { confirm } = await inquirerModule.prompt([
1384
+ {
1385
+ type: 'confirm',
1386
+ name: 'confirm',
1387
+ message: 'Update AGENTS.md and .rulebook with latest templates?',
1388
+ default: true,
1389
+ },
1390
+ ]);
1391
+ if (!confirm) {
1392
+ console.log(chalk.yellow('\nUpdate cancelled'));
1200
1393
  process.exit(0);
1201
1394
  }
1202
- console.log(chalk.green(`\n✓ Found existing AGENTS.md with ${detection.existingAgents.blocks.length} blocks`));
1203
- // Get existing blocks to preserve user customizations
1204
- const existingBlocks = detection.existingAgents.blocks.map((b) => b.name);
1205
- console.log(chalk.gray(` Existing blocks: ${existingBlocks.join(', ')}`));
1206
- let inquirerModule = null;
1207
- if (!options.yes) {
1208
- inquirerModule = (await import('inquirer')).default;
1209
- const { confirm } = await inquirerModule.prompt([
1395
+ }
1396
+ const hasPreCommit = detection.gitHooks?.preCommitExists ?? false;
1397
+ const hasPrePush = detection.gitHooks?.prePushExists ?? false;
1398
+ const missingHooks = !hasPreCommit || !hasPrePush;
1399
+ let installHooksOnUpdate = false;
1400
+ let hooksInstalledOnUpdate = false;
1401
+ if (missingHooks) {
1402
+ if (options.yes) {
1403
+ console.log(chalk.yellow('\n⚠ Git hooks are missing. Re-run "rulebook update" without --yes to install automated hooks or install them manually.'));
1404
+ }
1405
+ else {
1406
+ if (!inquirerModule) {
1407
+ inquirerModule = (await import('inquirer')).default;
1408
+ }
1409
+ const { installHooks } = await inquirerModule.prompt([
1210
1410
  {
1211
1411
  type: 'confirm',
1212
- name: 'confirm',
1213
- message: 'Update AGENTS.md and .rulebook with latest templates?',
1412
+ name: 'installHooks',
1413
+ message: `Install Git hooks for automated quality checks? Missing: ${hasPreCommit ? '' : 'pre-commit '}${hasPrePush ? '' : 'pre-push'}`.trim(),
1214
1414
  default: true,
1215
1415
  },
1216
1416
  ]);
1217
- if (!confirm) {
1218
- console.log(chalk.yellow('\nUpdate cancelled'));
1219
- process.exit(0);
1220
- }
1221
- }
1222
- const hasPreCommit = detection.gitHooks?.preCommitExists ?? false;
1223
- const hasPrePush = detection.gitHooks?.prePushExists ?? false;
1224
- const missingHooks = !hasPreCommit || !hasPrePush;
1225
- let installHooksOnUpdate = false;
1226
- let hooksInstalledOnUpdate = false;
1227
- if (missingHooks) {
1228
- if (options.yes) {
1229
- console.log(chalk.yellow('\n⚠ Git hooks are missing. Re-run "rulebook update" without --yes to install automated hooks or install them manually.'));
1230
- }
1231
- else {
1232
- if (!inquirerModule) {
1233
- inquirerModule = (await import('inquirer')).default;
1234
- }
1235
- const { installHooks } = await inquirerModule.prompt([
1236
- {
1237
- type: 'confirm',
1238
- name: 'installHooks',
1239
- message: `Install Git hooks for automated quality checks? Missing: ${hasPreCommit ? '' : 'pre-commit '}${hasPrePush ? '' : 'pre-push'}`.trim(),
1240
- default: true,
1241
- },
1242
- ]);
1243
- installHooksOnUpdate = installHooks;
1244
- }
1245
- }
1246
- if (missingHooks && !installHooksOnUpdate && !options.yes) {
1247
- 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.'));
1417
+ installHooksOnUpdate = installHooks;
1248
1418
  }
1249
- const agentsPath = path.join(cwd, 'AGENTS.md');
1250
- // Load existing config using ConfigManager
1251
- const { createConfigManager } = await import('../core/config-manager.js');
1252
- const configManager = createConfigManager(cwd);
1253
- const existingConfig = await configManager.loadConfig();
1254
- let existingMode;
1255
- let existingLightMode;
1256
- if (existingConfig) {
1257
- if (existingConfig && (existingConfig.mode === 'minimal' || existingConfig.mode === 'full')) {
1258
- existingMode = existingConfig.mode;
1259
- }
1260
- if (existingConfig && existingConfig.lightMode !== undefined) {
1261
- existingLightMode = existingConfig.lightMode;
1262
- }
1263
- }
1264
- const minimalMode = options.minimal ?? existingMode === 'minimal';
1265
- const lightMode = options.light !== undefined ? options.light : (existingLightMode ?? false);
1266
- const leanMode = options.lean ?? existingConfig?.agentsMode === 'lean';
1267
- // Build config from detected project
1268
- const config = {
1269
- languages: detection.languages.map((l) => l.language),
1270
- modules: minimalMode ? [] : detection.modules.filter((m) => m.detected).map((m) => m.module),
1271
- frameworks: detection.frameworks.filter((f) => f.detected).map((f) => f.framework),
1272
- ides: [], // Preserve existing IDE choices
1273
- projectType: 'application',
1274
- coverageThreshold: 95,
1275
- strictDocs: true,
1276
- generateWorkflows: false, // Don't regenerate workflows on update
1277
- includeGitWorkflow: true,
1278
- gitPushMode: 'manual',
1279
- installGitHooks: installHooksOnUpdate,
1280
- minimal: minimalMode,
1281
- lightMode: lightMode,
1282
- ...(leanMode ? { agentsMode: 'lean' } : {}),
1283
- };
1284
- if (minimalMode) {
1285
- config.ides = [];
1286
- config.generateWorkflows = true;
1287
- }
1288
- let minimalArtifacts = [];
1289
- if (minimalMode) {
1290
- minimalArtifacts = await scaffoldMinimalProject(cwd, {
1291
- projectName: path.basename(cwd),
1292
- description: 'Essential project scaffolding refreshed via Rulebook minimal mode.',
1293
- license: 'MIT',
1294
- });
1295
- }
1296
- // Generate Rulebook commands if Cursor is detected
1297
- // This ensures commands are available for all Cursor projects
1298
- const cursorRulesPath = path.join(cwd, '.cursorrules');
1299
- const cursorCommandsDir = path.join(cwd, '.cursor', 'commands');
1300
- const usesCursor = existsSync(cursorRulesPath) || existsSync(cursorCommandsDir);
1301
- // Deprecated notice: .cursorrules is superseded by .cursor/rules/*.mdc in Cursor v0.45+
1302
- if (existsSync(cursorRulesPath)) {
1303
- console.log(chalk.yellow(' ⚠ .cursorrules is deprecated as of Cursor v0.45. Use .cursor/rules/*.mdc instead.'));
1304
- }
1305
- if (usesCursor) {
1306
- // Check if commands already exist to avoid duplicate generation
1307
- const existingCommandsDir = path.join(cwd, '.cursor', 'commands');
1308
- if (existsSync(existingCommandsDir)) {
1309
- const { readdir } = await import('fs/promises');
1310
- const existingFiles = await readdir(existingCommandsDir);
1311
- const hasRulebookCommands = existingFiles.some((file) => file.startsWith('rulebook-task-'));
1312
- if (!hasRulebookCommands) {
1313
- const { generateCursorCommands } = await import('../core/workflow-generator.js');
1314
- const generatedCommands = await generateCursorCommands(cwd);
1315
- if (generatedCommands.length > 0) {
1316
- console.log(chalk.green(` Generated ${generatedCommands.length} Rulebook command(s) in .cursor/commands/`));
1317
- }
1318
- }
1319
- }
1320
- else {
1321
- // Directory doesn't exist, create it and generate commands
1419
+ }
1420
+ if (missingHooks && !installHooksOnUpdate && !options.yes) {
1421
+ 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.'));
1422
+ }
1423
+ const agentsPath = path.join(cwd, 'AGENTS.md');
1424
+ // Load existing config using ConfigManager
1425
+ const { createConfigManager } = await import('../core/config-manager.js');
1426
+ const configManager = createConfigManager(cwd);
1427
+ const existingConfig = await configManager.loadConfig();
1428
+ let existingMode;
1429
+ let existingLightMode;
1430
+ if (existingConfig) {
1431
+ if (existingConfig && (existingConfig.mode === 'minimal' || existingConfig.mode === 'full')) {
1432
+ existingMode = existingConfig.mode;
1433
+ }
1434
+ if (existingConfig && existingConfig.lightMode !== undefined) {
1435
+ existingLightMode = existingConfig.lightMode;
1436
+ }
1437
+ }
1438
+ const minimalMode = options.minimal ?? existingMode === 'minimal';
1439
+ const lightMode = options.light !== undefined ? options.light : (existingLightMode ?? false);
1440
+ const leanMode = options.lean ?? existingConfig?.agentsMode === 'lean';
1441
+ // Build config from detected project
1442
+ const config = {
1443
+ languages: detection.languages.map((l) => l.language),
1444
+ modules: minimalMode ? [] : detection.modules.filter((m) => m.detected).map((m) => m.module),
1445
+ frameworks: detection.frameworks.filter((f) => f.detected).map((f) => f.framework),
1446
+ ides: [], // Preserve existing IDE choices
1447
+ projectType: 'application',
1448
+ coverageThreshold: 95,
1449
+ strictDocs: true,
1450
+ generateWorkflows: false, // Don't regenerate workflows on update
1451
+ includeGitWorkflow: true,
1452
+ gitPushMode: 'manual',
1453
+ installGitHooks: installHooksOnUpdate,
1454
+ minimal: minimalMode,
1455
+ lightMode: lightMode,
1456
+ ...(leanMode ? { agentsMode: 'lean' } : {}),
1457
+ };
1458
+ if (minimalMode) {
1459
+ config.ides = [];
1460
+ config.generateWorkflows = true;
1461
+ }
1462
+ let minimalArtifacts = [];
1463
+ if (minimalMode) {
1464
+ minimalArtifacts = await scaffoldMinimalProject(cwd, {
1465
+ projectName: path.basename(cwd),
1466
+ description: 'Essential project scaffolding refreshed via Rulebook minimal mode.',
1467
+ license: 'MIT',
1468
+ });
1469
+ }
1470
+ // Generate Rulebook commands if Cursor is detected
1471
+ // This ensures commands are available for all Cursor projects
1472
+ const cursorRulesPath = path.join(cwd, '.cursorrules');
1473
+ const cursorCommandsDir = path.join(cwd, '.cursor', 'commands');
1474
+ const usesCursor = existsSync(cursorRulesPath) || existsSync(cursorCommandsDir);
1475
+ // Deprecated notice: .cursorrules is superseded by .cursor/rules/*.mdc in Cursor v0.45+
1476
+ if (existsSync(cursorRulesPath)) {
1477
+ console.log(chalk.yellow(' ⚠ .cursorrules is deprecated as of Cursor v0.45. Use .cursor/rules/*.mdc instead.'));
1478
+ }
1479
+ if (usesCursor) {
1480
+ // Check if commands already exist to avoid duplicate generation
1481
+ const existingCommandsDir = path.join(cwd, '.cursor', 'commands');
1482
+ if (existsSync(existingCommandsDir)) {
1483
+ const { readdir } = await import('fs/promises');
1484
+ const existingFiles = await readdir(existingCommandsDir);
1485
+ const hasRulebookCommands = existingFiles.some((file) => file.startsWith('rulebook-task-'));
1486
+ if (!hasRulebookCommands) {
1322
1487
  const { generateCursorCommands } = await import('../core/workflow-generator.js');
1323
1488
  const generatedCommands = await generateCursorCommands(cwd);
1324
1489
  if (generatedCommands.length > 0) {
@@ -1326,256 +1491,260 @@ export async function updateCommand(options) {
1326
1491
  }
1327
1492
  }
1328
1493
  }
1329
- // Migration already done via configManager.loadConfig() -> migrateConfig() -> migrateDirectoryStructure()
1330
- // No need to call it again here
1331
- // Load existing config to preserve skills and ralph settings (already loaded above)
1332
- const existingSkills = existingConfig.skills?.enabled || [];
1333
- const existingRalph = existingConfig.ralph;
1334
- // Auto-detect skills based on project detection (v2.0)
1335
- let detectedSkills = [];
1336
- try {
1337
- const { SkillsManager, getDefaultTemplatesPath } = await import('../core/skills-manager.js');
1338
- const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
1339
- // Build a RulebookConfig-like object for skill detection
1340
- const rulebookConfigForSkills = {
1341
- languages: config.languages,
1342
- frameworks: config.frameworks,
1343
- modules: config.modules,
1344
- services: config.services,
1345
- };
1346
- detectedSkills = await skillsManager.autoDetectSkills(rulebookConfigForSkills);
1347
- // Merge with existing skills (keep existing, add new detected)
1348
- const mergedSkills = [...new Set([...existingSkills, ...detectedSkills])];
1349
- if (detectedSkills.length > existingSkills.length) {
1350
- const newSkills = detectedSkills.filter((s) => !existingSkills.includes(s));
1351
- if (newSkills.length > 0) {
1352
- console.log(chalk.green('\n✓ New skills detected:'));
1353
- for (const skillId of newSkills) {
1354
- console.log(chalk.gray(` - ${skillId}`));
1355
- }
1356
- }
1494
+ else {
1495
+ // Directory doesn't exist, create it and generate commands
1496
+ const { generateCursorCommands } = await import('../core/workflow-generator.js');
1497
+ const generatedCommands = await generateCursorCommands(cwd);
1498
+ if (generatedCommands.length > 0) {
1499
+ console.log(chalk.green(` Generated ${generatedCommands.length} Rulebook command(s) in .cursor/commands/`));
1357
1500
  }
1358
- detectedSkills = mergedSkills;
1359
- }
1360
- catch {
1361
- // Skills system not available or error - preserve existing skills
1362
- detectedSkills = existingSkills;
1363
1501
  }
1364
- await configManager.updateConfig({
1502
+ }
1503
+ // Migration already done via configManager.loadConfig() -> migrateConfig() -> migrateDirectoryStructure()
1504
+ // No need to call it again here
1505
+ // Load existing config to preserve skills and ralph settings (already loaded above)
1506
+ const existingSkills = existingConfig.skills?.enabled || [];
1507
+ const existingRalph = existingConfig.ralph;
1508
+ // Auto-detect skills based on project detection (v2.0)
1509
+ let detectedSkills = [];
1510
+ try {
1511
+ const { SkillsManager, getDefaultTemplatesPath } = await import('../core/skills-manager.js');
1512
+ const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
1513
+ // Build a RulebookConfig-like object for skill detection
1514
+ const rulebookConfigForSkills = {
1365
1515
  languages: config.languages,
1366
1516
  frameworks: config.frameworks,
1367
1517
  modules: config.modules,
1368
1518
  services: config.services,
1369
- modular: config.modular ?? true,
1370
- rulebookDir: config.rulebookDir || '.rulebook',
1371
- skills: detectedSkills.length > 0 ? { enabled: detectedSkills } : undefined,
1372
- ralph: existingRalph,
1373
- memory: existingConfig.memory,
1374
- });
1375
- // Ensure .rulebook is in .gitignore with exceptions for specs/tasks
1376
- await configManager.ensureGitignore();
1377
- // Migrate flat layout to specs/ subdirectory if needed
1378
- {
1379
- const { hasFlatLayout, migrateFlatToSpecs } = await import('../core/migrator.js');
1380
- const rulebookDirForMigration = config.rulebookDir || '.rulebook';
1381
- if (await hasFlatLayout(cwd, rulebookDirForMigration)) {
1382
- const migrationSpinner = ora('Migrating rulebook files to specs/ subdirectory...').start();
1383
- const { migratedFiles } = await migrateFlatToSpecs(cwd, rulebookDirForMigration);
1384
- if (migratedFiles.length > 0) {
1385
- migrationSpinner.succeed(`Migrated ${migratedFiles.length} file(s) to /${rulebookDirForMigration}/specs/`);
1386
- }
1387
- else {
1388
- migrationSpinner.info('No files to migrate');
1519
+ };
1520
+ detectedSkills = await skillsManager.autoDetectSkills(rulebookConfigForSkills);
1521
+ // Merge with existing skills (keep existing, add new detected)
1522
+ const mergedSkills = [...new Set([...existingSkills, ...detectedSkills])];
1523
+ if (detectedSkills.length > existingSkills.length) {
1524
+ const newSkills = detectedSkills.filter((s) => !existingSkills.includes(s));
1525
+ if (newSkills.length > 0) {
1526
+ console.log(chalk.green('\n✓ New skills detected:'));
1527
+ for (const skillId of newSkills) {
1528
+ console.log(chalk.gray(` - ${skillId}`));
1389
1529
  }
1390
1530
  }
1391
1531
  }
1392
- // Merge with existing AGENTS.md (with migration support)
1393
- const mergeSpinner = ora('Updating AGENTS.md with latest templates...').start();
1394
- config.modular = config.modular ?? true; // Enable modular by default
1395
- const mergedContent = await mergeFullAgents(detection.existingAgents, config, cwd);
1396
- await writeFile(agentsPath, mergedContent);
1397
- mergeSpinner.succeed('AGENTS.md updated');
1398
- // Show multi-tool config feedback (update command)
1399
- if (detection.geminiCli?.detected) {
1400
- console.log(chalk.gray(' • Gemini CLI config updated: GEMINI.md'));
1401
- }
1402
- if (detection.continueDev?.detected) {
1403
- console.log(chalk.gray(' • Continue.dev rules updated in .continue/rules/'));
1404
- }
1405
- if (detection.windsurf?.detected) {
1406
- console.log(chalk.gray(' • Windsurf rules updated: .windsurfrules'));
1407
- }
1408
- if (detection.githubCopilot?.detected) {
1409
- console.log(chalk.gray(' • GitHub Copilot instructions updated in .github/'));
1410
- }
1411
- if (installHooksOnUpdate) {
1412
- const hookLanguages = detection.languages.length > 0
1413
- ? detection.languages
1414
- : config.languages.map((language) => ({
1415
- language: language,
1416
- confidence: 1,
1417
- indicators: [],
1418
- }));
1419
- const hookSpinner = ora('Installing Git hooks (pre-commit & pre-push)...').start();
1420
- try {
1421
- await installGitHooks({ languages: hookLanguages, cwd });
1422
- hookSpinner.succeed('Git hooks installed successfully');
1423
- hooksInstalledOnUpdate = true;
1424
- }
1425
- catch (error) {
1426
- hookSpinner.fail('Failed to install Git hooks');
1427
- console.error(chalk.red(' ➤'), error instanceof Error ? error.message : error);
1428
- console.log(chalk.yellow(' ⚠ Skipping automatic hook installation. You can rerun "rulebook update" later to retry or install manually.'));
1429
- }
1430
- }
1431
- const gitHooksActiveAfterUpdate = hooksInstalledOnUpdate || (hasPreCommit && hasPrePush);
1432
- config.installGitHooks = gitHooksActiveAfterUpdate;
1433
- // Update .rulebook config
1434
- const configSpinner = ora('Updating .rulebook configuration...').start();
1435
- const rulebookFeatures = {
1436
- watcher: false,
1437
- agent: false,
1438
- logging: true,
1439
- telemetry: false,
1440
- notifications: false,
1441
- dryRun: false,
1442
- gitHooks: gitHooksActiveAfterUpdate,
1443
- repl: false,
1444
- templates: true,
1445
- context: minimalMode ? false : true,
1446
- health: true,
1447
- plugins: false,
1448
- parallel: minimalMode ? false : true,
1449
- smartContinue: minimalMode ? false : true,
1450
- };
1451
- const rulebookConfig = {
1452
- version: getRulebookVersion(),
1453
- installedAt: detection.existingAgents.content?.match(/Generated at: (.+)/)?.[1] ||
1454
- new Date().toISOString(),
1455
- updatedAt: new Date().toISOString(),
1456
- projectId: path.basename(cwd),
1457
- mode: minimalMode ? 'minimal' : 'full',
1458
- features: rulebookFeatures,
1459
- coverageThreshold: existingConfig.coverageThreshold ?? 95,
1460
- language: existingConfig.language ?? 'en',
1461
- outputLanguage: existingConfig.outputLanguage ?? 'en',
1462
- cliTools: existingConfig.cliTools ?? [],
1463
- maxParallelTasks: existingConfig.maxParallelTasks ?? 5,
1464
- timeouts: existingConfig.timeouts ?? {
1465
- taskExecution: 3600000,
1466
- cliResponse: 180000,
1467
- testRun: 600000,
1468
- },
1469
- ...(existingConfig.memory ? { memory: existingConfig.memory } : {}),
1470
- ...(existingConfig.ralph ? { ralph: existingConfig.ralph } : {}),
1471
- ...(existingConfig.skills ? { skills: existingConfig.skills } : {}),
1472
- ...(leanMode
1473
- ? { agentsMode: 'lean' }
1474
- : existingConfig.agentsMode
1475
- ? { agentsMode: existingConfig.agentsMode }
1476
- : {}),
1477
- };
1478
- await configManager.saveConfig(rulebookConfig);
1479
- configSpinner.succeed('.rulebook configuration updated');
1480
- // Auto-setup Claude Code integration (MCP + skills)
1481
- const claudeSpinner = ora('Checking Claude Code integration...').start();
1482
- try {
1483
- const { setupClaudeCodeIntegration } = await import('../core/claude-mcp.js');
1484
- const result = await setupClaudeCodeIntegration(cwd);
1485
- if (result.detected) {
1486
- claudeSpinner.succeed('Claude Code integration updated');
1487
- if (result.mcpConfigured) {
1488
- console.log(chalk.gray(' • MCP server added to .mcp.json'));
1489
- }
1490
- if (result.skillsInstalled.length > 0) {
1491
- console.log(chalk.gray(` • ${result.skillsInstalled.length} skills updated in .claude/commands/`));
1492
- }
1493
- if (result.agentTeamsEnabled) {
1494
- console.log(chalk.gray(' • Multi-agent teams enabled in .claude/settings.json'));
1495
- }
1496
- if (result.agentDefinitionsInstalled.length > 0) {
1497
- console.log(chalk.gray(` • ${result.agentDefinitionsInstalled.length} agent definitions updated in .claude/agents/`));
1498
- }
1532
+ detectedSkills = mergedSkills;
1533
+ }
1534
+ catch {
1535
+ // Skills system not available or error - preserve existing skills
1536
+ detectedSkills = existingSkills;
1537
+ }
1538
+ await configManager.updateConfig({
1539
+ languages: config.languages,
1540
+ frameworks: config.frameworks,
1541
+ modules: config.modules,
1542
+ services: config.services,
1543
+ modular: config.modular ?? true,
1544
+ rulebookDir: config.rulebookDir || '.rulebook',
1545
+ skills: detectedSkills.length > 0 ? { enabled: detectedSkills } : undefined,
1546
+ ralph: existingRalph,
1547
+ memory: existingConfig.memory,
1548
+ });
1549
+ // Ensure .rulebook is in .gitignore with exceptions for specs/tasks
1550
+ await configManager.ensureGitignore();
1551
+ // Migrate flat layout to specs/ subdirectory if needed
1552
+ {
1553
+ const { hasFlatLayout, migrateFlatToSpecs } = await import('../core/migrator.js');
1554
+ const rulebookDirForMigration = config.rulebookDir || '.rulebook';
1555
+ if (await hasFlatLayout(cwd, rulebookDirForMigration)) {
1556
+ const migrationSpinner = ora('Migrating rulebook files to specs/ subdirectory...').start();
1557
+ const { migratedFiles } = await migrateFlatToSpecs(cwd, rulebookDirForMigration);
1558
+ if (migratedFiles.length > 0) {
1559
+ migrationSpinner.succeed(`Migrated ${migratedFiles.length} file(s) to /${rulebookDirForMigration}/specs/`);
1499
1560
  }
1500
1561
  else {
1501
- claudeSpinner.info('Claude Code not detected (skipped)');
1562
+ migrationSpinner.info('No files to migrate');
1502
1563
  }
1503
1564
  }
1504
- catch {
1505
- claudeSpinner.info('Claude Code integration skipped');
1506
- }
1507
- // Install/update Ralph shell scripts
1565
+ }
1566
+ // Merge with existing AGENTS.md (with migration support)
1567
+ const mergeSpinner = ora('Updating AGENTS.md with latest templates...').start();
1568
+ config.modular = config.modular ?? true; // Enable modular by default
1569
+ const mergedContent = await mergeFullAgents(detection.existingAgents, config, cwd);
1570
+ await writeFile(agentsPath, mergedContent);
1571
+ mergeSpinner.succeed('AGENTS.md updated');
1572
+ // Show multi-tool config feedback (update command)
1573
+ if (detection.geminiCli?.detected) {
1574
+ console.log(chalk.gray(' • Gemini CLI config updated: GEMINI.md'));
1575
+ }
1576
+ if (detection.continueDev?.detected) {
1577
+ console.log(chalk.gray(' • Continue.dev rules updated in .continue/rules/'));
1578
+ }
1579
+ if (detection.windsurf?.detected) {
1580
+ console.log(chalk.gray(' • Windsurf rules updated: .windsurfrules'));
1581
+ }
1582
+ if (detection.githubCopilot?.detected) {
1583
+ console.log(chalk.gray(' • GitHub Copilot instructions updated in .github/'));
1584
+ }
1585
+ if (installHooksOnUpdate) {
1586
+ const hookLanguages = detection.languages.length > 0
1587
+ ? detection.languages
1588
+ : config.languages.map((language) => ({
1589
+ language: language,
1590
+ confidence: 1,
1591
+ indicators: [],
1592
+ }));
1593
+ const hookSpinner = ora('Installing Git hooks (pre-commit & pre-push)...').start();
1508
1594
  try {
1509
- const { installRalphScripts } = await import('../core/ralph-scripts.js');
1510
- const scripts = await installRalphScripts(cwd);
1511
- if (scripts.length > 0) {
1512
- console.log(chalk.gray(` • ${scripts.length} Ralph scripts updated in .rulebook/scripts/`));
1595
+ await installGitHooks({ languages: hookLanguages, cwd });
1596
+ hookSpinner.succeed('Git hooks installed successfully');
1597
+ hooksInstalledOnUpdate = true;
1598
+ }
1599
+ catch (error) {
1600
+ hookSpinner.fail('Failed to install Git hooks');
1601
+ console.error(chalk.red(' ➤'), error instanceof Error ? error.message : error);
1602
+ console.log(chalk.yellow(' ⚠ Skipping automatic hook installation. You can rerun "rulebook update" later to retry or install manually.'));
1603
+ }
1604
+ }
1605
+ const gitHooksActiveAfterUpdate = hooksInstalledOnUpdate || (hasPreCommit && hasPrePush);
1606
+ config.installGitHooks = gitHooksActiveAfterUpdate;
1607
+ // Update .rulebook config
1608
+ const configSpinner = ora('Updating .rulebook configuration...').start();
1609
+ const rulebookFeatures = {
1610
+ watcher: false,
1611
+ agent: false,
1612
+ logging: true,
1613
+ telemetry: false,
1614
+ notifications: false,
1615
+ dryRun: false,
1616
+ gitHooks: gitHooksActiveAfterUpdate,
1617
+ repl: false,
1618
+ templates: true,
1619
+ context: minimalMode ? false : true,
1620
+ health: true,
1621
+ plugins: false,
1622
+ parallel: minimalMode ? false : true,
1623
+ smartContinue: minimalMode ? false : true,
1624
+ };
1625
+ const rulebookConfig = {
1626
+ version: getRulebookVersion(),
1627
+ installedAt: detection.existingAgents.content?.match(/Generated at: (.+)/)?.[1] ||
1628
+ new Date().toISOString(),
1629
+ updatedAt: new Date().toISOString(),
1630
+ projectId: path.basename(cwd),
1631
+ mode: minimalMode ? 'minimal' : 'full',
1632
+ features: rulebookFeatures,
1633
+ coverageThreshold: existingConfig.coverageThreshold ?? 95,
1634
+ language: existingConfig.language ?? 'en',
1635
+ outputLanguage: existingConfig.outputLanguage ?? 'en',
1636
+ cliTools: existingConfig.cliTools ?? [],
1637
+ maxParallelTasks: existingConfig.maxParallelTasks ?? 5,
1638
+ timeouts: existingConfig.timeouts ?? {
1639
+ taskExecution: 3600000,
1640
+ cliResponse: 180000,
1641
+ testRun: 600000,
1642
+ },
1643
+ ...(existingConfig.memory ? { memory: existingConfig.memory } : {}),
1644
+ ...(existingConfig.ralph ? { ralph: existingConfig.ralph } : {}),
1645
+ ...(existingConfig.skills ? { skills: existingConfig.skills } : {}),
1646
+ ...(leanMode
1647
+ ? { agentsMode: 'lean' }
1648
+ : existingConfig.agentsMode
1649
+ ? { agentsMode: existingConfig.agentsMode }
1650
+ : {}),
1651
+ };
1652
+ await configManager.saveConfig(rulebookConfig);
1653
+ configSpinner.succeed('.rulebook configuration updated');
1654
+ // Auto-setup Claude Code integration (MCP + skills)
1655
+ const claudeSpinner = ora('Checking Claude Code integration...').start();
1656
+ try {
1657
+ const { setupClaudeCodeIntegration } = await import('../core/claude-mcp.js');
1658
+ const result = await setupClaudeCodeIntegration(cwd);
1659
+ if (result.detected) {
1660
+ claudeSpinner.succeed('Claude Code integration updated');
1661
+ if (result.mcpConfigured) {
1662
+ console.log(chalk.gray(' • MCP server added to .mcp.json'));
1663
+ }
1664
+ if (result.skillsInstalled.length > 0) {
1665
+ console.log(chalk.gray(` • ${result.skillsInstalled.length} skills updated in .claude/commands/`));
1666
+ }
1667
+ if (result.agentTeamsEnabled) {
1668
+ console.log(chalk.gray(' • Multi-agent teams enabled in .claude/settings.json'));
1669
+ }
1670
+ if (result.agentDefinitionsInstalled.length > 0) {
1671
+ console.log(chalk.gray(` • ${result.agentDefinitionsInstalled.length} agent definitions updated in .claude/agents/`));
1513
1672
  }
1514
1673
  }
1515
- catch {
1516
- // Skip if Ralph scripts installation fails
1517
- }
1518
- // Ensure PLANS.md exists (create if missing, never overwrite)
1519
- try {
1520
- const { initPlans } = await import('../core/plans-manager.js');
1521
- await initPlans(cwd);
1522
- }
1523
- catch {
1524
- // Non-blocking
1525
- }
1526
- // Migrate memory directory if old structure exists
1527
- try {
1528
- await migrateMemoryDirectory();
1529
- }
1530
- catch {
1531
- // Silently skip if migration fails
1532
- }
1533
- // Install plugin in Claude Code
1534
- try {
1535
- await setupClaudeCodePlugin();
1536
- }
1537
- catch {
1538
- // Silently skip if plugin installation fails
1674
+ else {
1675
+ claudeSpinner.info('Claude Code not detected (skipped)');
1539
1676
  }
1540
- // Clean up any accidental duplicate directories
1541
- try {
1542
- const fsPromises = await import('fs/promises');
1543
- const accidentalDir = path.join(cwd, '.rulebook', '.rulebook');
1544
- if (existsSync(accidentalDir)) {
1545
- await fsPromises.rm(accidentalDir, { recursive: true, force: true });
1546
- }
1677
+ }
1678
+ catch {
1679
+ claudeSpinner.info('Claude Code integration skipped');
1680
+ }
1681
+ // Install/update Ralph shell scripts
1682
+ try {
1683
+ const { installRalphScripts } = await import('../core/ralph-scripts.js');
1684
+ const scripts = await installRalphScripts(cwd);
1685
+ if (scripts.length > 0) {
1686
+ console.log(chalk.gray(` • ${scripts.length} Ralph scripts updated in .rulebook/scripts/`));
1547
1687
  }
1548
- catch {
1549
- // Ignore cleanup errors
1550
- }
1551
- // Success message
1552
- console.log(chalk.bold.green('\n✅ Update complete!\n'));
1553
- console.log(chalk.white('Updated components:'));
1554
- console.log(chalk.green(' ✓ AGENTS.md - Merged with latest templates'));
1555
- console.log(chalk.green(` ✓ .rulebook - Updated to v${getRulebookVersion()}`));
1556
- console.log(chalk.white('\nWhat was updated:'));
1557
- console.log(chalk.gray(` - ${detection.languages.length} language templates`));
1558
- console.log(chalk.gray(` - ${detection.modules.filter((m) => m.detected).length} MCP modules`));
1559
- console.log(chalk.gray(' - Git workflow rules'));
1560
- console.log(chalk.gray(' - Rulebook task management'));
1561
- console.log(chalk.gray(' - Pre-commit command standardization'));
1562
- console.log(chalk.yellow('\n⚠ Review the updated AGENTS.md to ensure your custom rules are preserved'));
1563
- console.log(chalk.white('\nNext steps:'));
1564
- console.log(chalk.gray(' 1. Review AGENTS.md changes'));
1565
- console.log(chalk.gray(' 2. Test that your project still builds'));
1566
- console.log(chalk.gray(' 3. Run quality checks (lint, test, build)'));
1567
- console.log(chalk.gray(' 4. Commit the updated files\n'));
1568
- if (minimalMode && minimalArtifacts.length > 0) {
1569
- console.log(chalk.green('Essentials ensured:'));
1570
- for (const artifact of minimalArtifacts) {
1571
- console.log(chalk.gray(` - ${path.relative(cwd, artifact)}`));
1572
- }
1573
- console.log('');
1688
+ }
1689
+ catch {
1690
+ // Skip if Ralph scripts installation fails
1691
+ }
1692
+ // Ensure PLANS.md exists (create if missing, never overwrite)
1693
+ try {
1694
+ const { initPlans } = await import('../core/plans-manager.js');
1695
+ await initPlans(cwd);
1696
+ }
1697
+ catch {
1698
+ // Non-blocking
1699
+ }
1700
+ // Migrate memory directory if old structure exists
1701
+ try {
1702
+ await migrateMemoryDirectory();
1703
+ }
1704
+ catch {
1705
+ // Silently skip if migration fails
1706
+ }
1707
+ // Install plugin in Claude Code
1708
+ try {
1709
+ await setupClaudeCodePlugin();
1710
+ }
1711
+ catch {
1712
+ // Silently skip if plugin installation fails
1713
+ }
1714
+ // Clean up any accidental duplicate directories
1715
+ try {
1716
+ const fsPromises = await import('fs/promises');
1717
+ const accidentalDir = path.join(cwd, '.rulebook', '.rulebook');
1718
+ if (existsSync(accidentalDir)) {
1719
+ await fsPromises.rm(accidentalDir, { recursive: true, force: true });
1574
1720
  }
1575
1721
  }
1576
- catch (error) {
1577
- console.error(chalk.red('\n❌ Update failed:'), error);
1578
- process.exit(1);
1722
+ catch {
1723
+ // Ignore cleanup errors
1724
+ }
1725
+ // Success message
1726
+ console.log(chalk.bold.green('\n✅ Update complete!\n'));
1727
+ console.log(chalk.white('Updated components:'));
1728
+ console.log(chalk.green(' ✓ AGENTS.md - Merged with latest templates'));
1729
+ console.log(chalk.green(` ✓ .rulebook - Updated to v${getRulebookVersion()}`));
1730
+ console.log(chalk.white('\nWhat was updated:'));
1731
+ console.log(chalk.gray(` - ${detection.languages.length} language templates`));
1732
+ console.log(chalk.gray(` - ${detection.modules.filter((m) => m.detected).length} MCP modules`));
1733
+ console.log(chalk.gray(' - Git workflow rules'));
1734
+ console.log(chalk.gray(' - Rulebook task management'));
1735
+ console.log(chalk.gray(' - Pre-commit command standardization'));
1736
+ console.log(chalk.yellow('\n⚠ Review the updated AGENTS.md to ensure your custom rules are preserved'));
1737
+ console.log(chalk.white('\nNext steps:'));
1738
+ console.log(chalk.gray(' 1. Review AGENTS.md changes'));
1739
+ console.log(chalk.gray(' 2. Test that your project still builds'));
1740
+ console.log(chalk.gray(' 3. Run quality checks (lint, test, build)'));
1741
+ console.log(chalk.gray(' 4. Commit the updated files\n'));
1742
+ if (minimalMode && minimalArtifacts.length > 0) {
1743
+ console.log(chalk.green('Essentials ensured:'));
1744
+ for (const artifact of minimalArtifacts) {
1745
+ console.log(chalk.gray(` - ${path.relative(cwd, artifact)}`));
1746
+ }
1747
+ console.log('');
1579
1748
  }
1580
1749
  }
1581
1750
  // ============================================
@@ -3336,4 +3505,156 @@ export async function ralphImportIssuesCommand(options) {
3336
3505
  process.exit(1);
3337
3506
  }
3338
3507
  }
3508
+ // ============================================
3509
+ // Workspace Commands (v4.2)
3510
+ // ============================================
3511
+ /** Resolve the workspace config path inside .rulebook/ */
3512
+ function getWorkspaceConfigPath(cwd) {
3513
+ return path.join(cwd, '.rulebook', 'workspace.json');
3514
+ }
3515
+ export async function workspaceInitCommand() {
3516
+ const cwd = process.cwd();
3517
+ const configPath = getWorkspaceConfigPath(cwd);
3518
+ if (existsSync(configPath)) {
3519
+ console.log(chalk.yellow('Workspace already initialized at .rulebook/workspace.json'));
3520
+ return;
3521
+ }
3522
+ const spinner = ora('Detecting workspace structure...').start();
3523
+ // Try auto-discovery first
3524
+ let config = WorkspaceManager.findWorkspaceConfig(cwd);
3525
+ if (config) {
3526
+ spinner.succeed(`Detected workspace: ${config.name} (${config.projects.length} projects)`);
3527
+ console.log('\n Projects found:');
3528
+ for (const p of config.projects) {
3529
+ console.log(` - ${chalk.cyan(p.name)} → ${p.path}`);
3530
+ }
3531
+ }
3532
+ else {
3533
+ spinner.info('No workspace structure detected. Creating empty workspace config.');
3534
+ config = {
3535
+ name: path.basename(cwd),
3536
+ version: '1.0.0',
3537
+ projects: [],
3538
+ };
3539
+ }
3540
+ // Ensure .rulebook/ directory exists
3541
+ const rulebookDir = path.join(cwd, '.rulebook');
3542
+ if (!existsSync(rulebookDir)) {
3543
+ const { mkdirSync } = await import('fs');
3544
+ mkdirSync(rulebookDir, { recursive: true });
3545
+ }
3546
+ // Write config
3547
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3548
+ console.log(chalk.green(`\n Created: .rulebook/workspace.json`));
3549
+ // Check for legacy .mcp.json files
3550
+ const migration = await migrateLegacyMcpConfigs(cwd);
3551
+ if (migration.migratedFiles.length > 0) {
3552
+ console.log(chalk.yellow(`\n Migrated ${migration.migratedFiles.length} legacy .mcp.json files (backups at *.mcp.json.bak)`));
3553
+ }
3554
+ console.log(chalk.dim('\n Use `rulebook workspace add <path>` to add more projects'));
3555
+ console.log(chalk.dim(' Use `rulebook mcp init --workspace` to configure MCP for workspace'));
3556
+ }
3557
+ export async function workspaceAddCommand(projectPath) {
3558
+ const cwd = process.cwd();
3559
+ const configPath = getWorkspaceConfigPath(cwd);
3560
+ if (!existsSync(configPath)) {
3561
+ console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
3562
+ process.exit(1);
3563
+ }
3564
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
3565
+ const resolvedPath = path.resolve(cwd, projectPath);
3566
+ const name = path.basename(resolvedPath);
3567
+ // Check for duplicates
3568
+ if (config.projects.some((p) => p.name === name)) {
3569
+ console.error(chalk.red(`Project "${name}" already exists in workspace.`));
3570
+ process.exit(1);
3571
+ }
3572
+ // Use relative path if within workspace, absolute otherwise
3573
+ const isSubpath = resolvedPath.startsWith(cwd);
3574
+ const storedPath = isSubpath ? path.relative(cwd, resolvedPath) : resolvedPath;
3575
+ const project = {
3576
+ name,
3577
+ path: storedPath.startsWith('.') ? storedPath : `./${storedPath}`,
3578
+ };
3579
+ config.projects.push(project);
3580
+ if (!config.defaultProject) {
3581
+ config.defaultProject = name;
3582
+ }
3583
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3584
+ console.log(chalk.green(`Added project "${name}" → ${project.path}`));
3585
+ }
3586
+ export async function workspaceRemoveCommand(projectName) {
3587
+ const cwd = process.cwd();
3588
+ const configPath = getWorkspaceConfigPath(cwd);
3589
+ if (!existsSync(configPath)) {
3590
+ console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
3591
+ process.exit(1);
3592
+ }
3593
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
3594
+ const idx = config.projects.findIndex((p) => p.name === projectName);
3595
+ if (idx === -1) {
3596
+ console.error(chalk.red(`Project "${projectName}" not found in workspace.`));
3597
+ process.exit(1);
3598
+ }
3599
+ config.projects.splice(idx, 1);
3600
+ if (config.defaultProject === projectName) {
3601
+ config.defaultProject = config.projects[0]?.name;
3602
+ }
3603
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3604
+ console.log(chalk.green(`Removed project "${projectName}" from workspace.`));
3605
+ }
3606
+ export async function workspaceListCommand() {
3607
+ const cwd = process.cwd();
3608
+ const config = WorkspaceManager.findWorkspaceConfig(cwd);
3609
+ if (!config) {
3610
+ console.log(chalk.yellow('No workspace found. Run `rulebook workspace init` to create one.'));
3611
+ return;
3612
+ }
3613
+ console.log(chalk.bold(`\nWorkspace: ${config.name}`));
3614
+ console.log(chalk.dim(` Version: ${config.version}`));
3615
+ if (config.defaultProject) {
3616
+ console.log(chalk.dim(` Default: ${config.defaultProject}`));
3617
+ }
3618
+ console.log();
3619
+ for (const p of config.projects) {
3620
+ const isDefault = p.name === config.defaultProject;
3621
+ const marker = isDefault ? chalk.green(' (default)') : '';
3622
+ const disabled = p.enabled === false ? chalk.red(' [disabled]') : '';
3623
+ console.log(` ${chalk.cyan(p.name)}${marker}${disabled}`);
3624
+ console.log(` ${chalk.dim(p.path)}`);
3625
+ }
3626
+ console.log(chalk.dim(`\n ${config.projects.length} project(s) total`));
3627
+ }
3628
+ export async function workspaceStatusCommand() {
3629
+ const cwd = process.cwd();
3630
+ const config = WorkspaceManager.findWorkspaceConfig(cwd);
3631
+ if (!config) {
3632
+ console.log(chalk.yellow('No workspace found. Run `rulebook workspace init` to create one.'));
3633
+ return;
3634
+ }
3635
+ const manager = new WorkspaceManager(config, cwd);
3636
+ const spinner = ora('Checking workspace status...').start();
3637
+ try {
3638
+ const status = await manager.getStatus();
3639
+ spinner.stop();
3640
+ console.log(chalk.bold(`\nWorkspace: ${status.name}`));
3641
+ console.log(` Projects: ${status.totalProjects} | Active workers: ${status.activeWorkers}`);
3642
+ console.log();
3643
+ for (const p of status.projects) {
3644
+ const configBadge = p.hasRulebookConfig ? chalk.green('.rulebook') : chalk.dim('no config');
3645
+ const memBadge = p.memoryEnabled ? chalk.blue('memory') : '';
3646
+ const taskBadge = p.taskCount > 0 ? chalk.yellow(`${p.taskCount} tasks`) : '';
3647
+ const badges = [configBadge, memBadge, taskBadge].filter(Boolean).join(' ');
3648
+ console.log(` ${chalk.cyan(p.name)} ${badges}`);
3649
+ console.log(` ${chalk.dim(p.path)}`);
3650
+ }
3651
+ console.log();
3652
+ }
3653
+ catch (error) {
3654
+ spinner.fail(`Failed: ${String(error)}`);
3655
+ }
3656
+ finally {
3657
+ await manager.shutdownAll();
3658
+ }
3659
+ }
3339
3660
  //# sourceMappingURL=commands.js.map