@massu/core 0.1.1 → 0.1.2

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 (87) hide show
  1. package/README.md +2 -2
  2. package/dist/hooks/cost-tracker.js +23 -35
  3. package/dist/hooks/post-edit-context.js +2 -2
  4. package/dist/hooks/post-tool-use.js +43 -58
  5. package/dist/hooks/pre-compact.js +23 -38
  6. package/dist/hooks/pre-delete-check.js +18 -31
  7. package/dist/hooks/quality-event.js +23 -35
  8. package/dist/hooks/session-end.js +62 -78
  9. package/dist/hooks/session-start.js +33 -42
  10. package/dist/hooks/user-prompt.js +23 -38
  11. package/package.json +8 -14
  12. package/src/adr-generator.ts +9 -2
  13. package/src/analytics.ts +9 -3
  14. package/src/audit-trail.ts +10 -3
  15. package/src/cloud-sync.ts +14 -18
  16. package/src/commands/init.ts +1 -5
  17. package/src/cost-tracker.ts +11 -6
  18. package/src/dependency-scorer.ts +9 -2
  19. package/src/docs-tools.ts +13 -10
  20. package/src/hooks/post-edit-context.ts +3 -3
  21. package/src/hooks/session-end.ts +3 -3
  22. package/src/hooks/session-start.ts +2 -2
  23. package/src/memory-db.ts +1351 -23
  24. package/src/memory-tools.ts +14 -15
  25. package/src/observability-tools.ts +13 -2
  26. package/src/prompt-analyzer.ts +9 -2
  27. package/src/regression-detector.ts +9 -3
  28. package/src/security-scorer.ts +9 -2
  29. package/src/sentinel-db.ts +43 -88
  30. package/src/sentinel-tools.ts +8 -11
  31. package/src/server.ts +1 -2
  32. package/src/team-knowledge.ts +9 -2
  33. package/src/tools.ts +771 -35
  34. package/src/validate-features-runner.ts +0 -1
  35. package/src/validation-engine.ts +9 -2
  36. package/dist/cli.js +0 -7890
  37. package/dist/server.js +0 -7008
  38. package/src/__tests__/adr-generator.test.ts +0 -260
  39. package/src/__tests__/analytics.test.ts +0 -282
  40. package/src/__tests__/audit-trail.test.ts +0 -382
  41. package/src/__tests__/backfill-sessions.test.ts +0 -690
  42. package/src/__tests__/cli.test.ts +0 -290
  43. package/src/__tests__/cloud-sync.test.ts +0 -261
  44. package/src/__tests__/config-sections.test.ts +0 -359
  45. package/src/__tests__/config.test.ts +0 -732
  46. package/src/__tests__/cost-tracker.test.ts +0 -348
  47. package/src/__tests__/db.test.ts +0 -177
  48. package/src/__tests__/dependency-scorer.test.ts +0 -325
  49. package/src/__tests__/docs-integration.test.ts +0 -178
  50. package/src/__tests__/docs-tools.test.ts +0 -199
  51. package/src/__tests__/domains.test.ts +0 -236
  52. package/src/__tests__/hooks.test.ts +0 -221
  53. package/src/__tests__/import-resolver.test.ts +0 -95
  54. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  55. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  56. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  57. package/src/__tests__/memory-db.test.ts +0 -404
  58. package/src/__tests__/memory-enhancements.test.ts +0 -316
  59. package/src/__tests__/memory-tools.test.ts +0 -199
  60. package/src/__tests__/middleware-tree.test.ts +0 -177
  61. package/src/__tests__/observability-tools.test.ts +0 -595
  62. package/src/__tests__/observability.test.ts +0 -437
  63. package/src/__tests__/observation-extractor.test.ts +0 -167
  64. package/src/__tests__/page-deps.test.ts +0 -60
  65. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  66. package/src/__tests__/regression-detector.test.ts +0 -295
  67. package/src/__tests__/rules.test.ts +0 -87
  68. package/src/__tests__/schema-mapper.test.ts +0 -29
  69. package/src/__tests__/security-scorer.test.ts +0 -238
  70. package/src/__tests__/security-utils.test.ts +0 -175
  71. package/src/__tests__/sentinel-db.test.ts +0 -491
  72. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  73. package/src/__tests__/sentinel-tools.test.ts +0 -324
  74. package/src/__tests__/sentinel-types.test.ts +0 -750
  75. package/src/__tests__/server.test.ts +0 -452
  76. package/src/__tests__/session-archiver.test.ts +0 -524
  77. package/src/__tests__/session-state-generator.test.ts +0 -900
  78. package/src/__tests__/team-knowledge.test.ts +0 -327
  79. package/src/__tests__/tools.test.ts +0 -340
  80. package/src/__tests__/transcript-parser.test.ts +0 -195
  81. package/src/__tests__/trpc-index.test.ts +0 -25
  82. package/src/__tests__/validate-features-runner.test.ts +0 -517
  83. package/src/__tests__/validation-engine.test.ts +0 -300
  84. package/src/core-tools.ts +0 -685
  85. package/src/memory-queries.ts +0 -804
  86. package/src/memory-schema.ts +0 -546
  87. package/src/tool-helpers.ts +0 -41
@@ -1,452 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- /**
5
- * Tests for server.ts JSON-RPC 2.0 protocol handling.
6
- *
7
- * server.ts is a standalone script with no exports. It processes JSON-RPC 2.0
8
- * requests via stdin/stdout. Tests here validate:
9
- * - The request routing logic (initialize, tools/list, tools/call, ping, etc.)
10
- * - Error responses for unknown methods and malformed JSON
11
- * - The shape of JSON-RPC 2.0 responses
12
- *
13
- * Because server.ts has no exports, we replicate its handleRequest logic
14
- * inline (mirroring the switch statement exactly) and mock the same dependencies
15
- * it imports. This validates the routing and response-shaping logic without
16
- * requiring stdin/stdout wiring.
17
- */
18
-
19
- import { describe, it, expect, vi, beforeEach } from 'vitest';
20
-
21
- // ---------------------------------------------------------------------------
22
- // Mocks — must match what server.ts imports
23
- // ---------------------------------------------------------------------------
24
-
25
- vi.mock('../db.ts', () => ({
26
- getCodeGraphDb: vi.fn(() => ({ close: vi.fn() })),
27
- getDataDb: vi.fn(() => ({ close: vi.fn() })),
28
- }));
29
-
30
- vi.mock('../config.ts', () => ({
31
- getConfig: vi.fn(() => ({
32
- toolPrefix: 'massu',
33
- framework: { type: 'typescript', router: 'trpc', orm: 'prisma' },
34
- paths: { source: 'src', routers: 'src/server/api/routers', middleware: 'src/middleware.ts' },
35
- domains: [],
36
- })),
37
- getProjectRoot: vi.fn(() => '/test/project'),
38
- getResolvedPaths: vi.fn(() => ({
39
- codegraphDbPath: '/test/codegraph.db',
40
- dataDbPath: '/test/data.db',
41
- })),
42
- }));
43
-
44
- vi.mock('../tools.ts', () => ({
45
- getToolDefinitions: vi.fn(() => [
46
- {
47
- name: 'massu_sync',
48
- description: 'Sync the project index',
49
- inputSchema: { type: 'object', properties: {}, required: [] },
50
- },
51
- {
52
- name: 'massu_context',
53
- description: 'Get context for a file',
54
- inputSchema: { type: 'object', properties: { file: { type: 'string' } }, required: ['file'] },
55
- },
56
- ]),
57
- handleToolCall: vi.fn((_name: string, _args: Record<string, unknown>) => ({
58
- content: [{ type: 'text', text: 'tool result' }],
59
- })),
60
- }));
61
-
62
- // ---------------------------------------------------------------------------
63
- // Re-implement handleRequest mirroring server.ts exactly
64
- // ---------------------------------------------------------------------------
65
-
66
- import { getToolDefinitions, handleToolCall } from '../tools.ts';
67
- import { getCodeGraphDb, getDataDb } from '../db.ts';
68
-
69
- interface JsonRpcRequest {
70
- jsonrpc: '2.0';
71
- id?: number | string;
72
- method: string;
73
- params?: Record<string, unknown>;
74
- }
75
-
76
- interface JsonRpcResponse {
77
- jsonrpc: '2.0';
78
- id: number | string | null;
79
- result?: unknown;
80
- error?: { code: number; message: string; data?: unknown };
81
- }
82
-
83
- // Mirrors server.ts getDb() / handleRequest() — kept in sync with the source.
84
- let codegraphDb: ReturnType<typeof getCodeGraphDb> | null = null;
85
- let dataDb: ReturnType<typeof getDataDb> | null = null;
86
-
87
- function getDb() {
88
- if (!codegraphDb) codegraphDb = getCodeGraphDb();
89
- if (!dataDb) dataDb = getDataDb();
90
- return { codegraphDb, dataDb };
91
- }
92
-
93
- function handleRequest(request: JsonRpcRequest): JsonRpcResponse {
94
- const { method, params, id } = request;
95
-
96
- switch (method) {
97
- case 'initialize': {
98
- return {
99
- jsonrpc: '2.0',
100
- id: id ?? null,
101
- result: {
102
- protocolVersion: '2024-11-05',
103
- capabilities: { tools: {} },
104
- serverInfo: { name: 'massu', version: '1.0.0' },
105
- },
106
- };
107
- }
108
-
109
- case 'notifications/initialized': {
110
- return { jsonrpc: '2.0', id: id ?? null, result: {} };
111
- }
112
-
113
- case 'tools/list': {
114
- const tools = getToolDefinitions();
115
- return { jsonrpc: '2.0', id: id ?? null, result: { tools } };
116
- }
117
-
118
- case 'tools/call': {
119
- const toolName = (params as { name: string })?.name;
120
- const toolArgs = (params as { arguments?: Record<string, unknown> })?.arguments ?? {};
121
- const { codegraphDb: cgDb, dataDb: lDb } = getDb();
122
- const result = handleToolCall(toolName, toolArgs, lDb as never, cgDb as never);
123
- return { jsonrpc: '2.0', id: id ?? null, result };
124
- }
125
-
126
- case 'ping': {
127
- return { jsonrpc: '2.0', id: id ?? null, result: {} };
128
- }
129
-
130
- default: {
131
- return {
132
- jsonrpc: '2.0',
133
- id: id ?? null,
134
- error: { code: -32601, message: `Method not found: ${method}` },
135
- };
136
- }
137
- }
138
- }
139
-
140
- // ---------------------------------------------------------------------------
141
- // Helper: simulate the newline-delimited JSON-RPC parse + dispatch loop
142
- // that lives in server.ts's stdin 'data' handler.
143
- // ---------------------------------------------------------------------------
144
-
145
- function simulateStdinLine(line: string): JsonRpcResponse | null {
146
- const trimmed = line.trim();
147
- if (!trimmed) return null;
148
- const request = JSON.parse(trimmed) as JsonRpcRequest;
149
- return handleRequest(request);
150
- }
151
-
152
- function simulateMalformedLine(line: string): JsonRpcResponse {
153
- try {
154
- JSON.parse(line);
155
- throw new Error('Expected parse failure');
156
- } catch (error) {
157
- return {
158
- jsonrpc: '2.0',
159
- id: null,
160
- error: {
161
- code: -32700,
162
- message: `Parse error: ${error instanceof Error ? error.message : String(error)}`,
163
- },
164
- };
165
- }
166
- }
167
-
168
- // ---------------------------------------------------------------------------
169
- // Tests
170
- // ---------------------------------------------------------------------------
171
-
172
- describe('Server JSON-RPC 2.0 — response structure', () => {
173
- it('all successful responses include jsonrpc: "2.0"', () => {
174
- const methods = ['initialize', 'tools/list', 'ping', 'notifications/initialized'] as const;
175
- for (const method of methods) {
176
- const resp = handleRequest({ jsonrpc: '2.0', id: 1, method });
177
- expect(resp.jsonrpc).toBe('2.0');
178
- }
179
- });
180
-
181
- it('response id mirrors request id (number)', () => {
182
- const resp = handleRequest({ jsonrpc: '2.0', id: 42, method: 'ping' });
183
- expect(resp.id).toBe(42);
184
- });
185
-
186
- it('response id mirrors request id (string)', () => {
187
- const resp = handleRequest({ jsonrpc: '2.0', id: 'req-abc', method: 'ping' });
188
- expect(resp.id).toBe('req-abc');
189
- });
190
-
191
- it('response id is null when request has no id', () => {
192
- const resp = handleRequest({ jsonrpc: '2.0', method: 'ping' });
193
- expect(resp.id).toBeNull();
194
- });
195
- });
196
-
197
- describe('Server JSON-RPC 2.0 — initialize', () => {
198
- it('returns protocolVersion 2024-11-05', () => {
199
- const resp = handleRequest({ jsonrpc: '2.0', id: 1, method: 'initialize' });
200
- expect(resp.error).toBeUndefined();
201
- const result = resp.result as Record<string, unknown>;
202
- expect(result.protocolVersion).toBe('2024-11-05');
203
- });
204
-
205
- it('returns capabilities.tools object', () => {
206
- const resp = handleRequest({ jsonrpc: '2.0', id: 1, method: 'initialize' });
207
- const result = resp.result as Record<string, unknown>;
208
- expect((result.capabilities as Record<string, unknown>).tools).toBeDefined();
209
- });
210
-
211
- it('returns serverInfo with name massu and version 1.0.0', () => {
212
- const resp = handleRequest({ jsonrpc: '2.0', id: 1, method: 'initialize' });
213
- const result = resp.result as { serverInfo: { name: string; version: string } };
214
- expect(result.serverInfo.name).toBe('massu');
215
- expect(result.serverInfo.version).toBe('1.0.0');
216
- });
217
-
218
- it('works with string id', () => {
219
- const resp = handleRequest({ jsonrpc: '2.0', id: 'init-1', method: 'initialize' });
220
- expect(resp.id).toBe('init-1');
221
- expect((resp.result as Record<string, unknown>).protocolVersion).toBe('2024-11-05');
222
- });
223
- });
224
-
225
- describe('Server JSON-RPC 2.0 — notifications/initialized', () => {
226
- it('returns empty result object', () => {
227
- const resp = handleRequest({ jsonrpc: '2.0', id: 2, method: 'notifications/initialized' });
228
- expect(resp.error).toBeUndefined();
229
- expect(resp.result).toEqual({});
230
- });
231
-
232
- it('returns null id when no id provided (notification pattern)', () => {
233
- const resp = handleRequest({ jsonrpc: '2.0', method: 'notifications/initialized' });
234
- expect(resp.id).toBeNull();
235
- });
236
- });
237
-
238
- describe('Server JSON-RPC 2.0 — tools/list', () => {
239
- it('calls getToolDefinitions and returns result.tools array', () => {
240
- const resp = handleRequest({ jsonrpc: '2.0', id: 3, method: 'tools/list' });
241
- expect(resp.error).toBeUndefined();
242
- const result = resp.result as { tools: unknown[] };
243
- expect(Array.isArray(result.tools)).toBe(true);
244
- });
245
-
246
- it('returns mocked tool definitions', () => {
247
- const resp = handleRequest({ jsonrpc: '2.0', id: 3, method: 'tools/list' });
248
- const result = resp.result as { tools: Array<{ name: string }> };
249
- const names = result.tools.map((t) => t.name);
250
- expect(names).toContain('massu_sync');
251
- expect(names).toContain('massu_context');
252
- });
253
-
254
- it('each tool definition has name, description, inputSchema', () => {
255
- const resp = handleRequest({ jsonrpc: '2.0', id: 3, method: 'tools/list' });
256
- const result = resp.result as { tools: Array<{ name: string; description: string; inputSchema: object }> };
257
- for (const tool of result.tools) {
258
- expect(tool.name).toBeTruthy();
259
- expect(tool.description).toBeTruthy();
260
- expect(typeof tool.inputSchema).toBe('object');
261
- }
262
- });
263
-
264
- it('calls getToolDefinitions exactly once per request', () => {
265
- const spy = vi.mocked(getToolDefinitions);
266
- spy.mockClear();
267
-
268
- handleRequest({ jsonrpc: '2.0', id: 4, method: 'tools/list' });
269
- expect(spy).toHaveBeenCalledTimes(1);
270
- });
271
- });
272
-
273
- describe('Server JSON-RPC 2.0 — tools/call', () => {
274
- beforeEach(() => {
275
- // Reset db state between tests
276
- codegraphDb = null;
277
- dataDb = null;
278
- vi.mocked(handleToolCall).mockClear();
279
- });
280
-
281
- it('delegates to handleToolCall and returns its result', () => {
282
- const resp = handleRequest({
283
- jsonrpc: '2.0',
284
- id: 5,
285
- method: 'tools/call',
286
- params: { name: 'massu_sync', arguments: {} },
287
- });
288
- expect(resp.error).toBeUndefined();
289
- const result = resp.result as { content: Array<{ type: string; text: string }> };
290
- expect(result.content[0].text).toBe('tool result');
291
- });
292
-
293
- it('passes tool name from params.name to handleToolCall', () => {
294
- handleRequest({
295
- jsonrpc: '2.0',
296
- id: 5,
297
- method: 'tools/call',
298
- params: { name: 'massu_context', arguments: { file: 'src/index.ts' } },
299
- });
300
- expect(vi.mocked(handleToolCall)).toHaveBeenCalledWith(
301
- 'massu_context',
302
- { file: 'src/index.ts' },
303
- expect.anything(),
304
- expect.anything(),
305
- );
306
- });
307
-
308
- it('uses empty object for arguments when params.arguments is omitted', () => {
309
- handleRequest({
310
- jsonrpc: '2.0',
311
- id: 6,
312
- method: 'tools/call',
313
- params: { name: 'massu_sync' },
314
- });
315
- expect(vi.mocked(handleToolCall)).toHaveBeenCalledWith(
316
- 'massu_sync',
317
- {},
318
- expect.anything(),
319
- expect.anything(),
320
- );
321
- });
322
-
323
- it('lazy-initializes databases on first tools/call', () => {
324
- vi.mocked(getCodeGraphDb).mockClear();
325
- vi.mocked(getDataDb).mockClear();
326
-
327
- handleRequest({
328
- jsonrpc: '2.0',
329
- id: 7,
330
- method: 'tools/call',
331
- params: { name: 'massu_sync', arguments: {} },
332
- });
333
-
334
- expect(vi.mocked(getCodeGraphDb)).toHaveBeenCalledTimes(1);
335
- expect(vi.mocked(getDataDb)).toHaveBeenCalledTimes(1);
336
- });
337
-
338
- it('reuses existing db connections on subsequent tools/call', () => {
339
- vi.mocked(getCodeGraphDb).mockClear();
340
- vi.mocked(getDataDb).mockClear();
341
-
342
- // First call initializes dbs
343
- handleRequest({ jsonrpc: '2.0', id: 8, method: 'tools/call', params: { name: 'massu_sync' } });
344
- // Second call should reuse
345
- handleRequest({ jsonrpc: '2.0', id: 9, method: 'tools/call', params: { name: 'massu_sync' } });
346
-
347
- expect(vi.mocked(getCodeGraphDb)).toHaveBeenCalledTimes(1);
348
- expect(vi.mocked(getDataDb)).toHaveBeenCalledTimes(1);
349
- });
350
- });
351
-
352
- describe('Server JSON-RPC 2.0 — ping', () => {
353
- it('returns empty result', () => {
354
- const resp = handleRequest({ jsonrpc: '2.0', id: 10, method: 'ping' });
355
- expect(resp.error).toBeUndefined();
356
- expect(resp.result).toEqual({});
357
- });
358
-
359
- it('returns id matching request', () => {
360
- const resp = handleRequest({ jsonrpc: '2.0', id: 99, method: 'ping' });
361
- expect(resp.id).toBe(99);
362
- });
363
- });
364
-
365
- describe('Server JSON-RPC 2.0 — unknown method', () => {
366
- it('returns error code -32601 for unknown method', () => {
367
- const resp = handleRequest({ jsonrpc: '2.0', id: 11, method: 'nonexistent/method' });
368
- expect(resp.result).toBeUndefined();
369
- expect(resp.error).toBeDefined();
370
- expect(resp.error!.code).toBe(-32601);
371
- });
372
-
373
- it('error message contains the unknown method name', () => {
374
- const resp = handleRequest({ jsonrpc: '2.0', id: 11, method: 'some/unknown' });
375
- expect(resp.error!.message).toContain('some/unknown');
376
- expect(resp.error!.message).toContain('Method not found');
377
- });
378
-
379
- it('preserves id in error response', () => {
380
- const resp = handleRequest({ jsonrpc: '2.0', id: 'err-id', method: 'bad/method' });
381
- expect(resp.id).toBe('err-id');
382
- });
383
-
384
- it('various unknown methods all get -32601', () => {
385
- const unknowns = ['foo', 'bar/baz', 'tools/execute', 'rpc.discover'];
386
- for (const method of unknowns) {
387
- const resp = handleRequest({ jsonrpc: '2.0', id: 1, method });
388
- expect(resp.error!.code).toBe(-32601);
389
- }
390
- });
391
- });
392
-
393
- describe('Server JSON-RPC 2.0 — stdin line parsing', () => {
394
- it('parses and dispatches a valid newline-terminated JSON line', () => {
395
- const line = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping' }) + '\n';
396
- const resp = simulateStdinLine(line);
397
- expect(resp).not.toBeNull();
398
- expect(resp!.result).toEqual({});
399
- });
400
-
401
- it('ignores empty or whitespace-only lines', () => {
402
- expect(simulateStdinLine('')).toBeNull();
403
- expect(simulateStdinLine(' ')).toBeNull();
404
- expect(simulateStdinLine('\t\n')).toBeNull();
405
- });
406
-
407
- it('returns parse error response for malformed JSON', () => {
408
- const resp = simulateMalformedLine('{not valid json}');
409
- expect(resp.jsonrpc).toBe('2.0');
410
- expect(resp.id).toBeNull();
411
- expect(resp.error).toBeDefined();
412
- expect(resp.error!.code).toBe(-32700);
413
- expect(resp.error!.message).toContain('Parse error');
414
- });
415
-
416
- it('returns parse error for truncated JSON', () => {
417
- const resp = simulateMalformedLine('{"jsonrpc":"2.0","id":1,"method":');
418
- expect(resp.error!.code).toBe(-32700);
419
- });
420
-
421
- it('returns parse error for empty braces with trailing garbage', () => {
422
- const resp = simulateMalformedLine('{} garbage after');
423
- // JSON.parse("{} garbage after") throws a SyntaxError
424
- expect(resp.error!.code).toBe(-32700);
425
- });
426
- });
427
-
428
- describe('Server JSON-RPC 2.0 — notification suppression (no id)', () => {
429
- /**
430
- * Per the JSON-RPC 2.0 spec and server.ts implementation, when request.id
431
- * is undefined, the server must not send a response. We validate the
432
- * condition the server checks: request.id !== undefined.
433
- */
434
- it('notifications/initialized has no id — server would not write response', () => {
435
- const request = { jsonrpc: '2.0' as const, method: 'notifications/initialized' };
436
- // The server checks: if (request.id !== undefined) → write response
437
- expect(request.id).toBeUndefined();
438
- });
439
-
440
- it('requests with explicit id get a response', () => {
441
- const request = { jsonrpc: '2.0' as const, id: 0, method: 'ping' };
442
- // id === 0 is defined, so response IS written
443
- expect(request.id).toBeDefined();
444
- const resp = handleRequest(request);
445
- expect(resp.id).toBe(0);
446
- });
447
-
448
- it('id: 0 is a valid id (not falsy-excluded)', () => {
449
- const resp = handleRequest({ jsonrpc: '2.0', id: 0, method: 'ping' });
450
- expect(resp.id).toBe(0);
451
- });
452
- });