@massu/core 1.10.8 → 1.12.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 (178) hide show
  1. package/CHANGELOG.md +1303 -0
  2. package/dist/adapter.js +6 -4
  3. package/dist/cli.js +517 -397
  4. package/dist/detect/adapters/.bundle-shasums.json +4 -7
  5. package/dist/detect/adapters/rails.js +6 -4
  6. package/dist/detect/adapters/spring.js +6 -4
  7. package/dist/detect/adapters/tree-sitter-loader.js +6 -4
  8. package/dist/hooks/auto-learning-pipeline.js +24 -1
  9. package/dist/hooks/classify-failure.js +32 -3
  10. package/dist/hooks/cost-tracker.js +24 -0
  11. package/dist/hooks/fix-detector.js +97 -1
  12. package/dist/hooks/incident-pipeline.js +30 -1
  13. package/dist/hooks/intent-suggester.js +6 -3
  14. package/dist/hooks/post-edit-context.js +24 -1
  15. package/dist/hooks/post-tool-use.js +66 -18
  16. package/dist/hooks/pre-compact.js +27 -2
  17. package/dist/hooks/pre-delete-check.js +48 -22
  18. package/dist/hooks/pre-tool-use-gate.js +962 -0
  19. package/dist/hooks/quality-event.js +24 -0
  20. package/dist/hooks/rule-enforcement-pipeline.js +24 -1
  21. package/dist/hooks/security-gate.js +29 -16
  22. package/dist/hooks/session-end.js +24 -0
  23. package/dist/hooks/session-start.js +8710 -8460
  24. package/dist/hooks/user-prompt.js +24 -0
  25. package/package.json +15 -18
  26. package/commands/README.md +0 -134
  27. package/src/adapter.ts +0 -121
  28. package/src/adr-generator.ts +0 -292
  29. package/src/analytics.ts +0 -373
  30. package/src/audit-trail.ts +0 -450
  31. package/src/backfill-sessions.ts +0 -182
  32. package/src/changelog-generator.ts +0 -178
  33. package/src/cli.ts +0 -246
  34. package/src/cloud-sync.ts +0 -234
  35. package/src/commands/adapters.ts +0 -824
  36. package/src/commands/changelog.ts +0 -165
  37. package/src/commands/config-check-drift.ts +0 -133
  38. package/src/commands/config-refresh.ts +0 -396
  39. package/src/commands/config-upgrade.ts +0 -127
  40. package/src/commands/doctor.ts +0 -485
  41. package/src/commands/hook-runner.ts +0 -145
  42. package/src/commands/init.ts +0 -1823
  43. package/src/commands/install-commands.ts +0 -692
  44. package/src/commands/install-hooks.ts +0 -27
  45. package/src/commands/permissions.ts +0 -150
  46. package/src/commands/refresh-log.ts +0 -37
  47. package/src/commands/show-template.ts +0 -65
  48. package/src/commands/template-engine.ts +0 -301
  49. package/src/commands/watch.ts +0 -430
  50. package/src/config.ts +0 -765
  51. package/src/cost-tracker.ts +0 -355
  52. package/src/db.ts +0 -370
  53. package/src/dependency-scorer.ts +0 -337
  54. package/src/detect/adapters/aspnet.ts +0 -4
  55. package/src/detect/adapters/discover.ts +0 -469
  56. package/src/detect/adapters/file-sampler.ts +0 -225
  57. package/src/detect/adapters/go-chi.ts +0 -4
  58. package/src/detect/adapters/index.ts +0 -51
  59. package/src/detect/adapters/nextjs-trpc.ts +0 -166
  60. package/src/detect/adapters/parse-guard.ts +0 -133
  61. package/src/detect/adapters/phoenix.ts +0 -4
  62. package/src/detect/adapters/python-django.ts +0 -208
  63. package/src/detect/adapters/python-fastapi.ts +0 -223
  64. package/src/detect/adapters/python-flask.ts +0 -235
  65. package/src/detect/adapters/query-helpers.ts +0 -170
  66. package/src/detect/adapters/rails.ts +0 -4
  67. package/src/detect/adapters/runner.ts +0 -284
  68. package/src/detect/adapters/spring.ts +0 -4
  69. package/src/detect/adapters/swift-swiftui.ts +0 -171
  70. package/src/detect/adapters/tree-sitter-loader.ts +0 -517
  71. package/src/detect/adapters/types.ts +0 -191
  72. package/src/detect/codebase-introspector.ts +0 -210
  73. package/src/detect/domain-inferrer.ts +0 -197
  74. package/src/detect/drift.ts +0 -199
  75. package/src/detect/framework-detector.ts +0 -307
  76. package/src/detect/index.ts +0 -209
  77. package/src/detect/manifest-registry.ts +0 -261
  78. package/src/detect/migrate.ts +0 -362
  79. package/src/detect/monorepo-detector.ts +0 -348
  80. package/src/detect/package-detector.ts +0 -828
  81. package/src/detect/passthrough.ts +0 -108
  82. package/src/detect/regex-fallback.ts +0 -449
  83. package/src/detect/source-dir-detector.ts +0 -297
  84. package/src/detect/vr-command-map.ts +0 -167
  85. package/src/docs-map.json +0 -100
  86. package/src/docs-tools.ts +0 -519
  87. package/src/domains.ts +0 -182
  88. package/src/hooks/auto-learning-pipeline.ts +0 -228
  89. package/src/hooks/classify-failure.ts +0 -259
  90. package/src/hooks/cost-tracker.ts +0 -66
  91. package/src/hooks/fix-detector.ts +0 -186
  92. package/src/hooks/incident-pipeline.ts +0 -190
  93. package/src/hooks/intent-suggester.ts +0 -131
  94. package/src/hooks/post-edit-context.ts +0 -91
  95. package/src/hooks/post-tool-use.ts +0 -413
  96. package/src/hooks/pre-compact.ts +0 -168
  97. package/src/hooks/pre-delete-check.ts +0 -243
  98. package/src/hooks/quality-event.ts +0 -127
  99. package/src/hooks/rule-enforcement-pipeline.ts +0 -159
  100. package/src/hooks/security-gate.ts +0 -153
  101. package/src/hooks/session-end.ts +0 -467
  102. package/src/hooks/session-start.ts +0 -436
  103. package/src/hooks/user-prompt.ts +0 -157
  104. package/src/import-resolver.ts +0 -226
  105. package/src/knowledge-db.ts +0 -176
  106. package/src/knowledge-indexer.ts +0 -708
  107. package/src/knowledge-tools.ts +0 -1414
  108. package/src/lib/fileLock.ts +0 -203
  109. package/src/lib/framework-supports.ts +0 -85
  110. package/src/lib/gitToplevel.ts +0 -22
  111. package/src/lib/hook-registry.ts +0 -43
  112. package/src/lib/installLock.ts +0 -66
  113. package/src/lib/memory-path.ts +0 -49
  114. package/src/lib/pidLiveness.ts +0 -67
  115. package/src/lib/settings-local.ts +0 -110
  116. package/src/lib/sql-table-names.ts +0 -71
  117. package/src/license.ts +0 -547
  118. package/src/lsp/auto-detect.ts +0 -98
  119. package/src/lsp/client.ts +0 -776
  120. package/src/lsp/enrich.ts +0 -127
  121. package/src/lsp/types.ts +0 -221
  122. package/src/memory-db.ts +0 -1614
  123. package/src/memory-file-ingest.ts +0 -128
  124. package/src/memory-tools.ts +0 -424
  125. package/src/middleware-tree.ts +0 -71
  126. package/src/observability-tools.ts +0 -343
  127. package/src/observation-extractor.ts +0 -418
  128. package/src/page-deps.ts +0 -285
  129. package/src/permissions.ts +0 -422
  130. package/src/prompt-analyzer.ts +0 -332
  131. package/src/python/coupling-detector.ts +0 -125
  132. package/src/python/domain-enforcer.ts +0 -84
  133. package/src/python/impact-analyzer.ts +0 -96
  134. package/src/python/import-parser.ts +0 -244
  135. package/src/python/import-resolver.ts +0 -136
  136. package/src/python/migration-indexer.ts +0 -116
  137. package/src/python/migration-parser.ts +0 -332
  138. package/src/python/model-indexer.ts +0 -71
  139. package/src/python/model-parser.ts +0 -279
  140. package/src/python/route-indexer.ts +0 -59
  141. package/src/python/route-parser.ts +0 -317
  142. package/src/python-tools.ts +0 -630
  143. package/src/regression-detector.ts +0 -319
  144. package/src/rules.ts +0 -57
  145. package/src/schema-mapper.ts +0 -232
  146. package/src/security/adapter-origin.ts +0 -130
  147. package/src/security/adapter-verifier.ts +0 -319
  148. package/src/security/atomic-write.ts +0 -164
  149. package/src/security/fetcher.ts +0 -200
  150. package/src/security/install-tracking.ts +0 -319
  151. package/src/security/license-pubkey.generated.ts +0 -30
  152. package/src/security/license-response-verifier.ts +0 -149
  153. package/src/security/local-fingerprint.ts +0 -225
  154. package/src/security/manifest-cache.ts +0 -333
  155. package/src/security/manifest-schema.ts +0 -129
  156. package/src/security/registry-pubkey.generated.ts +0 -35
  157. package/src/security/telemetry.ts +0 -320
  158. package/src/security-scorer.ts +0 -405
  159. package/src/security-utils.ts +0 -133
  160. package/src/sentinel-db.ts +0 -580
  161. package/src/sentinel-scanner.ts +0 -406
  162. package/src/sentinel-tools.ts +0 -513
  163. package/src/sentinel-types.ts +0 -140
  164. package/src/server-dispatch.ts +0 -225
  165. package/src/server.ts +0 -107
  166. package/src/session-archiver.ts +0 -111
  167. package/src/session-state-generator.ts +0 -174
  168. package/src/team-knowledge.ts +0 -407
  169. package/src/tool-db-needs.ts +0 -232
  170. package/src/tools.ts +0 -1206
  171. package/src/transcript-parser.ts +0 -458
  172. package/src/trpc-index.ts +0 -215
  173. package/src/validate-features-runner.ts +0 -107
  174. package/src/validation-engine.ts +0 -358
  175. package/src/watch/daemon.ts +0 -385
  176. package/src/watch/lockfile-detector.ts +0 -65
  177. package/src/watch/paths.ts +0 -279
  178. package/src/watch/state.ts +0 -178
package/src/docs-tools.ts DELETED
@@ -1,519 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import { readFileSync, existsSync } from 'fs';
5
- import { resolve, basename } from 'path';
6
- import { getConfig, getResolvedPaths, getProjectRoot } from './config.ts';
7
- import { ensureWithinRoot } from './security-utils.ts';
8
- import type { ToolDefinition, ToolResult } from './tools.ts';
9
-
10
- /** Prefix a base tool name with the configured tool prefix. */
11
- function p(baseName: string): string {
12
- return `${getConfig().toolPrefix}_${baseName}`;
13
- }
14
-
15
- // ============================================================
16
- // Help Site Auto-Sync: MCP Docs Tools
17
- // docs_audit + docs_coverage
18
- // ============================================================
19
-
20
- interface DocsMapping {
21
- id: string;
22
- helpPage: string;
23
- appRoutes: string[];
24
- routers: string[];
25
- components: string[];
26
- keywords: string[];
27
- }
28
-
29
- interface DocsMap {
30
- version: number;
31
- mappings: DocsMapping[];
32
- userGuideInheritance: {
33
- examples: Record<string, string>;
34
- };
35
- }
36
-
37
- interface AuditResult {
38
- helpPage: string;
39
- mappingId: string;
40
- status: 'STALE' | 'NEW' | 'OK';
41
- reason: string;
42
- sections: string[];
43
- changedFiles: string[];
44
- suggestedAction: string;
45
- }
46
-
47
- interface AuditReport {
48
- affectedPages: AuditResult[];
49
- summary: string;
50
- }
51
-
52
- interface CoverageEntry {
53
- id: string;
54
- helpPage: string;
55
- exists: boolean;
56
- hasContent: boolean;
57
- lineCount: number;
58
- lastVerified: string | null;
59
- status: string | null;
60
- }
61
-
62
- interface CoverageReport {
63
- totalMappings: number;
64
- pagesExisting: number;
65
- pagesWithContent: number;
66
- coveragePercent: number;
67
- entries: CoverageEntry[];
68
- gaps: string[];
69
- }
70
-
71
- // ============================================================
72
- // Tool Definitions
73
- // ============================================================
74
-
75
- export function getDocsToolDefinitions(): ToolDefinition[] {
76
- return [
77
- {
78
- name: p('docs_audit'),
79
- description: 'Audit which help site pages need updating based on changed files. Maps code changes to affected documentation pages using docs-map.json.',
80
- inputSchema: {
81
- type: 'object',
82
- properties: {
83
- changed_files: {
84
- type: 'array',
85
- items: { type: 'string' },
86
- description: 'List of changed files from git diff (relative to project root)',
87
- },
88
- commit_message: {
89
- type: 'string',
90
- description: 'Optional commit message for context',
91
- },
92
- },
93
- required: ['changed_files'],
94
- },
95
- },
96
- {
97
- name: p('docs_coverage'),
98
- description: 'Report docs coverage: which help pages exist, have content, and are up-to-date. Identifies documentation gaps.',
99
- inputSchema: {
100
- type: 'object',
101
- properties: {
102
- domain: {
103
- type: 'string',
104
- description: 'Optional: filter by mapping ID (e.g., "dashboard", "users")',
105
- },
106
- },
107
- required: [],
108
- },
109
- },
110
- ];
111
- }
112
-
113
- // ============================================================
114
- // Tool Handler Router
115
- // ============================================================
116
-
117
- export function handleDocsToolCall(
118
- name: string,
119
- args: Record<string, unknown>
120
- ): ToolResult {
121
- const prefix = getConfig().toolPrefix + '_';
122
- const baseName = name.startsWith(prefix) ? name.slice(prefix.length) : name;
123
-
124
- switch (baseName) {
125
- case 'docs_audit':
126
- return handleDocsAudit(args);
127
- case 'docs_coverage':
128
- return handleDocsCoverage(args);
129
- default:
130
- return text(`Unknown docs tool: ${name}`);
131
- }
132
- }
133
-
134
- // ============================================================
135
- // Core Logic
136
- // ============================================================
137
-
138
- function loadDocsMap(): DocsMap {
139
- const mapPath = getResolvedPaths().docsMapPath;
140
- if (!existsSync(mapPath)) {
141
- throw new Error(`docs-map.json not found at ${mapPath}`);
142
- }
143
- return JSON.parse(readFileSync(mapPath, 'utf-8'));
144
- }
145
-
146
- /**
147
- * Check if a file path matches a glob-like pattern.
148
- * Supports ** (any depth) and * (single segment).
149
- */
150
- function matchesPattern(filePath: string, pattern: string): boolean {
151
- // Convert glob pattern to regex
152
- const regexStr = pattern
153
- .replace(/\./g, '\\.')
154
- .replace(/\*\*/g, '{{GLOBSTAR}}')
155
- .replace(/\*/g, '[^/]*')
156
- .replace(/\{\{GLOBSTAR\}\}/g, '.*');
157
- return new RegExp(`^${regexStr}$`).test(filePath);
158
- }
159
-
160
- /**
161
- * Find which mappings are affected by a set of changed files.
162
- */
163
- function findAffectedMappings(docsMap: DocsMap, changedFiles: string[]): Map<string, string[]> {
164
- // Map of mapping ID -> list of changed files that triggered it
165
- const affected = new Map<string, string[]>();
166
-
167
- for (const file of changedFiles) {
168
- const fileName = basename(file);
169
-
170
- for (const mapping of docsMap.mappings) {
171
- let matched = false;
172
-
173
- // Check app routes (glob patterns)
174
- for (const routePattern of mapping.appRoutes) {
175
- if (matchesPattern(file, routePattern)) {
176
- matched = true;
177
- break;
178
- }
179
- }
180
-
181
- // Check routers (filename match)
182
- if (!matched) {
183
- for (const router of mapping.routers) {
184
- if (fileName === router || file.endsWith(`/routers/${router}`)) {
185
- matched = true;
186
- break;
187
- }
188
- }
189
- }
190
-
191
- // Check components (glob patterns)
192
- if (!matched) {
193
- for (const compPattern of mapping.components) {
194
- if (matchesPattern(file, compPattern)) {
195
- matched = true;
196
- break;
197
- }
198
- }
199
- }
200
-
201
- if (matched) {
202
- const existing = affected.get(mapping.id) || [];
203
- existing.push(file);
204
- affected.set(mapping.id, existing);
205
- }
206
- }
207
-
208
- // Check user guide inheritance
209
- // If a file matches a parent feature, the user guide also needs review
210
- // (handled implicitly - the parent mapping is what gets flagged)
211
- }
212
-
213
- return affected;
214
- }
215
-
216
- /**
217
- * Extract headings (H2/H3) from MDX content.
218
- */
219
- function extractSections(content: string): string[] {
220
- const headingRegex = /^#{2,3}\s+(.+)$/gm;
221
- const sections: string[] = [];
222
- let match;
223
- while ((match = headingRegex.exec(content)) !== null) {
224
- sections.push(match[0].trim());
225
- }
226
- return sections;
227
- }
228
-
229
- /**
230
- * Extract frontmatter from MDX content.
231
- */
232
- function extractFrontmatter(content: string): Record<string, string> | null {
233
- if (!content.startsWith('---')) return null;
234
- const endIndex = content.indexOf('---', 3);
235
- if (endIndex === -1) return null;
236
-
237
- const frontmatterStr = content.substring(3, endIndex).trim();
238
- const result: Record<string, string> = {};
239
-
240
- for (const line of frontmatterStr.split('\n')) {
241
- const colonIndex = line.indexOf(':');
242
- if (colonIndex > 0) {
243
- const key = line.substring(0, colonIndex).trim();
244
- const value = line.substring(colonIndex + 1).trim().replace(/^["']|["']$/g, '');
245
- result[key] = value;
246
- }
247
- }
248
-
249
- return result;
250
- }
251
-
252
- /**
253
- * Extract procedure names from a router file.
254
- */
255
- function extractProcedureNames(routerPath: string): string[] {
256
- const root = getProjectRoot();
257
- const absPath = ensureWithinRoot(resolve(getResolvedPaths().srcDir, '..', routerPath), root);
258
- if (!existsSync(absPath)) {
259
- // Try from project root
260
- const altPath = ensureWithinRoot(resolve(getResolvedPaths().srcDir, '../server/api/routers', basename(routerPath)), root);
261
- if (!existsSync(altPath)) return [];
262
- return extractProcedureNamesFromContent(readFileSync(altPath, 'utf-8'));
263
- }
264
- return extractProcedureNamesFromContent(readFileSync(absPath, 'utf-8'));
265
- }
266
-
267
- function extractProcedureNamesFromContent(content: string): string[] {
268
- const procRegex = /\.(?:query|mutation)\s*\(/g;
269
- const nameRegex = /(\w+)\s*:\s*(?:protected|public)Procedure/g;
270
- const procedures: string[] = [];
271
-
272
- let match;
273
- while ((match = nameRegex.exec(content)) !== null) {
274
- procedures.push(match[1]);
275
- }
276
-
277
- return procedures;
278
- }
279
-
280
- /**
281
- * Check if MDX content mentions a procedure/feature name.
282
- */
283
- function contentMentions(content: string, term: string): boolean {
284
- // Check for the term in various formats
285
- const lowerContent = content.toLowerCase();
286
- const lowerTerm = term.toLowerCase();
287
-
288
- // Direct mention
289
- if (lowerContent.includes(lowerTerm)) return true;
290
-
291
- // camelCase to words: bulkUpdateStatus -> bulk update status
292
- const words = term.replace(/([A-Z])/g, ' $1').toLowerCase().trim();
293
- if (lowerContent.includes(words)) return true;
294
-
295
- // kebab-case
296
- const kebab = term.replace(/([A-Z])/g, '-$1').toLowerCase().trim().replace(/^-/, '');
297
- if (lowerContent.includes(kebab)) return true;
298
-
299
- return false;
300
- }
301
-
302
- // ============================================================
303
- // Tool Handlers
304
- // ============================================================
305
-
306
- function handleDocsAudit(args: Record<string, unknown>): ToolResult {
307
- const changedFiles = args.changed_files as string[];
308
- const commitMessage = (args.commit_message as string) || '';
309
-
310
- if (!changedFiles || changedFiles.length === 0) {
311
- return text(JSON.stringify({ affectedPages: [], summary: 'No changed files provided.' }));
312
- }
313
-
314
- const docsMap = loadDocsMap();
315
- const affectedMappings = findAffectedMappings(docsMap, changedFiles);
316
-
317
- if (affectedMappings.size === 0) {
318
- return text(JSON.stringify({
319
- affectedPages: [],
320
- summary: `0 help pages affected by ${changedFiles.length} changed files. No docs update needed.`,
321
- }));
322
- }
323
-
324
- const results: AuditResult[] = [];
325
-
326
- for (const [mappingId, triggeringFiles] of affectedMappings) {
327
- const mapping = docsMap.mappings.find(m => m.id === mappingId);
328
- if (!mapping) continue;
329
-
330
- const helpPagePath = ensureWithinRoot(resolve(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
331
-
332
- if (!existsSync(helpPagePath)) {
333
- results.push({
334
- helpPage: mapping.helpPage,
335
- mappingId,
336
- status: 'NEW',
337
- reason: `Help page does not exist: ${mapping.helpPage}`,
338
- sections: [],
339
- changedFiles: triggeringFiles,
340
- suggestedAction: `Create ${mapping.helpPage} with documentation for this feature`,
341
- });
342
- continue;
343
- }
344
-
345
- const content = readFileSync(helpPagePath, 'utf-8');
346
- const sections = extractSections(content);
347
- const frontmatter = extractFrontmatter(content);
348
-
349
- // Check for staleness indicators
350
- const staleReasons: string[] = [];
351
-
352
- // Check router changes - are new procedures documented?
353
- for (const file of triggeringFiles) {
354
- const fileName = basename(file);
355
- if (mapping.routers.includes(fileName)) {
356
- const procedures = extractProcedureNames(file);
357
- const undocumented = procedures.filter(p => !contentMentions(content, p));
358
- if (undocumented.length > 0) {
359
- staleReasons.push(
360
- `Router ${fileName}: procedures not documented: ${undocumented.slice(0, 5).join(', ')}${undocumented.length > 5 ? ` (+${undocumented.length - 5} more)` : ''}`
361
- );
362
- }
363
- }
364
- }
365
-
366
- // Check if lastVerified is old (> 30 days)
367
- if (frontmatter?.lastVerified) {
368
- const lastDate = new Date(frontmatter.lastVerified);
369
- const daysSince = Math.floor((Date.now() - lastDate.getTime()) / (1000 * 60 * 60 * 24));
370
- if (daysSince > 30) {
371
- staleReasons.push(`lastVerified is ${daysSince} days old`);
372
- }
373
- } else {
374
- staleReasons.push('No lastVerified frontmatter');
375
- }
376
-
377
- // Check commit message for new feature indicators
378
- if (commitMessage && /\b(add|new|feature|implement)\b/i.test(commitMessage)) {
379
- staleReasons.push(`Commit message suggests new functionality: "${commitMessage}"`);
380
- }
381
-
382
- const status = staleReasons.length > 0 ? 'STALE' : 'OK';
383
-
384
- results.push({
385
- helpPage: mapping.helpPage,
386
- mappingId,
387
- status,
388
- reason: staleReasons.length > 0 ? staleReasons.join('; ') : 'Content appears current',
389
- sections,
390
- changedFiles: triggeringFiles,
391
- suggestedAction: status === 'STALE'
392
- ? `Review and update ${mapping.helpPage} to reflect changes in: ${triggeringFiles.map(f => basename(f)).join(', ')}`
393
- : 'No action needed',
394
- });
395
-
396
- // Also flag inherited user guides
397
- for (const [guideName, parentId] of Object.entries(docsMap.userGuideInheritance.examples)) {
398
- if (parentId === mappingId) {
399
- const guidePath = ensureWithinRoot(resolve(getResolvedPaths().helpSitePath, `pages/user-guides/${guideName}/index.mdx`), getProjectRoot());
400
- if (existsSync(guidePath)) {
401
- const guideContent = readFileSync(guidePath, 'utf-8');
402
- const guideFrontmatter = extractFrontmatter(guideContent);
403
-
404
- if (!guideFrontmatter?.lastVerified || status === 'STALE') {
405
- results.push({
406
- helpPage: `pages/user-guides/${guideName}/index.mdx`,
407
- mappingId: `${mappingId}:${guideName}`,
408
- status: 'STALE',
409
- reason: `Inherited from parent mapping "${mappingId}" which has changes`,
410
- sections: extractSections(guideContent),
411
- changedFiles: triggeringFiles,
412
- suggestedAction: `Review user guide "${guideName}" for consistency with updated ${mapping.helpPage}`,
413
- });
414
- }
415
- }
416
- }
417
- }
418
- }
419
-
420
- const staleCount = results.filter(r => r.status === 'STALE').length;
421
- const newCount = results.filter(r => r.status === 'NEW').length;
422
- const okCount = results.filter(r => r.status === 'OK').length;
423
-
424
- const report: AuditReport = {
425
- affectedPages: results,
426
- summary: `${results.length} pages checked: ${staleCount} STALE, ${newCount} NEW, ${okCount} OK. ${staleCount + newCount > 0 ? `${staleCount + newCount} pages need updates.` : 'All docs are current.'}`,
427
- };
428
-
429
- return text(JSON.stringify(report, null, 2));
430
- }
431
-
432
- function handleDocsCoverage(args: Record<string, unknown>): ToolResult {
433
- const filterDomain = args.domain as string | undefined;
434
- const docsMap = loadDocsMap();
435
-
436
- const entries: CoverageEntry[] = [];
437
- const gaps: string[] = [];
438
-
439
- const mappings = filterDomain
440
- ? docsMap.mappings.filter(m => m.id === filterDomain)
441
- : docsMap.mappings;
442
-
443
- for (const mapping of mappings) {
444
- const helpPagePath = ensureWithinRoot(resolve(getResolvedPaths().helpSitePath, mapping.helpPage), getProjectRoot());
445
- const exists = existsSync(helpPagePath);
446
- let hasContent = false;
447
- let lineCount = 0;
448
- let lastVerified: string | null = null;
449
- let status: string | null = null;
450
-
451
- if (exists) {
452
- const content = readFileSync(helpPagePath, 'utf-8');
453
- lineCount = content.split('\n').length;
454
- hasContent = lineCount > 10; // More than just frontmatter
455
-
456
- const frontmatter = extractFrontmatter(content);
457
- if (frontmatter) {
458
- lastVerified = frontmatter.lastVerified || null;
459
- status = frontmatter.status || null;
460
- }
461
- } else {
462
- gaps.push(`${mapping.id}: Help page missing (${mapping.helpPage})`);
463
- }
464
-
465
- entries.push({
466
- id: mapping.id,
467
- helpPage: mapping.helpPage,
468
- exists,
469
- hasContent,
470
- lineCount,
471
- lastVerified,
472
- status,
473
- });
474
- }
475
-
476
- const report: CoverageReport = {
477
- totalMappings: mappings.length,
478
- pagesExisting: entries.filter(e => e.exists).length,
479
- pagesWithContent: entries.filter(e => e.hasContent).length,
480
- coveragePercent: Math.round((entries.filter(e => e.hasContent).length / mappings.length) * 100),
481
- entries,
482
- gaps,
483
- };
484
-
485
- const lines: string[] = [];
486
- lines.push(`## Docs Coverage Report${filterDomain ? ` (${filterDomain})` : ''}`);
487
- lines.push('');
488
- lines.push(`- Total mappings: ${report.totalMappings}`);
489
- lines.push(`- Pages existing: ${report.pagesExisting}`);
490
- lines.push(`- Pages with content: ${report.pagesWithContent}`);
491
- lines.push(`- Coverage: ${report.coveragePercent}%`);
492
- lines.push('');
493
-
494
- if (report.gaps.length > 0) {
495
- lines.push('### Gaps');
496
- for (const gap of report.gaps) {
497
- lines.push(`- ${gap}`);
498
- }
499
- lines.push('');
500
- }
501
-
502
- lines.push('### Page Status');
503
- for (const entry of report.entries) {
504
- const verified = entry.lastVerified ? ` (verified: ${entry.lastVerified})` : ' (not verified)';
505
- const pageStatus = entry.status ? ` [${entry.status}]` : '';
506
- const icon = entry.hasContent ? 'OK' : entry.exists ? 'THIN' : 'MISSING';
507
- lines.push(`- [${icon}] ${entry.id}: ${entry.helpPage}${verified}${pageStatus} (${entry.lineCount} lines)`);
508
- }
509
-
510
- return text(lines.join('\n'));
511
- }
512
-
513
- // ============================================================
514
- // Utilities
515
- // ============================================================
516
-
517
- function text(content: string): ToolResult {
518
- return { content: [{ type: 'text', text: content }] };
519
- }
package/src/domains.ts DELETED
@@ -1,182 +0,0 @@
1
- // Copyright (c) 2026 Massu. All rights reserved.
2
- // Licensed under BSL 1.1 - see LICENSE file for details.
3
-
4
- import type Database from 'better-sqlite3';
5
- import { globMatch } from './rules.ts';
6
- import { getConfig, getResolvedPaths } from './config.ts';
7
- import type { DomainConfig } from './config.ts';
8
- import { t } from './lib/sql-table-names.ts';
9
-
10
- // Re-export for backward compatibility
11
- export type { DomainConfig };
12
-
13
- /**
14
- * Get domain configurations from the config file.
15
- * Returns an empty array if no domains are configured.
16
- */
17
- function getDomains(): DomainConfig[] {
18
- return getConfig().domains;
19
- }
20
-
21
- /**
22
- * Classify a router name into its domain.
23
- * Returns the domain name or 'Unknown' if no match.
24
- */
25
- export function classifyRouter(routerName: string): string {
26
- const domains = getDomains();
27
- for (const domain of domains) {
28
- for (const pattern of domain.routers) {
29
- if (globMatchSimple(routerName, pattern)) {
30
- return domain.name;
31
- }
32
- }
33
- }
34
- return 'Unknown';
35
- }
36
-
37
- /**
38
- * Classify a file path into its domain.
39
- */
40
- export function classifyFile(filePath: string): string {
41
- const domains = getDomains();
42
- const config = getConfig();
43
- const normalized = filePath.replace(/\\/g, '/');
44
-
45
- // Check page patterns
46
- for (const domain of domains) {
47
- for (const pattern of domain.pages) {
48
- if (globMatch(normalized, pattern)) {
49
- return domain.name;
50
- }
51
- }
52
- }
53
-
54
- // Check if it's a router file - derive router dir from config
55
- const routersPath = config.paths.routers ?? 'src/server/api/routers';
56
- const routerPrefix = routersPath.replace(/\\/g, '/');
57
- if (normalized.includes(routerPrefix + '/')) {
58
- const routerName = normalized
59
- .replace(routerPrefix + '/', '')
60
- .replace(/\.ts$/, '')
61
- .replace(/\/index$/, '');
62
- return classifyRouter(routerName);
63
- }
64
-
65
- // Check component paths
66
- if (normalized.includes('/components/')) {
67
- const parts = normalized.split('/');
68
- const compIdx = parts.indexOf('components');
69
- if (compIdx >= 0 && compIdx + 1 < parts.length) {
70
- const compGroup = parts[compIdx + 1];
71
- for (const domain of domains) {
72
- for (const pattern of domain.routers) {
73
- if (globMatchSimple(compGroup, pattern)) {
74
- return domain.name;
75
- }
76
- }
77
- }
78
- }
79
- }
80
-
81
- return 'Unknown';
82
- }
83
-
84
- /**
85
- * Simple glob matching for router/table names (single-level, no path separators).
86
- */
87
- function globMatchSimple(name: string, pattern: string): boolean {
88
- const regexStr = pattern
89
- .replace(/\./g, '\\.')
90
- .replace(/\*/g, '.*')
91
- .replace(/\?/g, '.');
92
- return new RegExp(`^${regexStr}$`).test(name);
93
- }
94
-
95
- /**
96
- * Find all cross-domain imports using the imports table.
97
- */
98
- export function findCrossDomainImports(dataDb: Database.Database): {
99
- source: string;
100
- target: string;
101
- sourceDomain: string;
102
- targetDomain: string;
103
- allowed: boolean;
104
- }[] {
105
- const domains = getDomains();
106
- const config = getConfig();
107
- const srcPrefix = config.paths.source;
108
-
109
- const srcPattern = srcPrefix + '/%';
110
- const imports = dataDb.prepare(
111
- `SELECT source_file, target_file FROM ${t('imports')} WHERE source_file LIKE ? AND target_file LIKE ?`
112
- ).all(srcPattern, srcPattern) as { source_file: string; target_file: string }[];
113
-
114
- const crossings: {
115
- source: string; target: string;
116
- sourceDomain: string; targetDomain: string;
117
- allowed: boolean;
118
- }[] = [];
119
-
120
- for (const imp of imports) {
121
- const sourceDomain = classifyFile(imp.source_file);
122
- const targetDomain = classifyFile(imp.target_file);
123
-
124
- if (sourceDomain === 'Unknown' || targetDomain === 'Unknown') continue;
125
- if (sourceDomain === targetDomain) continue;
126
-
127
- // Check if source domain allows wildcard imports
128
- const sourceConfig = domains.find(d => d.name === sourceDomain);
129
- if (sourceConfig?.allowedImportsFrom.length === 0) continue; // System domain
130
- const allowed = sourceConfig?.allowedImportsFrom.includes('*') ||
131
- sourceConfig?.allowedImportsFrom.includes(targetDomain) || false;
132
-
133
- crossings.push({
134
- source: imp.source_file,
135
- target: imp.target_file,
136
- sourceDomain,
137
- targetDomain,
138
- allowed,
139
- });
140
- }
141
-
142
- return crossings;
143
- }
144
-
145
- /**
146
- * Get all files in a specific domain.
147
- */
148
- export function getFilesInDomain(dataDb: Database.Database, codegraphDb: Database.Database, domainName: string): {
149
- routers: string[];
150
- pages: string[];
151
- components: string[];
152
- } {
153
- const domains = getDomains();
154
- const config = getConfig();
155
- const domain = domains.find(d => d.name === domainName);
156
- if (!domain) return { routers: [], pages: [], components: [] };
157
-
158
- const srcPrefix = config.paths.source;
159
- const routersPath = config.paths.routers ?? 'src/server/api/routers';
160
-
161
- const srcPattern = srcPrefix + '/%';
162
- const allFiles = codegraphDb.prepare('SELECT path FROM files WHERE path LIKE ?').all(srcPattern) as { path: string }[];
163
-
164
- const routers: string[] = [];
165
- const pages: string[] = [];
166
- const components: string[] = [];
167
-
168
- for (const file of allFiles) {
169
- const fileDomain = classifyFile(file.path);
170
- if (fileDomain !== domainName) continue;
171
-
172
- if (file.path.includes(routersPath + '/')) {
173
- routers.push(file.path);
174
- } else if (file.path.match(/page\.tsx?$/)) {
175
- pages.push(file.path);
176
- } else if (file.path.includes('/components/')) {
177
- components.push(file.path);
178
- }
179
- }
180
-
181
- return { routers, pages, components };
182
- }