@massu/core 0.1.1 → 0.4.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 (151) hide show
  1. package/commands/_shared-preamble.md +76 -0
  2. package/commands/massu-audit-deps.md +211 -0
  3. package/commands/massu-changelog.md +174 -0
  4. package/commands/massu-cleanup.md +315 -0
  5. package/commands/massu-commit.md +481 -0
  6. package/commands/massu-create-plan.md +752 -0
  7. package/commands/massu-dead-code.md +131 -0
  8. package/commands/massu-debug.md +484 -0
  9. package/commands/massu-deploy.md +91 -0
  10. package/commands/massu-deps.md +374 -0
  11. package/commands/massu-doc-gen.md +279 -0
  12. package/commands/massu-docs.md +364 -0
  13. package/commands/massu-estimate.md +313 -0
  14. package/commands/massu-golden-path.md +973 -0
  15. package/commands/massu-guide.md +167 -0
  16. package/commands/massu-hotfix.md +480 -0
  17. package/commands/massu-loop-playwright.md +837 -0
  18. package/commands/massu-loop.md +775 -0
  19. package/commands/massu-new-feature.md +511 -0
  20. package/commands/massu-parity.md +214 -0
  21. package/commands/massu-plan.md +456 -0
  22. package/commands/massu-push-light.md +207 -0
  23. package/commands/massu-push.md +434 -0
  24. package/commands/massu-refactor.md +410 -0
  25. package/commands/massu-release.md +363 -0
  26. package/commands/massu-review.md +238 -0
  27. package/commands/massu-simplify.md +281 -0
  28. package/commands/massu-status.md +278 -0
  29. package/commands/massu-tdd.md +201 -0
  30. package/commands/massu-test.md +516 -0
  31. package/commands/massu-verify-playwright.md +281 -0
  32. package/commands/massu-verify.md +667 -0
  33. package/dist/cli.js +7772 -3140
  34. package/dist/hooks/cost-tracker.js +103 -40
  35. package/dist/hooks/post-edit-context.js +74 -8
  36. package/dist/hooks/post-tool-use.js +268 -106
  37. package/dist/hooks/pre-compact.js +167 -43
  38. package/dist/hooks/pre-delete-check.js +159 -42
  39. package/dist/hooks/quality-event.js +103 -40
  40. package/dist/hooks/security-gate.js +29 -0
  41. package/dist/hooks/session-end.js +143 -84
  42. package/dist/hooks/session-start.js +186 -49
  43. package/dist/hooks/user-prompt.js +189 -43
  44. package/package.json +10 -15
  45. package/src/adr-generator.ts +9 -2
  46. package/src/analytics.ts +9 -3
  47. package/src/audit-trail.ts +10 -3
  48. package/src/backfill-sessions.ts +5 -4
  49. package/src/cli.ts +6 -0
  50. package/src/cloud-sync.ts +14 -18
  51. package/src/commands/doctor.ts +193 -6
  52. package/src/commands/init.ts +230 -5
  53. package/src/commands/install-commands.ts +137 -0
  54. package/src/config.ts +68 -2
  55. package/src/cost-tracker.ts +11 -6
  56. package/src/db.ts +115 -2
  57. package/src/dependency-scorer.ts +9 -2
  58. package/src/docs-tools.ts +21 -16
  59. package/src/hooks/post-edit-context.ts +4 -4
  60. package/src/hooks/post-tool-use.ts +130 -0
  61. package/src/hooks/pre-compact.ts +23 -1
  62. package/src/hooks/pre-delete-check.ts +92 -4
  63. package/src/hooks/security-gate.ts +32 -0
  64. package/src/hooks/session-end.ts +3 -3
  65. package/src/hooks/session-start.ts +99 -6
  66. package/src/hooks/user-prompt.ts +46 -1
  67. package/src/import-resolver.ts +2 -1
  68. package/src/knowledge-db.ts +169 -0
  69. package/src/knowledge-indexer.ts +704 -0
  70. package/src/knowledge-tools.ts +1413 -0
  71. package/src/license.ts +482 -0
  72. package/src/memory-db.ts +1364 -23
  73. package/src/memory-tools.ts +14 -15
  74. package/src/observability-tools.ts +13 -2
  75. package/src/observation-extractor.ts +11 -4
  76. package/src/page-deps.ts +3 -2
  77. package/src/prompt-analyzer.ts +9 -2
  78. package/src/python/coupling-detector.ts +124 -0
  79. package/src/python/domain-enforcer.ts +83 -0
  80. package/src/python/impact-analyzer.ts +95 -0
  81. package/src/python/import-parser.ts +244 -0
  82. package/src/python/import-resolver.ts +135 -0
  83. package/src/python/migration-indexer.ts +115 -0
  84. package/src/python/migration-parser.ts +332 -0
  85. package/src/python/model-indexer.ts +70 -0
  86. package/src/python/model-parser.ts +279 -0
  87. package/src/python/route-indexer.ts +58 -0
  88. package/src/python/route-parser.ts +317 -0
  89. package/src/python-tools.ts +629 -0
  90. package/src/regression-detector.ts +9 -3
  91. package/src/security-scorer.ts +9 -2
  92. package/src/sentinel-db.ts +45 -89
  93. package/src/sentinel-tools.ts +8 -11
  94. package/src/server.ts +29 -7
  95. package/src/session-archiver.ts +4 -5
  96. package/src/team-knowledge.ts +9 -2
  97. package/src/tools.ts +1032 -44
  98. package/src/validate-features-runner.ts +0 -1
  99. package/src/validation-engine.ts +9 -2
  100. package/README.md +0 -40
  101. package/dist/server.js +0 -7008
  102. package/src/__tests__/adr-generator.test.ts +0 -260
  103. package/src/__tests__/analytics.test.ts +0 -282
  104. package/src/__tests__/audit-trail.test.ts +0 -382
  105. package/src/__tests__/backfill-sessions.test.ts +0 -690
  106. package/src/__tests__/cli.test.ts +0 -290
  107. package/src/__tests__/cloud-sync.test.ts +0 -261
  108. package/src/__tests__/config-sections.test.ts +0 -359
  109. package/src/__tests__/config.test.ts +0 -732
  110. package/src/__tests__/cost-tracker.test.ts +0 -348
  111. package/src/__tests__/db.test.ts +0 -177
  112. package/src/__tests__/dependency-scorer.test.ts +0 -325
  113. package/src/__tests__/docs-integration.test.ts +0 -178
  114. package/src/__tests__/docs-tools.test.ts +0 -199
  115. package/src/__tests__/domains.test.ts +0 -236
  116. package/src/__tests__/hooks.test.ts +0 -221
  117. package/src/__tests__/import-resolver.test.ts +0 -95
  118. package/src/__tests__/integration/path-traversal.test.ts +0 -134
  119. package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
  120. package/src/__tests__/integration/tool-registration.test.ts +0 -146
  121. package/src/__tests__/memory-db.test.ts +0 -404
  122. package/src/__tests__/memory-enhancements.test.ts +0 -316
  123. package/src/__tests__/memory-tools.test.ts +0 -199
  124. package/src/__tests__/middleware-tree.test.ts +0 -177
  125. package/src/__tests__/observability-tools.test.ts +0 -595
  126. package/src/__tests__/observability.test.ts +0 -437
  127. package/src/__tests__/observation-extractor.test.ts +0 -167
  128. package/src/__tests__/page-deps.test.ts +0 -60
  129. package/src/__tests__/prompt-analyzer.test.ts +0 -298
  130. package/src/__tests__/regression-detector.test.ts +0 -295
  131. package/src/__tests__/rules.test.ts +0 -87
  132. package/src/__tests__/schema-mapper.test.ts +0 -29
  133. package/src/__tests__/security-scorer.test.ts +0 -238
  134. package/src/__tests__/security-utils.test.ts +0 -175
  135. package/src/__tests__/sentinel-db.test.ts +0 -491
  136. package/src/__tests__/sentinel-scanner.test.ts +0 -750
  137. package/src/__tests__/sentinel-tools.test.ts +0 -324
  138. package/src/__tests__/sentinel-types.test.ts +0 -750
  139. package/src/__tests__/server.test.ts +0 -452
  140. package/src/__tests__/session-archiver.test.ts +0 -524
  141. package/src/__tests__/session-state-generator.test.ts +0 -900
  142. package/src/__tests__/team-knowledge.test.ts +0 -327
  143. package/src/__tests__/tools.test.ts +0 -340
  144. package/src/__tests__/transcript-parser.test.ts +0 -195
  145. package/src/__tests__/trpc-index.test.ts +0 -25
  146. package/src/__tests__/validate-features-runner.test.ts +0 -517
  147. package/src/__tests__/validation-engine.test.ts +0 -300
  148. package/src/core-tools.ts +0 -685
  149. package/src/memory-queries.ts +0 -804
  150. package/src/memory-schema.ts +0 -546
  151. 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
- });