@massu/core 1.10.7 → 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.
Files changed (177) hide show
  1. package/CHANGELOG.md +1303 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +559 -400
  4. package/dist/detect/adapters/.bundle-shasums.json +4 -7
  5. package/dist/detect/adapters/rails.js +6 -4
  6. package/dist/detect/adapters/spring.js +6 -4
  7. package/dist/detect/adapters/tree-sitter-loader.js +6 -4
  8. package/dist/hooks/auto-learning-pipeline.js +24 -1
  9. package/dist/hooks/classify-failure.js +32 -3
  10. package/dist/hooks/cost-tracker.js +24 -0
  11. package/dist/hooks/fix-detector.js +97 -1
  12. package/dist/hooks/incident-pipeline.js +30 -1
  13. package/dist/hooks/intent-suggester.js +6 -3
  14. package/dist/hooks/post-edit-context.js +24 -1
  15. package/dist/hooks/post-tool-use.js +66 -18
  16. package/dist/hooks/pre-compact.js +27 -2
  17. package/dist/hooks/pre-delete-check.js +48 -22
  18. package/dist/hooks/pre-tool-use-gate.js +962 -0
  19. package/dist/hooks/quality-event.js +24 -0
  20. package/dist/hooks/rule-enforcement-pipeline.js +24 -1
  21. package/dist/hooks/security-gate.js +29 -16
  22. package/dist/hooks/session-end.js +24 -0
  23. package/dist/hooks/session-start.js +8710 -8460
  24. package/dist/hooks/user-prompt.js +24 -0
  25. package/package.json +15 -18
  26. package/commands/README.md +0 -134
  27. package/src/adapter.ts +0 -121
  28. package/src/adr-generator.ts +0 -292
  29. package/src/analytics.ts +0 -373
  30. package/src/audit-trail.ts +0 -450
  31. package/src/backfill-sessions.ts +0 -182
  32. package/src/changelog-generator.ts +0 -178
  33. package/src/cli.ts +0 -246
  34. package/src/cloud-sync.ts +0 -234
  35. package/src/commands/adapters.ts +0 -824
  36. package/src/commands/changelog.ts +0 -165
  37. package/src/commands/config-check-drift.ts +0 -133
  38. package/src/commands/config-refresh.ts +0 -396
  39. package/src/commands/config-upgrade.ts +0 -127
  40. package/src/commands/doctor.ts +0 -485
  41. package/src/commands/hook-runner.ts +0 -145
  42. package/src/commands/init.ts +0 -1823
  43. package/src/commands/install-commands.ts +0 -692
  44. package/src/commands/install-hooks.ts +0 -27
  45. package/src/commands/permissions.ts +0 -150
  46. package/src/commands/refresh-log.ts +0 -37
  47. package/src/commands/show-template.ts +0 -65
  48. package/src/commands/template-engine.ts +0 -301
  49. package/src/commands/watch.ts +0 -430
  50. package/src/config.ts +0 -765
  51. package/src/cost-tracker.ts +0 -355
  52. package/src/db.ts +0 -370
  53. package/src/dependency-scorer.ts +0 -337
  54. package/src/detect/adapters/aspnet.ts +0 -4
  55. package/src/detect/adapters/discover.ts +0 -469
  56. package/src/detect/adapters/file-sampler.ts +0 -225
  57. package/src/detect/adapters/go-chi.ts +0 -4
  58. package/src/detect/adapters/index.ts +0 -51
  59. package/src/detect/adapters/nextjs-trpc.ts +0 -166
  60. package/src/detect/adapters/parse-guard.ts +0 -133
  61. package/src/detect/adapters/phoenix.ts +0 -4
  62. package/src/detect/adapters/python-django.ts +0 -208
  63. package/src/detect/adapters/python-fastapi.ts +0 -223
  64. package/src/detect/adapters/python-flask.ts +0 -235
  65. package/src/detect/adapters/query-helpers.ts +0 -170
  66. package/src/detect/adapters/rails.ts +0 -4
  67. package/src/detect/adapters/runner.ts +0 -284
  68. package/src/detect/adapters/spring.ts +0 -4
  69. package/src/detect/adapters/swift-swiftui.ts +0 -171
  70. package/src/detect/adapters/tree-sitter-loader.ts +0 -517
  71. package/src/detect/adapters/types.ts +0 -191
  72. package/src/detect/codebase-introspector.ts +0 -210
  73. package/src/detect/domain-inferrer.ts +0 -197
  74. package/src/detect/drift.ts +0 -199
  75. package/src/detect/framework-detector.ts +0 -307
  76. package/src/detect/index.ts +0 -209
  77. package/src/detect/manifest-registry.ts +0 -261
  78. package/src/detect/migrate.ts +0 -362
  79. package/src/detect/monorepo-detector.ts +0 -348
  80. package/src/detect/package-detector.ts +0 -828
  81. package/src/detect/passthrough.ts +0 -108
  82. package/src/detect/regex-fallback.ts +0 -449
  83. package/src/detect/source-dir-detector.ts +0 -297
  84. package/src/detect/vr-command-map.ts +0 -167
  85. package/src/docs-map.json +0 -100
  86. package/src/docs-tools.ts +0 -519
  87. package/src/domains.ts +0 -182
  88. package/src/hooks/auto-learning-pipeline.ts +0 -228
  89. package/src/hooks/classify-failure.ts +0 -259
  90. package/src/hooks/cost-tracker.ts +0 -66
  91. package/src/hooks/fix-detector.ts +0 -186
  92. package/src/hooks/incident-pipeline.ts +0 -190
  93. package/src/hooks/intent-suggester.ts +0 -131
  94. package/src/hooks/post-edit-context.ts +0 -91
  95. package/src/hooks/post-tool-use.ts +0 -413
  96. package/src/hooks/pre-compact.ts +0 -168
  97. package/src/hooks/pre-delete-check.ts +0 -243
  98. package/src/hooks/quality-event.ts +0 -127
  99. package/src/hooks/rule-enforcement-pipeline.ts +0 -159
  100. package/src/hooks/security-gate.ts +0 -153
  101. package/src/hooks/session-end.ts +0 -467
  102. package/src/hooks/session-start.ts +0 -436
  103. package/src/hooks/user-prompt.ts +0 -157
  104. package/src/import-resolver.ts +0 -226
  105. package/src/knowledge-db.ts +0 -176
  106. package/src/knowledge-indexer.ts +0 -708
  107. package/src/knowledge-tools.ts +0 -1414
  108. package/src/lib/fileLock.ts +0 -203
  109. package/src/lib/gitToplevel.ts +0 -22
  110. package/src/lib/hook-registry.ts +0 -43
  111. package/src/lib/installLock.ts +0 -66
  112. package/src/lib/memory-path.ts +0 -49
  113. package/src/lib/pidLiveness.ts +0 -67
  114. package/src/lib/settings-local.ts +0 -110
  115. package/src/lib/sql-table-names.ts +0 -71
  116. package/src/license.ts +0 -547
  117. package/src/lsp/auto-detect.ts +0 -98
  118. package/src/lsp/client.ts +0 -776
  119. package/src/lsp/enrich.ts +0 -127
  120. package/src/lsp/types.ts +0 -221
  121. package/src/memory-db.ts +0 -1614
  122. package/src/memory-file-ingest.ts +0 -128
  123. package/src/memory-tools.ts +0 -424
  124. package/src/middleware-tree.ts +0 -71
  125. package/src/observability-tools.ts +0 -343
  126. package/src/observation-extractor.ts +0 -418
  127. package/src/page-deps.ts +0 -285
  128. package/src/permissions.ts +0 -422
  129. package/src/prompt-analyzer.ts +0 -332
  130. package/src/python/coupling-detector.ts +0 -125
  131. package/src/python/domain-enforcer.ts +0 -84
  132. package/src/python/impact-analyzer.ts +0 -96
  133. package/src/python/import-parser.ts +0 -244
  134. package/src/python/import-resolver.ts +0 -136
  135. package/src/python/migration-indexer.ts +0 -116
  136. package/src/python/migration-parser.ts +0 -332
  137. package/src/python/model-indexer.ts +0 -71
  138. package/src/python/model-parser.ts +0 -279
  139. package/src/python/route-indexer.ts +0 -59
  140. package/src/python/route-parser.ts +0 -317
  141. package/src/python-tools.ts +0 -630
  142. package/src/regression-detector.ts +0 -319
  143. package/src/rules.ts +0 -57
  144. package/src/schema-mapper.ts +0 -232
  145. package/src/security/adapter-origin.ts +0 -130
  146. package/src/security/adapter-verifier.ts +0 -319
  147. package/src/security/atomic-write.ts +0 -164
  148. package/src/security/fetcher.ts +0 -200
  149. package/src/security/install-tracking.ts +0 -319
  150. package/src/security/license-pubkey.generated.ts +0 -30
  151. package/src/security/license-response-verifier.ts +0 -149
  152. package/src/security/local-fingerprint.ts +0 -225
  153. package/src/security/manifest-cache.ts +0 -333
  154. package/src/security/manifest-schema.ts +0 -129
  155. package/src/security/registry-pubkey.generated.ts +0 -35
  156. package/src/security/telemetry.ts +0 -320
  157. package/src/security-scorer.ts +0 -405
  158. package/src/security-utils.ts +0 -133
  159. package/src/sentinel-db.ts +0 -580
  160. package/src/sentinel-scanner.ts +0 -406
  161. package/src/sentinel-tools.ts +0 -513
  162. package/src/sentinel-types.ts +0 -140
  163. package/src/server-dispatch.ts +0 -225
  164. package/src/server.ts +0 -107
  165. package/src/session-archiver.ts +0 -111
  166. package/src/session-state-generator.ts +0 -174
  167. package/src/team-knowledge.ts +0 -407
  168. package/src/tool-db-needs.ts +0 -232
  169. package/src/tools.ts +0 -1205
  170. package/src/transcript-parser.ts +0 -458
  171. package/src/trpc-index.ts +0 -215
  172. package/src/validate-features-runner.ts +0 -107
  173. package/src/validation-engine.ts +0 -358
  174. package/src/watch/daemon.ts +0 -385
  175. package/src/watch/lockfile-detector.ts +0 -65
  176. package/src/watch/paths.ts +0 -279
  177. package/src/watch/state.ts +0 -178
@@ -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
- });
@@ -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
- }