@massu/core 1.10.8 → 1.12.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.
- package/CHANGELOG.md +1303 -0
- package/dist/adapter.js +6 -4
- package/dist/cli.js +517 -397
- package/dist/detect/adapters/.bundle-shasums.json +4 -7
- package/dist/detect/adapters/rails.js +6 -4
- package/dist/detect/adapters/spring.js +6 -4
- package/dist/detect/adapters/tree-sitter-loader.js +6 -4
- package/dist/hooks/auto-learning-pipeline.js +24 -1
- package/dist/hooks/classify-failure.js +32 -3
- package/dist/hooks/cost-tracker.js +24 -0
- package/dist/hooks/fix-detector.js +97 -1
- package/dist/hooks/incident-pipeline.js +30 -1
- package/dist/hooks/intent-suggester.js +6 -3
- package/dist/hooks/post-edit-context.js +24 -1
- package/dist/hooks/post-tool-use.js +66 -18
- package/dist/hooks/pre-compact.js +27 -2
- package/dist/hooks/pre-delete-check.js +48 -22
- package/dist/hooks/pre-tool-use-gate.js +962 -0
- package/dist/hooks/quality-event.js +24 -0
- package/dist/hooks/rule-enforcement-pipeline.js +24 -1
- package/dist/hooks/security-gate.js +29 -16
- package/dist/hooks/session-end.js +24 -0
- package/dist/hooks/session-start.js +8710 -8460
- package/dist/hooks/user-prompt.js +24 -0
- package/package.json +15 -18
- package/commands/README.md +0 -134
- package/src/adapter.ts +0 -121
- package/src/adr-generator.ts +0 -292
- package/src/analytics.ts +0 -373
- package/src/audit-trail.ts +0 -450
- package/src/backfill-sessions.ts +0 -182
- package/src/changelog-generator.ts +0 -178
- package/src/cli.ts +0 -246
- package/src/cloud-sync.ts +0 -234
- package/src/commands/adapters.ts +0 -824
- package/src/commands/changelog.ts +0 -165
- package/src/commands/config-check-drift.ts +0 -133
- package/src/commands/config-refresh.ts +0 -396
- package/src/commands/config-upgrade.ts +0 -127
- package/src/commands/doctor.ts +0 -485
- package/src/commands/hook-runner.ts +0 -145
- package/src/commands/init.ts +0 -1823
- package/src/commands/install-commands.ts +0 -692
- package/src/commands/install-hooks.ts +0 -27
- package/src/commands/permissions.ts +0 -150
- package/src/commands/refresh-log.ts +0 -37
- package/src/commands/show-template.ts +0 -65
- package/src/commands/template-engine.ts +0 -301
- package/src/commands/watch.ts +0 -430
- package/src/config.ts +0 -765
- package/src/cost-tracker.ts +0 -355
- package/src/db.ts +0 -370
- package/src/dependency-scorer.ts +0 -337
- package/src/detect/adapters/aspnet.ts +0 -4
- package/src/detect/adapters/discover.ts +0 -469
- package/src/detect/adapters/file-sampler.ts +0 -225
- package/src/detect/adapters/go-chi.ts +0 -4
- package/src/detect/adapters/index.ts +0 -51
- package/src/detect/adapters/nextjs-trpc.ts +0 -166
- package/src/detect/adapters/parse-guard.ts +0 -133
- package/src/detect/adapters/phoenix.ts +0 -4
- package/src/detect/adapters/python-django.ts +0 -208
- package/src/detect/adapters/python-fastapi.ts +0 -223
- package/src/detect/adapters/python-flask.ts +0 -235
- package/src/detect/adapters/query-helpers.ts +0 -170
- package/src/detect/adapters/rails.ts +0 -4
- package/src/detect/adapters/runner.ts +0 -284
- package/src/detect/adapters/spring.ts +0 -4
- package/src/detect/adapters/swift-swiftui.ts +0 -171
- package/src/detect/adapters/tree-sitter-loader.ts +0 -517
- package/src/detect/adapters/types.ts +0 -191
- package/src/detect/codebase-introspector.ts +0 -210
- package/src/detect/domain-inferrer.ts +0 -197
- package/src/detect/drift.ts +0 -199
- package/src/detect/framework-detector.ts +0 -307
- package/src/detect/index.ts +0 -209
- package/src/detect/manifest-registry.ts +0 -261
- package/src/detect/migrate.ts +0 -362
- package/src/detect/monorepo-detector.ts +0 -348
- package/src/detect/package-detector.ts +0 -828
- package/src/detect/passthrough.ts +0 -108
- package/src/detect/regex-fallback.ts +0 -449
- package/src/detect/source-dir-detector.ts +0 -297
- package/src/detect/vr-command-map.ts +0 -167
- package/src/docs-map.json +0 -100
- package/src/docs-tools.ts +0 -519
- package/src/domains.ts +0 -182
- package/src/hooks/auto-learning-pipeline.ts +0 -228
- package/src/hooks/classify-failure.ts +0 -259
- package/src/hooks/cost-tracker.ts +0 -66
- package/src/hooks/fix-detector.ts +0 -186
- package/src/hooks/incident-pipeline.ts +0 -190
- package/src/hooks/intent-suggester.ts +0 -131
- package/src/hooks/post-edit-context.ts +0 -91
- package/src/hooks/post-tool-use.ts +0 -413
- package/src/hooks/pre-compact.ts +0 -168
- package/src/hooks/pre-delete-check.ts +0 -243
- package/src/hooks/quality-event.ts +0 -127
- package/src/hooks/rule-enforcement-pipeline.ts +0 -159
- package/src/hooks/security-gate.ts +0 -153
- package/src/hooks/session-end.ts +0 -467
- package/src/hooks/session-start.ts +0 -436
- package/src/hooks/user-prompt.ts +0 -157
- package/src/import-resolver.ts +0 -226
- package/src/knowledge-db.ts +0 -176
- package/src/knowledge-indexer.ts +0 -708
- package/src/knowledge-tools.ts +0 -1414
- package/src/lib/fileLock.ts +0 -203
- package/src/lib/framework-supports.ts +0 -85
- package/src/lib/gitToplevel.ts +0 -22
- package/src/lib/hook-registry.ts +0 -43
- package/src/lib/installLock.ts +0 -66
- package/src/lib/memory-path.ts +0 -49
- package/src/lib/pidLiveness.ts +0 -67
- package/src/lib/settings-local.ts +0 -110
- package/src/lib/sql-table-names.ts +0 -71
- package/src/license.ts +0 -547
- package/src/lsp/auto-detect.ts +0 -98
- package/src/lsp/client.ts +0 -776
- package/src/lsp/enrich.ts +0 -127
- package/src/lsp/types.ts +0 -221
- package/src/memory-db.ts +0 -1614
- package/src/memory-file-ingest.ts +0 -128
- package/src/memory-tools.ts +0 -424
- package/src/middleware-tree.ts +0 -71
- package/src/observability-tools.ts +0 -343
- package/src/observation-extractor.ts +0 -418
- package/src/page-deps.ts +0 -285
- package/src/permissions.ts +0 -422
- package/src/prompt-analyzer.ts +0 -332
- package/src/python/coupling-detector.ts +0 -125
- package/src/python/domain-enforcer.ts +0 -84
- package/src/python/impact-analyzer.ts +0 -96
- package/src/python/import-parser.ts +0 -244
- package/src/python/import-resolver.ts +0 -136
- package/src/python/migration-indexer.ts +0 -116
- package/src/python/migration-parser.ts +0 -332
- package/src/python/model-indexer.ts +0 -71
- package/src/python/model-parser.ts +0 -279
- package/src/python/route-indexer.ts +0 -59
- package/src/python/route-parser.ts +0 -317
- package/src/python-tools.ts +0 -630
- package/src/regression-detector.ts +0 -319
- package/src/rules.ts +0 -57
- package/src/schema-mapper.ts +0 -232
- package/src/security/adapter-origin.ts +0 -130
- package/src/security/adapter-verifier.ts +0 -319
- package/src/security/atomic-write.ts +0 -164
- package/src/security/fetcher.ts +0 -200
- package/src/security/install-tracking.ts +0 -319
- package/src/security/license-pubkey.generated.ts +0 -30
- package/src/security/license-response-verifier.ts +0 -149
- package/src/security/local-fingerprint.ts +0 -225
- package/src/security/manifest-cache.ts +0 -333
- package/src/security/manifest-schema.ts +0 -129
- package/src/security/registry-pubkey.generated.ts +0 -35
- package/src/security/telemetry.ts +0 -320
- package/src/security-scorer.ts +0 -405
- package/src/security-utils.ts +0 -133
- package/src/sentinel-db.ts +0 -580
- package/src/sentinel-scanner.ts +0 -406
- package/src/sentinel-tools.ts +0 -513
- package/src/sentinel-types.ts +0 -140
- package/src/server-dispatch.ts +0 -225
- package/src/server.ts +0 -107
- package/src/session-archiver.ts +0 -111
- package/src/session-state-generator.ts +0 -174
- package/src/team-knowledge.ts +0 -407
- package/src/tool-db-needs.ts +0 -232
- package/src/tools.ts +0 -1206
- package/src/transcript-parser.ts +0 -458
- package/src/trpc-index.ts +0 -215
- package/src/validate-features-runner.ts +0 -107
- package/src/validation-engine.ts +0 -358
- package/src/watch/daemon.ts +0 -385
- package/src/watch/lockfile-detector.ts +0 -65
- package/src/watch/paths.ts +0 -279
- package/src/watch/state.ts +0 -178
package/src/server-dispatch.ts
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* MCP server dispatch logic — pure, factory-based, no module-level mutable state.
|
|
6
|
-
*
|
|
7
|
-
* Production (`server.ts`) calls `createDispatcher()` once at startup and wires
|
|
8
|
-
* its `processLine` into stdin. Tests call `createDispatcher()` per test for
|
|
9
|
-
* fresh DB cache state (no test bleed).
|
|
10
|
-
*
|
|
11
|
-
* Three error envelopes live here (CR-12 / plan-1.6.2-server-lazy-db-deps):
|
|
12
|
-
* -32001 Tool needs CodeGraph but `.codegraph/codegraph.db` is missing
|
|
13
|
-
* (CodegraphDbNotInitializedError → structured remedy data)
|
|
14
|
-
* -32602 Tool not registered in `TOOL_DB_NEEDS` manifest
|
|
15
|
-
* (UnknownToolError → points at tool-db-needs.ts)
|
|
16
|
-
* -32603 Other internal errors raised by handleToolCall — request id
|
|
17
|
-
* is preserved (NOT id:null, which is reserved for -32700 parse
|
|
18
|
-
* failures per JSON-RPC §5.1).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type Database from 'better-sqlite3';
|
|
22
|
-
import { getCodeGraphDb, getDataDb, CodegraphDbNotInitializedError } from './db.ts';
|
|
23
|
-
import { getConfig } from './config.ts';
|
|
24
|
-
import { getToolDefinitions, handleToolCall } from './tools.ts';
|
|
25
|
-
import { getToolDbNeeds, UnknownToolError, type DbNeed } from './tool-db-needs.ts';
|
|
26
|
-
|
|
27
|
-
export interface JsonRpcRequest {
|
|
28
|
-
jsonrpc: '2.0';
|
|
29
|
-
id?: number | string;
|
|
30
|
-
method: string;
|
|
31
|
-
params?: Record<string, unknown>;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface JsonRpcResponse {
|
|
35
|
-
jsonrpc: '2.0';
|
|
36
|
-
id: number | string | null;
|
|
37
|
-
result?: unknown;
|
|
38
|
-
error?: { code: number; message: string; data?: unknown };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** Per-line dispatch result. `emit=false` when the request was a notification (no id). */
|
|
42
|
-
export interface ProcessLineResult {
|
|
43
|
-
response: JsonRpcResponse;
|
|
44
|
-
emit: boolean;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface DispatcherOptions {
|
|
48
|
-
/** Version string surfaced in `initialize.result.serverInfo.version`. */
|
|
49
|
-
serverInfoVersion: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface Dispatcher {
|
|
53
|
-
handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse>;
|
|
54
|
-
processLine(line: string): Promise<ProcessLineResult | null>;
|
|
55
|
-
closeCachedDbs(): void;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function createDispatcher(options: DispatcherOptions): Dispatcher {
|
|
59
|
-
let codegraphDbCache: Database.Database | null = null;
|
|
60
|
-
let dataDbCache: Database.Database | null = null;
|
|
61
|
-
|
|
62
|
-
function resolveDbsForTool(toolName: string): {
|
|
63
|
-
needs: readonly DbNeed[];
|
|
64
|
-
dataDb?: Database.Database;
|
|
65
|
-
codegraphDb?: Database.Database;
|
|
66
|
-
} {
|
|
67
|
-
const needs = getToolDbNeeds(toolName, getConfig().toolPrefix);
|
|
68
|
-
|
|
69
|
-
let dataDbResolved: Database.Database | undefined;
|
|
70
|
-
let codegraphDbResolved: Database.Database | undefined;
|
|
71
|
-
|
|
72
|
-
if (needs.includes('data')) {
|
|
73
|
-
if (!dataDbCache) dataDbCache = getDataDb();
|
|
74
|
-
dataDbResolved = dataDbCache;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (needs.includes('codegraph')) {
|
|
78
|
-
// Throws CodegraphDbNotInitializedError when .codegraph/codegraph.db is missing.
|
|
79
|
-
if (!codegraphDbCache) codegraphDbCache = getCodeGraphDb();
|
|
80
|
-
codegraphDbResolved = codegraphDbCache;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { needs, dataDb: dataDbResolved, codegraphDb: codegraphDbResolved };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {
|
|
87
|
-
const { method, params, id } = request;
|
|
88
|
-
|
|
89
|
-
switch (method) {
|
|
90
|
-
case 'initialize': {
|
|
91
|
-
return {
|
|
92
|
-
jsonrpc: '2.0',
|
|
93
|
-
id: id ?? null,
|
|
94
|
-
result: {
|
|
95
|
-
protocolVersion: '2024-11-05',
|
|
96
|
-
capabilities: { tools: {} },
|
|
97
|
-
serverInfo: {
|
|
98
|
-
name: getConfig().toolPrefix || 'massu',
|
|
99
|
-
version: options.serverInfoVersion,
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
case 'notifications/initialized': {
|
|
106
|
-
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
case 'tools/list': {
|
|
110
|
-
const tools = getToolDefinitions();
|
|
111
|
-
return { jsonrpc: '2.0', id: id ?? null, result: { tools } };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
case 'tools/call': {
|
|
115
|
-
const toolName = (params as { name?: string })?.name ?? '';
|
|
116
|
-
const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
const { dataDb, codegraphDb } = resolveDbsForTool(toolName);
|
|
120
|
-
const result = await handleToolCall(toolName, toolArgs, dataDb, codegraphDb);
|
|
121
|
-
return { jsonrpc: '2.0', id: id ?? null, result };
|
|
122
|
-
} catch (err) {
|
|
123
|
-
if (err instanceof CodegraphDbNotInitializedError) {
|
|
124
|
-
return {
|
|
125
|
-
jsonrpc: '2.0',
|
|
126
|
-
id: id ?? null,
|
|
127
|
-
error: {
|
|
128
|
-
code: -32001,
|
|
129
|
-
message: 'Tool requires CodeGraph database which is not initialized for this repo',
|
|
130
|
-
data: {
|
|
131
|
-
remedy: 'npx @colbymchenry/codegraph@0.7.4 init . && npx @colbymchenry/codegraph@0.7.4 index .',
|
|
132
|
-
codegraphDbPath: err.dbPath,
|
|
133
|
-
tool: toolName,
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
if (err instanceof UnknownToolError) {
|
|
139
|
-
return {
|
|
140
|
-
jsonrpc: '2.0',
|
|
141
|
-
id: id ?? null,
|
|
142
|
-
error: {
|
|
143
|
-
code: -32602,
|
|
144
|
-
message: `Unknown tool: ${err.toolName}`,
|
|
145
|
-
data: {
|
|
146
|
-
remedy: 'Tool not registered in TOOL_DB_NEEDS manifest. See packages/core/src/tool-db-needs.ts.',
|
|
147
|
-
tool: toolName,
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
throw err;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
case 'ping': {
|
|
157
|
-
return { jsonrpc: '2.0', id: id ?? null, result: {} };
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
default: {
|
|
161
|
-
return {
|
|
162
|
-
jsonrpc: '2.0',
|
|
163
|
-
id: id ?? null,
|
|
164
|
-
error: { code: -32601, message: `Method not found: ${method}` },
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function processLine(line: string): Promise<ProcessLineResult | null> {
|
|
171
|
-
const trimmed = line.trim();
|
|
172
|
-
if (!trimmed) return null;
|
|
173
|
-
|
|
174
|
-
let request: JsonRpcRequest;
|
|
175
|
-
try {
|
|
176
|
-
request = JSON.parse(trimmed) as JsonRpcRequest;
|
|
177
|
-
} catch (parseError) {
|
|
178
|
-
// JSON-RPC §5.1: parse failure → -32700 + id:null (no id is extractable).
|
|
179
|
-
return {
|
|
180
|
-
response: {
|
|
181
|
-
jsonrpc: '2.0',
|
|
182
|
-
id: null,
|
|
183
|
-
error: {
|
|
184
|
-
code: -32700,
|
|
185
|
-
message: `Parse error: ${parseError instanceof Error ? parseError.message : String(parseError)}`,
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
emit: true,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
const response = await handleRequest(request);
|
|
194
|
-
// Notifications (no id) MUST NOT receive a response per JSON-RPC §4.1.
|
|
195
|
-
return { response, emit: request.id !== undefined };
|
|
196
|
-
} catch (error) {
|
|
197
|
-
// Request-processing failure: -32603 with the request id preserved.
|
|
198
|
-
// Specific subclasses (-32001/-32602) are handled inside tools/call.
|
|
199
|
-
return {
|
|
200
|
-
response: {
|
|
201
|
-
jsonrpc: '2.0',
|
|
202
|
-
id: request.id ?? null,
|
|
203
|
-
error: {
|
|
204
|
-
code: -32603,
|
|
205
|
-
message: `Internal error: ${error instanceof Error ? error.message : String(error)}`,
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
emit: true,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function closeCachedDbs(): void {
|
|
214
|
-
if (codegraphDbCache) {
|
|
215
|
-
codegraphDbCache.close();
|
|
216
|
-
codegraphDbCache = null;
|
|
217
|
-
}
|
|
218
|
-
if (dataDbCache) {
|
|
219
|
-
dataDbCache.close();
|
|
220
|
-
dataDbCache = null;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return { handleRequest, processLine, closeCachedDbs };
|
|
225
|
-
}
|
package/src/server.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
3
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Massu MCP Server
|
|
7
|
-
*
|
|
8
|
-
* An MCP server that provides project-specific intelligence on top of
|
|
9
|
-
* vanilla CodeGraph. Communicates via JSON-RPC 2.0 over stdio.
|
|
10
|
-
*
|
|
11
|
-
* Tool names are configurable via massu.config.yaml toolPrefix.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { readFileSync } from 'fs';
|
|
15
|
-
import { resolve, dirname } from 'path';
|
|
16
|
-
import { fileURLToPath } from 'url';
|
|
17
|
-
import { getMemoryDb, pruneOldConversationTurns, pruneOldObservations } from './memory-db.ts';
|
|
18
|
-
import { getCurrentTier } from './license.ts';
|
|
19
|
-
import { createDispatcher } from './server-dispatch.ts';
|
|
20
|
-
|
|
21
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
-
const PKG_VERSION = (() => {
|
|
23
|
-
try {
|
|
24
|
-
const pkg = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8'));
|
|
25
|
-
return pkg.version ?? '0.0.0';
|
|
26
|
-
} catch {
|
|
27
|
-
return '0.0.0';
|
|
28
|
-
}
|
|
29
|
-
})();
|
|
30
|
-
|
|
31
|
-
const dispatcher = createDispatcher({ serverInfoVersion: PKG_VERSION });
|
|
32
|
-
|
|
33
|
-
// === Startup: prune stale memory data (non-blocking) ===
|
|
34
|
-
|
|
35
|
-
function pruneMemoryOnStartup(): void {
|
|
36
|
-
try {
|
|
37
|
-
const memDb = getMemoryDb();
|
|
38
|
-
try {
|
|
39
|
-
const turns = pruneOldConversationTurns(memDb, 7);
|
|
40
|
-
const obsDeleted = pruneOldObservations(memDb, 90);
|
|
41
|
-
|
|
42
|
-
const totalPruned = turns.turnsDeleted + turns.detailsDeleted + obsDeleted;
|
|
43
|
-
if (totalPruned > 0) {
|
|
44
|
-
process.stderr.write(
|
|
45
|
-
`massu: Pruned memory DB on startup — ` +
|
|
46
|
-
`${turns.turnsDeleted} conversation turns, ` +
|
|
47
|
-
`${turns.detailsDeleted} tool call details (>7d), ` +
|
|
48
|
-
`${obsDeleted} observations (>90d)\n`
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
} finally {
|
|
52
|
-
memDb.close();
|
|
53
|
-
}
|
|
54
|
-
} catch (error) {
|
|
55
|
-
process.stderr.write(
|
|
56
|
-
`massu: Memory pruning failed (non-fatal): ${error instanceof Error ? error.message : String(error)}\n`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
pruneMemoryOnStartup();
|
|
62
|
-
|
|
63
|
-
// === License init: pre-cache tier status ===
|
|
64
|
-
getCurrentTier().then(tier => {
|
|
65
|
-
process.stderr.write(`massu: License tier: ${tier}\n`);
|
|
66
|
-
}).catch(error => {
|
|
67
|
-
process.stderr.write(
|
|
68
|
-
`massu: License check failed (non-fatal): ${error instanceof Error ? error.message : String(error)}\n`
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// === stdio JSON-RPC transport ===
|
|
73
|
-
|
|
74
|
-
let buffer = '';
|
|
75
|
-
|
|
76
|
-
process.stdin.setEncoding('utf-8');
|
|
77
|
-
process.stdin.on('data', async (chunk: string) => {
|
|
78
|
-
buffer += chunk;
|
|
79
|
-
|
|
80
|
-
// Process complete messages (newline-delimited JSON-RPC)
|
|
81
|
-
let newlineIndex: number;
|
|
82
|
-
while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
|
|
83
|
-
const line = buffer.slice(0, newlineIndex);
|
|
84
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
85
|
-
|
|
86
|
-
const result = await dispatcher.processLine(line);
|
|
87
|
-
if (result && result.emit) {
|
|
88
|
-
process.stdout.write(JSON.stringify(result.response) + '\n');
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
process.stdin.on('end', () => {
|
|
94
|
-
// Close cached CodeGraph + Data connections. Memory + Knowledge are
|
|
95
|
-
// per-call (closed inside their routing branches in tools.ts).
|
|
96
|
-
dispatcher.closeCachedDbs();
|
|
97
|
-
process.exit(0);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// Handle errors gracefully
|
|
101
|
-
process.on('uncaughtException', (error) => {
|
|
102
|
-
process.stderr.write(`massu: Uncaught exception: ${error.message}\n`);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
process.on('unhandledRejection', (reason) => {
|
|
106
|
-
process.stderr.write(`massu: Unhandled rejection: ${reason}\n`);
|
|
107
|
-
});
|
package/src/session-archiver.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'fs';
|
|
5
|
-
import { resolve, dirname } from 'path';
|
|
6
|
-
import type Database from 'better-sqlite3';
|
|
7
|
-
import { generateCurrentMd } from './session-state-generator.ts';
|
|
8
|
-
import { getResolvedPaths } from './config.ts';
|
|
9
|
-
|
|
10
|
-
// ============================================================
|
|
11
|
-
// P5-002: Session Archiver
|
|
12
|
-
// ============================================================
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Archive the current CURRENT.md and generate a new one from memory DB.
|
|
16
|
-
*/
|
|
17
|
-
export function archiveAndRegenerate(db: Database.Database, sessionId: string): {
|
|
18
|
-
archived: boolean;
|
|
19
|
-
archivePath?: string;
|
|
20
|
-
newContent: string;
|
|
21
|
-
} {
|
|
22
|
-
const resolved = getResolvedPaths();
|
|
23
|
-
const currentMdPath = resolved.sessionStatePath;
|
|
24
|
-
const archiveDir = resolved.sessionArchivePath;
|
|
25
|
-
let archived = false;
|
|
26
|
-
let archivePath: string | undefined;
|
|
27
|
-
|
|
28
|
-
// 1. Archive existing CURRENT.md if it exists and has content
|
|
29
|
-
if (existsSync(currentMdPath)) {
|
|
30
|
-
const existingContent = readFileSync(currentMdPath, 'utf-8');
|
|
31
|
-
if (existingContent.trim().length > 10) {
|
|
32
|
-
// Extract date and task description for filename
|
|
33
|
-
const { date, slug } = extractArchiveInfo(existingContent);
|
|
34
|
-
archivePath = resolve(archiveDir, `${date}-${slug}.md`);
|
|
35
|
-
|
|
36
|
-
// Ensure archive directory exists
|
|
37
|
-
if (!existsSync(archiveDir)) {
|
|
38
|
-
mkdirSync(archiveDir, { recursive: true });
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Move to archive (rename is atomic on same filesystem)
|
|
42
|
-
try {
|
|
43
|
-
renameSync(currentMdPath, archivePath);
|
|
44
|
-
archived = true;
|
|
45
|
-
} catch (_e) {
|
|
46
|
-
// If rename fails (cross-device), copy+delete
|
|
47
|
-
writeFileSync(archivePath, existingContent);
|
|
48
|
-
archived = true;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 2. Generate new CURRENT.md from memory DB
|
|
54
|
-
const newContent = generateCurrentMd(db, sessionId);
|
|
55
|
-
|
|
56
|
-
// 3. Write new CURRENT.md
|
|
57
|
-
const dir = dirname(currentMdPath);
|
|
58
|
-
if (!existsSync(dir)) {
|
|
59
|
-
mkdirSync(dir, { recursive: true });
|
|
60
|
-
}
|
|
61
|
-
writeFileSync(currentMdPath, newContent, 'utf-8');
|
|
62
|
-
|
|
63
|
-
return { archived, archivePath, newContent };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Extract date and slug from existing CURRENT.md content for archive naming.
|
|
68
|
-
*/
|
|
69
|
-
function extractArchiveInfo(content: string): { date: string; slug: string } {
|
|
70
|
-
// Try to extract date from "# Session State - January 30, 2026"
|
|
71
|
-
const dateMatch = content.match(/# Session State - (\w+ \d+, \d+)/);
|
|
72
|
-
let date = new Date().toISOString().split('T')[0]; // fallback
|
|
73
|
-
|
|
74
|
-
if (dateMatch) {
|
|
75
|
-
const parsed = new Date(dateMatch[1]);
|
|
76
|
-
if (!isNaN(parsed.getTime())) {
|
|
77
|
-
date = parsed.toISOString().split('T')[0];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Also try ISO date format "**Last Updated**: 2026-01-30"
|
|
82
|
-
const isoMatch = content.match(/(\d{4}-\d{2}-\d{2})/);
|
|
83
|
-
if (isoMatch) {
|
|
84
|
-
date = isoMatch[1];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Extract task description for slug
|
|
88
|
-
let slug = 'session';
|
|
89
|
-
const taskMatch = content.match(/\*\*Task\*\*:\s*(.+)/);
|
|
90
|
-
if (taskMatch) {
|
|
91
|
-
slug = taskMatch[1]
|
|
92
|
-
.toLowerCase()
|
|
93
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
94
|
-
.replace(/^-|-$/g, '')
|
|
95
|
-
.slice(0, 50);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Extract status for slug if no task
|
|
99
|
-
if (slug === 'session') {
|
|
100
|
-
const statusMatch = content.match(/\*\*Status\*\*:\s*\w+\s*-\s*(.+)/);
|
|
101
|
-
if (statusMatch) {
|
|
102
|
-
slug = statusMatch[1]
|
|
103
|
-
.toLowerCase()
|
|
104
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
105
|
-
.replace(/^-|-$/g, '')
|
|
106
|
-
.slice(0, 50);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return { date, slug };
|
|
111
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
import type Database from 'better-sqlite3';
|
|
5
|
-
|
|
6
|
-
// ============================================================
|
|
7
|
-
// P5-001: CURRENT.md Generator
|
|
8
|
-
// ============================================================
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Generate CURRENT.md content from memory database.
|
|
12
|
-
* Replaces manual session state maintenance.
|
|
13
|
-
*/
|
|
14
|
-
export function generateCurrentMd(db: Database.Database, sessionId: string): string {
|
|
15
|
-
const session = db.prepare('SELECT * FROM sessions WHERE session_id = ?').get(sessionId) as Record<string, unknown> | undefined;
|
|
16
|
-
if (!session) return '# Session State\n\nNo active session found.\n';
|
|
17
|
-
|
|
18
|
-
const observations = db.prepare(
|
|
19
|
-
'SELECT * FROM observations WHERE session_id = ? ORDER BY created_at_epoch ASC'
|
|
20
|
-
).all(sessionId) as Array<Record<string, unknown>>;
|
|
21
|
-
|
|
22
|
-
const summary = db.prepare(
|
|
23
|
-
'SELECT * FROM session_summaries WHERE session_id = ? ORDER BY created_at_epoch DESC LIMIT 1'
|
|
24
|
-
).get(sessionId) as Record<string, unknown> | undefined;
|
|
25
|
-
|
|
26
|
-
const prompts = db.prepare(
|
|
27
|
-
'SELECT prompt_text FROM user_prompts WHERE session_id = ? ORDER BY prompt_number ASC LIMIT 1'
|
|
28
|
-
).all(sessionId) as Array<{ prompt_text: string }>;
|
|
29
|
-
|
|
30
|
-
const date = new Date().toISOString().split('T')[0];
|
|
31
|
-
const firstPrompt = prompts[0]?.prompt_text ?? 'Unknown task';
|
|
32
|
-
const taskSummary = firstPrompt.slice(0, 100).replace(/\n/g, ' ');
|
|
33
|
-
|
|
34
|
-
const lines: string[] = [];
|
|
35
|
-
lines.push(`# Session State - ${formatDate(date)}`);
|
|
36
|
-
lines.push('');
|
|
37
|
-
lines.push(`**Last Updated**: ${new Date().toISOString().replace('T', ' ').slice(0, 19)} (auto-generated from massu-memory)`);
|
|
38
|
-
lines.push(`**Status**: ${session.status === 'active' ? 'IN PROGRESS' : (session.status as string).toUpperCase()} - ${taskSummary}`);
|
|
39
|
-
lines.push(`**Task**: ${taskSummary}`);
|
|
40
|
-
lines.push(`**Session ID**: ${sessionId}`);
|
|
41
|
-
lines.push(`**Branch**: ${session.git_branch ?? 'unknown'}`);
|
|
42
|
-
lines.push('');
|
|
43
|
-
lines.push('---');
|
|
44
|
-
lines.push('');
|
|
45
|
-
|
|
46
|
-
// Completed work
|
|
47
|
-
const completedObs = observations.filter(o =>
|
|
48
|
-
['feature', 'bugfix', 'refactor', 'file_change'].includes(o.type as string)
|
|
49
|
-
);
|
|
50
|
-
if (completedObs.length > 0 || summary) {
|
|
51
|
-
lines.push('## COMPLETED WORK');
|
|
52
|
-
lines.push('');
|
|
53
|
-
|
|
54
|
-
if (summary?.completed) {
|
|
55
|
-
lines.push(summary.completed as string);
|
|
56
|
-
lines.push('');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Files created
|
|
60
|
-
const filesCreated = observations
|
|
61
|
-
.filter(o => o.type === 'file_change' && (o.title as string).startsWith('Created'))
|
|
62
|
-
.map(o => {
|
|
63
|
-
const files = safeParseJson(o.files_involved as string, []) as string[];
|
|
64
|
-
return files[0] ?? (o.title as string).replace('Created/wrote: ', '');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
if (filesCreated.length > 0) {
|
|
68
|
-
lines.push('### Files Created');
|
|
69
|
-
lines.push('');
|
|
70
|
-
lines.push('| File | Purpose |');
|
|
71
|
-
lines.push('|------|---------|');
|
|
72
|
-
for (const f of filesCreated) {
|
|
73
|
-
lines.push(`| \`${f}\` | |`);
|
|
74
|
-
}
|
|
75
|
-
lines.push('');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Files modified
|
|
79
|
-
const filesModified = observations
|
|
80
|
-
.filter(o => o.type === 'file_change' && (o.title as string).startsWith('Edited'))
|
|
81
|
-
.map(o => {
|
|
82
|
-
const files = safeParseJson(o.files_involved as string, []) as string[];
|
|
83
|
-
return files[0] ?? (o.title as string).replace('Edited: ', '');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
if (filesModified.length > 0) {
|
|
87
|
-
lines.push('### Files Modified');
|
|
88
|
-
lines.push('');
|
|
89
|
-
lines.push('| File | Change |');
|
|
90
|
-
lines.push('|------|--------|');
|
|
91
|
-
for (const f of [...new Set(filesModified)]) {
|
|
92
|
-
lines.push(`| \`${f}\` | |`);
|
|
93
|
-
}
|
|
94
|
-
lines.push('');
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Key decisions
|
|
99
|
-
const decisions = observations.filter(o => o.type === 'decision');
|
|
100
|
-
if (decisions.length > 0) {
|
|
101
|
-
lines.push('### Key Decisions');
|
|
102
|
-
lines.push('');
|
|
103
|
-
for (const d of decisions) {
|
|
104
|
-
lines.push(`- ${d.title}`);
|
|
105
|
-
}
|
|
106
|
-
lines.push('');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Failed attempts
|
|
110
|
-
const failures = observations.filter(o => o.type === 'failed_attempt');
|
|
111
|
-
if (failures.length > 0) {
|
|
112
|
-
lines.push('## FAILED ATTEMPTS (DO NOT RETRY)');
|
|
113
|
-
lines.push('');
|
|
114
|
-
for (const f of failures) {
|
|
115
|
-
lines.push(`- ${f.title}`);
|
|
116
|
-
if (f.detail) lines.push(` ${(f.detail as string).slice(0, 200)}`);
|
|
117
|
-
}
|
|
118
|
-
lines.push('');
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Verification evidence
|
|
122
|
-
const vrChecks = observations.filter(o => o.type === 'vr_check');
|
|
123
|
-
if (vrChecks.length > 0) {
|
|
124
|
-
lines.push('## VERIFICATION EVIDENCE');
|
|
125
|
-
lines.push('');
|
|
126
|
-
for (const v of vrChecks) {
|
|
127
|
-
lines.push(`- ${v.title}`);
|
|
128
|
-
}
|
|
129
|
-
lines.push('');
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Pending / next steps
|
|
133
|
-
if (summary?.next_steps) {
|
|
134
|
-
lines.push('## PENDING');
|
|
135
|
-
lines.push('');
|
|
136
|
-
lines.push(summary.next_steps as string);
|
|
137
|
-
lines.push('');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Plan document
|
|
141
|
-
if (session.plan_file) {
|
|
142
|
-
lines.push('## PLAN DOCUMENT');
|
|
143
|
-
lines.push('');
|
|
144
|
-
lines.push(`\`${session.plan_file}\``);
|
|
145
|
-
|
|
146
|
-
// Show plan progress if available
|
|
147
|
-
if (summary?.plan_progress) {
|
|
148
|
-
const progress = safeParseJson(summary.plan_progress as string, {}) as Record<string, string>;
|
|
149
|
-
const total = Object.keys(progress).length;
|
|
150
|
-
const complete = Object.values(progress).filter(v => v === 'complete').length;
|
|
151
|
-
if (total > 0) {
|
|
152
|
-
lines.push(`- Progress: ${complete}/${total} items complete`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
lines.push('');
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return lines.join('\n');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function formatDate(dateStr: string): string {
|
|
162
|
-
const months = ['January', 'February', 'March', 'April', 'May', 'June',
|
|
163
|
-
'July', 'August', 'September', 'October', 'November', 'December'];
|
|
164
|
-
const [year, month, day] = dateStr.split('-').map(Number);
|
|
165
|
-
return `${months[month - 1]} ${day}, ${year}`;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function safeParseJson(json: string, fallback: unknown): unknown {
|
|
169
|
-
try {
|
|
170
|
-
return JSON.parse(json);
|
|
171
|
-
} catch (_e) {
|
|
172
|
-
return fallback;
|
|
173
|
-
}
|
|
174
|
-
}
|