@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,325 +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
- getDependencyToolDefinitions,
10
- isDependencyTool,
11
- calculateDepRisk,
12
- getInstalledPackages,
13
- storeAssessment,
14
- getPreviousRemovals,
15
- handleDependencyToolCall,
16
- } from '../dependency-scorer.ts';
17
-
18
- function createTestDb(): Database.Database {
19
- const db = new Database(':memory:');
20
- db.exec(`
21
- CREATE TABLE dependency_assessments (
22
- id INTEGER PRIMARY KEY AUTOINCREMENT,
23
- package_name TEXT NOT NULL,
24
- version TEXT,
25
- risk_score INTEGER NOT NULL DEFAULT 0,
26
- vulnerabilities INTEGER NOT NULL DEFAULT 0,
27
- last_publish_days INTEGER,
28
- weekly_downloads INTEGER,
29
- license TEXT,
30
- bundle_size_kb INTEGER,
31
- previous_removals INTEGER NOT NULL DEFAULT 0,
32
- assessed_at TEXT DEFAULT (datetime('now'))
33
- );
34
- `);
35
- return db;
36
- }
37
-
38
- describe('dependency-scorer', () => {
39
- let db: Database.Database;
40
- const testDir = '/tmp/dependency-scorer-test';
41
-
42
- beforeEach(() => {
43
- db = createTestDb();
44
- try {
45
- rmSync(testDir, { recursive: true, force: true });
46
- } catch { /* ignore */ }
47
- mkdirSync(testDir, { recursive: true });
48
- });
49
-
50
- afterEach(() => {
51
- db.close();
52
- try {
53
- rmSync(testDir, { recursive: true, force: true });
54
- } catch { /* ignore */ }
55
- });
56
-
57
- describe('getDependencyToolDefinitions', () => {
58
- it('returns 2 tool definitions', () => {
59
- const tools = getDependencyToolDefinitions();
60
- expect(tools).toHaveLength(2);
61
- expect(tools.map(t => t.name.split('_').slice(-2).join('_'))).toEqual([
62
- 'dep_score',
63
- 'dep_alternatives',
64
- ]);
65
- });
66
-
67
- it('dep_score requires package_name', () => {
68
- const tools = getDependencyToolDefinitions();
69
- const scoreTool = tools.find(t => t.name.endsWith('_dep_score'));
70
- expect(scoreTool?.inputSchema.required).toEqual(['package_name']);
71
- });
72
- });
73
-
74
- describe('isDependencyTool', () => {
75
- it('returns true for dependency tool names', () => {
76
- expect(isDependencyTool('massu_dep_score')).toBe(true);
77
- expect(isDependencyTool('massu_dep_alternatives')).toBe(true);
78
- });
79
-
80
- it('returns false for non-dependency tool names', () => {
81
- expect(isDependencyTool('massu_security_score')).toBe(false);
82
- expect(isDependencyTool('massu_unknown')).toBe(false);
83
- });
84
- });
85
-
86
- describe('calculateDepRisk', () => {
87
- it('returns 0 for safe package', () => {
88
- const risk = calculateDepRisk({
89
- vulnerabilities: 0,
90
- lastPublishDays: 30,
91
- weeklyDownloads: 1000000,
92
- license: 'MIT',
93
- bundleSizeKb: 50,
94
- previousRemovals: 0,
95
- });
96
- expect(risk).toBe(0);
97
- });
98
-
99
- it('adds risk for vulnerabilities', () => {
100
- const risk = calculateDepRisk({
101
- vulnerabilities: 2,
102
- lastPublishDays: null,
103
- weeklyDownloads: null,
104
- license: null,
105
- bundleSizeKb: null,
106
- previousRemovals: 0,
107
- });
108
- expect(risk).toBeGreaterThanOrEqual(30); // 2 * 15 = 30
109
- });
110
-
111
- it('adds risk for stale packages', () => {
112
- const risk = calculateDepRisk({
113
- vulnerabilities: 0,
114
- lastPublishDays: 800, // Over 2 years
115
- weeklyDownloads: null,
116
- license: null,
117
- bundleSizeKb: null,
118
- previousRemovals: 0,
119
- });
120
- expect(risk).toBeGreaterThan(0);
121
- });
122
-
123
- it('adds risk for low popularity', () => {
124
- const risk = calculateDepRisk({
125
- vulnerabilities: 0,
126
- lastPublishDays: null,
127
- weeklyDownloads: 50, // Very low
128
- license: null,
129
- bundleSizeKb: null,
130
- previousRemovals: 0,
131
- });
132
- expect(risk).toBeGreaterThan(0);
133
- });
134
-
135
- it('adds risk for restrictive licenses', () => {
136
- const gplRisk = calculateDepRisk({
137
- vulnerabilities: 0,
138
- lastPublishDays: null,
139
- weeklyDownloads: null,
140
- license: 'GPL-3.0',
141
- bundleSizeKb: null,
142
- previousRemovals: 0,
143
- });
144
- expect(gplRisk).toBeGreaterThan(0);
145
-
146
- const agplRisk = calculateDepRisk({
147
- vulnerabilities: 0,
148
- lastPublishDays: null,
149
- weeklyDownloads: null,
150
- license: 'AGPL',
151
- bundleSizeKb: null,
152
- previousRemovals: 0,
153
- });
154
- expect(agplRisk).toBeGreaterThan(0);
155
- });
156
-
157
- it('adds risk for unknown license', () => {
158
- const risk = calculateDepRisk({
159
- vulnerabilities: 0,
160
- lastPublishDays: null,
161
- weeklyDownloads: null,
162
- license: null,
163
- bundleSizeKb: null,
164
- previousRemovals: 0,
165
- });
166
- expect(risk).toBe(5); // Unknown license penalty
167
- });
168
-
169
- it('adds risk for previous removals', () => {
170
- const risk = calculateDepRisk({
171
- vulnerabilities: 0,
172
- lastPublishDays: null,
173
- weeklyDownloads: null,
174
- license: null,
175
- bundleSizeKb: null,
176
- previousRemovals: 3,
177
- });
178
- expect(risk).toBeGreaterThan(5); // 5 (unknown license) + 15 (3 * 5)
179
- });
180
-
181
- it('caps risk at 100', () => {
182
- const risk = calculateDepRisk({
183
- vulnerabilities: 10,
184
- lastPublishDays: 1000,
185
- weeklyDownloads: 10,
186
- license: 'GPL',
187
- bundleSizeKb: null,
188
- previousRemovals: 5,
189
- });
190
- expect(risk).toBeLessThanOrEqual(100);
191
- });
192
- });
193
-
194
- describe('getInstalledPackages', () => {
195
- it('returns empty map for missing package.json', () => {
196
- const packages = getInstalledPackages(testDir);
197
- expect(packages.size).toBe(0);
198
- });
199
-
200
- it('parses dependencies from package.json', () => {
201
- const pkgPath = join(testDir, 'package.json');
202
- writeFileSync(pkgPath, JSON.stringify({
203
- dependencies: {
204
- 'express': '^4.18.0',
205
- 'lodash': '^4.17.21',
206
- },
207
- devDependencies: {
208
- 'vitest': '^1.0.0',
209
- },
210
- }));
211
-
212
- const packages = getInstalledPackages(testDir);
213
- expect(packages.size).toBe(3);
214
- expect(packages.get('express')).toBe('^4.18.0');
215
- expect(packages.get('lodash')).toBe('^4.17.21');
216
- expect(packages.get('vitest')).toBe('^1.0.0');
217
- });
218
-
219
- it('handles invalid JSON gracefully', () => {
220
- const pkgPath = join(testDir, 'package.json');
221
- writeFileSync(pkgPath, '{ invalid json');
222
-
223
- const packages = getInstalledPackages(testDir);
224
- expect(packages.size).toBe(0);
225
- });
226
-
227
- it('handles missing dependencies field', () => {
228
- const pkgPath = join(testDir, 'package.json');
229
- writeFileSync(pkgPath, JSON.stringify({ name: 'test' }));
230
-
231
- const packages = getInstalledPackages(testDir);
232
- expect(packages.size).toBe(0);
233
- });
234
- });
235
-
236
- describe('storeAssessment', () => {
237
- it('stores dependency assessment', () => {
238
- storeAssessment(db, 'express', '4.18.0', 25, {
239
- vulnerabilities: 1,
240
- lastPublishDays: 120,
241
- weeklyDownloads: 50000,
242
- license: 'MIT',
243
- bundleSizeKb: 200,
244
- previousRemovals: 0,
245
- });
246
-
247
- const row = db.prepare('SELECT * FROM dependency_assessments WHERE package_name = ?').get('express') as Record<string, unknown>;
248
- expect(row.package_name).toBe('express');
249
- expect(row.version).toBe('4.18.0');
250
- expect(row.risk_score).toBe(25);
251
- expect(row.vulnerabilities).toBe(1);
252
- expect(row.weekly_downloads).toBe(50000);
253
- });
254
- });
255
-
256
- describe('getPreviousRemovals', () => {
257
- it('returns 0 for package never assessed', () => {
258
- const count = getPreviousRemovals(db, 'unknown-package');
259
- expect(count).toBe(0);
260
- });
261
-
262
- it('returns max previous_removals value', () => {
263
- storeAssessment(db, 'moment', null, 50, {
264
- vulnerabilities: 0,
265
- lastPublishDays: null,
266
- weeklyDownloads: null,
267
- license: null,
268
- bundleSizeKb: null,
269
- previousRemovals: 2,
270
- });
271
-
272
- storeAssessment(db, 'moment', null, 60, {
273
- vulnerabilities: 0,
274
- lastPublishDays: null,
275
- weeklyDownloads: null,
276
- license: null,
277
- bundleSizeKb: null,
278
- previousRemovals: 3,
279
- });
280
-
281
- const count = getPreviousRemovals(db, 'moment');
282
- expect(count).toBe(3);
283
- });
284
- });
285
-
286
- describe('handleDependencyToolCall', () => {
287
- it('handles dep_score for package', () => {
288
- const result = handleDependencyToolCall('massu_dep_score', { package_name: 'express' }, db);
289
- const text = result.content[0].text;
290
- expect(text).toContain('Dependency Check: express');
291
- expect(text).toContain('Risk Score');
292
- });
293
-
294
- it('handles dep_score with installed package', () => {
295
- const pkgPath = join(testDir, 'package.json');
296
- writeFileSync(pkgPath, JSON.stringify({
297
- dependencies: { 'express': '^4.18.0' },
298
- }));
299
-
300
- // Mock getConfig to return testDir
301
- const result = handleDependencyToolCall('massu_dep_score', { package_name: 'express' }, db);
302
- const text = result.content[0].text;
303
- expect(text).toContain('express');
304
- });
305
-
306
- it('handles dep_alternatives for known package', () => {
307
- const result = handleDependencyToolCall('massu_dep_alternatives', { package_name: 'moment' }, db);
308
- const text = result.content[0].text;
309
- expect(text).toContain('Alternatives to: moment');
310
- expect(text).toContain('date-fns');
311
- });
312
-
313
- it('handles dep_alternatives for unknown package', () => {
314
- const result = handleDependencyToolCall('massu_dep_alternatives', { package_name: 'unknown-package' }, db);
315
- const text = result.content[0].text;
316
- expect(text).toContain('No known alternative mappings');
317
- });
318
-
319
- it('handles unknown tool name', () => {
320
- const result = handleDependencyToolCall('massu_dep_unknown', {}, db);
321
- const text = result.content[0].text;
322
- expect(text).toContain('Unknown dependency tool');
323
- });
324
- });
325
- });
@@ -1,178 +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, beforeAll } from 'vitest';
5
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
6
- import { resolve, join } from 'path';
7
- import { getResolvedPaths } from '../config.ts';
8
-
9
- /**
10
- * Integration tests for docs tools against the real help site.
11
- * These tests verify the tools work with actual MDX files.
12
- */
13
-
14
- const HELP_SITE_PATH = getResolvedPaths().helpSitePath;
15
- const DOCS_MAP_PATH = getResolvedPaths().docsMapPath;
16
-
17
- describe('docs integration: real help site', () => {
18
- let docsMap: any;
19
-
20
- beforeAll(() => {
21
- // Skip tests if help site is not available
22
- if (!existsSync(HELP_SITE_PATH)) {
23
- console.warn('Help site not found at', HELP_SITE_PATH);
24
- return;
25
- }
26
- docsMap = JSON.parse(readFileSync(DOCS_MAP_PATH, 'utf-8'));
27
- });
28
-
29
- it('help site exists and has pages directory', () => {
30
- if (!existsSync(HELP_SITE_PATH)) return; // Skip when help site not available
31
- expect(existsSync(resolve(HELP_SITE_PATH, 'pages'))).toBe(true);
32
- });
33
-
34
- it('all mapped help pages exist', () => {
35
- if (!docsMap) return;
36
-
37
- const missing: string[] = [];
38
- for (const mapping of docsMap.mappings) {
39
- const fullPath = resolve(HELP_SITE_PATH, mapping.helpPage);
40
- if (!existsSync(fullPath)) {
41
- missing.push(`${mapping.id}: ${mapping.helpPage}`);
42
- }
43
- }
44
- expect(missing).toEqual([]);
45
- });
46
-
47
- it('coverage report includes all 8+ top-level categories', () => {
48
- if (!docsMap) return;
49
-
50
- const topLevelPages = docsMap.mappings.filter((m: any) =>
51
- m.helpPage.startsWith('pages/') && !m.helpPage.includes('/index.mdx') ||
52
- m.helpPage.endsWith('/index.mdx')
53
- );
54
- expect(topLevelPages.length).toBeGreaterThanOrEqual(8);
55
- });
56
-
57
- it('frontmatter parsing works on real MDX files', () => {
58
- if (!docsMap) return;
59
-
60
- // Test a few real MDX files
61
- const testPages = ['pages/dashboard.mdx', 'pages/users.mdx', 'pages/billing.mdx'];
62
- for (const page of testPages) {
63
- const fullPath = resolve(HELP_SITE_PATH, page);
64
- if (!existsSync(fullPath)) continue;
65
-
66
- const content = readFileSync(fullPath, 'utf-8');
67
-
68
- // Check frontmatter exists
69
- expect(content.startsWith('---')).toBe(true);
70
-
71
- // Extract frontmatter
72
- const endIndex = content.indexOf('---', 3);
73
- expect(endIndex).toBeGreaterThan(3);
74
-
75
- const frontmatter = content.substring(3, endIndex).trim();
76
- expect(frontmatter).toContain('lastVerified');
77
- expect(frontmatter).toContain('status');
78
- }
79
- });
80
-
81
- it('known router change maps to correct help page', () => {
82
- if (!docsMap) return;
83
-
84
- // Simulate: dashboard.ts changed, should map to dashboard help page
85
- const changedFile = 'src/server/api/routers/dashboard.ts';
86
- const fileName = 'dashboard.ts';
87
-
88
- let foundMapping = false;
89
- for (const mapping of docsMap.mappings) {
90
- if (mapping.routers.includes(fileName)) {
91
- if (mapping.id === 'dashboard') {
92
- foundMapping = true;
93
- // Verify the help page exists
94
- const helpPath = resolve(HELP_SITE_PATH, mapping.helpPage);
95
- expect(existsSync(helpPath)).toBe(true);
96
- }
97
- }
98
- }
99
- expect(foundMapping).toBe(true);
100
- });
101
-
102
- it('non-user-facing changes return no affected mappings', () => {
103
- if (!docsMap) return;
104
-
105
- const nonUserFiles = [
106
- 'scripts/pattern-scanner.sh',
107
- 'package.json',
108
- '.gitignore',
109
- 'tsconfig.json',
110
- 'jest.config.js',
111
- ];
112
-
113
- for (const file of nonUserFiles) {
114
- const fileName = file.split('/').pop() || '';
115
- let matched = false;
116
-
117
- for (const mapping of docsMap.mappings) {
118
- if (mapping.routers.includes(fileName)) {
119
- matched = true;
120
- break;
121
- }
122
- }
123
-
124
- expect(matched).toBe(false);
125
- }
126
- });
127
-
128
- it('changelog page exists', () => {
129
- if (!existsSync(HELP_SITE_PATH)) return;
130
- const changelogPath = resolve(HELP_SITE_PATH, 'pages/changelog.mdx');
131
- expect(existsSync(changelogPath)).toBe(true);
132
-
133
- const content = readFileSync(changelogPath, 'utf-8');
134
- expect(content).toContain('lastVerified');
135
- expect(content).toContain("What's New");
136
- });
137
-
138
- it('_meta.json includes changelog', () => {
139
- if (!existsSync(HELP_SITE_PATH)) return;
140
- const metaPath = resolve(HELP_SITE_PATH, 'pages/_meta.json');
141
- expect(existsSync(metaPath)).toBe(true);
142
-
143
- const meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
144
- expect(meta.changelog).toBeDefined();
145
- });
146
-
147
- it('all MDX files have lastVerified frontmatter', () => {
148
- if (!existsSync(HELP_SITE_PATH)) return;
149
-
150
- function findMdxFiles(dir: string): string[] {
151
- const files: string[] = [];
152
- for (const entry of readdirSync(dir)) {
153
- const fullPath = join(dir, entry);
154
- const stat = statSync(fullPath);
155
- if (stat.isDirectory() && entry !== 'node_modules' && entry !== '.next') {
156
- files.push(...findMdxFiles(fullPath));
157
- } else if (entry.endsWith('.mdx')) {
158
- files.push(fullPath);
159
- }
160
- }
161
- return files;
162
- }
163
-
164
- const mdxFiles = findMdxFiles(resolve(HELP_SITE_PATH, 'pages'));
165
- const missingFrontmatter: string[] = [];
166
-
167
- for (const file of mdxFiles) {
168
- const content = readFileSync(file, 'utf-8');
169
- if (!content.includes('lastVerified')) {
170
- missingFrontmatter.push(file.replace(HELP_SITE_PATH + '/', ''));
171
- }
172
- }
173
-
174
- expect(missingFrontmatter).toEqual([]);
175
- // Expect at least 5 MDX files for a meaningful docs site
176
- expect(mdxFiles.length).toBeGreaterThanOrEqual(5);
177
- });
178
- });
@@ -1,199 +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, beforeAll } from 'vitest';
5
- import { readFileSync, existsSync } from 'fs';
6
- import { resolve } from 'path';
7
-
8
- // Test the docs-tools module functions
9
- // We import and test the exported functions directly
10
-
11
- describe('docs-map.json', () => {
12
- let docsMap: any;
13
-
14
- beforeAll(() => {
15
- const mapPath = resolve(__dirname, '../docs-map.json');
16
- expect(existsSync(mapPath)).toBe(true);
17
- docsMap = JSON.parse(readFileSync(mapPath, 'utf-8'));
18
- });
19
-
20
- it('has correct version', () => {
21
- expect(docsMap.version).toBe(1);
22
- });
23
-
24
- it('has at least 8 mappings', () => {
25
- expect(docsMap.mappings.length).toBeGreaterThanOrEqual(8);
26
- });
27
-
28
- it('each mapping has required fields', () => {
29
- for (const mapping of docsMap.mappings) {
30
- expect(mapping).toHaveProperty('id');
31
- expect(mapping).toHaveProperty('helpPage');
32
- expect(mapping).toHaveProperty('appRoutes');
33
- expect(mapping).toHaveProperty('routers');
34
- expect(mapping).toHaveProperty('components');
35
- expect(mapping).toHaveProperty('keywords');
36
- expect(typeof mapping.id).toBe('string');
37
- expect(typeof mapping.helpPage).toBe('string');
38
- expect(Array.isArray(mapping.appRoutes)).toBe(true);
39
- expect(Array.isArray(mapping.routers)).toBe(true);
40
- expect(Array.isArray(mapping.components)).toBe(true);
41
- expect(Array.isArray(mapping.keywords)).toBe(true);
42
- }
43
- });
44
-
45
- it('has unique mapping IDs', () => {
46
- const ids = docsMap.mappings.map((m: any) => m.id);
47
- expect(new Set(ids).size).toBe(ids.length);
48
- });
49
-
50
- it('has user guide inheritance mapping', () => {
51
- expect(docsMap.userGuideInheritance).toBeDefined();
52
- expect(docsMap.userGuideInheritance.examples).toBeDefined();
53
- expect(Object.keys(docsMap.userGuideInheritance.examples).length).toBeGreaterThanOrEqual(5);
54
- });
55
-
56
- it('all user guide parent IDs reference valid mapping IDs', () => {
57
- const validIds = new Set(docsMap.mappings.map((m: any) => m.id));
58
- for (const [guide, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
59
- expect(validIds.has(parentId as string)).toBe(true);
60
- }
61
- });
62
-
63
- it('maps key features correctly', () => {
64
- const findMapping = (id: string) => docsMap.mappings.find((m: any) => m.id === id);
65
-
66
- // Dashboard mapping should include dashboard routers
67
- const dashboard = findMapping('dashboard');
68
- expect(dashboard).toBeDefined();
69
- expect(dashboard.routers).toContain('dashboard.ts');
70
- expect(dashboard.appRoutes.some((r: string) => r.includes('dashboard'))).toBe(true);
71
-
72
- // Users mapping
73
- const users = findMapping('users');
74
- expect(users).toBeDefined();
75
- expect(users.routers).toContain('users.ts');
76
-
77
- // Billing mapping
78
- const billing = findMapping('billing');
79
- expect(billing).toBeDefined();
80
- expect(billing.routers).toContain('billing.ts');
81
- });
82
- });
83
-
84
- describe('matchesPattern (glob matching)', () => {
85
- // Test the glob matching logic used in docs-tools
86
- function matchesPattern(filePath: string, pattern: string): boolean {
87
- const regexStr = pattern
88
- .replace(/\./g, '\\.')
89
- .replace(/\*\*/g, '{{GLOBSTAR}}')
90
- .replace(/\*/g, '[^/]*')
91
- .replace(/\{\{GLOBSTAR\}\}/g, '.*');
92
- return new RegExp(`^${regexStr}$`).test(filePath);
93
- }
94
-
95
- it('matches ** glob patterns', () => {
96
- expect(matchesPattern('src/app/orders/page.tsx', 'src/app/orders/**')).toBe(true);
97
- expect(matchesPattern('src/app/orders/[id]/page.tsx', 'src/app/orders/**')).toBe(true);
98
- expect(matchesPattern('src/app/crm/page.tsx', 'src/app/orders/**')).toBe(false);
99
- });
100
-
101
- it('matches component patterns', () => {
102
- expect(matchesPattern('src/components/orders/OrderCard.tsx', 'src/components/orders/**')).toBe(true);
103
- expect(matchesPattern('src/components/crm/LeadList.tsx', 'src/components/orders/**')).toBe(false);
104
- });
105
-
106
- it('matches nested paths', () => {
107
- expect(matchesPattern('src/app/admin/dashboard/page.tsx', 'src/app/admin/dashboard/**')).toBe(true);
108
- expect(matchesPattern('src/app/admin/page.tsx', 'src/app/admin/**')).toBe(true);
109
- });
110
- });
111
-
112
- describe('findAffectedMappings logic', () => {
113
- let docsMap: any;
114
-
115
- beforeAll(() => {
116
- docsMap = JSON.parse(readFileSync(resolve(__dirname, '../docs-map.json'), 'utf-8'));
117
- });
118
-
119
- function findAffectedMappings(changedFiles: string[]): Map<string, string[]> {
120
- const affected = new Map<string, string[]>();
121
- const matchesPattern = (filePath: string, pattern: string): boolean => {
122
- const regexStr = pattern
123
- .replace(/\./g, '\\.')
124
- .replace(/\*\*/g, '{{GLOBSTAR}}')
125
- .replace(/\*/g, '[^/]*')
126
- .replace(/\{\{GLOBSTAR\}\}/g, '.*');
127
- return new RegExp(`^${regexStr}$`).test(filePath);
128
- };
129
-
130
- for (const file of changedFiles) {
131
- const fileName = file.split('/').pop() || '';
132
- for (const mapping of docsMap.mappings) {
133
- let matched = false;
134
- for (const routePattern of mapping.appRoutes) {
135
- if (matchesPattern(file, routePattern)) { matched = true; break; }
136
- }
137
- if (!matched) {
138
- for (const router of mapping.routers) {
139
- if (fileName === router || file.endsWith(`/routers/${router}`)) { matched = true; break; }
140
- }
141
- }
142
- if (!matched) {
143
- for (const compPattern of mapping.components) {
144
- if (matchesPattern(file, compPattern)) { matched = true; break; }
145
- }
146
- }
147
- if (matched) {
148
- const existing = affected.get(mapping.id) || [];
149
- existing.push(file);
150
- affected.set(mapping.id, existing);
151
- }
152
- }
153
- }
154
- return affected;
155
- }
156
-
157
- it('maps router changes to correct help pages', () => {
158
- const affected = findAffectedMappings(['src/server/api/routers/dashboard.ts']);
159
- expect(affected.has('dashboard')).toBe(true);
160
- expect(affected.has('users')).toBe(false);
161
- });
162
-
163
- it('maps page changes to correct help pages', () => {
164
- const affected = findAffectedMappings(['src/app/users/page.tsx']);
165
- expect(affected.has('users')).toBe(true);
166
- expect(affected.has('dashboard')).toBe(false);
167
- });
168
-
169
- it('maps component changes to correct help pages', () => {
170
- const affected = findAffectedMappings(['src/components/billing/PlanCard.tsx']);
171
- expect(affected.has('billing')).toBe(true);
172
- });
173
-
174
- it('returns empty for non-user-facing files', () => {
175
- const affected = findAffectedMappings(['scripts/some-script.sh', 'package.json', '.gitignore']);
176
- expect(affected.size).toBe(0);
177
- });
178
-
179
- it('handles multiple affected mappings', () => {
180
- const affected = findAffectedMappings([
181
- 'src/server/api/routers/dashboard.ts',
182
- 'src/server/api/routers/users.ts',
183
- 'src/app/billing/page.tsx',
184
- ]);
185
- expect(affected.has('dashboard')).toBe(true);
186
- expect(affected.has('users')).toBe(true);
187
- expect(affected.has('billing')).toBe(true);
188
- });
189
-
190
- it('maps notification routers correctly', () => {
191
- const affected = findAffectedMappings(['src/server/api/routers/notifications.ts']);
192
- expect(affected.has('notifications')).toBe(true);
193
- });
194
-
195
- it('maps integration routers correctly', () => {
196
- const affected = findAffectedMappings(['src/server/api/routers/integrations.ts']);
197
- expect(affected.has('integrations')).toBe(true);
198
- });
199
- });