@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,29 +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 } from 'vitest';
5
- import { existsSync } from 'fs';
6
- import { parsePrismaSchema } from '../schema-mapper.ts';
7
- import { getResolvedPaths } from '../config.ts';
8
-
9
- const schemaExists = existsSync(getResolvedPaths().prismaSchemaPath);
10
-
11
- describe('parsePrismaSchema', () => {
12
- it('parses the Prisma schema file', () => {
13
- if (!schemaExists) {
14
- // No schema in this project - verify it throws gracefully
15
- expect(() => parsePrismaSchema()).toThrow('Prisma schema not found');
16
- return;
17
- }
18
- const models = parsePrismaSchema();
19
- expect(models.length).toBeGreaterThan(0);
20
- });
21
-
22
- it('finds models with fields', () => {
23
- if (!schemaExists) return;
24
- const models = parsePrismaSchema();
25
- for (const model of models) {
26
- expect(model.fields.length).toBeGreaterThan(0);
27
- }
28
- });
29
- });
@@ -1,238 +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 { writeFileSync, mkdirSync, rmSync } from 'fs';
7
- import { join } from 'path';
8
- import {
9
- getSecurityToolDefinitions,
10
- isSecurityTool,
11
- scoreFileSecurity,
12
- storeSecurityScore,
13
- handleSecurityToolCall,
14
- } from '../security-scorer.ts';
15
-
16
- function createTestDb(): Database.Database {
17
- const db = new Database(':memory:');
18
- db.exec(`
19
- CREATE TABLE security_scores (
20
- id INTEGER PRIMARY KEY AUTOINCREMENT,
21
- session_id TEXT NOT NULL,
22
- file_path TEXT NOT NULL,
23
- risk_score INTEGER NOT NULL DEFAULT 0,
24
- findings TEXT,
25
- created_at TEXT DEFAULT (datetime('now'))
26
- );
27
-
28
- CREATE TABLE sessions (
29
- session_id TEXT PRIMARY KEY,
30
- status TEXT,
31
- started_at_epoch INTEGER
32
- );
33
- `);
34
- return db;
35
- }
36
-
37
- describe('security-scorer', () => {
38
- let db: Database.Database;
39
- const testDir = '/tmp/security-scorer-test';
40
-
41
- beforeEach(() => {
42
- db = createTestDb();
43
- try {
44
- rmSync(testDir, { recursive: true, force: true });
45
- } catch { /* ignore */ }
46
- mkdirSync(testDir, { recursive: true });
47
- });
48
-
49
- afterEach(() => {
50
- db.close();
51
- try {
52
- rmSync(testDir, { recursive: true, force: true });
53
- } catch { /* ignore */ }
54
- });
55
-
56
- describe('getSecurityToolDefinitions', () => {
57
- it('returns 3 tool definitions', () => {
58
- const tools = getSecurityToolDefinitions();
59
- expect(tools).toHaveLength(3);
60
- expect(tools.map(t => t.name.split('_').slice(-2).join('_'))).toEqual([
61
- 'security_score',
62
- 'security_heatmap',
63
- 'security_trend',
64
- ]);
65
- });
66
-
67
- it('has required fields in tool definitions', () => {
68
- const tools = getSecurityToolDefinitions();
69
- tools.forEach(tool => {
70
- expect(tool.name).toBeTruthy();
71
- expect(tool.description).toBeTruthy();
72
- expect(tool.inputSchema).toBeDefined();
73
- });
74
- });
75
- });
76
-
77
- describe('isSecurityTool', () => {
78
- it('returns true for security tool names', () => {
79
- expect(isSecurityTool('massu_security_score')).toBe(true);
80
- expect(isSecurityTool('massu_security_heatmap')).toBe(true);
81
- expect(isSecurityTool('massu_security_trend')).toBe(true);
82
- });
83
-
84
- it('returns false for non-security tool names', () => {
85
- expect(isSecurityTool('massu_adr_list')).toBe(false);
86
- expect(isSecurityTool('massu_unknown')).toBe(false);
87
- });
88
- });
89
-
90
- describe('scoreFileSecurity', () => {
91
- it('returns 0 for non-existent file', () => {
92
- const result = scoreFileSecurity('nonexistent.ts', testDir);
93
- expect(result.riskScore).toBe(0);
94
- expect(result.findings).toEqual([]);
95
- });
96
-
97
- it('detects hardcoded credentials', () => {
98
- const filePath = join(testDir, 'test.ts');
99
- writeFileSync(filePath, `const api_key = "sk-1234567890abcdef";\n`);
100
-
101
- const result = scoreFileSecurity(filePath, testDir);
102
- expect(result.riskScore).toBeGreaterThan(0);
103
- expect(result.findings.length).toBeGreaterThan(0);
104
- expect(result.findings[0].severity).toBe('critical');
105
- expect(result.findings[0].description).toContain('credential');
106
- });
107
-
108
- it('detects publicProcedure.mutation vulnerability', () => {
109
- const filePath = join(testDir, 'router.ts');
110
- writeFileSync(filePath, `export const router = publicProcedure.mutation(async () => {});\n`);
111
-
112
- const result = scoreFileSecurity(filePath, testDir);
113
- expect(result.riskScore).toBeGreaterThan(0);
114
- const mutation = result.findings.find(f => f.description.includes('Mutation without authentication'));
115
- expect(mutation).toBeDefined();
116
- expect(mutation?.severity).toBe('critical');
117
- });
118
-
119
- it('detects eval usage', () => {
120
- const filePath = join(testDir, 'dangerous.ts');
121
- writeFileSync(filePath, `const result = eval(userInput);\n`);
122
-
123
- const result = scoreFileSecurity(filePath, testDir);
124
- expect(result.riskScore).toBeGreaterThan(0);
125
- const evalFinding = result.findings.find(f => f.description.includes('eval()'));
126
- expect(evalFinding).toBeDefined();
127
- expect(evalFinding?.severity).toBe('high');
128
- });
129
-
130
- it('detects dangerouslySetInnerHTML in tsx files', () => {
131
- const filePath = join(testDir, 'component.tsx');
132
- writeFileSync(filePath, `<div dangerouslySetInnerHTML={{ __html: html }} />\n`);
133
-
134
- const result = scoreFileSecurity(filePath, testDir);
135
- expect(result.riskScore).toBeGreaterThan(0);
136
- const xss = result.findings.find(f => f.description.includes('XSS risk'));
137
- expect(xss).toBeDefined();
138
- expect(xss?.severity).toBe('high');
139
- });
140
-
141
- it('returns 0 for clean file', () => {
142
- const filePath = join(testDir, 'clean.ts');
143
- writeFileSync(filePath, `export function add(a: number, b: number) { return a + b; }\n`);
144
-
145
- const result = scoreFileSecurity(filePath, testDir);
146
- expect(result.riskScore).toBe(0);
147
- expect(result.findings).toEqual([]);
148
- });
149
-
150
- it('caps risk score at 100', () => {
151
- const filePath = join(testDir, 'critical.ts');
152
- writeFileSync(filePath, `
153
- const password = "hardcoded-secret-12345";
154
- const token = "sk-1234567890abcdef";
155
- const apiKey = "another-secret-key-abc";
156
- publicProcedure.mutation(async () => {});
157
- eval(userInput);
158
- exec(\`rm -rf \${dir}\`);
159
- `);
160
-
161
- const result = scoreFileSecurity(filePath, testDir);
162
- expect(result.riskScore).toBeLessThanOrEqual(100);
163
- });
164
-
165
- it('blocks path traversal attacks', () => {
166
- const result = scoreFileSecurity('../../../etc/passwd', testDir);
167
- expect(result.riskScore).toBe(100);
168
- expect(result.findings[0].severity).toBe('critical');
169
- expect(result.findings[0].description).toContain('Path traversal blocked');
170
- });
171
- });
172
-
173
- describe('storeSecurityScore', () => {
174
- it('stores security score in database', () => {
175
- storeSecurityScore(db, 'session-123', 'src/test.ts', 42, [
176
- { pattern: 'test', severity: 'high', line: 10, description: 'Test finding' },
177
- ]);
178
-
179
- const row = db.prepare('SELECT * FROM security_scores WHERE session_id = ?').get('session-123') as Record<string, unknown>;
180
- expect(row.file_path).toBe('src/test.ts');
181
- expect(row.risk_score).toBe(42);
182
-
183
- const findings = JSON.parse(row.findings as string) as Array<Record<string, unknown>>;
184
- expect(findings).toHaveLength(1);
185
- expect(findings[0].description).toBe('Test finding');
186
- });
187
- });
188
-
189
- describe('handleSecurityToolCall', () => {
190
- it('handles security_score for file', () => {
191
- const filePath = join(testDir, 'test.ts');
192
- writeFileSync(filePath, `export const x = 1;\n`);
193
-
194
- // Create active session
195
- db.prepare(`INSERT INTO sessions (session_id, status, started_at_epoch) VALUES (?, ?, ?)`).run(
196
- 'test-session',
197
- 'active',
198
- Math.floor(Date.now() / 1000)
199
- );
200
-
201
- const result = handleSecurityToolCall('massu_security_score', { file_path: filePath }, db);
202
- const text = result.content[0].text;
203
- expect(text).toContain('Security Score');
204
- expect(text).toContain(filePath);
205
- });
206
-
207
- it('handles security_heatmap with no data', () => {
208
- const result = handleSecurityToolCall('massu_security_heatmap', {}, db);
209
- const text = result.content[0].text;
210
- expect(text).toContain('No files with risk score');
211
- });
212
-
213
- it('handles security_heatmap with threshold', () => {
214
- storeSecurityScore(db, 'session-1', 'file1.ts', 50, []);
215
- storeSecurityScore(db, 'session-1', 'file2.ts', 80, []);
216
- storeSecurityScore(db, 'session-1', 'file3.ts', 20, []);
217
-
218
- const result = handleSecurityToolCall('massu_security_heatmap', { threshold: 30 }, db);
219
- const text = result.content[0].text;
220
- expect(text).toContain('Security Heat Map');
221
- expect(text).toContain('file1.ts');
222
- expect(text).toContain('file2.ts');
223
- expect(text).not.toContain('file3.ts');
224
- });
225
-
226
- it('handles security_trend with no data', () => {
227
- const result = handleSecurityToolCall('massu_security_trend', {}, db);
228
- const text = result.content[0].text;
229
- expect(text).toContain('No security scan data');
230
- });
231
-
232
- it('handles unknown tool name', () => {
233
- const result = handleSecurityToolCall('massu_security_unknown', {}, db);
234
- const text = result.content[0].text;
235
- expect(text).toContain('Unknown security tool');
236
- });
237
- });
238
- });
@@ -1,175 +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 } from 'vitest';
5
- import {
6
- ensureWithinRoot,
7
- escapeRegex,
8
- safeRegex,
9
- globToSafeRegex,
10
- redactSensitiveContent,
11
- enforceSeverityFloors,
12
- MINIMUM_SEVERITY_WEIGHTS,
13
- } from '../security-utils.ts';
14
-
15
- describe('ensureWithinRoot', () => {
16
- const root = '/projects/my-app';
17
-
18
- it('allows paths within root', () => {
19
- expect(ensureWithinRoot('src/index.ts', root)).toBe('/projects/my-app/src/index.ts');
20
- });
21
-
22
- it('allows nested paths', () => {
23
- expect(ensureWithinRoot('src/lib/utils/helpers.ts', root)).toBe('/projects/my-app/src/lib/utils/helpers.ts');
24
- });
25
-
26
- it('blocks path traversal with ../', () => {
27
- expect(() => ensureWithinRoot('../../etc/passwd', root)).toThrow('Path traversal blocked');
28
- });
29
-
30
- it('blocks path traversal with encoded sequences', () => {
31
- expect(() => ensureWithinRoot('../../../etc/shadow', root)).toThrow('Path traversal blocked');
32
- });
33
-
34
- it('normalizes paths with ./ segments', () => {
35
- expect(ensureWithinRoot('./src/../src/index.ts', root)).toBe('/projects/my-app/src/index.ts');
36
- });
37
-
38
- it('blocks traversal that resolves outside after normalization', () => {
39
- expect(() => ensureWithinRoot('src/../../../../etc/passwd', root)).toThrow('Path traversal blocked');
40
- });
41
- });
42
-
43
- describe('escapeRegex', () => {
44
- it('escapes special regex characters', () => {
45
- expect(escapeRegex('hello.world')).toBe('hello\\.world');
46
- expect(escapeRegex('a+b*c')).toBe('a\\+b\\*c');
47
- expect(escapeRegex('foo(bar)')).toBe('foo\\(bar\\)');
48
- });
49
-
50
- it('leaves normal strings unchanged', () => {
51
- expect(escapeRegex('hello world')).toBe('hello world');
52
- });
53
-
54
- it('escapes all PCRE special chars', () => {
55
- const special = '.*+?^${}()|[]\\';
56
- const escaped = escapeRegex(special);
57
- // Every char should be escaped
58
- expect(escaped).toBe('\\.\\*\\+\\?\\^\\$\\{\\}\\(\\)\\|\\[\\]\\\\');
59
- });
60
- });
61
-
62
- describe('safeRegex', () => {
63
- it('compiles simple patterns', () => {
64
- const re = safeRegex('hello|world');
65
- expect(re).toBeInstanceOf(RegExp);
66
- expect(re!.test('hello')).toBe(true);
67
- });
68
-
69
- it('rejects nested quantifiers (ReDoS)', () => {
70
- expect(safeRegex('(a+)+')).toBeNull();
71
- expect(safeRegex('(a*)*')).toBeNull();
72
- expect(safeRegex('(a+){2,}')).toBeNull();
73
- });
74
-
75
- it('rejects excessively long patterns', () => {
76
- expect(safeRegex('a'.repeat(501))).toBeNull();
77
- });
78
-
79
- it('rejects invalid regex syntax', () => {
80
- expect(safeRegex('(?P<invalid')).toBeNull();
81
- });
82
-
83
- it('accepts reasonable patterns', () => {
84
- expect(safeRegex('\\bctx\\.prisma\\b')).toBeInstanceOf(RegExp);
85
- expect(safeRegex('import.*from')).toBeInstanceOf(RegExp);
86
- });
87
-
88
- it('supports flags', () => {
89
- const re = safeRegex('hello', 'i');
90
- expect(re!.test('HELLO')).toBe(true);
91
- });
92
- });
93
-
94
- describe('globToSafeRegex', () => {
95
- it('converts simple glob with single star', () => {
96
- const re = globToSafeRegex('src/**/*.ts');
97
- expect(re.test('src/lib/utils.ts')).toBe(true);
98
- expect(re.test('src/deep/nested/file.ts')).toBe(true);
99
- });
100
-
101
- it('does not match across path separators with single star', () => {
102
- const re = globToSafeRegex('src/*.ts');
103
- expect(re.test('src/index.ts')).toBe(true);
104
- expect(re.test('src/lib/index.ts')).toBe(false);
105
- });
106
-
107
- it('escapes special regex chars in the glob', () => {
108
- const re = globToSafeRegex('src/components/(portal)/*.tsx');
109
- expect(re.test('src/components/(portal)/page.tsx')).toBe(true);
110
- });
111
- });
112
-
113
- describe('redactSensitiveContent', () => {
114
- it('redacts API keys', () => {
115
- expect(redactSensitiveContent('key: sk-abc123def456ghij')).toContain('[REDACTED_KEY]');
116
- expect(redactSensitiveContent('token: ghp_1234567890abcdef')).toContain('[REDACTED_KEY]');
117
- });
118
-
119
- it('redacts email addresses', () => {
120
- expect(redactSensitiveContent('contact user@example.com')).toContain('[REDACTED_EMAIL]');
121
- });
122
-
123
- it('redacts Bearer tokens', () => {
124
- expect(redactSensitiveContent('Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.payload.sig'))
125
- .toContain('[REDACTED_TOKEN]');
126
- });
127
-
128
- it('redacts absolute file paths', () => {
129
- expect(redactSensitiveContent('file at /Users/john/secrets/key.pem')).toContain('[REDACTED_PATH]');
130
- });
131
-
132
- it('redacts connection strings', () => {
133
- expect(redactSensitiveContent('postgres://admin:s3cret@db.host.com/mydb'))
134
- .toContain('[REDACTED_CREDENTIALS]');
135
- });
136
-
137
- it('preserves non-sensitive content', () => {
138
- const safe = 'This is a normal prompt about implementing a feature';
139
- expect(redactSensitiveContent(safe)).toBe(safe);
140
- });
141
- });
142
-
143
- describe('enforceSeverityFloors', () => {
144
- const defaults = { critical: 25, high: 15, medium: 8, low: 3 };
145
-
146
- it('uses config values when above floor', () => {
147
- const result = enforceSeverityFloors({ critical: 30, high: 20 }, defaults);
148
- expect(result.critical).toBe(30);
149
- expect(result.high).toBe(20);
150
- });
151
-
152
- it('enforces minimum floors', () => {
153
- const result = enforceSeverityFloors({ critical: 0, high: 0, medium: 0, low: 0 }, defaults);
154
- expect(result.critical).toBe(MINIMUM_SEVERITY_WEIGHTS.critical);
155
- expect(result.high).toBe(MINIMUM_SEVERITY_WEIGHTS.high);
156
- expect(result.medium).toBe(MINIMUM_SEVERITY_WEIGHTS.medium);
157
- expect(result.low).toBe(MINIMUM_SEVERITY_WEIGHTS.low);
158
- });
159
-
160
- it('preserves defaults for missing config keys', () => {
161
- const result = enforceSeverityFloors({ critical: 50 }, defaults);
162
- expect(result.critical).toBe(50);
163
- expect(result.high).toBe(15); // default preserved
164
- expect(result.medium).toBe(8); // default preserved
165
- });
166
-
167
- it('prevents complete disabling of security scoring', () => {
168
- const result = enforceSeverityFloors(
169
- { critical: 0, high: 0, medium: 0, low: 0 },
170
- defaults
171
- );
172
- const totalWeight = Object.values(result).reduce((sum, v) => sum + v, 0);
173
- expect(totalWeight).toBeGreaterThan(0);
174
- });
175
- });