@hivehub/rulebook 5.2.1 → 5.3.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 (157) hide show
  1. package/.claude/commands/analysis.md +35 -0
  2. package/.claude/commands/rulebook-task-apply.md +7 -25
  3. package/.claude/commands/rulebook-task-archive.md +10 -19
  4. package/.claude/commands/rulebook-task-create.md +1 -1
  5. package/README.md +354 -965
  6. package/dist/cli/commands/analysis.d.ts +8 -0
  7. package/dist/cli/commands/analysis.d.ts.map +1 -0
  8. package/dist/cli/commands/analysis.js +78 -0
  9. package/dist/cli/commands/analysis.js.map +1 -0
  10. package/dist/cli/commands/context-intelligence.d.ts +33 -0
  11. package/dist/cli/commands/context-intelligence.d.ts.map +1 -0
  12. package/dist/cli/commands/context-intelligence.js +181 -0
  13. package/dist/cli/commands/context-intelligence.js.map +1 -0
  14. package/dist/cli/commands/index.d.ts +19 -0
  15. package/dist/cli/commands/index.d.ts.map +1 -0
  16. package/dist/cli/commands/index.js +19 -0
  17. package/dist/cli/commands/index.js.map +1 -0
  18. package/dist/cli/commands/init.d.ts +15 -0
  19. package/dist/cli/commands/init.d.ts.map +1 -0
  20. package/dist/cli/commands/init.js +608 -0
  21. package/dist/cli/commands/init.js.map +1 -0
  22. package/dist/cli/commands/mcp.d.ts +10 -0
  23. package/dist/cli/commands/mcp.d.ts.map +1 -0
  24. package/dist/cli/commands/mcp.js +128 -0
  25. package/dist/cli/commands/mcp.js.map +1 -0
  26. package/dist/cli/commands/memory.d.ts +24 -0
  27. package/dist/cli/commands/memory.d.ts.map +1 -0
  28. package/dist/cli/commands/memory.js +265 -0
  29. package/dist/cli/commands/memory.js.map +1 -0
  30. package/dist/cli/commands/misc.d.ts +33 -0
  31. package/dist/cli/commands/misc.d.ts.map +1 -0
  32. package/dist/cli/commands/misc.js +576 -0
  33. package/dist/cli/commands/misc.js.map +1 -0
  34. package/dist/cli/commands/plans.d.ts +15 -0
  35. package/dist/cli/commands/plans.d.ts.map +1 -0
  36. package/dist/cli/commands/plans.js +266 -0
  37. package/dist/cli/commands/plans.js.map +1 -0
  38. package/dist/cli/commands/ralph.d.ts +45 -0
  39. package/dist/cli/commands/ralph.d.ts.map +1 -0
  40. package/dist/cli/commands/ralph.js +694 -0
  41. package/dist/cli/commands/ralph.js.map +1 -0
  42. package/dist/cli/commands/skills.d.ts +9 -0
  43. package/dist/cli/commands/skills.d.ts.map +1 -0
  44. package/dist/cli/commands/skills.js +249 -0
  45. package/dist/cli/commands/skills.js.map +1 -0
  46. package/dist/cli/commands/task.d.ts +16 -0
  47. package/dist/cli/commands/task.d.ts.map +1 -0
  48. package/dist/cli/commands/task.js +256 -0
  49. package/dist/cli/commands/task.js.map +1 -0
  50. package/dist/cli/commands/update.d.ts +14 -0
  51. package/dist/cli/commands/update.d.ts.map +1 -0
  52. package/dist/cli/commands/update.js +636 -0
  53. package/dist/cli/commands/update.js.map +1 -0
  54. package/dist/cli/commands/workspace.d.ts +6 -0
  55. package/dist/cli/commands/workspace.d.ts.map +1 -0
  56. package/dist/cli/commands/workspace.js +141 -0
  57. package/dist/cli/commands/workspace.js.map +1 -0
  58. package/dist/core/agent-template-engine.js +28 -28
  59. package/dist/core/analysis-manager.d.ts +56 -0
  60. package/dist/core/analysis-manager.d.ts.map +1 -0
  61. package/dist/core/analysis-manager.js +218 -0
  62. package/dist/core/analysis-manager.js.map +1 -0
  63. package/dist/core/claude-md-generator.d.ts +52 -0
  64. package/dist/core/claude-md-generator.d.ts.map +1 -0
  65. package/dist/core/claude-md-generator.js +104 -0
  66. package/dist/core/claude-md-generator.js.map +1 -0
  67. package/dist/core/claude-settings-manager.d.ts +37 -0
  68. package/dist/core/claude-settings-manager.d.ts.map +1 -0
  69. package/dist/core/claude-settings-manager.js +168 -0
  70. package/dist/core/claude-settings-manager.js.map +1 -0
  71. package/dist/core/compact-context-manager.d.ts +34 -0
  72. package/dist/core/compact-context-manager.d.ts.map +1 -0
  73. package/dist/core/compact-context-manager.js +60 -0
  74. package/dist/core/compact-context-manager.js.map +1 -0
  75. package/dist/core/doctor.d.ts +19 -0
  76. package/dist/core/doctor.d.ts.map +1 -0
  77. package/dist/core/doctor.js +163 -0
  78. package/dist/core/doctor.js.map +1 -0
  79. package/dist/core/generator.js +28 -28
  80. package/dist/core/mcp-reference-generator.d.ts +13 -0
  81. package/dist/core/mcp-reference-generator.d.ts.map +1 -0
  82. package/dist/core/mcp-reference-generator.js +66 -0
  83. package/dist/core/mcp-reference-generator.js.map +1 -0
  84. package/dist/core/merger.d.ts +35 -0
  85. package/dist/core/merger.d.ts.map +1 -1
  86. package/dist/core/merger.js +120 -0
  87. package/dist/core/merger.js.map +1 -1
  88. package/dist/core/prd-generator.d.ts.map +1 -1
  89. package/dist/core/prd-generator.js +7 -1
  90. package/dist/core/prd-generator.js.map +1 -1
  91. package/dist/core/ralph-manager.d.ts.map +1 -1
  92. package/dist/core/ralph-manager.js +17 -0
  93. package/dist/core/ralph-manager.js.map +1 -1
  94. package/dist/core/rules-generator.d.ts +73 -0
  95. package/dist/core/rules-generator.d.ts.map +1 -0
  96. package/dist/core/rules-generator.js +201 -0
  97. package/dist/core/rules-generator.js.map +1 -0
  98. package/dist/core/state-writer.d.ts +35 -0
  99. package/dist/core/state-writer.d.ts.map +1 -0
  100. package/dist/core/state-writer.js +81 -0
  101. package/dist/core/state-writer.js.map +1 -0
  102. package/dist/core/task-manager.d.ts +35 -0
  103. package/dist/core/task-manager.d.ts.map +1 -1
  104. package/dist/core/task-manager.js +135 -38
  105. package/dist/core/task-manager.js.map +1 -1
  106. package/dist/core/telemetry.d.ts +29 -0
  107. package/dist/core/telemetry.d.ts.map +1 -0
  108. package/dist/core/telemetry.js +57 -0
  109. package/dist/core/telemetry.js.map +1 -0
  110. package/dist/core/workflow-generator.d.ts.map +1 -1
  111. package/dist/core/workflow-generator.js +2 -177
  112. package/dist/core/workflow-generator.js.map +1 -1
  113. package/dist/index.js +28 -1
  114. package/dist/index.js.map +1 -1
  115. package/dist/mcp/rulebook-server.d.ts.map +1 -1
  116. package/dist/mcp/rulebook-server.js +190 -7
  117. package/dist/mcp/rulebook-server.js.map +1 -1
  118. package/dist/memory/memory-store.js +91 -91
  119. package/dist/types.d.ts +11 -0
  120. package/dist/types.d.ts.map +1 -1
  121. package/dist/utils/gitignore.d.ts +10 -0
  122. package/dist/utils/gitignore.d.ts.map +1 -0
  123. package/dist/utils/gitignore.js +38 -0
  124. package/dist/utils/gitignore.js.map +1 -0
  125. package/package.json +1 -1
  126. package/templates/compact-context/_default.md +23 -0
  127. package/templates/compact-context/cpp.md +26 -0
  128. package/templates/compact-context/go.md +26 -0
  129. package/templates/compact-context/python.md +26 -0
  130. package/templates/compact-context/rust.md +28 -0
  131. package/templates/compact-context/typescript.md +29 -0
  132. package/templates/core/CLAUDE_MD_v2.md +71 -0
  133. package/templates/hooks/check-context-and-handoff.ps1 +50 -0
  134. package/templates/hooks/check-context-and-handoff.sh +69 -0
  135. package/templates/hooks/enforce-mcp-for-tasks.sh +31 -0
  136. package/templates/hooks/enforce-no-deferred.sh +21 -0
  137. package/templates/hooks/enforce-no-shortcuts.sh +31 -0
  138. package/templates/hooks/enforce-team-for-background-agents.ps1 +63 -0
  139. package/templates/hooks/enforce-team-for-background-agents.sh +55 -0
  140. package/templates/hooks/on-compact-reinject.sh +34 -0
  141. package/templates/hooks/resume-from-handoff.ps1 +33 -0
  142. package/templates/hooks/resume-from-handoff.sh +55 -0
  143. package/templates/rules/consult-analysis-before-implementing.md +23 -0
  144. package/templates/rules/cpp.md +46 -0
  145. package/templates/rules/csharp.md +44 -0
  146. package/templates/rules/diagnostic-first.md +39 -0
  147. package/templates/rules/fail-twice-escalate.md +46 -0
  148. package/templates/rules/go.md +40 -0
  149. package/templates/rules/java.md +43 -0
  150. package/templates/rules/javascript.md +39 -0
  151. package/templates/rules/multi-agent-teams.md +75 -0
  152. package/templates/rules/python.md +43 -0
  153. package/templates/rules/respect-handoff-trigger.md +41 -0
  154. package/templates/rules/rust.md +40 -0
  155. package/templates/rules/typescript.md +40 -0
  156. package/templates/skills/dev/analysis/SKILL.md +19 -0
  157. package/templates/skills/dev/handoff/SKILL.md +27 -0
@@ -0,0 +1,636 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { detectProject } from '../../core/detector.js';
4
+ import { mergeFullAgents, mergeClaudeMd } from '../../core/merger.js';
5
+ import { writeFile } from '../../utils/file-system.js';
6
+ import { existsSync } from 'fs';
7
+ import { installGitHooks } from '../../utils/git-hooks.js';
8
+ import { scaffoldMinimalProject } from '../../core/minimal-scaffolder.js';
9
+ import path from 'path';
10
+ import { readFileSync } from 'fs';
11
+ import { fileURLToPath } from 'url';
12
+ import { SkillsManager, getDefaultTemplatesPath } from '../../core/skills-manager.js';
13
+ import { WorkspaceManager } from '../../core/workspace/workspace-manager.js';
14
+ import { setupClaudeCodePlugin } from './misc.js';
15
+ import { migrateMemoryDirectory } from './misc.js';
16
+ function getRulebookVersion() {
17
+ try {
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const packagePath = path.join(__dirname, '..', '..', '..', 'package.json');
20
+ const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
21
+ return packageJson.version;
22
+ }
23
+ catch {
24
+ return '0.12.1';
25
+ }
26
+ }
27
+ /** Update a single project at the given root directory. */
28
+ export async function updateSingleProject(cwd, options) {
29
+ const spinner = ora('Detecting project structure...').start();
30
+ const detection = await detectProject(cwd);
31
+ spinner.succeed('Project detection complete');
32
+ if (detection.languages.length > 0) {
33
+ console.log(chalk.green('\n✓ Detected languages:'));
34
+ for (const lang of detection.languages) {
35
+ console.log(` - ${lang.language} (${(lang.confidence * 100).toFixed(0)}% confidence)`);
36
+ }
37
+ }
38
+ if (!detection.existingAgents) {
39
+ console.log(chalk.yellow('\n⚠ No AGENTS.md found. Use "rulebook init" instead.'));
40
+ process.exit(0);
41
+ }
42
+ console.log(chalk.green(`\n✓ Found existing AGENTS.md with ${detection.existingAgents.blocks.length} blocks`));
43
+ const existingBlocks = detection.existingAgents.blocks.map((b) => b.name);
44
+ console.log(chalk.gray(` Existing blocks: ${existingBlocks.join(', ')}`));
45
+ let inquirerModule = null;
46
+ if (!options.yes) {
47
+ inquirerModule = (await import('inquirer')).default;
48
+ const { confirm } = await inquirerModule.prompt([
49
+ {
50
+ type: 'confirm',
51
+ name: 'confirm',
52
+ message: 'Update AGENTS.md and .rulebook with latest templates?',
53
+ default: true,
54
+ },
55
+ ]);
56
+ if (!confirm) {
57
+ console.log(chalk.yellow('\nUpdate cancelled'));
58
+ process.exit(0);
59
+ }
60
+ }
61
+ const hasPreCommit = detection.gitHooks?.preCommitExists ?? false;
62
+ const hasPrePush = detection.gitHooks?.prePushExists ?? false;
63
+ const missingHooks = !hasPreCommit || !hasPrePush;
64
+ let installHooksOnUpdate = false;
65
+ let hooksInstalledOnUpdate = false;
66
+ if (missingHooks) {
67
+ if (options.yes) {
68
+ console.log(chalk.yellow('\n⚠ Git hooks are missing. Re-run "rulebook update" without --yes to install automated hooks or install them manually.'));
69
+ }
70
+ else {
71
+ if (!inquirerModule) {
72
+ inquirerModule = (await import('inquirer')).default;
73
+ }
74
+ const { installHooks } = await inquirerModule.prompt([
75
+ {
76
+ type: 'confirm',
77
+ name: 'installHooks',
78
+ message: `Install Git hooks for automated quality checks? Missing: ${hasPreCommit ? '' : 'pre-commit '}${hasPrePush ? '' : 'pre-push'}`.trim(),
79
+ default: true,
80
+ },
81
+ ]);
82
+ installHooksOnUpdate = installHooks;
83
+ }
84
+ }
85
+ if (missingHooks && !installHooksOnUpdate && !options.yes) {
86
+ 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.'));
87
+ }
88
+ const agentsPath = path.join(cwd, 'AGENTS.md');
89
+ const { createConfigManager } = await import('../../core/config-manager.js');
90
+ const configManager = createConfigManager(cwd);
91
+ const existingConfig = await configManager.loadConfig();
92
+ let existingMode;
93
+ let existingLightMode;
94
+ if (existingConfig) {
95
+ if (existingConfig && (existingConfig.mode === 'minimal' || existingConfig.mode === 'full')) {
96
+ existingMode = existingConfig.mode;
97
+ }
98
+ if (existingConfig && existingConfig.lightMode !== undefined) {
99
+ existingLightMode = existingConfig.lightMode;
100
+ }
101
+ }
102
+ const minimalMode = options.minimal ?? existingMode === 'minimal';
103
+ const lightMode = options.light !== undefined ? options.light : (existingLightMode ?? false);
104
+ const leanMode = options.lean ?? existingConfig?.agentsMode === 'lean';
105
+ const config = {
106
+ languages: detection.languages.map((l) => l.language),
107
+ modules: minimalMode ? [] : detection.modules.filter((m) => m.detected).map((m) => m.module),
108
+ frameworks: detection.frameworks.filter((f) => f.detected).map((f) => f.framework),
109
+ ides: [],
110
+ projectType: 'application',
111
+ coverageThreshold: 95,
112
+ strictDocs: true,
113
+ generateWorkflows: false,
114
+ includeGitWorkflow: true,
115
+ gitPushMode: 'manual',
116
+ installGitHooks: installHooksOnUpdate,
117
+ minimal: minimalMode,
118
+ lightMode: lightMode,
119
+ ...(leanMode ? { agentsMode: 'lean' } : {}),
120
+ };
121
+ if (minimalMode) {
122
+ config.ides = [];
123
+ config.generateWorkflows = true;
124
+ }
125
+ let minimalArtifacts = [];
126
+ if (minimalMode) {
127
+ minimalArtifacts = await scaffoldMinimalProject(cwd, {
128
+ projectName: path.basename(cwd),
129
+ description: 'Essential project scaffolding refreshed via Rulebook minimal mode.',
130
+ license: 'MIT',
131
+ });
132
+ }
133
+ const cursorRulesPath = path.join(cwd, '.cursorrules');
134
+ const cursorCommandsDir = path.join(cwd, '.cursor', 'commands');
135
+ const usesCursor = existsSync(cursorRulesPath) || existsSync(cursorCommandsDir);
136
+ if (existsSync(cursorRulesPath)) {
137
+ console.log(chalk.yellow(' ⚠ .cursorrules is deprecated as of Cursor v0.45. Use .cursor/rules/*.mdc instead.'));
138
+ }
139
+ if (usesCursor) {
140
+ const existingCommandsDir = path.join(cwd, '.cursor', 'commands');
141
+ if (existsSync(existingCommandsDir)) {
142
+ const { readdir } = await import('fs/promises');
143
+ const existingFiles = await readdir(existingCommandsDir);
144
+ const hasRulebookCommands = existingFiles.some((file) => file.startsWith('rulebook-task-'));
145
+ if (!hasRulebookCommands) {
146
+ const { generateCursorCommands } = await import('../../core/workflow-generator.js');
147
+ const generatedCommands = await generateCursorCommands(cwd);
148
+ if (generatedCommands.length > 0) {
149
+ console.log(chalk.green(` Generated ${generatedCommands.length} Rulebook command(s) in .cursor/commands/`));
150
+ }
151
+ }
152
+ }
153
+ else {
154
+ const { generateCursorCommands } = await import('../../core/workflow-generator.js');
155
+ const generatedCommands = await generateCursorCommands(cwd);
156
+ if (generatedCommands.length > 0) {
157
+ console.log(chalk.green(` Generated ${generatedCommands.length} Rulebook command(s) in .cursor/commands/`));
158
+ }
159
+ }
160
+ }
161
+ const existingSkills = existingConfig.skills?.enabled || [];
162
+ const existingRalph = existingConfig.ralph;
163
+ let detectedSkills = [];
164
+ try {
165
+ const skillsManager = new SkillsManager(getDefaultTemplatesPath(), cwd);
166
+ const rulebookConfigForSkills = {
167
+ languages: config.languages,
168
+ frameworks: config.frameworks,
169
+ modules: config.modules,
170
+ services: config.services,
171
+ };
172
+ detectedSkills = await skillsManager.autoDetectSkills(rulebookConfigForSkills);
173
+ const mergedSkills = [...new Set([...existingSkills, ...detectedSkills])];
174
+ if (detectedSkills.length > existingSkills.length) {
175
+ const newSkills = detectedSkills.filter((s) => !existingSkills.includes(s));
176
+ if (newSkills.length > 0) {
177
+ console.log(chalk.green('\n✓ New skills detected:'));
178
+ for (const skillId of newSkills) {
179
+ console.log(chalk.gray(` - ${skillId}`));
180
+ }
181
+ }
182
+ }
183
+ detectedSkills = mergedSkills;
184
+ }
185
+ catch {
186
+ detectedSkills = existingSkills;
187
+ }
188
+ await configManager.updateConfig({
189
+ languages: config.languages,
190
+ frameworks: config.frameworks,
191
+ modules: config.modules,
192
+ services: config.services,
193
+ modular: config.modular ?? true,
194
+ rulebookDir: config.rulebookDir || '.rulebook',
195
+ skills: detectedSkills.length > 0 ? { enabled: detectedSkills } : undefined,
196
+ ralph: existingRalph,
197
+ memory: existingConfig.memory,
198
+ });
199
+ await configManager.ensureGitignore();
200
+ {
201
+ const { hasFlatLayout, migrateFlatToSpecs } = await import('../../core/migrator.js');
202
+ const rulebookDirForMigration = config.rulebookDir || '.rulebook';
203
+ if (await hasFlatLayout(cwd, rulebookDirForMigration)) {
204
+ const migrationSpinner = ora('Migrating rulebook files to specs/ subdirectory...').start();
205
+ const { migratedFiles } = await migrateFlatToSpecs(cwd, rulebookDirForMigration);
206
+ if (migratedFiles.length > 0) {
207
+ migrationSpinner.succeed(`Migrated ${migratedFiles.length} file(s) to /${rulebookDirForMigration}/specs/`);
208
+ }
209
+ else {
210
+ migrationSpinner.info('No files to migrate');
211
+ }
212
+ }
213
+ }
214
+ {
215
+ const { createTaskManager } = await import('../../core/task-manager.js');
216
+ const rulebookDirForArchive = config.rulebookDir || '.rulebook';
217
+ const tm = createTaskManager(cwd, rulebookDirForArchive);
218
+ const migrated = await tm.migrateArchive();
219
+ if (migrated) {
220
+ console.log(chalk.gray(' • Migrated task archive to .rulebook/archive/'));
221
+ }
222
+ }
223
+ const mergeSpinner = ora('Updating AGENTS.md with latest templates...').start();
224
+ config.modular = config.modular ?? true;
225
+ const mergedContent = await mergeFullAgents(detection.existingAgents, config, cwd);
226
+ await writeFile(agentsPath, mergedContent);
227
+ mergeSpinner.succeed('AGENTS.md updated');
228
+ const claudeUpdateSpinner = ora('Updating CLAUDE.md (v5.3.0 @import format)...').start();
229
+ try {
230
+ const claudeResult = await mergeClaudeMd(cwd);
231
+ const label = claudeResult.mode === 'create'
232
+ ? 'created'
233
+ : claudeResult.mode === 'replace'
234
+ ? 'updated in-place'
235
+ : 'migrated (legacy directives moved to AGENTS.override.md)';
236
+ claudeUpdateSpinner.succeed(`CLAUDE.md ${label}`);
237
+ if (claudeResult.backupPath) {
238
+ console.log(chalk.gray(` • backup: ${path.relative(cwd, claudeResult.backupPath)}`));
239
+ }
240
+ if (claudeResult.mode === 'migrate' && claudeResult.overridePath) {
241
+ console.log(chalk.yellow(` ! Your previous CLAUDE.md directives were migrated to ${path.relative(cwd, claudeResult.overridePath)}.`));
242
+ console.log(chalk.yellow(' Claude Code still loads them at session start via @AGENTS.override.md.'));
243
+ console.log(chalk.yellow(' Review and prune AGENTS.override.md when convenient — rulebook will never overwrite it.'));
244
+ }
245
+ }
246
+ catch (err) {
247
+ claudeUpdateSpinner.warn(`CLAUDE.md update skipped: ${err instanceof Error ? err.message : String(err)}`);
248
+ }
249
+ try {
250
+ const { seedCompactContext } = await import('../../core/compact-context-manager.js');
251
+ await seedCompactContext(cwd, { languages: detection.languages });
252
+ }
253
+ catch {
254
+ // non-fatal
255
+ }
256
+ try {
257
+ const { ensureGitignoreEntries } = await import('../../utils/gitignore.js');
258
+ await ensureGitignoreEntries(cwd, [
259
+ 'CLAUDE.local.md',
260
+ '.rulebook/backup/',
261
+ '.rulebook/handoff/_pending.md',
262
+ '.rulebook/handoff/.urgent',
263
+ '.rulebook/telemetry/',
264
+ ]);
265
+ }
266
+ catch {
267
+ // non-fatal
268
+ }
269
+ try {
270
+ const { generateMcpReference } = await import('../../core/mcp-reference-generator.js');
271
+ const mcpRef = await generateMcpReference(cwd);
272
+ if (mcpRef.written) {
273
+ console.log(chalk.gray(' • .claude/rules/mcp-tool-reference.md refreshed'));
274
+ }
275
+ }
276
+ catch {
277
+ // non-fatal
278
+ }
279
+ try {
280
+ const { applyClaudeSettings } = await import('../../core/claude-settings-manager.js');
281
+ const rulebookCfg = await configManager.loadConfig();
282
+ const multiAgentEnabled = rulebookCfg?.multiAgent?.enabled ?? false;
283
+ const handoffEnabled = rulebookCfg?.handoff?.enabled ?? true;
284
+ const settingsResult = await applyClaudeSettings(cwd, {
285
+ teamEnforcement: multiAgentEnabled,
286
+ sessionHandoff: handoffEnabled,
287
+ compactContextReinject: true,
288
+ qualityEnforcement: true,
289
+ });
290
+ if (settingsResult.changed) {
291
+ console.log(chalk.gray(` • .claude/settings.json refreshed (hooks wired)`));
292
+ }
293
+ }
294
+ catch (err) {
295
+ console.log(chalk.gray(` · .claude/settings.json refresh skipped: ${err instanceof Error ? err.message : String(err)}`));
296
+ }
297
+ const rulesUpdateSpinner = ora('Refreshing path-scoped .claude/rules/ for detected languages...').start();
298
+ try {
299
+ const { generateRules } = await import('../../core/rules-generator.js');
300
+ const rulesResult = await generateRules(cwd, { languages: detection.languages });
301
+ if (rulesResult.written.length > 0) {
302
+ rulesUpdateSpinner.succeed(`Refreshed ${rulesResult.written.length} language rule file(s) in .claude/rules/`);
303
+ }
304
+ else {
305
+ rulesUpdateSpinner.info('No language rule templates applicable');
306
+ }
307
+ if (rulesResult.preserved.length > 0) {
308
+ console.log(chalk.gray(` · ${rulesResult.preserved.length} user-authored rule file(s) preserved`));
309
+ }
310
+ }
311
+ catch (err) {
312
+ rulesUpdateSpinner.warn(`Rules refresh skipped: ${err instanceof Error ? err.message : String(err)}`);
313
+ }
314
+ {
315
+ const { projectRules, installRule, loadCanonicalRules } = await import('../../core/rule-engine.js');
316
+ const existingRules = await loadCanonicalRules(cwd);
317
+ if (existingRules.length === 0) {
318
+ const { assessComplexity } = await import('../../core/complexity-detector.js');
319
+ const { getTemplatesDir } = await import('../../core/generator.js');
320
+ const complexity = assessComplexity(cwd);
321
+ const templatesDir = getTemplatesDir();
322
+ const tier1 = [
323
+ 'no-shortcuts',
324
+ 'git-safety',
325
+ 'sequential-editing',
326
+ 'research-first',
327
+ 'follow-task-sequence',
328
+ 'incremental-implementation',
329
+ 'knowledge-base-usage',
330
+ ];
331
+ const tier2 = ['task-decomposition', 'incremental-tests', 'no-deferred', 'session-workflow'];
332
+ const toInstall = [...tier1];
333
+ if (complexity.recommendations.tier2Rules) {
334
+ toInstall.push(...tier2);
335
+ }
336
+ let installed = 0;
337
+ for (const name of toInstall) {
338
+ const result = await installRule(cwd, name, templatesDir);
339
+ if (result)
340
+ installed++;
341
+ }
342
+ if (installed > 0) {
343
+ console.log(chalk.gray(` • Installed ${installed} v5 canonical rules (${complexity.tier} project, ${complexity.metrics.estimatedLoc.toLocaleString()} LOC)`));
344
+ }
345
+ }
346
+ const ruleResult = await projectRules(cwd, {
347
+ claudeCode: existsSync(path.join(cwd, '.claude')) || existsSync(path.join(cwd, 'CLAUDE.md')),
348
+ cursor: detection.cursor?.detected,
349
+ gemini: detection.geminiCli?.detected,
350
+ windsurf: detection.windsurf?.detected,
351
+ copilot: detection.githubCopilot?.detected,
352
+ continueDev: detection.continueDev?.detected,
353
+ });
354
+ const totalProjected = ruleResult.claudeCode.length +
355
+ ruleResult.cursor.length +
356
+ ruleResult.gemini.length +
357
+ ruleResult.copilot.length +
358
+ ruleResult.windsurf.length +
359
+ ruleResult.continueDev.length;
360
+ if (totalProjected > 0) {
361
+ console.log(chalk.gray(` • Projected ${totalProjected} canonical rules to detected tools`));
362
+ }
363
+ }
364
+ if (detection.geminiCli?.detected) {
365
+ console.log(chalk.gray(' • Gemini CLI config updated: GEMINI.md'));
366
+ }
367
+ if (detection.continueDev?.detected) {
368
+ console.log(chalk.gray(' • Continue.dev rules updated in .continue/rules/'));
369
+ }
370
+ if (detection.windsurf?.detected) {
371
+ console.log(chalk.gray(' • Windsurf rules updated: .windsurfrules'));
372
+ }
373
+ if (detection.githubCopilot?.detected) {
374
+ console.log(chalk.gray(' • GitHub Copilot instructions updated in .github/'));
375
+ }
376
+ if (installHooksOnUpdate) {
377
+ const hookLanguages = detection.languages.length > 0
378
+ ? detection.languages
379
+ : config.languages.map((language) => ({
380
+ language: language,
381
+ confidence: 1,
382
+ indicators: [],
383
+ }));
384
+ const hookSpinner = ora('Installing Git hooks (pre-commit & pre-push)...').start();
385
+ try {
386
+ await installGitHooks({ languages: hookLanguages, cwd });
387
+ hookSpinner.succeed('Git hooks installed successfully');
388
+ hooksInstalledOnUpdate = true;
389
+ }
390
+ catch (error) {
391
+ hookSpinner.fail('Failed to install Git hooks');
392
+ console.error(chalk.red(' ➤'), error instanceof Error ? error.message : error);
393
+ console.log(chalk.yellow(' ⚠ Skipping automatic hook installation. You can rerun "rulebook update" later to retry or install manually.'));
394
+ }
395
+ }
396
+ const gitHooksActiveAfterUpdate = hooksInstalledOnUpdate || (hasPreCommit && hasPrePush);
397
+ config.installGitHooks = gitHooksActiveAfterUpdate;
398
+ const configSpinner = ora('Updating .rulebook configuration...').start();
399
+ const rulebookFeatures = {
400
+ watcher: false,
401
+ agent: false,
402
+ logging: true,
403
+ telemetry: false,
404
+ notifications: false,
405
+ dryRun: false,
406
+ gitHooks: gitHooksActiveAfterUpdate,
407
+ repl: false,
408
+ templates: true,
409
+ context: minimalMode ? false : true,
410
+ health: true,
411
+ plugins: false,
412
+ parallel: minimalMode ? false : true,
413
+ smartContinue: minimalMode ? false : true,
414
+ };
415
+ const rulebookConfig = {
416
+ version: getRulebookVersion(),
417
+ installedAt: detection.existingAgents.content?.match(/Generated at: (.+)/)?.[1] ||
418
+ new Date().toISOString(),
419
+ updatedAt: new Date().toISOString(),
420
+ projectId: path.basename(cwd),
421
+ mode: minimalMode ? 'minimal' : 'full',
422
+ features: rulebookFeatures,
423
+ coverageThreshold: existingConfig.coverageThreshold ?? 95,
424
+ language: existingConfig.language ?? 'en',
425
+ outputLanguage: existingConfig.outputLanguage ?? 'en',
426
+ cliTools: existingConfig.cliTools ?? [],
427
+ maxParallelTasks: existingConfig.maxParallelTasks ?? 5,
428
+ timeouts: existingConfig.timeouts ?? {
429
+ taskExecution: 3600000,
430
+ cliResponse: 180000,
431
+ testRun: 600000,
432
+ },
433
+ ...(existingConfig.memory ? { memory: existingConfig.memory } : {}),
434
+ ...(existingConfig.ralph ? { ralph: existingConfig.ralph } : {}),
435
+ ...(existingConfig.skills ? { skills: existingConfig.skills } : {}),
436
+ ...(leanMode
437
+ ? { agentsMode: 'lean' }
438
+ : existingConfig.agentsMode
439
+ ? { agentsMode: existingConfig.agentsMode }
440
+ : {}),
441
+ };
442
+ await configManager.saveConfig(rulebookConfig);
443
+ configSpinner.succeed('.rulebook configuration updated');
444
+ const claudeSpinner = ora('Checking Claude Code integration...').start();
445
+ try {
446
+ const { setupClaudeCodeIntegration } = await import('../../core/claude-mcp.js');
447
+ const result = await setupClaudeCodeIntegration(cwd);
448
+ if (result.detected) {
449
+ claudeSpinner.succeed('Claude Code integration updated');
450
+ if (result.mcpConfigured) {
451
+ console.log(chalk.gray(' • MCP server added to .mcp.json'));
452
+ }
453
+ if (result.skillsInstalled.length > 0) {
454
+ console.log(chalk.gray(` • ${result.skillsInstalled.length} skills updated in .claude/commands/`));
455
+ }
456
+ if (result.agentTeamsEnabled) {
457
+ console.log(chalk.gray(' • Multi-agent teams enabled in .claude/settings.json'));
458
+ }
459
+ if (result.agentDefinitionsInstalled.length > 0) {
460
+ console.log(chalk.gray(` • ${result.agentDefinitionsInstalled.length} agent definitions updated in .claude/agents/`));
461
+ }
462
+ }
463
+ else {
464
+ claudeSpinner.info('Claude Code not detected (skipped)');
465
+ }
466
+ }
467
+ catch {
468
+ claudeSpinner.info('Claude Code integration skipped');
469
+ }
470
+ try {
471
+ const { installRalphScripts } = await import('../../core/ralph-scripts.js');
472
+ const scripts = await installRalphScripts(cwd);
473
+ if (scripts.length > 0) {
474
+ console.log(chalk.gray(` • ${scripts.length} Ralph scripts updated in .rulebook/scripts/`));
475
+ }
476
+ }
477
+ catch {
478
+ // Skip if Ralph scripts installation fails
479
+ }
480
+ try {
481
+ const { initPlans } = await import('../../core/plans-manager.js');
482
+ await initPlans(cwd);
483
+ }
484
+ catch {
485
+ // Non-blocking
486
+ }
487
+ try {
488
+ await migrateMemoryDirectory();
489
+ }
490
+ catch {
491
+ // Silently skip if migration fails
492
+ }
493
+ try {
494
+ await setupClaudeCodePlugin();
495
+ }
496
+ catch {
497
+ // Silently skip if plugin installation fails
498
+ }
499
+ try {
500
+ const fsPromises = await import('fs/promises');
501
+ const accidentalDir = path.join(cwd, '.rulebook', '.rulebook');
502
+ if (existsSync(accidentalDir)) {
503
+ await fsPromises.rm(accidentalDir, { recursive: true, force: true });
504
+ }
505
+ }
506
+ catch {
507
+ // Ignore cleanup errors
508
+ }
509
+ try {
510
+ const { runDoctor } = await import('../../core/doctor.js');
511
+ const doctorReport = await runDoctor(cwd);
512
+ if (doctorReport.warnCount > 0 || doctorReport.failCount > 0) {
513
+ console.log(chalk.yellow(`\n⚠ Doctor: ${doctorReport.passCount} pass, ${doctorReport.warnCount} warn, ${doctorReport.failCount} fail`));
514
+ for (const check of doctorReport.checks.filter((c) => c.status !== 'pass')) {
515
+ const icon = check.status === 'warn' ? chalk.yellow('⚠') : chalk.red('✗');
516
+ console.log(` ${icon} ${check.name}: ${check.message}`);
517
+ }
518
+ }
519
+ }
520
+ catch {
521
+ // non-fatal
522
+ }
523
+ // F-NEW-3: scan active tasks for missing mandatory tail and offer to append
524
+ try {
525
+ const { checkMandatoryTail, renderMandatoryTail } = await import('../../core/task-manager.js');
526
+ const { promises: fsP } = await import('fs');
527
+ const tasksDir = path.join(cwd, '.rulebook', 'tasks');
528
+ if (existsSync(tasksDir)) {
529
+ const taskDirs = (await fsP.readdir(tasksDir, { withFileTypes: true })).filter((d) => d.isDirectory() && d.name.startsWith('phase'));
530
+ let appendedCount = 0;
531
+ for (const dir of taskDirs) {
532
+ const tasksPath = path.join(tasksDir, dir.name, 'tasks.md');
533
+ if (!existsSync(tasksPath))
534
+ continue;
535
+ const content = await fsP.readFile(tasksPath, 'utf-8');
536
+ const tail = checkMandatoryTail(content);
537
+ if (!tail.present && tail.missing.length > 0) {
538
+ // Count existing sections to pick the right number
539
+ const sectionMatches = content.match(/^## \d+\./gm);
540
+ const nextSection = (sectionMatches?.length ?? 0) + 1;
541
+ const appendix = '\n' + renderMandatoryTail(nextSection);
542
+ await fsP.writeFile(tasksPath, content.trimEnd() + '\n' + appendix);
543
+ appendedCount++;
544
+ }
545
+ }
546
+ if (appendedCount > 0) {
547
+ console.log(chalk.yellow(` • Appended mandatory tail (docs+tests+verify) to ${appendedCount} task(s) missing it`));
548
+ }
549
+ }
550
+ }
551
+ catch {
552
+ // non-fatal
553
+ }
554
+ console.log(chalk.bold.green('\n✅ Update complete!\n'));
555
+ console.log(chalk.white('Updated components:'));
556
+ console.log(chalk.green(' ✓ AGENTS.md - Merged with latest templates'));
557
+ console.log(chalk.green(` ✓ .rulebook - Updated to v${getRulebookVersion()}`));
558
+ console.log(chalk.white('\nWhat was updated:'));
559
+ console.log(chalk.gray(` - ${detection.languages.length} language templates`));
560
+ console.log(chalk.gray(` - ${detection.modules.filter((m) => m.detected).length} MCP modules`));
561
+ console.log(chalk.gray(' - Git workflow rules'));
562
+ console.log(chalk.gray(' - Rulebook task management'));
563
+ console.log(chalk.gray(' - Pre-commit command standardization'));
564
+ console.log(chalk.yellow('\n⚠ Review the updated AGENTS.md to ensure your custom rules are preserved'));
565
+ console.log(chalk.white('\nNext steps:'));
566
+ console.log(chalk.gray(' 1. Review AGENTS.md changes'));
567
+ console.log(chalk.gray(' 2. Test that your project still builds'));
568
+ console.log(chalk.gray(' 3. Run quality checks (lint, test, build)'));
569
+ console.log(chalk.gray(' 4. Commit the updated files\n'));
570
+ if (minimalMode && minimalArtifacts.length > 0) {
571
+ console.log(chalk.green('Essentials ensured:'));
572
+ for (const artifact of minimalArtifacts) {
573
+ console.log(chalk.gray(` - ${path.relative(cwd, artifact)}`));
574
+ }
575
+ console.log('');
576
+ }
577
+ }
578
+ export async function updateCommand(options) {
579
+ try {
580
+ const cwd = process.cwd();
581
+ const ws = WorkspaceManager.findWorkspaceFromCwd(cwd);
582
+ if (ws && ws.config.projects.length > 1) {
583
+ console.log(chalk.bold.blue('\n🔄 Rulebook Workspace Update\n'));
584
+ console.log(chalk.gray(`Workspace "${ws.config.name}" detected — updating ${ws.config.projects.length} projects\n`));
585
+ const { isAbsolute, resolve, join } = await import('path');
586
+ const fsPromises = await import('fs/promises');
587
+ let updatedCount = 0;
588
+ const projectListMd = ws.config.projects
589
+ .map((p) => {
590
+ const root = isAbsolute(p.path) ? p.path : resolve(ws.root, p.path);
591
+ return `- **${p.name}** → \`${root}\``;
592
+ })
593
+ .join('\n');
594
+ const { getDefaultTemplatesPath: getTemplatesPath } = await import('../../core/skills-manager.js');
595
+ let workspaceTplContent = '';
596
+ try {
597
+ const tplPath = join(getTemplatesPath(), 'core', 'WORKSPACE.md');
598
+ workspaceTplContent = await fsPromises.readFile(tplPath, 'utf-8');
599
+ }
600
+ catch {
601
+ // Template not available — skip
602
+ }
603
+ for (const project of ws.config.projects) {
604
+ const projectRoot = isAbsolute(project.path)
605
+ ? project.path
606
+ : resolve(ws.root, project.path);
607
+ console.log(chalk.bold.cyan(`\n━━━ [${project.name}] ${projectRoot} ━━━\n`));
608
+ try {
609
+ await updateSingleProject(projectRoot, options);
610
+ if (workspaceTplContent) {
611
+ const specsDir = join(projectRoot, '.rulebook', 'specs');
612
+ await fsPromises.mkdir(specsDir, { recursive: true });
613
+ const rendered = workspaceTplContent
614
+ .replace('{{DEFAULT_PROJECT}}', ws.config.defaultProject ?? ws.config.projects[0]?.name ?? '')
615
+ .replace('{{WORKSPACE_PROJECTS}}', projectListMd);
616
+ await fsPromises.writeFile(join(specsDir, 'WORKSPACE.md'), rendered, 'utf-8');
617
+ }
618
+ updatedCount++;
619
+ }
620
+ catch (error) {
621
+ console.error(chalk.red(` ❌ Failed to update ${project.name}: ${error.message}`));
622
+ }
623
+ }
624
+ console.log(chalk.bold.green(`\n✅ Workspace update complete — ${updatedCount}/${ws.config.projects.length} projects updated\n`));
625
+ return;
626
+ }
627
+ console.log(chalk.bold.blue('\n🔄 Rulebook Update Tool\n'));
628
+ console.log(chalk.gray('This will update your AGENTS.md and .rulebook to the latest version\n'));
629
+ await updateSingleProject(cwd, options);
630
+ }
631
+ catch (error) {
632
+ console.error(chalk.red('\n❌ Update failed:'), error);
633
+ process.exit(1);
634
+ }
635
+ }
636
+ //# sourceMappingURL=update.js.map