@hivehub/rulebook 4.4.1 → 5.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +68 -8
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +86 -0
- package/dist/cli/commands.js.map +1 -1
- package/dist/core/agent-template-engine.d.ts +51 -0
- package/dist/core/agent-template-engine.d.ts.map +1 -0
- package/dist/core/agent-template-engine.js +285 -0
- package/dist/core/agent-template-engine.js.map +1 -0
- package/dist/core/complexity-detector.d.ts +36 -0
- package/dist/core/complexity-detector.d.ts.map +1 -0
- package/dist/core/complexity-detector.js +254 -0
- package/dist/core/complexity-detector.js.map +1 -0
- package/dist/core/config-manager.d.ts.map +1 -1
- package/dist/core/config-manager.js +7 -8
- package/dist/core/config-manager.js.map +1 -1
- package/dist/core/generator.d.ts +1 -0
- package/dist/core/generator.d.ts.map +1 -1
- package/dist/core/generator.js +21 -3
- package/dist/core/generator.js.map +1 -1
- package/dist/core/indexer/background-indexer.d.ts +2 -2
- package/dist/core/indexer/background-indexer.d.ts.map +1 -1
- package/dist/core/indexer/background-indexer.js +55 -61
- package/dist/core/indexer/background-indexer.js.map +1 -1
- package/dist/core/rule-engine.d.ts +64 -0
- package/dist/core/rule-engine.d.ts.map +1 -0
- package/dist/core/rule-engine.js +333 -0
- package/dist/core/rule-engine.js.map +1 -0
- package/dist/core/task-manager.d.ts +4 -0
- package/dist/core/task-manager.d.ts.map +1 -1
- package/dist/core/task-manager.js +39 -24
- package/dist/core/task-manager.js.map +1 -1
- package/dist/index.js +182 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/rulebook-server.d.ts +6 -0
- package/dist/mcp/rulebook-server.d.ts.map +1 -1
- package/dist/mcp/rulebook-server.js +394 -49
- package/dist/mcp/rulebook-server.js.map +1 -1
- package/dist/memory/hnsw-index.d.ts +6 -1
- package/dist/memory/hnsw-index.d.ts.map +1 -1
- package/dist/memory/hnsw-index.js +133 -18
- package/dist/memory/hnsw-index.js.map +1 -1
- package/dist/memory/memory-manager.d.ts +2 -0
- package/dist/memory/memory-manager.d.ts.map +1 -1
- package/dist/memory/memory-manager.js +16 -7
- package/dist/memory/memory-manager.js.map +1 -1
- package/dist/memory/memory-store.d.ts +15 -3
- package/dist/memory/memory-store.d.ts.map +1 -1
- package/dist/memory/memory-store.js +327 -274
- package/dist/memory/memory-store.js.map +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -3
- package/templates/agents/compiler/codegen-debugger.md +34 -0
- package/templates/agents/compiler/stdlib-engineer.md +28 -0
- package/templates/agents/compiler/test-coverage-guardian.md +31 -0
- package/templates/agents/game-engine/cpp-core-expert.md +35 -0
- package/templates/agents/game-engine/render-engineer.md +22 -0
- package/templates/agents/game-engine/shader-engineer.md +38 -0
- package/templates/agents/game-engine/systems-integration.md +43 -0
- package/templates/agents/generic/code-reviewer.md +41 -0
- package/templates/agents/generic/docs-writer.md +25 -0
- package/templates/agents/generic/project-manager.md +36 -0
- package/templates/agents/generic/researcher.md +34 -0
- package/templates/agents/generic/test-engineer.md +41 -0
- package/templates/agents/implementer.md +8 -4
- package/templates/agents/mobile/platform-specialist.md +22 -0
- package/templates/agents/mobile/ui-engineer.md +22 -0
- package/templates/agents/tester.md +7 -4
- package/templates/agents/web-app/api-designer.md +22 -0
- package/templates/agents/web-app/backend-engineer.md +30 -0
- package/templates/agents/web-app/database-engineer.md +22 -0
- package/templates/agents/web-app/frontend-engineer.md +29 -0
- package/templates/agents/web-app/security-reviewer.md +32 -0
- package/templates/core/AGENT_AUTOMATION.md +8 -0
- package/templates/core/RULEBOOK.md +12 -0
- package/templates/core/TIER1_PROHIBITIONS.md +154 -0
- package/templates/core/TOKEN_OPTIMIZATION.md +49 -0
- package/templates/git/GIT_WORKFLOW.md +35 -0
- package/templates/rules/follow-task-sequence.md +36 -0
- package/templates/rules/git-safety.md +29 -0
- package/templates/rules/incremental-implementation.md +56 -0
- package/templates/rules/incremental-tests.md +29 -0
- package/templates/rules/no-deferred.md +31 -0
- package/templates/rules/no-shortcuts.md +30 -0
- package/templates/rules/research-first.md +30 -0
- package/templates/rules/sequential-editing.md +21 -0
- package/templates/rules/session-workflow.md +24 -0
- package/templates/rules/task-decomposition.md +32 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
-
import { existsSync, readFileSync, statSync } from 'fs';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'fs';
|
|
5
5
|
import { basename, dirname, join, resolve } from 'path';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import { ConfigManager } from '../core/config-manager.js';
|
|
@@ -17,7 +17,13 @@ function withTimeout(promise, ms = MCP_TOOL_TIMEOUT_MS, label = 'tool') {
|
|
|
17
17
|
const timer = setTimeout(() => {
|
|
18
18
|
reject(new Error(`[rulebook-mcp] ${label} timed out after ${ms}ms`));
|
|
19
19
|
}, ms);
|
|
20
|
-
promise.then((val) => {
|
|
20
|
+
promise.then((val) => {
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
resolve(val);
|
|
23
|
+
}, (err) => {
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
reject(err);
|
|
26
|
+
});
|
|
21
27
|
});
|
|
22
28
|
}
|
|
23
29
|
// Find .rulebook file/directory by walking up directories
|
|
@@ -66,6 +72,58 @@ function loadConfig() {
|
|
|
66
72
|
const archiveDir = resolve(projectRoot, mcp.archiveDir || '.rulebook/archive');
|
|
67
73
|
return { projectRoot, tasksDir, archiveDir };
|
|
68
74
|
}
|
|
75
|
+
// --- PID file guard: prevent multiple MCP server instances for the same project ---
|
|
76
|
+
const PID_FILE_NAME = 'mcp-server.pid';
|
|
77
|
+
/**
|
|
78
|
+
* Check if a PID file exists and if the recorded process is still alive.
|
|
79
|
+
* If alive, exit this instance. If stale (process died), take over.
|
|
80
|
+
*/
|
|
81
|
+
export function acquirePidLock(projectRoot) {
|
|
82
|
+
const pidDir = join(projectRoot, '.rulebook');
|
|
83
|
+
const pidPath = join(pidDir, PID_FILE_NAME);
|
|
84
|
+
if (existsSync(pidPath)) {
|
|
85
|
+
try {
|
|
86
|
+
const raw = readFileSync(pidPath, 'utf8').trim();
|
|
87
|
+
const existingPid = parseInt(raw, 10);
|
|
88
|
+
if (!isNaN(existingPid)) {
|
|
89
|
+
// Check if process is still alive (signal 0 = existence check)
|
|
90
|
+
try {
|
|
91
|
+
process.kill(existingPid, 0);
|
|
92
|
+
// Process is alive — another server is running
|
|
93
|
+
console.error(`[rulebook-mcp] Another instance is already running (PID ${existingPid}). Exiting.`);
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Process is dead — stale PID file, take over
|
|
98
|
+
console.error(`[rulebook-mcp] Stale PID file found (PID ${existingPid} no longer exists). Taking over.`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Corrupt PID file — overwrite
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Write our PID
|
|
107
|
+
if (!existsSync(pidDir)) {
|
|
108
|
+
mkdirSync(pidDir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
writeFileSync(pidPath, String(process.pid), 'utf8');
|
|
111
|
+
return pidPath;
|
|
112
|
+
}
|
|
113
|
+
export function releasePidLock(pidPath) {
|
|
114
|
+
try {
|
|
115
|
+
if (existsSync(pidPath)) {
|
|
116
|
+
const raw = readFileSync(pidPath, 'utf8').trim();
|
|
117
|
+
// Only remove if it's OUR PID (another instance may have taken over)
|
|
118
|
+
if (parseInt(raw, 10) === process.pid) {
|
|
119
|
+
unlinkSync(pidPath);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
// Best-effort cleanup
|
|
125
|
+
}
|
|
126
|
+
}
|
|
69
127
|
export async function startRulebookMcpServer() {
|
|
70
128
|
// --- Workspace vs Single-Project Mode ---
|
|
71
129
|
let isWorkspaceMode = process.argv.includes('--workspace');
|
|
@@ -123,6 +181,8 @@ export async function startRulebookMcpServer() {
|
|
|
123
181
|
skillsManager = new SkillsManager(getDefaultTemplatesPath(), projectRoot);
|
|
124
182
|
configManager = new ConfigManager(projectRoot);
|
|
125
183
|
}
|
|
184
|
+
// --- PID file lock: prevent multiple instances for the same project ---
|
|
185
|
+
const pidFilePath = acquirePidLock(projectRoot);
|
|
126
186
|
// --- Manager Resolution Helpers (workspace-aware) ---
|
|
127
187
|
async function getTaskMgr(projectId) {
|
|
128
188
|
if (!projectId || !workspaceManager)
|
|
@@ -152,7 +212,7 @@ export async function startRulebookMcpServer() {
|
|
|
152
212
|
}
|
|
153
213
|
const server = new McpServer({
|
|
154
214
|
name: 'rulebook-task-management',
|
|
155
|
-
version: '
|
|
215
|
+
version: '5.1.1',
|
|
156
216
|
});
|
|
157
217
|
// --- Wrap all tool handlers with timeout guard ---
|
|
158
218
|
// Intercept registerTool to automatically add timeout protection to every handler.
|
|
@@ -166,7 +226,9 @@ export async function startRulebookMcpServer() {
|
|
|
166
226
|
const msg = error instanceof Error ? error.message : String(error);
|
|
167
227
|
console.error(`[rulebook-mcp] ${name} error: ${msg}`);
|
|
168
228
|
return {
|
|
169
|
-
content: [
|
|
229
|
+
content: [
|
|
230
|
+
{ type: 'text', text: JSON.stringify({ success: false, error: msg }) },
|
|
231
|
+
],
|
|
170
232
|
};
|
|
171
233
|
}
|
|
172
234
|
};
|
|
@@ -701,16 +763,19 @@ export async function startRulebookMcpServer() {
|
|
|
701
763
|
console.error(`[rulebook-mcp] Memory DB: ${memoryDbPath}`);
|
|
702
764
|
memoryManager = createMemoryManager(projectRoot, rulebookConfig.memory);
|
|
703
765
|
autoCaptureEnabled = rulebookConfig.memory.autoCapture !== false;
|
|
704
|
-
// Boot Background Indexer
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
766
|
+
// Boot Background Indexer only if memory is enabled (opt-in to save resources)
|
|
767
|
+
const indexerEnabled = rulebookConfig.memory?.enabled === true;
|
|
768
|
+
if (indexerEnabled) {
|
|
769
|
+
bgIndexer = new BackgroundIndexer(memoryManager, projectRoot, { enabled: true });
|
|
770
|
+
setTimeout(() => {
|
|
771
|
+
try {
|
|
772
|
+
bgIndexer?.start();
|
|
773
|
+
}
|
|
774
|
+
catch (e) {
|
|
775
|
+
console.error('[rulebook-mcp] BackgroundIndexer start failed:', e);
|
|
776
|
+
}
|
|
777
|
+
}, 5000);
|
|
778
|
+
}
|
|
714
779
|
global.__indexerStatus = () => bgIndexer?.getStatus();
|
|
715
780
|
}
|
|
716
781
|
catch (e) {
|
|
@@ -718,31 +783,67 @@ export async function startRulebookMcpServer() {
|
|
|
718
783
|
}
|
|
719
784
|
}
|
|
720
785
|
}
|
|
721
|
-
// Graceful shutdown for both modes
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
786
|
+
// --- Graceful shutdown for both modes ---
|
|
787
|
+
// Handles SIGINT (Ctrl+C), SIGTERM (parent exit), SIGHUP (terminal close),
|
|
788
|
+
// and stdin close (parent process died without signaling).
|
|
789
|
+
let isShuttingDown = false;
|
|
790
|
+
async function gracefulShutdown(reason) {
|
|
791
|
+
if (isShuttingDown)
|
|
792
|
+
return; // Prevent double-shutdown races
|
|
793
|
+
isShuttingDown = true;
|
|
794
|
+
console.error(`[rulebook-mcp] Shutting down (${reason})...`);
|
|
795
|
+
try {
|
|
796
|
+
if (bgIndexer)
|
|
797
|
+
bgIndexer.stop();
|
|
798
|
+
if (workspaceManager) {
|
|
799
|
+
await workspaceManager.shutdownAll();
|
|
800
|
+
}
|
|
801
|
+
if (memoryManager)
|
|
802
|
+
await memoryManager.close();
|
|
803
|
+
releasePidLock(pidFilePath);
|
|
804
|
+
}
|
|
805
|
+
catch (e) {
|
|
806
|
+
console.error('[rulebook-mcp] Error during shutdown:', e);
|
|
726
807
|
}
|
|
727
|
-
if (bgIndexer)
|
|
728
|
-
bgIndexer.stop();
|
|
729
|
-
if (memoryManager)
|
|
730
|
-
await memoryManager.close();
|
|
731
808
|
process.exit(0);
|
|
732
|
-
}
|
|
809
|
+
}
|
|
810
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
811
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
812
|
+
process.on('SIGHUP', () => gracefulShutdown('SIGHUP'));
|
|
813
|
+
// Detect parent process exit: when the MCP client (e.g. Claude Code) dies,
|
|
814
|
+
// stdin closes. Without this, the server becomes an orphan that leaks memory.
|
|
815
|
+
process.stdin.on('end', () => gracefulShutdown('stdin closed'));
|
|
816
|
+
process.stdin.on('close', () => gracefulShutdown('stdin closed'));
|
|
817
|
+
// Safety net: if stdin becomes unreadable but doesn't emit 'end'/'close'
|
|
818
|
+
// (can happen on Windows), poll stdin.readable every 30s.
|
|
819
|
+
const STDIN_POLL_INTERVAL_MS = 30_000;
|
|
820
|
+
const stdinPollTimer = setInterval(() => {
|
|
821
|
+
if (process.stdin.destroyed || !process.stdin.readable) {
|
|
822
|
+
clearInterval(stdinPollTimer);
|
|
823
|
+
gracefulShutdown('stdin unreadable');
|
|
824
|
+
}
|
|
825
|
+
}, STDIN_POLL_INTERVAL_MS);
|
|
826
|
+
stdinPollTimer.unref(); // Don't keep the process alive just for this timer
|
|
733
827
|
/**
|
|
734
828
|
* Auto-capture: save tool interactions to memory in the background.
|
|
735
829
|
* Fire-and-forget — never blocks or fails the original tool call.
|
|
736
830
|
* Has its own 2s timeout to prevent hanging the event loop.
|
|
831
|
+
*
|
|
832
|
+
* The dynamic import is cached after the first call to avoid repeated
|
|
833
|
+
* module loading overhead on every tool invocation.
|
|
737
834
|
*/
|
|
738
835
|
const AUTO_CAPTURE_TIMEOUT_MS = 2000;
|
|
836
|
+
let _captureFromToolCall = null;
|
|
739
837
|
async function autoCapture(toolName, args, resultText) {
|
|
740
838
|
if (!memoryManager || !autoCaptureEnabled)
|
|
741
839
|
return;
|
|
742
840
|
try {
|
|
743
841
|
await withTimeout((async () => {
|
|
744
|
-
|
|
745
|
-
|
|
842
|
+
if (!_captureFromToolCall) {
|
|
843
|
+
const mod = await import('../memory/memory-hooks.js');
|
|
844
|
+
_captureFromToolCall = mod.captureFromToolCall;
|
|
845
|
+
}
|
|
846
|
+
const captured = _captureFromToolCall(toolName, args, resultText);
|
|
746
847
|
if (!captured)
|
|
747
848
|
return;
|
|
748
849
|
await memoryManager.saveMemory({
|
|
@@ -1046,12 +1147,9 @@ export async function startRulebookMcpServer() {
|
|
|
1046
1147
|
],
|
|
1047
1148
|
};
|
|
1048
1149
|
}
|
|
1049
|
-
//
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
};
|
|
1053
|
-
process.on('SIGTERM', cleanupLock);
|
|
1054
|
-
process.on('SIGINT', cleanupLock);
|
|
1150
|
+
// Lock cleanup is handled by the top-level SIGINT handler (line ~858)
|
|
1151
|
+
// which shuts down all managers. Adding per-call SIGINT/SIGTERM listeners
|
|
1152
|
+
// causes accumulation and race conditions with the server shutdown handler.
|
|
1055
1153
|
try {
|
|
1056
1154
|
// Validate tool is available before starting
|
|
1057
1155
|
const toolCmdNames = {
|
|
@@ -1312,8 +1410,6 @@ export async function startRulebookMcpServer() {
|
|
|
1312
1410
|
finally {
|
|
1313
1411
|
// Always release lock, even on error
|
|
1314
1412
|
await ralphManager.releaseLock();
|
|
1315
|
-
process.removeListener('SIGTERM', cleanupLock);
|
|
1316
|
-
process.removeListener('SIGINT', cleanupLock);
|
|
1317
1413
|
}
|
|
1318
1414
|
}
|
|
1319
1415
|
catch (error) {
|
|
@@ -1724,7 +1820,10 @@ export async function startRulebookMcpServer() {
|
|
|
1724
1820
|
content: [
|
|
1725
1821
|
{
|
|
1726
1822
|
type: 'text',
|
|
1727
|
-
text: JSON.stringify({
|
|
1823
|
+
text: JSON.stringify({
|
|
1824
|
+
success: false,
|
|
1825
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1826
|
+
}),
|
|
1728
1827
|
},
|
|
1729
1828
|
],
|
|
1730
1829
|
};
|
|
@@ -1735,7 +1834,10 @@ export async function startRulebookMcpServer() {
|
|
|
1735
1834
|
title: 'List Decision Records',
|
|
1736
1835
|
description: 'List all architectural decision records',
|
|
1737
1836
|
inputSchema: {
|
|
1738
|
-
status: z
|
|
1837
|
+
status: z
|
|
1838
|
+
.string()
|
|
1839
|
+
.optional()
|
|
1840
|
+
.describe('Filter by status (proposed, accepted, rejected, superseded, deprecated)'),
|
|
1739
1841
|
projectId: projectIdSchema,
|
|
1740
1842
|
},
|
|
1741
1843
|
}, async (args) => {
|
|
@@ -1760,7 +1862,10 @@ export async function startRulebookMcpServer() {
|
|
|
1760
1862
|
content: [
|
|
1761
1863
|
{
|
|
1762
1864
|
type: 'text',
|
|
1763
|
-
text: JSON.stringify({
|
|
1865
|
+
text: JSON.stringify({
|
|
1866
|
+
success: false,
|
|
1867
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1868
|
+
}),
|
|
1764
1869
|
},
|
|
1765
1870
|
],
|
|
1766
1871
|
};
|
|
@@ -1796,7 +1901,11 @@ export async function startRulebookMcpServer() {
|
|
|
1796
1901
|
content: [
|
|
1797
1902
|
{
|
|
1798
1903
|
type: 'text',
|
|
1799
|
-
text: JSON.stringify({
|
|
1904
|
+
text: JSON.stringify({
|
|
1905
|
+
success: true,
|
|
1906
|
+
decision: result.decision,
|
|
1907
|
+
content: result.content,
|
|
1908
|
+
}),
|
|
1800
1909
|
},
|
|
1801
1910
|
],
|
|
1802
1911
|
};
|
|
@@ -1806,7 +1915,10 @@ export async function startRulebookMcpServer() {
|
|
|
1806
1915
|
content: [
|
|
1807
1916
|
{
|
|
1808
1917
|
type: 'text',
|
|
1809
|
-
text: JSON.stringify({
|
|
1918
|
+
text: JSON.stringify({
|
|
1919
|
+
success: false,
|
|
1920
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1921
|
+
}),
|
|
1810
1922
|
},
|
|
1811
1923
|
],
|
|
1812
1924
|
};
|
|
@@ -1818,7 +1930,10 @@ export async function startRulebookMcpServer() {
|
|
|
1818
1930
|
description: 'Update an existing architectural decision record',
|
|
1819
1931
|
inputSchema: {
|
|
1820
1932
|
id: z.number().describe('Decision ID to update'),
|
|
1821
|
-
status: z
|
|
1933
|
+
status: z
|
|
1934
|
+
.string()
|
|
1935
|
+
.optional()
|
|
1936
|
+
.describe('New status (proposed, accepted, rejected, superseded, deprecated)'),
|
|
1822
1937
|
context: z.string().optional().describe('Updated context'),
|
|
1823
1938
|
decision: z.string().optional().describe('Updated decision text'),
|
|
1824
1939
|
projectId: projectIdSchema,
|
|
@@ -1859,7 +1974,10 @@ export async function startRulebookMcpServer() {
|
|
|
1859
1974
|
content: [
|
|
1860
1975
|
{
|
|
1861
1976
|
type: 'text',
|
|
1862
|
-
text: JSON.stringify({
|
|
1977
|
+
text: JSON.stringify({
|
|
1978
|
+
success: false,
|
|
1979
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1980
|
+
}),
|
|
1863
1981
|
},
|
|
1864
1982
|
],
|
|
1865
1983
|
};
|
|
@@ -1909,7 +2027,10 @@ export async function startRulebookMcpServer() {
|
|
|
1909
2027
|
content: [
|
|
1910
2028
|
{
|
|
1911
2029
|
type: 'text',
|
|
1912
|
-
text: JSON.stringify({
|
|
2030
|
+
text: JSON.stringify({
|
|
2031
|
+
success: false,
|
|
2032
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2033
|
+
}),
|
|
1913
2034
|
},
|
|
1914
2035
|
],
|
|
1915
2036
|
};
|
|
@@ -1946,7 +2067,10 @@ export async function startRulebookMcpServer() {
|
|
|
1946
2067
|
content: [
|
|
1947
2068
|
{
|
|
1948
2069
|
type: 'text',
|
|
1949
|
-
text: JSON.stringify({
|
|
2070
|
+
text: JSON.stringify({
|
|
2071
|
+
success: false,
|
|
2072
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2073
|
+
}),
|
|
1950
2074
|
},
|
|
1951
2075
|
],
|
|
1952
2076
|
};
|
|
@@ -1973,7 +2097,10 @@ export async function startRulebookMcpServer() {
|
|
|
1973
2097
|
content: [
|
|
1974
2098
|
{
|
|
1975
2099
|
type: 'text',
|
|
1976
|
-
text: JSON.stringify({
|
|
2100
|
+
text: JSON.stringify({
|
|
2101
|
+
success: false,
|
|
2102
|
+
error: `Knowledge entry "${args.id}" not found`,
|
|
2103
|
+
}),
|
|
1977
2104
|
},
|
|
1978
2105
|
],
|
|
1979
2106
|
};
|
|
@@ -1992,7 +2119,10 @@ export async function startRulebookMcpServer() {
|
|
|
1992
2119
|
content: [
|
|
1993
2120
|
{
|
|
1994
2121
|
type: 'text',
|
|
1995
|
-
text: JSON.stringify({
|
|
2122
|
+
text: JSON.stringify({
|
|
2123
|
+
success: false,
|
|
2124
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2125
|
+
}),
|
|
1996
2126
|
},
|
|
1997
2127
|
],
|
|
1998
2128
|
};
|
|
@@ -2034,7 +2164,10 @@ export async function startRulebookMcpServer() {
|
|
|
2034
2164
|
content: [
|
|
2035
2165
|
{
|
|
2036
2166
|
type: 'text',
|
|
2037
|
-
text: JSON.stringify({
|
|
2167
|
+
text: JSON.stringify({
|
|
2168
|
+
success: false,
|
|
2169
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2170
|
+
}),
|
|
2038
2171
|
},
|
|
2039
2172
|
],
|
|
2040
2173
|
};
|
|
@@ -2070,7 +2203,10 @@ export async function startRulebookMcpServer() {
|
|
|
2070
2203
|
content: [
|
|
2071
2204
|
{
|
|
2072
2205
|
type: 'text',
|
|
2073
|
-
text: JSON.stringify({
|
|
2206
|
+
text: JSON.stringify({
|
|
2207
|
+
success: false,
|
|
2208
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2209
|
+
}),
|
|
2074
2210
|
},
|
|
2075
2211
|
],
|
|
2076
2212
|
};
|
|
@@ -2082,7 +2218,9 @@ export async function startRulebookMcpServer() {
|
|
|
2082
2218
|
description: 'Promote a learning to a knowledge entry or decision record',
|
|
2083
2219
|
inputSchema: {
|
|
2084
2220
|
id: z.string().describe('Learning ID to promote'),
|
|
2085
|
-
target: z
|
|
2221
|
+
target: z
|
|
2222
|
+
.enum(['knowledge', 'decision'])
|
|
2223
|
+
.describe('Promote to knowledge base or decision record'),
|
|
2086
2224
|
title: z.string().optional().describe('Override title for the promoted entry'),
|
|
2087
2225
|
projectId: projectIdSchema,
|
|
2088
2226
|
},
|
|
@@ -2118,7 +2256,214 @@ export async function startRulebookMcpServer() {
|
|
|
2118
2256
|
content: [
|
|
2119
2257
|
{
|
|
2120
2258
|
type: 'text',
|
|
2121
|
-
text: JSON.stringify({
|
|
2259
|
+
text: JSON.stringify({
|
|
2260
|
+
success: false,
|
|
2261
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2262
|
+
}),
|
|
2263
|
+
},
|
|
2264
|
+
],
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
// ── v5.0 Tools: Session Management, Rules, Blockers ──────────────────
|
|
2269
|
+
// Register tool: rulebook_session_start
|
|
2270
|
+
server.registerTool('rulebook_session_start', {
|
|
2271
|
+
title: 'Start Session',
|
|
2272
|
+
description: 'Load session context: reads PLANS.md and searches relevant memories. Call at the start of every session.',
|
|
2273
|
+
inputSchema: {
|
|
2274
|
+
query: z
|
|
2275
|
+
.string()
|
|
2276
|
+
.optional()
|
|
2277
|
+
.describe('Optional search query to find relevant past memories'),
|
|
2278
|
+
projectId: projectIdSchema,
|
|
2279
|
+
},
|
|
2280
|
+
}, async (args) => {
|
|
2281
|
+
try {
|
|
2282
|
+
const root = args.projectId && workspaceManager
|
|
2283
|
+
? (await workspaceManager.getWorker(args.projectId)).projectRoot
|
|
2284
|
+
: projectRoot;
|
|
2285
|
+
const { join } = await import('path');
|
|
2286
|
+
const { existsSync } = await import('fs');
|
|
2287
|
+
const { readFile } = await import('fs/promises');
|
|
2288
|
+
const result = { plans: null, memories: [] };
|
|
2289
|
+
// Read PLANS.md
|
|
2290
|
+
const plansPath = join(root, '.rulebook', 'PLANS.md');
|
|
2291
|
+
if (existsSync(plansPath)) {
|
|
2292
|
+
result.plans = await readFile(plansPath, 'utf-8');
|
|
2293
|
+
}
|
|
2294
|
+
// Search relevant memories
|
|
2295
|
+
if (args.query && memoryManager) {
|
|
2296
|
+
const searchResults = await memoryManager.searchMemories({
|
|
2297
|
+
query: args.query,
|
|
2298
|
+
mode: 'hybrid',
|
|
2299
|
+
limit: 5,
|
|
2300
|
+
});
|
|
2301
|
+
result.memories = searchResults;
|
|
2302
|
+
}
|
|
2303
|
+
return {
|
|
2304
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true, ...result }) }],
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
catch (error) {
|
|
2308
|
+
return {
|
|
2309
|
+
content: [
|
|
2310
|
+
{
|
|
2311
|
+
type: 'text',
|
|
2312
|
+
text: JSON.stringify({
|
|
2313
|
+
success: false,
|
|
2314
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2315
|
+
}),
|
|
2316
|
+
},
|
|
2317
|
+
],
|
|
2318
|
+
};
|
|
2319
|
+
}
|
|
2320
|
+
});
|
|
2321
|
+
// Register tool: rulebook_session_end
|
|
2322
|
+
server.registerTool('rulebook_session_end', {
|
|
2323
|
+
title: 'End Session',
|
|
2324
|
+
description: 'Save session summary to PLANS.md history section. Call at the end of every session.',
|
|
2325
|
+
inputSchema: {
|
|
2326
|
+
summary: z.string().describe('Session summary: what was accomplished, key decisions, next steps'),
|
|
2327
|
+
projectId: projectIdSchema,
|
|
2328
|
+
},
|
|
2329
|
+
}, async (args) => {
|
|
2330
|
+
try {
|
|
2331
|
+
const root = args.projectId && workspaceManager
|
|
2332
|
+
? (await workspaceManager.getWorker(args.projectId)).projectRoot
|
|
2333
|
+
: projectRoot;
|
|
2334
|
+
const { join } = await import('path');
|
|
2335
|
+
const { existsSync } = await import('fs');
|
|
2336
|
+
const { readFile, writeFile } = await import('fs/promises');
|
|
2337
|
+
const plansPath = join(root, '.rulebook', 'PLANS.md');
|
|
2338
|
+
const date = new Date().toISOString().split('T')[0];
|
|
2339
|
+
const entry = `### ${date}\n${args.summary}\n`;
|
|
2340
|
+
if (existsSync(plansPath)) {
|
|
2341
|
+
let content = await readFile(plansPath, 'utf-8');
|
|
2342
|
+
// Insert after <!-- PLANS:HISTORY:START -->
|
|
2343
|
+
if (content.includes('<!-- PLANS:HISTORY:START -->')) {
|
|
2344
|
+
content = content.replace('<!-- PLANS:HISTORY:START -->', `<!-- PLANS:HISTORY:START -->\n${entry}`);
|
|
2345
|
+
}
|
|
2346
|
+
else {
|
|
2347
|
+
content += `\n## Session History\n\n<!-- PLANS:HISTORY:START -->\n${entry}<!-- PLANS:HISTORY:END -->\n`;
|
|
2348
|
+
}
|
|
2349
|
+
await writeFile(plansPath, content, 'utf-8');
|
|
2350
|
+
}
|
|
2351
|
+
else {
|
|
2352
|
+
// Create PLANS.md from scratch
|
|
2353
|
+
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`;
|
|
2354
|
+
const { mkdirSync } = await import('fs');
|
|
2355
|
+
const dir = join(root, '.rulebook');
|
|
2356
|
+
if (!existsSync(dir))
|
|
2357
|
+
mkdirSync(dir, { recursive: true });
|
|
2358
|
+
await writeFile(plansPath, newContent, 'utf-8');
|
|
2359
|
+
}
|
|
2360
|
+
// Also save to memory if available
|
|
2361
|
+
if (memoryManager) {
|
|
2362
|
+
await memoryManager.saveMemory({
|
|
2363
|
+
type: 'observation',
|
|
2364
|
+
title: `Session summary ${date}`,
|
|
2365
|
+
content: args.summary,
|
|
2366
|
+
tags: ['session', 'summary'],
|
|
2367
|
+
});
|
|
2368
|
+
}
|
|
2369
|
+
return {
|
|
2370
|
+
content: [
|
|
2371
|
+
{ type: 'text', text: JSON.stringify({ success: true, message: 'Session summary saved to PLANS.md' }) },
|
|
2372
|
+
],
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
catch (error) {
|
|
2376
|
+
return {
|
|
2377
|
+
content: [
|
|
2378
|
+
{
|
|
2379
|
+
type: 'text',
|
|
2380
|
+
text: JSON.stringify({
|
|
2381
|
+
success: false,
|
|
2382
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2383
|
+
}),
|
|
2384
|
+
},
|
|
2385
|
+
],
|
|
2386
|
+
};
|
|
2387
|
+
}
|
|
2388
|
+
});
|
|
2389
|
+
// Register tool: rulebook_rules_list
|
|
2390
|
+
server.registerTool('rulebook_rules_list', {
|
|
2391
|
+
title: 'List Rules',
|
|
2392
|
+
description: 'List all canonical rules from .rulebook/rules/ with tier and tool targeting info',
|
|
2393
|
+
inputSchema: {
|
|
2394
|
+
projectId: projectIdSchema,
|
|
2395
|
+
},
|
|
2396
|
+
}, async (args) => {
|
|
2397
|
+
try {
|
|
2398
|
+
const root = args.projectId && workspaceManager
|
|
2399
|
+
? (await workspaceManager.getWorker(args.projectId)).projectRoot
|
|
2400
|
+
: projectRoot;
|
|
2401
|
+
const { listRules } = await import('../core/rule-engine.js');
|
|
2402
|
+
const rules = await listRules(root);
|
|
2403
|
+
return {
|
|
2404
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true, rules, count: rules.length }) }],
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
catch (error) {
|
|
2408
|
+
return {
|
|
2409
|
+
content: [
|
|
2410
|
+
{
|
|
2411
|
+
type: 'text',
|
|
2412
|
+
text: JSON.stringify({
|
|
2413
|
+
success: false,
|
|
2414
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2415
|
+
}),
|
|
2416
|
+
},
|
|
2417
|
+
],
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
});
|
|
2421
|
+
// Register tool: rulebook_blockers
|
|
2422
|
+
server.registerTool('rulebook_blockers', {
|
|
2423
|
+
title: 'Show Blockers',
|
|
2424
|
+
description: 'Show task blocker chain with cascade impact analysis',
|
|
2425
|
+
inputSchema: {
|
|
2426
|
+
projectId: projectIdSchema,
|
|
2427
|
+
},
|
|
2428
|
+
}, async (args) => {
|
|
2429
|
+
try {
|
|
2430
|
+
const tm = await getTaskMgr(args.projectId);
|
|
2431
|
+
const tasks = await tm.listTasks();
|
|
2432
|
+
// Build blocker chain from task metadata
|
|
2433
|
+
const blockers = [];
|
|
2434
|
+
for (const task of tasks) {
|
|
2435
|
+
const metadata = await tm.getTaskMetadata(task.id);
|
|
2436
|
+
const blocks = Array.isArray(metadata?.blocks) ? metadata.blocks : [];
|
|
2437
|
+
const blockedBy = Array.isArray(metadata?.blockedBy) ? metadata.blockedBy : [];
|
|
2438
|
+
if (blocks.length > 0 || blockedBy.length > 0) {
|
|
2439
|
+
blockers.push({
|
|
2440
|
+
taskId: task.id,
|
|
2441
|
+
blocks,
|
|
2442
|
+
blockedBy,
|
|
2443
|
+
cascadeImpact: metadata?.cascadeImpact || blocks.length,
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
// Sort by cascade impact (highest first)
|
|
2448
|
+
blockers.sort((a, b) => b.cascadeImpact - a.cascadeImpact);
|
|
2449
|
+
return {
|
|
2450
|
+
content: [
|
|
2451
|
+
{
|
|
2452
|
+
type: 'text',
|
|
2453
|
+
text: JSON.stringify({ success: true, blockers, count: blockers.length }),
|
|
2454
|
+
},
|
|
2455
|
+
],
|
|
2456
|
+
};
|
|
2457
|
+
}
|
|
2458
|
+
catch (error) {
|
|
2459
|
+
return {
|
|
2460
|
+
content: [
|
|
2461
|
+
{
|
|
2462
|
+
type: 'text',
|
|
2463
|
+
text: JSON.stringify({
|
|
2464
|
+
success: false,
|
|
2465
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2466
|
+
}),
|
|
2122
2467
|
},
|
|
2123
2468
|
],
|
|
2124
2469
|
};
|