@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,260 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
- import Database from 'better-sqlite3';
6
- import {
7
- getAdrToolDefinitions,
8
- isAdrTool,
9
- detectDecisionPatterns,
10
- extractAlternatives,
11
- storeDecision,
12
- handleAdrToolCall,
13
- } from '../adr-generator.ts';
14
-
15
- function createTestDb(): Database.Database {
16
- const db = new Database(':memory:');
17
- db.exec(`
18
- CREATE TABLE architecture_decisions (
19
- id INTEGER PRIMARY KEY AUTOINCREMENT,
20
- session_id TEXT,
21
- title TEXT NOT NULL,
22
- context TEXT,
23
- decision TEXT NOT NULL,
24
- status TEXT NOT NULL DEFAULT 'accepted' CHECK(status IN ('accepted', 'superseded', 'deprecated')),
25
- alternatives TEXT,
26
- consequences TEXT,
27
- created_at TEXT DEFAULT (datetime('now'))
28
- );
29
- `);
30
- return db;
31
- }
32
-
33
- describe('adr-generator', () => {
34
- let db: Database.Database;
35
-
36
- beforeEach(() => {
37
- db = createTestDb();
38
- });
39
-
40
- afterEach(() => {
41
- db.close();
42
- });
43
-
44
- describe('getAdrToolDefinitions', () => {
45
- it('returns 3 tool definitions with correct names', () => {
46
- const tools = getAdrToolDefinitions();
47
- expect(tools).toHaveLength(3);
48
- expect(tools.map(t => t.name.split('_').slice(-2).join('_'))).toEqual([
49
- 'adr_list',
50
- 'adr_detail',
51
- 'adr_create',
52
- ]);
53
- });
54
-
55
- it('has required fields in tool definitions', () => {
56
- const tools = getAdrToolDefinitions();
57
- tools.forEach(tool => {
58
- expect(tool.name).toBeTruthy();
59
- expect(tool.description).toBeTruthy();
60
- expect(tool.inputSchema).toBeDefined();
61
- expect(tool.inputSchema.type).toBe('object');
62
- });
63
- });
64
-
65
- it('adr_create requires title and decision', () => {
66
- const tools = getAdrToolDefinitions();
67
- const createTool = tools.find(t => t.name.endsWith('_adr_create'));
68
- expect(createTool?.inputSchema.required).toEqual(['title', 'decision']);
69
- });
70
- });
71
-
72
- describe('isAdrTool', () => {
73
- it('returns true for ADR tool names', () => {
74
- expect(isAdrTool('massu_adr_list')).toBe(true);
75
- expect(isAdrTool('massu_adr_detail')).toBe(true);
76
- expect(isAdrTool('massu_adr_create')).toBe(true);
77
- });
78
-
79
- it('returns false for non-ADR tool names', () => {
80
- expect(isAdrTool('massu_security_score')).toBe(false);
81
- expect(isAdrTool('massu_unknown')).toBe(false);
82
- expect(isAdrTool('adr_list')).toBe(true); // base name without prefix
83
- });
84
- });
85
-
86
- describe('detectDecisionPatterns', () => {
87
- it('detects decision phrases in text', () => {
88
- expect(detectDecisionPatterns('We chose Redis for caching')).toBe(true);
89
- expect(detectDecisionPatterns('Decided to use TypeScript')).toBe(true);
90
- expect(detectDecisionPatterns('switching to ESM modules')).toBe(true);
91
- expect(detectDecisionPatterns('moving from CommonJS to ESM')).toBe(true);
92
- expect(detectDecisionPatterns('going with Fastify')).toBe(true);
93
- });
94
-
95
- it('is case insensitive', () => {
96
- expect(detectDecisionPatterns('DECIDED to refactor')).toBe(true);
97
- expect(detectDecisionPatterns('Chose the new architecture')).toBe(true);
98
- });
99
-
100
- it('returns false when no decision pattern found', () => {
101
- expect(detectDecisionPatterns('This is a regular sentence')).toBe(false);
102
- expect(detectDecisionPatterns('No patterns here')).toBe(false);
103
- });
104
- });
105
-
106
- describe('extractAlternatives', () => {
107
- it('extracts alternatives from "X over Y" pattern', () => {
108
- const result = extractAlternatives('We chose Redis over Memcached for caching');
109
- // The regex captures from the last word before "over" to next word, so "chose Redis" and "Memcached for caching"
110
- expect(result.length).toBeGreaterThan(0);
111
- // Just verify the function works and returns alternatives
112
- expect(result.some(alt => alt.includes('Redis'))).toBe(true);
113
- expect(result.some(alt => alt.includes('Memcached'))).toBe(true);
114
- });
115
-
116
- it('extracts alternatives from "X instead of Y" pattern', () => {
117
- const result = extractAlternatives('Use TypeScript instead of JavaScript');
118
- expect(result.length).toBeGreaterThan(0);
119
- expect(result.some(alt => alt.includes('TypeScript'))).toBe(true);
120
- expect(result.some(alt => alt.includes('JavaScript'))).toBe(true);
121
- });
122
-
123
- it('extracts alternatives from "switching from X to Y" pattern', () => {
124
- const result = extractAlternatives('switching from CommonJS to ESM modules');
125
- expect(result.length).toBeGreaterThan(0);
126
- expect(result.some(alt => alt.includes('ESM'))).toBe(true);
127
- expect(result.some(alt => alt.includes('CommonJS'))).toBe(true);
128
- });
129
-
130
- it('returns unique alternatives only', () => {
131
- const result = extractAlternatives('Redis over Memcached instead of Redis');
132
- const unique = [...new Set(result)];
133
- expect(result.length).toBe(unique.length);
134
- });
135
-
136
- it('returns empty array when no alternatives found', () => {
137
- const result = extractAlternatives('This has no alternatives');
138
- expect(result).toEqual([]);
139
- });
140
- });
141
-
142
- describe('storeDecision', () => {
143
- it('stores a decision and returns ID', () => {
144
- const id = storeDecision(db, {
145
- title: 'Use Redis for caching',
146
- context: 'Need fast cache',
147
- decision: 'Redis over Memcached',
148
- alternatives: ['Redis', 'Memcached'],
149
- consequences: 'Better performance',
150
- });
151
-
152
- expect(id).toBeGreaterThan(0);
153
-
154
- const row = db.prepare('SELECT * FROM architecture_decisions WHERE id = ?').get(id) as Record<string, unknown>;
155
- expect(row.title).toBe('Use Redis for caching');
156
- expect(row.decision).toBe('Redis over Memcached');
157
- expect(JSON.parse(row.alternatives as string)).toEqual(['Redis', 'Memcached']);
158
- });
159
-
160
- it('uses default status when not provided', () => {
161
- const id = storeDecision(db, {
162
- title: 'Test Decision',
163
- context: 'Context',
164
- decision: 'Decision text',
165
- alternatives: [],
166
- consequences: 'None',
167
- });
168
-
169
- const row = db.prepare('SELECT status FROM architecture_decisions WHERE id = ?').get(id) as { status: string };
170
- expect(row.status).toBe('accepted');
171
- });
172
-
173
- it('accepts custom status', () => {
174
- const id = storeDecision(db, {
175
- title: 'Test Decision',
176
- context: 'Context',
177
- decision: 'Decision text',
178
- alternatives: [],
179
- consequences: 'None',
180
- status: 'superseded',
181
- });
182
-
183
- const row = db.prepare('SELECT status FROM architecture_decisions WHERE id = ?').get(id) as { status: string };
184
- expect(row.status).toBe('superseded');
185
- });
186
- });
187
-
188
- describe('handleAdrToolCall', () => {
189
- it('handles adr_list with no decisions', () => {
190
- const result = handleAdrToolCall('massu_adr_list', {}, db);
191
- expect(result.content[0].type).toBe('text');
192
- const text = result.content[0].text;
193
- expect(text).toContain('No architecture decisions found');
194
- });
195
-
196
- it('handles adr_list with decisions', () => {
197
- storeDecision(db, {
198
- title: 'Test Decision',
199
- context: 'Context',
200
- decision: 'Decision',
201
- alternatives: [],
202
- consequences: 'None',
203
- });
204
-
205
- const result = handleAdrToolCall('massu_adr_list', {}, db);
206
- const text = result.content[0].text;
207
- expect(text).toContain('Architecture Decisions (1)');
208
- expect(text).toContain('Test Decision');
209
- });
210
-
211
- it('handles adr_detail for existing decision', () => {
212
- const id = storeDecision(db, {
213
- title: 'Test Decision',
214
- context: 'Test Context',
215
- decision: 'Test Decision Text',
216
- alternatives: ['Option A', 'Option B'],
217
- consequences: 'Test Consequences',
218
- });
219
-
220
- const result = handleAdrToolCall('massu_adr_detail', { id }, db);
221
- const text = result.content[0].text;
222
- expect(text).toContain(`ADR-${id}: Test Decision`);
223
- expect(text).toContain('Test Context');
224
- expect(text).toContain('Option A');
225
- expect(text).toContain('Option B');
226
- expect(text).toContain('Test Consequences');
227
- });
228
-
229
- it('handles adr_detail for non-existent decision', () => {
230
- const result = handleAdrToolCall('massu_adr_detail', { id: 999 }, db);
231
- const text = result.content[0].text;
232
- expect(text).toContain('ADR #999 not found');
233
- });
234
-
235
- it('handles adr_create and extracts alternatives', () => {
236
- const result = handleAdrToolCall('massu_adr_create', {
237
- title: 'Use Redis',
238
- decision: 'Redis over Memcached',
239
- context: 'Need caching',
240
- consequences: 'Better performance',
241
- }, db);
242
-
243
- const text = result.content[0].text;
244
- expect(text).toContain('ADR-1 Created');
245
- expect(text).toContain('Redis');
246
- expect(text).toContain('Memcached');
247
-
248
- const row = db.prepare('SELECT * FROM architecture_decisions WHERE id = 1').get() as Record<string, unknown>;
249
- const alternatives = JSON.parse(row.alternatives as string) as string[];
250
- expect(alternatives).toContain('Redis');
251
- expect(alternatives).toContain('Memcached');
252
- });
253
-
254
- it('handles unknown tool name', () => {
255
- const result = handleAdrToolCall('massu_adr_unknown', {}, db);
256
- const text = result.content[0].text;
257
- expect(text).toContain('Unknown ADR tool');
258
- });
259
- });
260
- });
@@ -1,282 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
5
- import Database from 'better-sqlite3';
6
- import {
7
- getAnalyticsToolDefinitions,
8
- isAnalyticsTool,
9
- calculateQualityScore,
10
- storeQualityScore,
11
- backfillQualityScores,
12
- handleAnalyticsToolCall,
13
- type QualityBreakdown,
14
- } from '../analytics.ts';
15
-
16
- function createTestDb(): Database.Database {
17
- const db = new Database(':memory:');
18
- db.pragma('journal_mode = WAL');
19
-
20
- // Create minimal required schema for analytics
21
- db.exec(`
22
- CREATE TABLE sessions (
23
- id INTEGER PRIMARY KEY AUTOINCREMENT,
24
- session_id TEXT UNIQUE NOT NULL,
25
- started_at TEXT NOT NULL,
26
- started_at_epoch INTEGER NOT NULL
27
- );
28
-
29
- CREATE TABLE observations (
30
- id INTEGER PRIMARY KEY AUTOINCREMENT,
31
- session_id TEXT NOT NULL,
32
- type TEXT NOT NULL,
33
- detail TEXT,
34
- created_at TEXT NOT NULL
35
- );
36
-
37
- CREATE TABLE session_quality_scores (
38
- id INTEGER PRIMARY KEY AUTOINCREMENT,
39
- session_id TEXT NOT NULL UNIQUE,
40
- score INTEGER NOT NULL,
41
- security_score INTEGER NOT NULL DEFAULT 0,
42
- architecture_score INTEGER NOT NULL DEFAULT 0,
43
- coupling_score INTEGER NOT NULL DEFAULT 0,
44
- test_score INTEGER NOT NULL DEFAULT 0,
45
- rule_compliance_score INTEGER NOT NULL DEFAULT 0,
46
- created_at TEXT DEFAULT (datetime('now'))
47
- );
48
- `);
49
-
50
- return db;
51
- }
52
-
53
- describe('analytics', () => {
54
- let db: Database.Database;
55
-
56
- beforeEach(() => {
57
- db = createTestDb();
58
- });
59
-
60
- afterEach(() => {
61
- db.close();
62
- });
63
-
64
- describe('getAnalyticsToolDefinitions', () => {
65
- it('should return tool definitions for analytics tools', () => {
66
- const defs = getAnalyticsToolDefinitions();
67
- expect(defs.length).toBe(3);
68
-
69
- const names = defs.map(d => d.name);
70
- expect(names.some(n => n.includes('quality_score'))).toBe(true);
71
- expect(names.some(n => n.includes('quality_trend'))).toBe(true);
72
- expect(names.some(n => n.includes('quality_report'))).toBe(true);
73
-
74
- // Check structure
75
- for (const def of defs) {
76
- expect(def.name).toBeTruthy();
77
- expect(def.description).toBeTruthy();
78
- expect(def.inputSchema).toBeTruthy();
79
- expect(def.inputSchema.type).toBe('object');
80
- }
81
- });
82
- });
83
-
84
- describe('isAnalyticsTool', () => {
85
- it('should return true for analytics tools', () => {
86
- expect(isAnalyticsTool('massu_quality_score')).toBe(true);
87
- expect(isAnalyticsTool('massu_quality_trend')).toBe(true);
88
- expect(isAnalyticsTool('massu_quality_report')).toBe(true);
89
- });
90
-
91
- it('should return false for non-analytics tools', () => {
92
- expect(isAnalyticsTool('massu_cost_session')).toBe(false);
93
- expect(isAnalyticsTool('massu_audit_log')).toBe(false);
94
- expect(isAnalyticsTool('random_tool')).toBe(false);
95
- });
96
-
97
- it('should handle base names without prefix', () => {
98
- expect(isAnalyticsTool('quality_score')).toBe(true);
99
- expect(isAnalyticsTool('quality_trend')).toBe(true);
100
- });
101
- });
102
-
103
- describe('calculateQualityScore', () => {
104
- it('should calculate quality score from observations', () => {
105
- const sessionId = 'test-session-1';
106
-
107
- // Insert session
108
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
109
- sessionId,
110
- new Date().toISOString(),
111
- Math.floor(Date.now() / 1000)
112
- );
113
-
114
- // Insert observations
115
- db.prepare('INSERT INTO observations (session_id, type, detail, created_at) VALUES (?, ?, ?, ?)').run(
116
- sessionId, 'vr_pass', 'Security check passed', new Date().toISOString()
117
- );
118
- db.prepare('INSERT INTO observations (session_id, type, detail, created_at) VALUES (?, ?, ?, ?)').run(
119
- sessionId, 'clean_commit', 'Clean commit with security improvements', new Date().toISOString()
120
- );
121
- db.prepare('INSERT INTO observations (session_id, type, detail, created_at) VALUES (?, ?, ?, ?)').run(
122
- sessionId, 'bug_found', 'Bug found in architecture', new Date().toISOString()
123
- );
124
-
125
- const result = calculateQualityScore(db, sessionId);
126
-
127
- expect(result.score).toBeGreaterThan(0);
128
- expect(result.score).toBeLessThanOrEqual(100);
129
- expect(result.breakdown).toBeDefined();
130
- expect(result.breakdown.security).toBeDefined();
131
- expect(result.breakdown.architecture).toBeDefined();
132
- });
133
-
134
- it('should start with base score of 50', () => {
135
- const sessionId = 'test-session-2';
136
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
137
- sessionId,
138
- new Date().toISOString(),
139
- Math.floor(Date.now() / 1000)
140
- );
141
-
142
- const result = calculateQualityScore(db, sessionId);
143
- expect(result.score).toBe(50); // No observations = base score
144
- });
145
-
146
- it('should clamp score between 0 and 100', () => {
147
- const sessionId = 'test-session-3';
148
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
149
- sessionId,
150
- new Date().toISOString(),
151
- Math.floor(Date.now() / 1000)
152
- );
153
-
154
- // Add many negative observations
155
- for (let i = 0; i < 10; i++) {
156
- db.prepare('INSERT INTO observations (session_id, type, detail, created_at) VALUES (?, ?, ?, ?)').run(
157
- sessionId, 'incident', 'Critical incident', new Date().toISOString()
158
- );
159
- }
160
-
161
- const result = calculateQualityScore(db, sessionId);
162
- expect(result.score).toBeGreaterThanOrEqual(0);
163
- expect(result.score).toBeLessThanOrEqual(100);
164
- });
165
- });
166
-
167
- describe('storeQualityScore', () => {
168
- it('should store quality score in database', () => {
169
- const sessionId = 'test-session-4';
170
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
171
- sessionId,
172
- new Date().toISOString(),
173
- Math.floor(Date.now() / 1000)
174
- );
175
-
176
- const breakdown: QualityBreakdown = {
177
- security: 5,
178
- architecture: -3,
179
- coupling: 0,
180
- tests: 2,
181
- rule_compliance: 1,
182
- };
183
-
184
- storeQualityScore(db, sessionId, 75, breakdown);
185
-
186
- const stored = db.prepare('SELECT * FROM session_quality_scores WHERE session_id = ?').get(sessionId) as Record<string, unknown>;
187
- expect(stored).toBeDefined();
188
- expect(stored.session_id).toBe(sessionId);
189
- expect(stored.score).toBe(75);
190
- expect(stored.security_score).toBe(5);
191
- expect(stored.architecture_score).toBe(-3);
192
- expect(stored.coupling_score).toBe(0);
193
- expect(stored.test_score).toBe(2);
194
- expect(stored.rule_compliance_score).toBe(1);
195
- });
196
- });
197
-
198
- describe('backfillQualityScores', () => {
199
- it('should backfill scores for sessions without them', () => {
200
- // Create sessions
201
- const sessions = ['session-1', 'session-2', 'session-3'];
202
- for (const sid of sessions) {
203
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
204
- sid,
205
- new Date().toISOString(),
206
- Math.floor(Date.now() / 1000)
207
- );
208
- db.prepare('INSERT INTO observations (session_id, type, detail, created_at) VALUES (?, ?, ?, ?)').run(
209
- sid, 'vr_pass', 'Test passed', new Date().toISOString()
210
- );
211
- }
212
-
213
- const backfilled = backfillQualityScores(db);
214
- expect(backfilled).toBe(3);
215
-
216
- // Verify all sessions now have scores
217
- const scores = db.prepare('SELECT COUNT(*) as count FROM session_quality_scores').get() as { count: number };
218
- expect(scores.count).toBe(3);
219
- });
220
-
221
- it('should not backfill sessions that already have scores', () => {
222
- const sessionId = 'session-with-score';
223
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
224
- sessionId,
225
- new Date().toISOString(),
226
- Math.floor(Date.now() / 1000)
227
- );
228
-
229
- // Already has a score
230
- const breakdown: QualityBreakdown = {
231
- security: 0,
232
- architecture: 0,
233
- coupling: 0,
234
- tests: 0,
235
- rule_compliance: 0,
236
- };
237
- storeQualityScore(db, sessionId, 50, breakdown);
238
-
239
- const backfilled = backfillQualityScores(db);
240
- expect(backfilled).toBe(0);
241
- });
242
- });
243
-
244
- describe('handleAnalyticsToolCall', () => {
245
- it('should handle quality_score tool call', () => {
246
- const sessionId = 'test-session-5';
247
- db.prepare('INSERT INTO sessions (session_id, started_at, started_at_epoch) VALUES (?, ?, ?)').run(
248
- sessionId,
249
- new Date().toISOString(),
250
- Math.floor(Date.now() / 1000)
251
- );
252
- db.prepare('INSERT INTO observations (session_id, type, detail, created_at) VALUES (?, ?, ?, ?)').run(
253
- sessionId, 'vr_pass', 'Security tests passed', new Date().toISOString()
254
- );
255
-
256
- const result = handleAnalyticsToolCall('massu_quality_score', { session_id: sessionId }, db);
257
-
258
- expect(result.content).toBeDefined();
259
- expect(result.content.length).toBeGreaterThan(0);
260
- expect(result.content[0].type).toBe('text');
261
- const text = result.content[0].text;
262
- expect(text).toContain('Quality Score');
263
- expect(text).toContain('Breakdown');
264
- });
265
-
266
- it('should return error for missing session_id', () => {
267
- const result = handleAnalyticsToolCall('massu_quality_score', {}, db);
268
-
269
- expect(result.content).toBeDefined();
270
- expect(result.content[0].type).toBe('text');
271
- expect(result.content[0].text).toContain('Usage');
272
- });
273
-
274
- it('should handle unknown tool name', () => {
275
- const result = handleAnalyticsToolCall('massu_unknown_analytics_tool', {}, db);
276
-
277
- expect(result.content).toBeDefined();
278
- expect(result.content[0].type).toBe('text');
279
- expect(result.content[0].text).toContain('Unknown analytics tool');
280
- });
281
- });
282
- });