@hivehub/rulebook 4.1.0 → 4.2.0

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 (334) 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/dist/cli/commands.d.ts +18 -6
  19. package/dist/cli/commands.d.ts.map +1 -1
  20. package/dist/cli/commands.js +717 -406
  21. package/dist/cli/commands.js.map +1 -1
  22. package/dist/core/claude-mcp.d.ts +4 -2
  23. package/dist/core/claude-mcp.d.ts.map +1 -1
  24. package/dist/core/claude-mcp.js +14 -9
  25. package/dist/core/claude-mcp.js.map +1 -1
  26. package/dist/core/generator.d.ts.map +1 -1
  27. package/dist/core/generator.js +13 -0
  28. package/dist/core/generator.js.map +1 -1
  29. package/dist/core/indexer/background-indexer.d.ts.map +1 -1
  30. package/dist/core/indexer/background-indexer.js +26 -5
  31. package/dist/core/indexer/background-indexer.js.map +1 -1
  32. package/dist/core/indexer/file-parser.d.ts.map +1 -1
  33. package/dist/core/indexer/file-parser.js +1 -1
  34. package/dist/core/indexer/file-parser.js.map +1 -1
  35. package/dist/core/indexer/indexer-types.d.ts.map +1 -1
  36. package/dist/core/workspace/legacy-migrator.d.ts +29 -0
  37. package/dist/core/workspace/legacy-migrator.d.ts.map +1 -0
  38. package/dist/core/workspace/legacy-migrator.js +142 -0
  39. package/dist/core/workspace/legacy-migrator.js.map +1 -0
  40. package/dist/core/workspace/project-worker.d.ts +49 -0
  41. package/dist/core/workspace/project-worker.d.ts.map +1 -0
  42. package/dist/core/workspace/project-worker.js +108 -0
  43. package/dist/core/workspace/project-worker.js.map +1 -0
  44. package/dist/core/workspace/workspace-manager.d.ts +90 -0
  45. package/dist/core/workspace/workspace-manager.d.ts.map +1 -0
  46. package/dist/core/workspace/workspace-manager.js +337 -0
  47. package/dist/core/workspace/workspace-manager.js.map +1 -0
  48. package/dist/core/workspace/workspace-types.d.ts +37 -0
  49. package/dist/core/workspace/workspace-types.d.ts.map +1 -0
  50. package/dist/core/workspace/workspace-types.js +8 -0
  51. package/dist/core/workspace/workspace-types.js.map +1 -0
  52. package/dist/index.js +43 -7
  53. package/dist/index.js.map +1 -1
  54. package/dist/mcp/rulebook-server.d.ts.map +1 -1
  55. package/dist/mcp/rulebook-server.js +367 -100
  56. package/dist/mcp/rulebook-server.js.map +1 -1
  57. package/dist/memory/memory-manager.js +2 -2
  58. package/dist/memory/memory-manager.js.map +1 -1
  59. package/dist/memory/memory-search.js.map +1 -1
  60. package/dist/memory/memory-store.d.ts.map +1 -1
  61. package/dist/memory/memory-store.js +1 -1
  62. package/dist/memory/memory-store.js.map +1 -1
  63. package/dist/types.d.ts +1 -0
  64. package/dist/types.d.ts.map +1 -1
  65. package/package.json +22 -21
  66. package/templates/agents/implementer.md +35 -35
  67. package/templates/agents/researcher.md +34 -34
  68. package/templates/agents/team-lead.md +34 -34
  69. package/templates/agents/tester.md +42 -42
  70. package/templates/ci/rulebook-review.yml +26 -26
  71. package/templates/cli/AIDER.md +49 -49
  72. package/templates/cli/AMAZON_Q.md +25 -25
  73. package/templates/cli/AUGGIE.md +32 -32
  74. package/templates/cli/CLAUDE.md +117 -117
  75. package/templates/cli/CLINE.md +99 -99
  76. package/templates/cli/CODEBUDDY.md +20 -20
  77. package/templates/cli/CODEIUM.md +20 -20
  78. package/templates/cli/CODEX.md +21 -21
  79. package/templates/cli/CONTINUE.md +34 -34
  80. package/templates/cli/CURSOR_CLI.md +62 -62
  81. package/templates/cli/FACTORY.md +18 -18
  82. package/templates/cli/GEMINI.md +35 -35
  83. package/templates/cli/KILOCODE.md +18 -18
  84. package/templates/cli/OPENCODE.md +18 -18
  85. package/templates/cli/_GENERIC_TEMPLATE.md +29 -29
  86. package/templates/commands/rulebook-memory-save.md +48 -48
  87. package/templates/commands/rulebook-memory-search.md +47 -47
  88. package/templates/commands/rulebook-task-apply.md +67 -67
  89. package/templates/commands/rulebook-task-archive.md +94 -94
  90. package/templates/commands/rulebook-task-create.md +93 -93
  91. package/templates/commands/rulebook-task-list.md +42 -42
  92. package/templates/commands/rulebook-task-show.md +52 -52
  93. package/templates/commands/rulebook-task-validate.md +53 -53
  94. package/templates/core/AGENTS_LEAN.md +25 -25
  95. package/templates/core/AGENTS_OVERRIDE.md +16 -16
  96. package/templates/core/AGENT_AUTOMATION.md +288 -288
  97. package/templates/core/DAG.md +304 -304
  98. package/templates/core/DOCUMENTATION_RULES.md +36 -36
  99. package/templates/core/MULTI_AGENT.md +74 -74
  100. package/templates/core/PLANS.md +28 -28
  101. package/templates/core/QUALITY_ENFORCEMENT.md +68 -68
  102. package/templates/core/RALPH.md +471 -471
  103. package/templates/core/RULEBOOK.md +1935 -1935
  104. package/templates/core/WORKSPACE.md +69 -0
  105. package/templates/frameworks/ANGULAR.md +36 -36
  106. package/templates/frameworks/DJANGO.md +83 -83
  107. package/templates/frameworks/ELECTRON.md +147 -147
  108. package/templates/frameworks/FLASK.md +38 -38
  109. package/templates/frameworks/FLUTTER.md +55 -55
  110. package/templates/frameworks/JQUERY.md +32 -32
  111. package/templates/frameworks/LARAVEL.md +38 -38
  112. package/templates/frameworks/NESTJS.md +43 -43
  113. package/templates/frameworks/NEXTJS.md +127 -127
  114. package/templates/frameworks/NUXT.md +40 -40
  115. package/templates/frameworks/RAILS.md +66 -66
  116. package/templates/frameworks/REACT.md +38 -38
  117. package/templates/frameworks/REACT_NATIVE.md +47 -47
  118. package/templates/frameworks/SPRING.md +39 -39
  119. package/templates/frameworks/SYMFONY.md +36 -36
  120. package/templates/frameworks/VUE.md +36 -36
  121. package/templates/frameworks/ZEND.md +35 -35
  122. package/templates/git/CI_CD_PATTERNS.md +661 -661
  123. package/templates/git/GITHUB_ACTIONS.md +728 -728
  124. package/templates/git/GITLAB_CI.md +730 -730
  125. package/templates/git/GIT_WORKFLOW.md +1157 -1157
  126. package/templates/git/SECRETS_MANAGEMENT.md +585 -585
  127. package/templates/hooks/COMMIT_MSG.md +530 -530
  128. package/templates/hooks/POST_CHECKOUT.md +546 -546
  129. package/templates/hooks/PREPARE_COMMIT_MSG.md +619 -619
  130. package/templates/hooks/PRE_COMMIT.md +414 -414
  131. package/templates/hooks/PRE_PUSH.md +601 -601
  132. package/templates/ides/CONTINUE_RULES.md +16 -16
  133. package/templates/ides/COPILOT.md +37 -37
  134. package/templates/ides/COPILOT_INSTRUCTIONS.md +23 -23
  135. package/templates/ides/CURSOR.md +43 -43
  136. package/templates/ides/GEMINI_RULES.md +17 -17
  137. package/templates/ides/JETBRAINS_AI.md +35 -35
  138. package/templates/ides/REPLIT.md +36 -36
  139. package/templates/ides/TABNINE.md +29 -29
  140. package/templates/ides/VSCODE.md +40 -40
  141. package/templates/ides/WINDSURF.md +36 -36
  142. package/templates/ides/WINDSURF_RULES.md +14 -14
  143. package/templates/ides/ZED.md +32 -32
  144. package/templates/ides/cursor-mdc/go.mdc +24 -24
  145. package/templates/ides/cursor-mdc/python.mdc +24 -24
  146. package/templates/ides/cursor-mdc/quality.mdc +25 -25
  147. package/templates/ides/cursor-mdc/ralph.mdc +39 -39
  148. package/templates/ides/cursor-mdc/rulebook.mdc +38 -38
  149. package/templates/ides/cursor-mdc/rust.mdc +24 -24
  150. package/templates/ides/cursor-mdc/typescript.mdc +25 -25
  151. package/templates/languages/C.md +333 -333
  152. package/templates/languages/CPP.md +743 -743
  153. package/templates/languages/CSHARP.md +417 -417
  154. package/templates/languages/ELIXIR.md +454 -454
  155. package/templates/languages/ERLANG.md +361 -361
  156. package/templates/languages/GO.md +645 -645
  157. package/templates/languages/HASKELL.md +177 -177
  158. package/templates/languages/JAVA.md +607 -607
  159. package/templates/languages/JAVASCRIPT.md +631 -631
  160. package/templates/languages/JULIA.md +97 -97
  161. package/templates/languages/KOTLIN.md +511 -511
  162. package/templates/languages/LISP.md +100 -100
  163. package/templates/languages/LUA.md +74 -74
  164. package/templates/languages/OBJECTIVEC.md +90 -90
  165. package/templates/languages/PHP.md +416 -416
  166. package/templates/languages/PYTHON.md +682 -682
  167. package/templates/languages/RUBY.md +421 -421
  168. package/templates/languages/RUST.md +477 -477
  169. package/templates/languages/SAS.md +73 -73
  170. package/templates/languages/SCALA.md +348 -348
  171. package/templates/languages/SOLIDITY.md +580 -580
  172. package/templates/languages/SQL.md +137 -137
  173. package/templates/languages/SWIFT.md +466 -466
  174. package/templates/languages/TYPESCRIPT.md +591 -591
  175. package/templates/languages/ZIG.md +265 -265
  176. package/templates/modules/ATLASSIAN.md +255 -255
  177. package/templates/modules/CONTEXT7.md +54 -54
  178. package/templates/modules/FIGMA.md +267 -267
  179. package/templates/modules/GITHUB_MCP.md +64 -64
  180. package/templates/modules/GRAFANA.md +328 -328
  181. package/templates/modules/MEMORY.md +126 -126
  182. package/templates/modules/NOTION.md +247 -247
  183. package/templates/modules/PLAYWRIGHT.md +90 -90
  184. package/templates/modules/RULEBOOK_MCP.md +156 -156
  185. package/templates/modules/SERENA.md +337 -337
  186. package/templates/modules/SUPABASE.md +223 -223
  187. package/templates/modules/SYNAP.md +69 -69
  188. package/templates/modules/VECTORIZER.md +63 -63
  189. package/templates/modules/sequential-thinking.md +42 -42
  190. package/templates/ralph/ralph-history.bat +4 -4
  191. package/templates/ralph/ralph-history.sh +5 -5
  192. package/templates/ralph/ralph-init.bat +5 -5
  193. package/templates/ralph/ralph-init.sh +5 -5
  194. package/templates/ralph/ralph-pause.bat +5 -5
  195. package/templates/ralph/ralph-pause.sh +5 -5
  196. package/templates/ralph/ralph-run.bat +5 -5
  197. package/templates/ralph/ralph-run.sh +5 -5
  198. package/templates/ralph/ralph-status.bat +4 -4
  199. package/templates/ralph/ralph-status.sh +5 -5
  200. package/templates/services/AZURE_BLOB.md +184 -184
  201. package/templates/services/CASSANDRA.md +239 -239
  202. package/templates/services/DATADOG.md +26 -26
  203. package/templates/services/DOCKER.md +124 -124
  204. package/templates/services/DOCKER_COMPOSE.md +168 -168
  205. package/templates/services/DYNAMODB.md +308 -308
  206. package/templates/services/ELASTICSEARCH.md +347 -347
  207. package/templates/services/GCS.md +178 -178
  208. package/templates/services/HELM.md +194 -194
  209. package/templates/services/INFLUXDB.md +265 -265
  210. package/templates/services/KAFKA.md +341 -341
  211. package/templates/services/KUBERNETES.md +208 -208
  212. package/templates/services/MARIADB.md +183 -183
  213. package/templates/services/MEMCACHED.md +242 -242
  214. package/templates/services/MINIO.md +201 -201
  215. package/templates/services/MONGODB.md +268 -268
  216. package/templates/services/MYSQL.md +358 -358
  217. package/templates/services/NEO4J.md +247 -247
  218. package/templates/services/OPENTELEMETRY.md +25 -25
  219. package/templates/services/ORACLE.md +290 -290
  220. package/templates/services/PINO.md +24 -24
  221. package/templates/services/POSTGRESQL.md +326 -326
  222. package/templates/services/PROMETHEUS.md +33 -33
  223. package/templates/services/RABBITMQ.md +286 -286
  224. package/templates/services/REDIS.md +292 -292
  225. package/templates/services/S3.md +298 -298
  226. package/templates/services/SENTRY.md +23 -23
  227. package/templates/services/SQLITE.md +294 -294
  228. package/templates/services/SQLSERVER.md +294 -294
  229. package/templates/services/WINSTON.md +30 -30
  230. package/templates/skills/cli/aider/SKILL.md +59 -59
  231. package/templates/skills/cli/amazon-q/SKILL.md +35 -35
  232. package/templates/skills/cli/auggie/SKILL.md +42 -42
  233. package/templates/skills/cli/claude/SKILL.md +42 -42
  234. package/templates/skills/cli/cline/SKILL.md +42 -42
  235. package/templates/skills/cli/codebuddy/SKILL.md +30 -30
  236. package/templates/skills/cli/codeium/SKILL.md +30 -30
  237. package/templates/skills/cli/codex/SKILL.md +31 -31
  238. package/templates/skills/cli/continue/SKILL.md +44 -44
  239. package/templates/skills/cli/cursor-cli/SKILL.md +38 -38
  240. package/templates/skills/cli/factory/SKILL.md +28 -28
  241. package/templates/skills/cli/gemini/SKILL.md +45 -45
  242. package/templates/skills/cli/kilocode/SKILL.md +28 -28
  243. package/templates/skills/cli/opencode/SKILL.md +28 -28
  244. package/templates/skills/core/agent-automation/SKILL.md +194 -194
  245. package/templates/skills/core/dag/SKILL.md +314 -314
  246. package/templates/skills/core/documentation-rules/SKILL.md +46 -46
  247. package/templates/skills/core/quality-enforcement/SKILL.md +78 -78
  248. package/templates/skills/core/rulebook/SKILL.md +176 -176
  249. package/templates/skills/frameworks/angular/SKILL.md +46 -46
  250. package/templates/skills/frameworks/django/SKILL.md +93 -93
  251. package/templates/skills/frameworks/electron/SKILL.md +157 -157
  252. package/templates/skills/frameworks/flask/SKILL.md +48 -48
  253. package/templates/skills/frameworks/flutter/SKILL.md +65 -65
  254. package/templates/skills/frameworks/jquery/SKILL.md +42 -42
  255. package/templates/skills/frameworks/laravel/SKILL.md +48 -48
  256. package/templates/skills/frameworks/nestjs/SKILL.md +53 -53
  257. package/templates/skills/frameworks/nextjs/SKILL.md +137 -137
  258. package/templates/skills/frameworks/nuxt/SKILL.md +50 -50
  259. package/templates/skills/frameworks/rails/SKILL.md +76 -76
  260. package/templates/skills/frameworks/react/SKILL.md +48 -48
  261. package/templates/skills/frameworks/react-native/SKILL.md +57 -57
  262. package/templates/skills/frameworks/spring/SKILL.md +49 -49
  263. package/templates/skills/frameworks/symfony/SKILL.md +46 -46
  264. package/templates/skills/frameworks/vue/SKILL.md +46 -46
  265. package/templates/skills/frameworks/zend/SKILL.md +45 -45
  266. package/templates/skills/ides/copilot/SKILL.md +47 -47
  267. package/templates/skills/ides/cursor/SKILL.md +53 -53
  268. package/templates/skills/ides/jetbrains-ai/SKILL.md +45 -45
  269. package/templates/skills/ides/replit/SKILL.md +46 -46
  270. package/templates/skills/ides/tabnine/SKILL.md +39 -39
  271. package/templates/skills/ides/vscode/SKILL.md +50 -50
  272. package/templates/skills/ides/windsurf/SKILL.md +46 -46
  273. package/templates/skills/ides/zed/SKILL.md +42 -42
  274. package/templates/skills/languages/c/SKILL.md +343 -343
  275. package/templates/skills/languages/cpp/SKILL.md +753 -753
  276. package/templates/skills/languages/csharp/SKILL.md +427 -427
  277. package/templates/skills/languages/elixir/SKILL.md +464 -464
  278. package/templates/skills/languages/erlang/SKILL.md +371 -371
  279. package/templates/skills/languages/go/SKILL.md +655 -655
  280. package/templates/skills/languages/haskell/SKILL.md +187 -187
  281. package/templates/skills/languages/java/SKILL.md +617 -617
  282. package/templates/skills/languages/javascript/SKILL.md +641 -641
  283. package/templates/skills/languages/julia/SKILL.md +107 -107
  284. package/templates/skills/languages/kotlin/SKILL.md +521 -521
  285. package/templates/skills/languages/lisp/SKILL.md +110 -110
  286. package/templates/skills/languages/lua/SKILL.md +84 -84
  287. package/templates/skills/languages/objectivec/SKILL.md +100 -100
  288. package/templates/skills/languages/php/SKILL.md +426 -426
  289. package/templates/skills/languages/python/SKILL.md +692 -692
  290. package/templates/skills/languages/ruby/SKILL.md +431 -431
  291. package/templates/skills/languages/rust/SKILL.md +487 -487
  292. package/templates/skills/languages/sas/SKILL.md +83 -83
  293. package/templates/skills/languages/scala/SKILL.md +358 -358
  294. package/templates/skills/languages/solidity/SKILL.md +590 -590
  295. package/templates/skills/languages/sql/SKILL.md +147 -147
  296. package/templates/skills/languages/swift/SKILL.md +476 -476
  297. package/templates/skills/languages/typescript/SKILL.md +302 -302
  298. package/templates/skills/languages/zig/SKILL.md +275 -275
  299. package/templates/skills/modules/atlassian/SKILL.md +265 -265
  300. package/templates/skills/modules/context7/SKILL.md +64 -64
  301. package/templates/skills/modules/figma/SKILL.md +277 -277
  302. package/templates/skills/modules/github-mcp/SKILL.md +74 -74
  303. package/templates/skills/modules/grafana/SKILL.md +338 -338
  304. package/templates/skills/modules/memory/SKILL.md +73 -73
  305. package/templates/skills/modules/notion/SKILL.md +257 -257
  306. package/templates/skills/modules/playwright/SKILL.md +100 -100
  307. package/templates/skills/modules/rulebook-mcp/SKILL.md +166 -166
  308. package/templates/skills/modules/serena/SKILL.md +347 -347
  309. package/templates/skills/modules/supabase/SKILL.md +233 -233
  310. package/templates/skills/modules/synap/SKILL.md +79 -79
  311. package/templates/skills/modules/vectorizer/SKILL.md +73 -73
  312. package/templates/skills/services/azure-blob/SKILL.md +194 -194
  313. package/templates/skills/services/cassandra/SKILL.md +249 -249
  314. package/templates/skills/services/dynamodb/SKILL.md +318 -318
  315. package/templates/skills/services/elasticsearch/SKILL.md +357 -357
  316. package/templates/skills/services/gcs/SKILL.md +188 -188
  317. package/templates/skills/services/influxdb/SKILL.md +275 -275
  318. package/templates/skills/services/kafka/SKILL.md +351 -351
  319. package/templates/skills/services/mariadb/SKILL.md +193 -193
  320. package/templates/skills/services/memcached/SKILL.md +252 -252
  321. package/templates/skills/services/minio/SKILL.md +211 -211
  322. package/templates/skills/services/mongodb/SKILL.md +278 -278
  323. package/templates/skills/services/mysql/SKILL.md +368 -368
  324. package/templates/skills/services/neo4j/SKILL.md +257 -257
  325. package/templates/skills/services/oracle/SKILL.md +300 -300
  326. package/templates/skills/services/postgresql/SKILL.md +336 -336
  327. package/templates/skills/services/rabbitmq/SKILL.md +296 -296
  328. package/templates/skills/services/redis/SKILL.md +302 -302
  329. package/templates/skills/services/s3/SKILL.md +308 -308
  330. package/templates/skills/services/sqlite/SKILL.md +304 -304
  331. package/templates/skills/services/sqlserver/SKILL.md +304 -304
  332. package/templates/skills/workflows/ralph/SKILL.md +309 -309
  333. package/templates/skills/workflows/ralph/install.sh +87 -87
  334. 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,146 @@ export async function ralphImportIssuesCommand(options) {
3336
3505
  process.exit(1);
3337
3506
  }
3338
3507
  }
3508
+ // ============================================
3509
+ // Workspace Commands (v4.2)
3510
+ // ============================================
3511
+ export async function workspaceInitCommand() {
3512
+ const cwd = process.cwd();
3513
+ const configPath = path.join(cwd, '.rulebook-workspace.json');
3514
+ if (existsSync(configPath)) {
3515
+ console.log(chalk.yellow('Workspace already initialized at .rulebook-workspace.json'));
3516
+ return;
3517
+ }
3518
+ const spinner = ora('Detecting workspace structure...').start();
3519
+ // Try auto-discovery first
3520
+ let config = WorkspaceManager.findWorkspaceConfig(cwd);
3521
+ if (config) {
3522
+ spinner.succeed(`Detected workspace: ${config.name} (${config.projects.length} projects)`);
3523
+ console.log('\n Projects found:');
3524
+ for (const p of config.projects) {
3525
+ console.log(` - ${chalk.cyan(p.name)} → ${p.path}`);
3526
+ }
3527
+ }
3528
+ else {
3529
+ spinner.info('No workspace structure detected. Creating empty workspace config.');
3530
+ config = {
3531
+ name: path.basename(cwd),
3532
+ version: '1.0.0',
3533
+ projects: [],
3534
+ };
3535
+ }
3536
+ // Write config
3537
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3538
+ console.log(chalk.green(`\n Created: ${configPath}`));
3539
+ // Check for legacy .mcp.json files
3540
+ const migration = await migrateLegacyMcpConfigs(cwd);
3541
+ if (migration.migratedFiles.length > 0) {
3542
+ console.log(chalk.yellow(`\n Migrated ${migration.migratedFiles.length} legacy .mcp.json files (backups at *.mcp.json.bak)`));
3543
+ }
3544
+ console.log(chalk.dim('\n Use `rulebook workspace add <path>` to add more projects'));
3545
+ console.log(chalk.dim(' Use `rulebook mcp init --workspace` to configure MCP for workspace'));
3546
+ }
3547
+ export async function workspaceAddCommand(projectPath) {
3548
+ const cwd = process.cwd();
3549
+ const configPath = path.join(cwd, '.rulebook-workspace.json');
3550
+ if (!existsSync(configPath)) {
3551
+ console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
3552
+ process.exit(1);
3553
+ }
3554
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
3555
+ const resolvedPath = path.resolve(cwd, projectPath);
3556
+ const name = path.basename(resolvedPath);
3557
+ // Check for duplicates
3558
+ if (config.projects.some((p) => p.name === name)) {
3559
+ console.error(chalk.red(`Project "${name}" already exists in workspace.`));
3560
+ process.exit(1);
3561
+ }
3562
+ // Use relative path if within workspace, absolute otherwise
3563
+ const isSubpath = resolvedPath.startsWith(cwd);
3564
+ const storedPath = isSubpath ? path.relative(cwd, resolvedPath) : resolvedPath;
3565
+ const project = {
3566
+ name,
3567
+ path: storedPath.startsWith('.') ? storedPath : `./${storedPath}`,
3568
+ };
3569
+ config.projects.push(project);
3570
+ if (!config.defaultProject) {
3571
+ config.defaultProject = name;
3572
+ }
3573
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3574
+ console.log(chalk.green(`Added project "${name}" → ${project.path}`));
3575
+ }
3576
+ export async function workspaceRemoveCommand(projectName) {
3577
+ const cwd = process.cwd();
3578
+ const configPath = path.join(cwd, '.rulebook-workspace.json');
3579
+ if (!existsSync(configPath)) {
3580
+ console.error(chalk.red('No workspace found. Run `rulebook workspace init` first.'));
3581
+ process.exit(1);
3582
+ }
3583
+ const config = JSON.parse(readFileSync(configPath, 'utf-8'));
3584
+ const idx = config.projects.findIndex((p) => p.name === projectName);
3585
+ if (idx === -1) {
3586
+ console.error(chalk.red(`Project "${projectName}" not found in workspace.`));
3587
+ process.exit(1);
3588
+ }
3589
+ config.projects.splice(idx, 1);
3590
+ if (config.defaultProject === projectName) {
3591
+ config.defaultProject = config.projects[0]?.name;
3592
+ }
3593
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
3594
+ console.log(chalk.green(`Removed project "${projectName}" from workspace.`));
3595
+ }
3596
+ export async function workspaceListCommand() {
3597
+ const cwd = process.cwd();
3598
+ const config = WorkspaceManager.findWorkspaceConfig(cwd);
3599
+ if (!config) {
3600
+ console.log(chalk.yellow('No workspace found. Run `rulebook workspace init` to create one.'));
3601
+ return;
3602
+ }
3603
+ console.log(chalk.bold(`\nWorkspace: ${config.name}`));
3604
+ console.log(chalk.dim(` Version: ${config.version}`));
3605
+ if (config.defaultProject) {
3606
+ console.log(chalk.dim(` Default: ${config.defaultProject}`));
3607
+ }
3608
+ console.log();
3609
+ for (const p of config.projects) {
3610
+ const isDefault = p.name === config.defaultProject;
3611
+ const marker = isDefault ? chalk.green(' (default)') : '';
3612
+ const disabled = p.enabled === false ? chalk.red(' [disabled]') : '';
3613
+ console.log(` ${chalk.cyan(p.name)}${marker}${disabled}`);
3614
+ console.log(` ${chalk.dim(p.path)}`);
3615
+ }
3616
+ console.log(chalk.dim(`\n ${config.projects.length} project(s) total`));
3617
+ }
3618
+ export async function workspaceStatusCommand() {
3619
+ const cwd = process.cwd();
3620
+ const config = WorkspaceManager.findWorkspaceConfig(cwd);
3621
+ if (!config) {
3622
+ console.log(chalk.yellow('No workspace found. Run `rulebook workspace init` to create one.'));
3623
+ return;
3624
+ }
3625
+ const manager = new WorkspaceManager(config, cwd);
3626
+ const spinner = ora('Checking workspace status...').start();
3627
+ try {
3628
+ const status = await manager.getStatus();
3629
+ spinner.stop();
3630
+ console.log(chalk.bold(`\nWorkspace: ${status.name}`));
3631
+ console.log(` Projects: ${status.totalProjects} | Active workers: ${status.activeWorkers}`);
3632
+ console.log();
3633
+ for (const p of status.projects) {
3634
+ const configBadge = p.hasRulebookConfig ? chalk.green('.rulebook') : chalk.dim('no config');
3635
+ const memBadge = p.memoryEnabled ? chalk.blue('memory') : '';
3636
+ const taskBadge = p.taskCount > 0 ? chalk.yellow(`${p.taskCount} tasks`) : '';
3637
+ const badges = [configBadge, memBadge, taskBadge].filter(Boolean).join(' ');
3638
+ console.log(` ${chalk.cyan(p.name)} ${badges}`);
3639
+ console.log(` ${chalk.dim(p.path)}`);
3640
+ }
3641
+ console.log();
3642
+ }
3643
+ catch (error) {
3644
+ spinner.fail(`Failed: ${String(error)}`);
3645
+ }
3646
+ finally {
3647
+ await manager.shutdownAll();
3648
+ }
3649
+ }
3339
3650
  //# sourceMappingURL=commands.js.map