@hivehub/rulebook 4.3.1 → 5.0.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 (115) hide show
  1. package/.claude/commands/rulebook-decision-create.md +55 -0
  2. package/.claude/commands/rulebook-decision-list.md +15 -0
  3. package/.claude/commands/rulebook-knowledge-add.md +41 -0
  4. package/.claude/commands/rulebook-knowledge-list.md +15 -0
  5. package/.claude/commands/rulebook-learn-capture.md +48 -0
  6. package/.claude/commands/rulebook-learn-list.md +13 -0
  7. package/.claude/commands/rulebook-task-archive.md +24 -0
  8. package/.claude-plugin/plugin.json +1 -1
  9. package/README.md +53 -10
  10. package/dist/cli/commands.d.ts +32 -0
  11. package/dist/cli/commands.d.ts.map +1 -1
  12. package/dist/cli/commands.js +257 -0
  13. package/dist/cli/commands.js.map +1 -1
  14. package/dist/core/agent-template-engine.d.ts +51 -0
  15. package/dist/core/agent-template-engine.d.ts.map +1 -0
  16. package/dist/core/agent-template-engine.js +273 -0
  17. package/dist/core/agent-template-engine.js.map +1 -0
  18. package/dist/core/complexity-detector.d.ts +36 -0
  19. package/dist/core/complexity-detector.d.ts.map +1 -0
  20. package/dist/core/complexity-detector.js +254 -0
  21. package/dist/core/complexity-detector.js.map +1 -0
  22. package/dist/core/decision-manager.d.ts +25 -0
  23. package/dist/core/decision-manager.d.ts.map +1 -0
  24. package/dist/core/decision-manager.js +188 -0
  25. package/dist/core/decision-manager.js.map +1 -0
  26. package/dist/core/generator.d.ts +1 -0
  27. package/dist/core/generator.d.ts.map +1 -1
  28. package/dist/core/generator.js +47 -3
  29. package/dist/core/generator.js.map +1 -1
  30. package/dist/core/indexer/background-indexer.d.ts +9 -2
  31. package/dist/core/indexer/background-indexer.d.ts.map +1 -1
  32. package/dist/core/indexer/background-indexer.js +99 -83
  33. package/dist/core/indexer/background-indexer.js.map +1 -1
  34. package/dist/core/knowledge-manager.d.ts +24 -0
  35. package/dist/core/knowledge-manager.d.ts.map +1 -0
  36. package/dist/core/knowledge-manager.js +173 -0
  37. package/dist/core/knowledge-manager.js.map +1 -0
  38. package/dist/core/learn-manager.d.ts +29 -0
  39. package/dist/core/learn-manager.d.ts.map +1 -0
  40. package/dist/core/learn-manager.js +159 -0
  41. package/dist/core/learn-manager.js.map +1 -0
  42. package/dist/core/rule-engine.d.ts +64 -0
  43. package/dist/core/rule-engine.d.ts.map +1 -0
  44. package/dist/core/rule-engine.js +333 -0
  45. package/dist/core/rule-engine.js.map +1 -0
  46. package/dist/core/task-manager.d.ts +4 -0
  47. package/dist/core/task-manager.d.ts.map +1 -1
  48. package/dist/core/task-manager.js +39 -24
  49. package/dist/core/task-manager.js.map +1 -1
  50. package/dist/core/workspace/project-worker.d.ts +9 -0
  51. package/dist/core/workspace/project-worker.d.ts.map +1 -1
  52. package/dist/core/workspace/project-worker.js +27 -0
  53. package/dist/core/workspace/project-worker.js.map +1 -1
  54. package/dist/index.js +250 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/mcp/rulebook-server.d.ts.map +1 -1
  57. package/dist/mcp/rulebook-server.js +758 -22
  58. package/dist/mcp/rulebook-server.js.map +1 -1
  59. package/dist/memory/hnsw-index.d.ts +3 -1
  60. package/dist/memory/hnsw-index.d.ts.map +1 -1
  61. package/dist/memory/hnsw-index.js +121 -18
  62. package/dist/memory/hnsw-index.js.map +1 -1
  63. package/dist/memory/memory-manager.d.ts +5 -0
  64. package/dist/memory/memory-manager.d.ts.map +1 -1
  65. package/dist/memory/memory-manager.js +34 -8
  66. package/dist/memory/memory-manager.js.map +1 -1
  67. package/dist/memory/memory-store.d.ts +15 -3
  68. package/dist/memory/memory-store.d.ts.map +1 -1
  69. package/dist/memory/memory-store.js +327 -272
  70. package/dist/memory/memory-store.js.map +1 -1
  71. package/dist/types.d.ts +60 -0
  72. package/dist/types.d.ts.map +1 -1
  73. package/package.json +8 -3
  74. package/templates/agents/compiler/codegen-debugger.md +34 -0
  75. package/templates/agents/compiler/stdlib-engineer.md +28 -0
  76. package/templates/agents/compiler/test-coverage-guardian.md +31 -0
  77. package/templates/agents/context-intelligence.md +52 -0
  78. package/templates/agents/game-engine/cpp-core-expert.md +35 -0
  79. package/templates/agents/game-engine/render-engineer.md +22 -0
  80. package/templates/agents/game-engine/shader-engineer.md +38 -0
  81. package/templates/agents/game-engine/systems-integration.md +43 -0
  82. package/templates/agents/generic/code-reviewer.md +39 -0
  83. package/templates/agents/generic/docs-writer.md +25 -0
  84. package/templates/agents/generic/project-manager.md +34 -0
  85. package/templates/agents/generic/researcher.md +34 -0
  86. package/templates/agents/generic/test-engineer.md +40 -0
  87. package/templates/agents/mobile/platform-specialist.md +22 -0
  88. package/templates/agents/mobile/ui-engineer.md +22 -0
  89. package/templates/agents/web-app/api-designer.md +22 -0
  90. package/templates/agents/web-app/backend-engineer.md +30 -0
  91. package/templates/agents/web-app/database-engineer.md +22 -0
  92. package/templates/agents/web-app/frontend-engineer.md +29 -0
  93. package/templates/agents/web-app/security-reviewer.md +32 -0
  94. package/templates/commands/rulebook-decision-create.md +55 -0
  95. package/templates/commands/rulebook-decision-list.md +15 -0
  96. package/templates/commands/rulebook-knowledge-add.md +41 -0
  97. package/templates/commands/rulebook-knowledge-list.md +15 -0
  98. package/templates/commands/rulebook-learn-capture.md +48 -0
  99. package/templates/commands/rulebook-learn-list.md +13 -0
  100. package/templates/core/AGENT_AUTOMATION.md +8 -0
  101. package/templates/core/DECISIONS.md +38 -0
  102. package/templates/core/KNOWLEDGE.md +49 -0
  103. package/templates/core/RULEBOOK.md +12 -0
  104. package/templates/core/TIER1_PROHIBITIONS.md +154 -0
  105. package/templates/core/TOKEN_OPTIMIZATION.md +49 -0
  106. package/templates/git/GIT_WORKFLOW.md +35 -0
  107. package/templates/rules/follow-task-sequence.md +36 -0
  108. package/templates/rules/git-safety.md +29 -0
  109. package/templates/rules/incremental-tests.md +29 -0
  110. package/templates/rules/no-deferred.md +31 -0
  111. package/templates/rules/no-shortcuts.md +30 -0
  112. package/templates/rules/research-first.md +30 -0
  113. package/templates/rules/sequential-editing.md +21 -0
  114. package/templates/rules/session-workflow.md +24 -0
  115. package/templates/rules/task-decomposition.md +32 -0
@@ -9,6 +9,23 @@ import { BackgroundIndexer } from '../core/indexer/background-indexer.js';
9
9
  import { SkillsManager, getDefaultTemplatesPath } from '../core/skills-manager.js';
10
10
  import { TaskManager } from '../core/task-manager.js';
11
11
  import { WorkspaceManager } from '../core/workspace/workspace-manager.js';
12
+ // --- Timeout guard for MCP tool handlers ---
13
+ // Prevents the MCP server from hanging when a tool handler blocks (SQLite, WASM, fs).
14
+ const MCP_TOOL_TIMEOUT_MS = parseInt(process.env.RULEBOOK_MCP_TIMEOUT_MS ?? '10000', 10);
15
+ function withTimeout(promise, ms = MCP_TOOL_TIMEOUT_MS, label = 'tool') {
16
+ return new Promise((resolve, reject) => {
17
+ const timer = setTimeout(() => {
18
+ reject(new Error(`[rulebook-mcp] ${label} timed out after ${ms}ms`));
19
+ }, ms);
20
+ promise.then((val) => {
21
+ clearTimeout(timer);
22
+ resolve(val);
23
+ }, (err) => {
24
+ clearTimeout(timer);
25
+ reject(err);
26
+ });
27
+ });
28
+ }
12
29
  // Find .rulebook file/directory by walking up directories
13
30
  export function findRulebookConfig(startDir) {
14
31
  let current = resolve(startDir);
@@ -141,7 +158,27 @@ export async function startRulebookMcpServer() {
141
158
  }
142
159
  const server = new McpServer({
143
160
  name: 'rulebook-task-management',
144
- version: '4.3.1',
161
+ version: '5.0.0',
162
+ });
163
+ // --- Wrap all tool handlers with timeout guard ---
164
+ // Intercept registerTool to automatically add timeout protection to every handler.
165
+ const originalRegisterTool = server.registerTool.bind(server);
166
+ server.registerTool = ((name, config, handler) => {
167
+ const wrappedHandler = async (...handlerArgs) => {
168
+ try {
169
+ return await withTimeout(handler(...handlerArgs), MCP_TOOL_TIMEOUT_MS, name);
170
+ }
171
+ catch (error) {
172
+ const msg = error instanceof Error ? error.message : String(error);
173
+ console.error(`[rulebook-mcp] ${name} error: ${msg}`);
174
+ return {
175
+ content: [
176
+ { type: 'text', text: JSON.stringify({ success: false, error: msg }) },
177
+ ],
178
+ };
179
+ }
180
+ };
181
+ return originalRegisterTool(name, config, wrappedHandler);
145
182
  });
146
183
  // Zod schema reused across tools for workspace project targeting
147
184
  const projectIdSchema = z
@@ -672,9 +709,16 @@ export async function startRulebookMcpServer() {
672
709
  console.error(`[rulebook-mcp] Memory DB: ${memoryDbPath}`);
673
710
  memoryManager = createMemoryManager(projectRoot, rulebookConfig.memory);
674
711
  autoCaptureEnabled = rulebookConfig.memory.autoCapture !== false;
675
- // Boot Background Indexer
712
+ // Boot Background Indexer — deferred to avoid blocking server startup
676
713
  bgIndexer = new BackgroundIndexer(memoryManager, projectRoot, { enabled: true });
677
- bgIndexer.start();
714
+ setTimeout(() => {
715
+ try {
716
+ bgIndexer?.start();
717
+ }
718
+ catch (e) {
719
+ console.error('[rulebook-mcp] BackgroundIndexer start failed:', e);
720
+ }
721
+ }, 5000);
678
722
  global.__indexerStatus = () => bgIndexer?.getStatus();
679
723
  }
680
724
  catch (e) {
@@ -697,24 +741,28 @@ export async function startRulebookMcpServer() {
697
741
  /**
698
742
  * Auto-capture: save tool interactions to memory in the background.
699
743
  * Fire-and-forget — never blocks or fails the original tool call.
744
+ * Has its own 2s timeout to prevent hanging the event loop.
700
745
  */
746
+ const AUTO_CAPTURE_TIMEOUT_MS = 2000;
701
747
  async function autoCapture(toolName, args, resultText) {
702
748
  if (!memoryManager || !autoCaptureEnabled)
703
749
  return;
704
750
  try {
705
- const { captureFromToolCall } = await import('../memory/memory-hooks.js');
706
- const captured = captureFromToolCall(toolName, args, resultText);
707
- if (!captured)
708
- return;
709
- await memoryManager.saveMemory({
710
- type: captured.type,
711
- title: captured.title,
712
- content: captured.content,
713
- tags: captured.tags,
714
- });
751
+ await withTimeout((async () => {
752
+ const { captureFromToolCall } = await import('../memory/memory-hooks.js');
753
+ const captured = captureFromToolCall(toolName, args, resultText);
754
+ if (!captured)
755
+ return;
756
+ await memoryManager.saveMemory({
757
+ type: captured.type,
758
+ title: captured.title,
759
+ content: captured.content,
760
+ tags: captured.tags,
761
+ });
762
+ })(), AUTO_CAPTURE_TIMEOUT_MS, `autoCapture(${toolName})`);
715
763
  }
716
764
  catch {
717
- // Never fail the original tool call
765
+ // Never fail the original tool call — timeout or error is silently dropped
718
766
  }
719
767
  }
720
768
  function memoryNotEnabled() {
@@ -1006,12 +1054,9 @@ export async function startRulebookMcpServer() {
1006
1054
  ],
1007
1055
  };
1008
1056
  }
1009
- // Ensure lock is released on exit, even on crashes
1010
- const cleanupLock = async () => {
1011
- await ralphManager.releaseLock();
1012
- };
1013
- process.on('SIGTERM', cleanupLock);
1014
- process.on('SIGINT', cleanupLock);
1057
+ // Lock cleanup is handled by the top-level SIGINT handler (line ~858)
1058
+ // which shuts down all managers. Adding per-call SIGINT/SIGTERM listeners
1059
+ // causes accumulation and race conditions with the server shutdown handler.
1015
1060
  try {
1016
1061
  // Validate tool is available before starting
1017
1062
  const toolCmdNames = {
@@ -1272,8 +1317,6 @@ export async function startRulebookMcpServer() {
1272
1317
  finally {
1273
1318
  // Always release lock, even on error
1274
1319
  await ralphManager.releaseLock();
1275
- process.removeListener('SIGTERM', cleanupLock);
1276
- process.removeListener('SIGINT', cleanupLock);
1277
1320
  }
1278
1321
  }
1279
1322
  catch (error) {
@@ -1640,6 +1683,699 @@ export async function startRulebookMcpServer() {
1640
1683
  }
1641
1684
  });
1642
1685
  }
1686
+ // ============================================
1687
+ // Context Intelligence Layer Tools (v4.4)
1688
+ // ============================================
1689
+ // Register tool: rulebook_decision_create
1690
+ server.registerTool('rulebook_decision_create', {
1691
+ title: 'Create Decision Record',
1692
+ description: 'Create a new architectural decision record (ADR)',
1693
+ inputSchema: {
1694
+ title: z.string().describe('Decision title'),
1695
+ context: z.string().optional().describe('Context and problem statement'),
1696
+ decision: z.string().optional().describe('The decision made'),
1697
+ alternatives: z.array(z.string()).optional().describe('Alternatives considered'),
1698
+ consequences: z.string().optional().describe('Consequences and tradeoffs'),
1699
+ relatedTasks: z.array(z.string()).optional().describe('Related task IDs'),
1700
+ projectId: projectIdSchema,
1701
+ },
1702
+ }, async (args) => {
1703
+ try {
1704
+ const root = args.projectId && workspaceManager
1705
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
1706
+ : projectRoot;
1707
+ const { DecisionManager } = await import('../core/decision-manager.js');
1708
+ const dm = new DecisionManager(root);
1709
+ const decision = await dm.create(args.title, {
1710
+ context: args.context,
1711
+ decision: args.decision,
1712
+ alternatives: args.alternatives,
1713
+ consequences: args.consequences,
1714
+ relatedTasks: args.relatedTasks,
1715
+ });
1716
+ return {
1717
+ content: [
1718
+ {
1719
+ type: 'text',
1720
+ text: JSON.stringify({ success: true, decision }),
1721
+ },
1722
+ ],
1723
+ };
1724
+ }
1725
+ catch (error) {
1726
+ return {
1727
+ content: [
1728
+ {
1729
+ type: 'text',
1730
+ text: JSON.stringify({
1731
+ success: false,
1732
+ error: error instanceof Error ? error.message : String(error),
1733
+ }),
1734
+ },
1735
+ ],
1736
+ };
1737
+ }
1738
+ });
1739
+ // Register tool: rulebook_decision_list
1740
+ server.registerTool('rulebook_decision_list', {
1741
+ title: 'List Decision Records',
1742
+ description: 'List all architectural decision records',
1743
+ inputSchema: {
1744
+ status: z
1745
+ .string()
1746
+ .optional()
1747
+ .describe('Filter by status (proposed, accepted, rejected, superseded, deprecated)'),
1748
+ projectId: projectIdSchema,
1749
+ },
1750
+ }, async (args) => {
1751
+ try {
1752
+ const root = args.projectId && workspaceManager
1753
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
1754
+ : projectRoot;
1755
+ const { DecisionManager } = await import('../core/decision-manager.js');
1756
+ const dm = new DecisionManager(root);
1757
+ const decisions = await dm.list(args.status);
1758
+ return {
1759
+ content: [
1760
+ {
1761
+ type: 'text',
1762
+ text: JSON.stringify({ success: true, decisions, count: decisions.length }),
1763
+ },
1764
+ ],
1765
+ };
1766
+ }
1767
+ catch (error) {
1768
+ return {
1769
+ content: [
1770
+ {
1771
+ type: 'text',
1772
+ text: JSON.stringify({
1773
+ success: false,
1774
+ error: error instanceof Error ? error.message : String(error),
1775
+ }),
1776
+ },
1777
+ ],
1778
+ };
1779
+ }
1780
+ });
1781
+ // Register tool: rulebook_decision_show
1782
+ server.registerTool('rulebook_decision_show', {
1783
+ title: 'Show Decision Record',
1784
+ description: 'Show full details of a decision record',
1785
+ inputSchema: {
1786
+ id: z.number().describe('Decision ID'),
1787
+ projectId: projectIdSchema,
1788
+ },
1789
+ }, async (args) => {
1790
+ try {
1791
+ const root = args.projectId && workspaceManager
1792
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
1793
+ : projectRoot;
1794
+ const { DecisionManager } = await import('../core/decision-manager.js');
1795
+ const dm = new DecisionManager(root);
1796
+ const result = await dm.show(args.id);
1797
+ if (!result) {
1798
+ return {
1799
+ content: [
1800
+ {
1801
+ type: 'text',
1802
+ text: JSON.stringify({ success: false, error: `Decision ${args.id} not found` }),
1803
+ },
1804
+ ],
1805
+ };
1806
+ }
1807
+ return {
1808
+ content: [
1809
+ {
1810
+ type: 'text',
1811
+ text: JSON.stringify({
1812
+ success: true,
1813
+ decision: result.decision,
1814
+ content: result.content,
1815
+ }),
1816
+ },
1817
+ ],
1818
+ };
1819
+ }
1820
+ catch (error) {
1821
+ return {
1822
+ content: [
1823
+ {
1824
+ type: 'text',
1825
+ text: JSON.stringify({
1826
+ success: false,
1827
+ error: error instanceof Error ? error.message : String(error),
1828
+ }),
1829
+ },
1830
+ ],
1831
+ };
1832
+ }
1833
+ });
1834
+ // Register tool: rulebook_decision_update
1835
+ server.registerTool('rulebook_decision_update', {
1836
+ title: 'Update Decision Record',
1837
+ description: 'Update an existing architectural decision record',
1838
+ inputSchema: {
1839
+ id: z.number().describe('Decision ID to update'),
1840
+ status: z
1841
+ .string()
1842
+ .optional()
1843
+ .describe('New status (proposed, accepted, rejected, superseded, deprecated)'),
1844
+ context: z.string().optional().describe('Updated context'),
1845
+ decision: z.string().optional().describe('Updated decision text'),
1846
+ projectId: projectIdSchema,
1847
+ },
1848
+ }, async (args) => {
1849
+ try {
1850
+ const root = args.projectId && workspaceManager
1851
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
1852
+ : projectRoot;
1853
+ const { DecisionManager } = await import('../core/decision-manager.js');
1854
+ const dm = new DecisionManager(root);
1855
+ const updated = await dm.update(args.id, {
1856
+ status: args.status,
1857
+ context: args.context,
1858
+ decision: args.decision,
1859
+ });
1860
+ if (!updated) {
1861
+ return {
1862
+ content: [
1863
+ {
1864
+ type: 'text',
1865
+ text: JSON.stringify({ success: false, error: `Decision ${args.id} not found` }),
1866
+ },
1867
+ ],
1868
+ };
1869
+ }
1870
+ return {
1871
+ content: [
1872
+ {
1873
+ type: 'text',
1874
+ text: JSON.stringify({ success: true, decision: updated }),
1875
+ },
1876
+ ],
1877
+ };
1878
+ }
1879
+ catch (error) {
1880
+ return {
1881
+ content: [
1882
+ {
1883
+ type: 'text',
1884
+ text: JSON.stringify({
1885
+ success: false,
1886
+ error: error instanceof Error ? error.message : String(error),
1887
+ }),
1888
+ },
1889
+ ],
1890
+ };
1891
+ }
1892
+ });
1893
+ // Register tool: rulebook_knowledge_add
1894
+ server.registerTool('rulebook_knowledge_add', {
1895
+ title: 'Add Knowledge Entry',
1896
+ description: 'Add a new pattern or anti-pattern to the project knowledge base',
1897
+ inputSchema: {
1898
+ type: z.string().describe('Knowledge type: "pattern" or "anti-pattern"'),
1899
+ title: z.string().describe('Entry title'),
1900
+ category: z.string().describe('Category (e.g. code, architecture, testing, security)'),
1901
+ description: z.string().describe('Description of the pattern'),
1902
+ example: z.string().optional().describe('Code or usage example'),
1903
+ whenToUse: z.string().optional().describe('When to use this pattern'),
1904
+ whenNotToUse: z.string().optional().describe('When NOT to use this pattern'),
1905
+ tags: z.array(z.string()).optional().describe('Tags for search'),
1906
+ projectId: projectIdSchema,
1907
+ },
1908
+ }, async (args) => {
1909
+ try {
1910
+ const root = args.projectId && workspaceManager
1911
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
1912
+ : projectRoot;
1913
+ const { KnowledgeManager } = await import('../core/knowledge-manager.js');
1914
+ const km = new KnowledgeManager(root);
1915
+ const entry = await km.add(args.type, args.title, {
1916
+ category: args.category,
1917
+ description: args.description,
1918
+ example: args.example,
1919
+ whenToUse: args.whenToUse,
1920
+ whenNotToUse: args.whenNotToUse,
1921
+ tags: args.tags,
1922
+ });
1923
+ return {
1924
+ content: [
1925
+ {
1926
+ type: 'text',
1927
+ text: JSON.stringify({ success: true, entry }),
1928
+ },
1929
+ ],
1930
+ };
1931
+ }
1932
+ catch (error) {
1933
+ return {
1934
+ content: [
1935
+ {
1936
+ type: 'text',
1937
+ text: JSON.stringify({
1938
+ success: false,
1939
+ error: error instanceof Error ? error.message : String(error),
1940
+ }),
1941
+ },
1942
+ ],
1943
+ };
1944
+ }
1945
+ });
1946
+ // Register tool: rulebook_knowledge_list
1947
+ server.registerTool('rulebook_knowledge_list', {
1948
+ title: 'List Knowledge Entries',
1949
+ description: 'List patterns and anti-patterns in the project knowledge base',
1950
+ inputSchema: {
1951
+ type: z.string().optional().describe('Filter by type: "pattern" or "anti-pattern"'),
1952
+ category: z.string().optional().describe('Filter by category'),
1953
+ projectId: projectIdSchema,
1954
+ },
1955
+ }, async (args) => {
1956
+ try {
1957
+ const root = args.projectId && workspaceManager
1958
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
1959
+ : projectRoot;
1960
+ const { KnowledgeManager } = await import('../core/knowledge-manager.js');
1961
+ const km = new KnowledgeManager(root);
1962
+ const entries = await km.list(args.type, args.category);
1963
+ return {
1964
+ content: [
1965
+ {
1966
+ type: 'text',
1967
+ text: JSON.stringify({ success: true, entries, count: entries.length }),
1968
+ },
1969
+ ],
1970
+ };
1971
+ }
1972
+ catch (error) {
1973
+ return {
1974
+ content: [
1975
+ {
1976
+ type: 'text',
1977
+ text: JSON.stringify({
1978
+ success: false,
1979
+ error: error instanceof Error ? error.message : String(error),
1980
+ }),
1981
+ },
1982
+ ],
1983
+ };
1984
+ }
1985
+ });
1986
+ // Register tool: rulebook_knowledge_show
1987
+ server.registerTool('rulebook_knowledge_show', {
1988
+ title: 'Show Knowledge Entry',
1989
+ description: 'Show full details of a knowledge entry',
1990
+ inputSchema: {
1991
+ id: z.string().describe('Knowledge entry ID (slug)'),
1992
+ projectId: projectIdSchema,
1993
+ },
1994
+ }, async (args) => {
1995
+ try {
1996
+ const root = args.projectId && workspaceManager
1997
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
1998
+ : projectRoot;
1999
+ const { KnowledgeManager } = await import('../core/knowledge-manager.js');
2000
+ const km = new KnowledgeManager(root);
2001
+ const result = await km.show(args.id);
2002
+ if (!result) {
2003
+ return {
2004
+ content: [
2005
+ {
2006
+ type: 'text',
2007
+ text: JSON.stringify({
2008
+ success: false,
2009
+ error: `Knowledge entry "${args.id}" not found`,
2010
+ }),
2011
+ },
2012
+ ],
2013
+ };
2014
+ }
2015
+ return {
2016
+ content: [
2017
+ {
2018
+ type: 'text',
2019
+ text: JSON.stringify({ success: true, entry: result.entry, content: result.content }),
2020
+ },
2021
+ ],
2022
+ };
2023
+ }
2024
+ catch (error) {
2025
+ return {
2026
+ content: [
2027
+ {
2028
+ type: 'text',
2029
+ text: JSON.stringify({
2030
+ success: false,
2031
+ error: error instanceof Error ? error.message : String(error),
2032
+ }),
2033
+ },
2034
+ ],
2035
+ };
2036
+ }
2037
+ });
2038
+ // Register tool: rulebook_learn_capture
2039
+ server.registerTool('rulebook_learn_capture', {
2040
+ title: 'Capture Learning',
2041
+ description: 'Capture a learning or insight for future reference',
2042
+ inputSchema: {
2043
+ title: z.string().describe('Brief title for the learning'),
2044
+ content: z.string().describe('Full content of the learning'),
2045
+ tags: z.array(z.string()).optional().describe('Tags for search'),
2046
+ relatedTask: z.string().optional().describe('Related task ID'),
2047
+ projectId: projectIdSchema,
2048
+ },
2049
+ }, async (args) => {
2050
+ try {
2051
+ const root = args.projectId && workspaceManager
2052
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
2053
+ : projectRoot;
2054
+ const { LearnManager } = await import('../core/learn-manager.js');
2055
+ const lm = new LearnManager(root);
2056
+ const learning = await lm.capture(args.title, args.content, {
2057
+ tags: args.tags,
2058
+ relatedTask: args.relatedTask,
2059
+ });
2060
+ return {
2061
+ content: [
2062
+ {
2063
+ type: 'text',
2064
+ text: JSON.stringify({ success: true, learning }),
2065
+ },
2066
+ ],
2067
+ };
2068
+ }
2069
+ catch (error) {
2070
+ return {
2071
+ content: [
2072
+ {
2073
+ type: 'text',
2074
+ text: JSON.stringify({
2075
+ success: false,
2076
+ error: error instanceof Error ? error.message : String(error),
2077
+ }),
2078
+ },
2079
+ ],
2080
+ };
2081
+ }
2082
+ });
2083
+ // Register tool: rulebook_learn_list
2084
+ server.registerTool('rulebook_learn_list', {
2085
+ title: 'List Learnings',
2086
+ description: 'List captured learnings, newest first',
2087
+ inputSchema: {
2088
+ limit: z.number().optional().describe('Maximum number of learnings to return'),
2089
+ projectId: projectIdSchema,
2090
+ },
2091
+ }, async (args) => {
2092
+ try {
2093
+ const root = args.projectId && workspaceManager
2094
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
2095
+ : projectRoot;
2096
+ const { LearnManager } = await import('../core/learn-manager.js');
2097
+ const lm = new LearnManager(root);
2098
+ const learnings = await lm.list(args.limit);
2099
+ return {
2100
+ content: [
2101
+ {
2102
+ type: 'text',
2103
+ text: JSON.stringify({ success: true, learnings, count: learnings.length }),
2104
+ },
2105
+ ],
2106
+ };
2107
+ }
2108
+ catch (error) {
2109
+ return {
2110
+ content: [
2111
+ {
2112
+ type: 'text',
2113
+ text: JSON.stringify({
2114
+ success: false,
2115
+ error: error instanceof Error ? error.message : String(error),
2116
+ }),
2117
+ },
2118
+ ],
2119
+ };
2120
+ }
2121
+ });
2122
+ // Register tool: rulebook_learn_promote
2123
+ server.registerTool('rulebook_learn_promote', {
2124
+ title: 'Promote Learning',
2125
+ description: 'Promote a learning to a knowledge entry or decision record',
2126
+ inputSchema: {
2127
+ id: z.string().describe('Learning ID to promote'),
2128
+ target: z
2129
+ .enum(['knowledge', 'decision'])
2130
+ .describe('Promote to knowledge base or decision record'),
2131
+ title: z.string().optional().describe('Override title for the promoted entry'),
2132
+ projectId: projectIdSchema,
2133
+ },
2134
+ }, async (args) => {
2135
+ try {
2136
+ const root = args.projectId && workspaceManager
2137
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
2138
+ : projectRoot;
2139
+ const { LearnManager } = await import('../core/learn-manager.js');
2140
+ const lm = new LearnManager(root);
2141
+ const result = await lm.promote(args.id, args.target, { title: args.title });
2142
+ if (!result) {
2143
+ return {
2144
+ content: [
2145
+ {
2146
+ type: 'text',
2147
+ text: JSON.stringify({ success: false, error: `Learning "${args.id}" not found` }),
2148
+ },
2149
+ ],
2150
+ };
2151
+ }
2152
+ return {
2153
+ content: [
2154
+ {
2155
+ type: 'text',
2156
+ text: JSON.stringify({ success: true, promoted: result }),
2157
+ },
2158
+ ],
2159
+ };
2160
+ }
2161
+ catch (error) {
2162
+ return {
2163
+ content: [
2164
+ {
2165
+ type: 'text',
2166
+ text: JSON.stringify({
2167
+ success: false,
2168
+ error: error instanceof Error ? error.message : String(error),
2169
+ }),
2170
+ },
2171
+ ],
2172
+ };
2173
+ }
2174
+ });
2175
+ // ── v5.0 Tools: Session Management, Rules, Blockers ──────────────────
2176
+ // Register tool: rulebook_session_start
2177
+ server.registerTool('rulebook_session_start', {
2178
+ title: 'Start Session',
2179
+ description: 'Load session context: reads PLANS.md and searches relevant memories. Call at the start of every session.',
2180
+ inputSchema: {
2181
+ query: z
2182
+ .string()
2183
+ .optional()
2184
+ .describe('Optional search query to find relevant past memories'),
2185
+ projectId: projectIdSchema,
2186
+ },
2187
+ }, async (args) => {
2188
+ try {
2189
+ const root = args.projectId && workspaceManager
2190
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
2191
+ : projectRoot;
2192
+ const { join } = await import('path');
2193
+ const { existsSync } = await import('fs');
2194
+ const { readFile } = await import('fs/promises');
2195
+ const result = { plans: null, memories: [] };
2196
+ // Read PLANS.md
2197
+ const plansPath = join(root, '.rulebook', 'PLANS.md');
2198
+ if (existsSync(plansPath)) {
2199
+ result.plans = await readFile(plansPath, 'utf-8');
2200
+ }
2201
+ // Search relevant memories
2202
+ if (args.query && memoryManager) {
2203
+ const searchResults = await memoryManager.searchMemories({
2204
+ query: args.query,
2205
+ mode: 'hybrid',
2206
+ limit: 5,
2207
+ });
2208
+ result.memories = searchResults;
2209
+ }
2210
+ return {
2211
+ content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }) }],
2212
+ };
2213
+ }
2214
+ catch (error) {
2215
+ return {
2216
+ content: [
2217
+ {
2218
+ type: 'text',
2219
+ text: JSON.stringify({
2220
+ success: false,
2221
+ error: error instanceof Error ? error.message : String(error),
2222
+ }),
2223
+ },
2224
+ ],
2225
+ };
2226
+ }
2227
+ });
2228
+ // Register tool: rulebook_session_end
2229
+ server.registerTool('rulebook_session_end', {
2230
+ title: 'End Session',
2231
+ description: 'Save session summary to PLANS.md history section. Call at the end of every session.',
2232
+ inputSchema: {
2233
+ summary: z.string().describe('Session summary: what was accomplished, key decisions, next steps'),
2234
+ projectId: projectIdSchema,
2235
+ },
2236
+ }, async (args) => {
2237
+ try {
2238
+ const root = args.projectId && workspaceManager
2239
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
2240
+ : projectRoot;
2241
+ const { join } = await import('path');
2242
+ const { existsSync } = await import('fs');
2243
+ const { readFile, writeFile } = await import('fs/promises');
2244
+ const plansPath = join(root, '.rulebook', 'PLANS.md');
2245
+ const date = new Date().toISOString().split('T')[0];
2246
+ const entry = `### ${date}\n${args.summary}\n`;
2247
+ if (existsSync(plansPath)) {
2248
+ let content = await readFile(plansPath, 'utf-8');
2249
+ // Insert after <!-- PLANS:HISTORY:START -->
2250
+ if (content.includes('<!-- PLANS:HISTORY:START -->')) {
2251
+ content = content.replace('<!-- PLANS:HISTORY:START -->', `<!-- PLANS:HISTORY:START -->\n${entry}`);
2252
+ }
2253
+ else {
2254
+ content += `\n## Session History\n\n<!-- PLANS:HISTORY:START -->\n${entry}<!-- PLANS:HISTORY:END -->\n`;
2255
+ }
2256
+ await writeFile(plansPath, content, 'utf-8');
2257
+ }
2258
+ else {
2259
+ // Create PLANS.md from scratch
2260
+ const newContent = `# Project Plans & Session Context\n\n<!-- PLANS:CONTEXT:START -->\n_No active context._\n<!-- PLANS:CONTEXT:END -->\n\n<!-- PLANS:TASK:START -->\n_No task in progress._\n<!-- PLANS:TASK:END -->\n\n## Session History\n\n<!-- PLANS:HISTORY:START -->\n${entry}<!-- PLANS:HISTORY:END -->\n`;
2261
+ const { mkdirSync } = await import('fs');
2262
+ const dir = join(root, '.rulebook');
2263
+ if (!existsSync(dir))
2264
+ mkdirSync(dir, { recursive: true });
2265
+ await writeFile(plansPath, newContent, 'utf-8');
2266
+ }
2267
+ // Also save to memory if available
2268
+ if (memoryManager) {
2269
+ await memoryManager.saveMemory({
2270
+ type: 'observation',
2271
+ title: `Session summary ${date}`,
2272
+ content: args.summary,
2273
+ tags: ['session', 'summary'],
2274
+ });
2275
+ }
2276
+ return {
2277
+ content: [
2278
+ { type: 'text', text: JSON.stringify({ success: true, message: 'Session summary saved to PLANS.md' }) },
2279
+ ],
2280
+ };
2281
+ }
2282
+ catch (error) {
2283
+ return {
2284
+ content: [
2285
+ {
2286
+ type: 'text',
2287
+ text: JSON.stringify({
2288
+ success: false,
2289
+ error: error instanceof Error ? error.message : String(error),
2290
+ }),
2291
+ },
2292
+ ],
2293
+ };
2294
+ }
2295
+ });
2296
+ // Register tool: rulebook_rules_list
2297
+ server.registerTool('rulebook_rules_list', {
2298
+ title: 'List Rules',
2299
+ description: 'List all canonical rules from .rulebook/rules/ with tier and tool targeting info',
2300
+ inputSchema: {
2301
+ projectId: projectIdSchema,
2302
+ },
2303
+ }, async (args) => {
2304
+ try {
2305
+ const root = args.projectId && workspaceManager
2306
+ ? (await workspaceManager.getWorker(args.projectId)).projectRoot
2307
+ : projectRoot;
2308
+ const { listRules } = await import('../core/rule-engine.js');
2309
+ const rules = await listRules(root);
2310
+ return {
2311
+ content: [{ type: 'text', text: JSON.stringify({ success: true, rules, count: rules.length }) }],
2312
+ };
2313
+ }
2314
+ catch (error) {
2315
+ return {
2316
+ content: [
2317
+ {
2318
+ type: 'text',
2319
+ text: JSON.stringify({
2320
+ success: false,
2321
+ error: error instanceof Error ? error.message : String(error),
2322
+ }),
2323
+ },
2324
+ ],
2325
+ };
2326
+ }
2327
+ });
2328
+ // Register tool: rulebook_blockers
2329
+ server.registerTool('rulebook_blockers', {
2330
+ title: 'Show Blockers',
2331
+ description: 'Show task blocker chain with cascade impact analysis',
2332
+ inputSchema: {
2333
+ projectId: projectIdSchema,
2334
+ },
2335
+ }, async (args) => {
2336
+ try {
2337
+ const tm = await getTaskMgr(args.projectId);
2338
+ const tasks = await tm.listTasks();
2339
+ // Build blocker chain from task metadata
2340
+ const blockers = [];
2341
+ for (const task of tasks) {
2342
+ const metadata = await tm.getTaskMetadata(task.id);
2343
+ const blocks = Array.isArray(metadata?.blocks) ? metadata.blocks : [];
2344
+ const blockedBy = Array.isArray(metadata?.blockedBy) ? metadata.blockedBy : [];
2345
+ if (blocks.length > 0 || blockedBy.length > 0) {
2346
+ blockers.push({
2347
+ taskId: task.id,
2348
+ blocks,
2349
+ blockedBy,
2350
+ cascadeImpact: metadata?.cascadeImpact || blocks.length,
2351
+ });
2352
+ }
2353
+ }
2354
+ // Sort by cascade impact (highest first)
2355
+ blockers.sort((a, b) => b.cascadeImpact - a.cascadeImpact);
2356
+ return {
2357
+ content: [
2358
+ {
2359
+ type: 'text',
2360
+ text: JSON.stringify({ success: true, blockers, count: blockers.length }),
2361
+ },
2362
+ ],
2363
+ };
2364
+ }
2365
+ catch (error) {
2366
+ return {
2367
+ content: [
2368
+ {
2369
+ type: 'text',
2370
+ text: JSON.stringify({
2371
+ success: false,
2372
+ error: error instanceof Error ? error.message : String(error),
2373
+ }),
2374
+ },
2375
+ ],
2376
+ };
2377
+ }
2378
+ });
1643
2379
  const transport = new StdioServerTransport();
1644
2380
  await server.connect(transport);
1645
2381
  }