@massu/core 1.10.6 → 1.10.8

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.
@@ -5,6 +5,7 @@ import type Database from 'better-sqlite3';
5
5
  import type { ToolDefinition, ToolResult } from './tools.ts';
6
6
  import { getConfig } from './config.ts';
7
7
  import { classifyPythonFileDomain } from './python/domain-enforcer.ts';
8
+ import { t } from './lib/sql-table-names.ts';
8
9
 
9
10
  /** Get the configured tool prefix */
10
11
  function p(name: string): string {
@@ -195,7 +196,7 @@ function handlePyImports(args: Record<string, unknown>, dataDb: Database.Databas
195
196
  if (depth > maxDepth || visited.has(f)) return;
196
197
  visited.add(f);
197
198
  const imports = dataDb.prepare(
198
- 'SELECT target_file, imported_names FROM massu_py_imports WHERE source_file = ?'
199
+ `SELECT target_file, imported_names FROM ${t('py_imports')} WHERE source_file = ?`
199
200
  ).all(f) as { target_file: string; imported_names: string }[];
200
201
  for (const imp of imports) {
201
202
  const indent = ' '.repeat(depth);
@@ -207,7 +208,7 @@ function handlePyImports(args: Record<string, unknown>, dataDb: Database.Databas
207
208
  traverse(file, 0);
208
209
  } else {
209
210
  const imports = dataDb.prepare(
210
- 'SELECT target_file, import_type, imported_names, line FROM massu_py_imports WHERE source_file = ? ORDER BY line'
211
+ `SELECT target_file, import_type, imported_names, line FROM ${t('py_imports')} WHERE source_file = ? ORDER BY line`
211
212
  ).all(file) as { target_file: string; import_type: string; imported_names: string; line: number }[];
212
213
  lines.push(`## Imports from ${file} (${imports.length} edges)`);
213
214
  for (const imp of imports) {
@@ -217,7 +218,7 @@ function handlePyImports(args: Record<string, unknown>, dataDb: Database.Databas
217
218
  }
218
219
  } else if (importedBy) {
219
220
  const importers = dataDb.prepare(
220
- 'SELECT source_file, imported_names, line FROM massu_py_imports WHERE target_file = ? ORDER BY source_file'
221
+ `SELECT source_file, imported_names, line FROM ${t('py_imports')} WHERE target_file = ? ORDER BY source_file`
221
222
  ).all(importedBy) as { source_file: string; imported_names: string; line: number }[];
222
223
  lines.push(`## Files importing ${importedBy} (${importers.length} edges)`);
223
224
  for (const imp of importers) {
@@ -225,8 +226,8 @@ function handlePyImports(args: Record<string, unknown>, dataDb: Database.Databas
225
226
  lines.push(`- ${imp.source_file}:${imp.line}${names.length > 0 ? ' (' + names.join(', ') + ')' : ''}`);
226
227
  }
227
228
  } else {
228
- const total = (dataDb.prepare('SELECT COUNT(*) as cnt FROM massu_py_imports').get() as { cnt: number }).cnt;
229
- const files = (dataDb.prepare('SELECT COUNT(DISTINCT source_file) as cnt FROM massu_py_imports').get() as { cnt: number }).cnt;
229
+ const total = (dataDb.prepare(`SELECT COUNT(*) as cnt FROM ${t('py_imports')}`).get() as { cnt: number }).cnt;
230
+ const files = (dataDb.prepare(`SELECT COUNT(DISTINCT source_file) as cnt FROM ${t('py_imports')}`).get() as { cnt: number }).cnt;
230
231
  lines.push('## Python Import Index Summary');
231
232
  lines.push(`- Total import edges: ${total}`);
232
233
  lines.push(`- Files with imports: ${files}`);
@@ -241,7 +242,7 @@ function handlePyImports(args: Record<string, unknown>, dataDb: Database.Databas
241
242
  function handlePyRoutes(args: Record<string, unknown>, dataDb: Database.Database): ToolResult {
242
243
  const lines: string[] = [];
243
244
  const limit = Math.min(Math.max((args.limit as number) || 500, 1), 5000);
244
- let query = 'SELECT * FROM massu_py_routes WHERE 1=1';
245
+ let query = `SELECT * FROM ${t('py_routes')} WHERE 1=1`;
245
246
  const params: unknown[] = [];
246
247
 
247
248
  if (args.method) { query += ' AND method = ?'; params.push((args.method as string).toUpperCase()); }
@@ -258,7 +259,7 @@ function handlePyRoutes(args: Record<string, unknown>, dataDb: Database.Database
258
259
  }[];
259
260
 
260
261
  if (args.uncoupled) {
261
- const callerCountStmt = dataDb.prepare('SELECT COUNT(*) as cnt FROM massu_py_route_callers WHERE route_id = ?');
262
+ const callerCountStmt = dataDb.prepare(`SELECT COUNT(*) as cnt FROM ${t('py_route_callers')} WHERE route_id = ?`);
262
263
  const uncoupledRoutes = routes.filter(r => {
263
264
  const callers = (callerCountStmt.get(r.id) as { cnt: number }).cnt;
264
265
  return callers === 0;
@@ -286,8 +287,8 @@ function handlePyCoupling(args: Record<string, unknown>, dataDb: Database.Databa
286
287
  let query = `
287
288
  SELECT rc.frontend_file, rc.call_pattern, rc.line as caller_line,
288
289
  r.method, r.path, r.function_name, r.file as backend_file
289
- FROM massu_py_route_callers rc
290
- JOIN massu_py_routes r ON rc.route_id = r.id
290
+ FROM ${t('py_route_callers')} rc
291
+ JOIN ${t('py_routes')} r ON rc.route_id = r.id
291
292
  WHERE 1=1
292
293
  `;
293
294
  const params: unknown[] = [];
@@ -319,7 +320,7 @@ function handlePyModels(args: Record<string, unknown>, dataDb: Database.Database
319
320
  const lines: string[] = [];
320
321
 
321
322
  if (args.fk_graph) {
322
- const edges = dataDb.prepare('SELECT * FROM massu_py_fk_edges ORDER BY source_table').all() as {
323
+ const edges = dataDb.prepare(`SELECT * FROM ${t('py_fk_edges')} ORDER BY source_table`).all() as {
323
324
  source_table: string; source_column: string; target_table: string; target_column: string;
324
325
  }[];
325
326
  lines.push(`## Foreign Key Graph (${edges.length} edges)`);
@@ -329,7 +330,7 @@ function handlePyModels(args: Record<string, unknown>, dataDb: Database.Database
329
330
  } else if (args.model || args.table) {
330
331
  const search = (args.model || args.table) as string;
331
332
  const model = dataDb.prepare(
332
- 'SELECT * FROM massu_py_models WHERE class_name = ? OR table_name = ?'
333
+ `SELECT * FROM ${t('py_models')} WHERE class_name = ? OR table_name = ?`
333
334
  ).get(search, search) as {
334
335
  class_name: string; table_name: string; file: string; line: number;
335
336
  columns: string; relationships: string; foreign_keys: string;
@@ -365,7 +366,7 @@ function handlePyModels(args: Record<string, unknown>, dataDb: Database.Database
365
366
  }
366
367
  } else {
367
368
  const limit = Math.min(Math.max((args.limit as number) || 500, 1), 5000);
368
- const models = dataDb.prepare(`SELECT class_name, table_name, file, line FROM massu_py_models ORDER BY class_name LIMIT ${limit}`).all() as {
369
+ const models = dataDb.prepare(`SELECT class_name, table_name, file, line FROM ${t('py_models')} ORDER BY class_name LIMIT ${limit}`).all() as {
369
370
  class_name: string; table_name: string; file: string; line: number;
370
371
  }[];
371
372
  lines.push(`## Python Models (${models.length})`);
@@ -382,10 +383,10 @@ function handlePyMigrations(args: Record<string, unknown>, dataDb: Database.Data
382
383
 
383
384
  if (args.drift) {
384
385
  // Compare models against migration operations
385
- const models = dataDb.prepare('SELECT class_name, table_name, columns FROM massu_py_models').all() as {
386
+ const models = dataDb.prepare(`SELECT class_name, table_name, columns FROM ${t('py_models')}`).all() as {
386
387
  class_name: string; table_name: string; columns: string;
387
388
  }[];
388
- const migrations = dataDb.prepare('SELECT revision, operations FROM massu_py_migrations').all() as {
389
+ const migrations = dataDb.prepare(`SELECT revision, operations FROM ${t('py_migrations')}`).all() as {
389
390
  revision: string; operations: string;
390
391
  }[];
391
392
 
@@ -409,7 +410,7 @@ function handlePyMigrations(args: Record<string, unknown>, dataDb: Database.Data
409
410
  }
410
411
  } else if (args.chain) {
411
412
  const limit = Math.min(Math.max((args.limit as number) || 500, 1), 5000);
412
- const migrations = dataDb.prepare(`SELECT * FROM massu_py_migrations ORDER BY id LIMIT ${limit}`).all() as {
413
+ const migrations = dataDb.prepare(`SELECT * FROM ${t('py_migrations')} ORDER BY id LIMIT ${limit}`).all() as {
413
414
  revision: string; down_revision: string | null; file: string; description: string | null; is_head: number;
414
415
  }[];
415
416
  lines.push(`## Migration Chain (${migrations.length} revisions)`);
@@ -418,7 +419,7 @@ function handlePyMigrations(args: Record<string, unknown>, dataDb: Database.Data
418
419
  lines.push(`- ${m.revision}${head} ← ${m.down_revision || 'BASE'}: ${m.description || '(no description)'} (${m.file})`);
419
420
  }
420
421
  } else if (args.revision) {
421
- const m = dataDb.prepare('SELECT * FROM massu_py_migrations WHERE revision = ?').get(args.revision) as {
422
+ const m = dataDb.prepare(`SELECT * FROM ${t('py_migrations')} WHERE revision = ?`).get(args.revision) as {
422
423
  revision: string; down_revision: string | null; file: string; description: string | null;
423
424
  operations: string; is_head: number;
424
425
  } | undefined;
@@ -435,8 +436,8 @@ function handlePyMigrations(args: Record<string, unknown>, dataDb: Database.Data
435
436
  }
436
437
  }
437
438
  } else {
438
- const total = (dataDb.prepare('SELECT COUNT(*) as cnt FROM massu_py_migrations').get() as { cnt: number }).cnt;
439
- const head = dataDb.prepare('SELECT revision FROM massu_py_migrations WHERE is_head = 1').get() as { revision: string } | undefined;
439
+ const total = (dataDb.prepare(`SELECT COUNT(*) as cnt FROM ${t('py_migrations')}`).get() as { cnt: number }).cnt;
440
+ const head = dataDb.prepare(`SELECT revision FROM ${t('py_migrations')} WHERE is_head = 1`).get() as { revision: string } | undefined;
440
441
  lines.push('## Migration Summary');
441
442
  lines.push(`- Total revisions: ${total}`);
442
443
  lines.push(`- Current head: ${head?.revision || 'N/A'}`);
@@ -460,7 +461,7 @@ function handlePyDomains(args: Record<string, unknown>, dataDb: Database.Databas
460
461
  lines.push(`Allowed imports from: ${domainConfig.allowed_imports_from.join(', ') || 'any'}`);
461
462
  }
462
463
  } else if (args.crossings) {
463
- const imports = dataDb.prepare('SELECT source_file, target_file FROM massu_py_imports').all() as {
464
+ const imports = dataDb.prepare(`SELECT source_file, target_file FROM ${t('py_imports')}`).all() as {
464
465
  source_file: string; target_file: string;
465
466
  }[];
466
467
  const violations: string[] = [];
@@ -481,7 +482,7 @@ function handlePyDomains(args: Record<string, unknown>, dataDb: Database.Databas
481
482
  if (violations.length > 50) lines.push(`... and ${violations.length - 50} more`);
482
483
  } else if (args.domain) {
483
484
  const domainName = args.domain as string;
484
- const allFiles = dataDb.prepare('SELECT DISTINCT source_file FROM massu_py_imports UNION SELECT DISTINCT target_file FROM massu_py_imports').all() as { source_file: string }[];
485
+ const allFiles = dataDb.prepare(`SELECT DISTINCT source_file FROM ${t('py_imports')} UNION SELECT DISTINCT target_file FROM ${t('py_imports')}`).all() as { source_file: string }[];
485
486
  const filesInDomain = allFiles.filter(f => classifyPythonFileDomain(f.source_file) === domainName);
486
487
  lines.push(`## Domain: ${domainName} (${filesInDomain.length} files)`);
487
488
  for (const f of filesInDomain) {
@@ -503,7 +504,7 @@ function handlePyImpact(args: Record<string, unknown>, dataDb: Database.Database
503
504
 
504
505
  // 1. Who imports this file
505
506
  const importedBy = dataDb.prepare(
506
- 'SELECT source_file FROM massu_py_imports WHERE target_file = ?'
507
+ `SELECT source_file FROM ${t('py_imports')} WHERE target_file = ?`
507
508
  ).all(file) as { source_file: string }[];
508
509
  lines.push(`### Imported By (${importedBy.length} files)`);
509
510
  for (const imp of importedBy.slice(0, 20)) {
@@ -512,7 +513,7 @@ function handlePyImpact(args: Record<string, unknown>, dataDb: Database.Database
512
513
 
513
514
  // 2. Routes in this file
514
515
  const routes = dataDb.prepare(
515
- 'SELECT method, path, function_name FROM massu_py_routes WHERE file = ?'
516
+ `SELECT method, path, function_name FROM ${t('py_routes')} WHERE file = ?`
516
517
  ).all(file) as { method: string; path: string; function_name: string }[];
517
518
  if (routes.length > 0) {
518
519
  lines.push(`\n### Routes Defined (${routes.length})`);
@@ -523,7 +524,7 @@ function handlePyImpact(args: Record<string, unknown>, dataDb: Database.Database
523
524
 
524
525
  // 3. Models in this file
525
526
  const models = dataDb.prepare(
526
- 'SELECT class_name, table_name FROM massu_py_models WHERE file = ?'
527
+ `SELECT class_name, table_name FROM ${t('py_models')} WHERE file = ?`
527
528
  ).all(file) as { class_name: string; table_name: string }[];
528
529
  if (models.length > 0) {
529
530
  lines.push(`\n### Models Defined (${models.length})`);
@@ -533,11 +534,11 @@ function handlePyImpact(args: Record<string, unknown>, dataDb: Database.Database
533
534
  }
534
535
 
535
536
  // 4. Frontend callers (via routes)
536
- const routeIds = dataDb.prepare('SELECT id FROM massu_py_routes WHERE file = ?').all(file) as { id: number }[];
537
+ const routeIds = dataDb.prepare(`SELECT id FROM ${t('py_routes')} WHERE file = ?`).all(file) as { id: number }[];
537
538
  if (routeIds.length > 0) {
538
539
  const placeholders = routeIds.map(() => '?').join(',');
539
540
  const callers = dataDb.prepare(
540
- `SELECT DISTINCT frontend_file FROM massu_py_route_callers WHERE route_id IN (${placeholders})`
541
+ `SELECT DISTINCT frontend_file FROM ${t('py_route_callers')} WHERE route_id IN (${placeholders})`
541
542
  ).all(...routeIds.map(r => r.id)) as { frontend_file: string }[];
542
543
  if (callers.length > 0) {
543
544
  lines.push(`\n### Frontend Callers (${callers.length} files)`);
@@ -566,7 +567,7 @@ function handlePyContext(args: Record<string, unknown>, dataDb: Database.Databas
566
567
 
567
568
  // Imports
568
569
  const imports = dataDb.prepare(
569
- 'SELECT target_file, imported_names FROM massu_py_imports WHERE source_file = ? LIMIT 20'
570
+ `SELECT target_file, imported_names FROM ${t('py_imports')} WHERE source_file = ? LIMIT 20`
570
571
  ).all(file) as { target_file: string; imported_names: string }[];
571
572
  if (imports.length > 0) {
572
573
  lines.push('\n### Imports');
@@ -578,7 +579,7 @@ function handlePyContext(args: Record<string, unknown>, dataDb: Database.Databas
578
579
 
579
580
  // Imported by
580
581
  const importedBy = dataDb.prepare(
581
- 'SELECT source_file FROM massu_py_imports WHERE target_file = ? LIMIT 10'
582
+ `SELECT source_file FROM ${t('py_imports')} WHERE target_file = ? LIMIT 10`
582
583
  ).all(file) as { source_file: string }[];
583
584
  if (importedBy.length > 0) {
584
585
  lines.push('\n### Imported By');
@@ -588,7 +589,7 @@ function handlePyContext(args: Record<string, unknown>, dataDb: Database.Databas
588
589
  }
589
590
 
590
591
  // Routes
591
- const routes = dataDb.prepare('SELECT method, path, function_name, line FROM massu_py_routes WHERE file = ?')
592
+ const routes = dataDb.prepare(`SELECT method, path, function_name, line FROM ${t('py_routes')} WHERE file = ?`)
592
593
  .all(file) as { method: string; path: string; function_name: string; line: number }[];
593
594
  if (routes.length > 0) {
594
595
  lines.push('\n### Routes');
@@ -598,7 +599,7 @@ function handlePyContext(args: Record<string, unknown>, dataDb: Database.Databas
598
599
  }
599
600
 
600
601
  // Models
601
- const models = dataDb.prepare('SELECT class_name, table_name, line FROM massu_py_models WHERE file = ?')
602
+ const models = dataDb.prepare(`SELECT class_name, table_name, line FROM ${t('py_models')} WHERE file = ?`)
602
603
  .all(file) as { class_name: string; table_name: string; line: number }[];
603
604
  if (models.length > 0) {
604
605
  lines.push('\n### Models');
@@ -1,4 +1,4 @@
1
- // AUTO-GENERATED by scripts/bundle-license-pubkey.mjs at 2026-05-17T15:41:04.385Z.
1
+ // AUTO-GENERATED by scripts/bundle-license-pubkey.mjs at 2026-05-17T16:00:08.526Z.
2
2
  // Source pem: packages/core/security/license-pubkey.pem
3
3
  // RAW-bytes sha256: 18a63d64fdec9e5a368fc45feaa49bed6ced815967e582bc7b8af534f22a9475
4
4
  // DO NOT EDIT — regenerate via `node scripts/bundle-license-pubkey.mjs` or
@@ -1,4 +1,4 @@
1
- // AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-17T15:41:04.323Z.
1
+ // AUTO-GENERATED by scripts/bundle-pubkey.mjs at 2026-05-17T16:00:08.460Z.
2
2
  // Source pem: packages/core/security/registry-pubkey.pem
3
3
  // RAW-bytes sha256: 3b6226d036c472e533110d11a7d0cd2773ce1d7d4f1003517d5bd69c5418ed4c
4
4
  // DO NOT EDIT — regenerate via `node scripts/bundle-pubkey.mjs` or
@@ -24,6 +24,7 @@ import type {
24
24
  } from './sentinel-types.ts';
25
25
  import { getProjectRoot } from './config.ts';
26
26
  import { sanitizeFts5Query } from './memory-db.ts';
27
+ import { t } from './lib/sql-table-names.ts';
27
28
 
28
29
  // ============================================================
29
30
  // Sentinel: Feature Registry Data Access Layer
@@ -63,11 +64,11 @@ function toFeature(row: Record<string, unknown>): Feature {
63
64
  // ============================================================
64
65
 
65
66
  export function upsertFeature(db: Database.Database, input: FeatureInput): number {
66
- const existing = db.prepare('SELECT id FROM massu_sentinel WHERE feature_key = ?').get(input.feature_key) as { id: number } | undefined;
67
+ const existing = db.prepare(`SELECT id FROM ${t('sentinel')} WHERE feature_key = ?`).get(input.feature_key) as { id: number } | undefined;
67
68
 
68
69
  if (existing) {
69
70
  db.prepare(`
70
- UPDATE massu_sentinel SET
71
+ UPDATE ${t('sentinel')} SET
71
72
  domain = ?, subdomain = ?, title = ?, description = ?,
72
73
  status = COALESCE(?, status), priority = COALESCE(?, priority),
73
74
  portal_scope = COALESCE(?, portal_scope),
@@ -87,7 +88,7 @@ export function upsertFeature(db: Database.Database, input: FeatureInput): numbe
87
88
  }
88
89
 
89
90
  const result = db.prepare(`
90
- INSERT INTO massu_sentinel (feature_key, domain, subdomain, title, description, status, priority, portal_scope)
91
+ INSERT INTO ${t('sentinel')} (feature_key, domain, subdomain, title, description, status, priority, portal_scope)
91
92
  VALUES (?, ?, ?, ?, ?, ?, ?, ?)
92
93
  `).run(
93
94
  input.feature_key,
@@ -104,12 +105,12 @@ export function upsertFeature(db: Database.Database, input: FeatureInput): numbe
104
105
  }
105
106
 
106
107
  export function getFeature(db: Database.Database, featureKey: string): Feature | null {
107
- const row = db.prepare('SELECT * FROM massu_sentinel WHERE feature_key = ?').get(featureKey) as Record<string, unknown> | undefined;
108
+ const row = db.prepare(`SELECT * FROM ${t('sentinel')} WHERE feature_key = ?`).get(featureKey) as Record<string, unknown> | undefined;
108
109
  return row ? toFeature(row) : null;
109
110
  }
110
111
 
111
112
  export function getFeatureById(db: Database.Database, id: number): Feature | null {
112
- const row = db.prepare('SELECT * FROM massu_sentinel WHERE id = ?').get(id) as Record<string, unknown> | undefined;
113
+ const row = db.prepare(`SELECT * FROM ${t('sentinel')} WHERE id = ?`).get(id) as Record<string, unknown> | undefined;
113
114
  return row ? toFeature(row) : null;
114
115
  }
115
116
 
@@ -129,21 +130,21 @@ export function searchFeatures(
129
130
  // FTS5 search
130
131
  sql = `
131
132
  SELECT s.*, fts.rank,
132
- (SELECT COUNT(*) FROM massu_sentinel_components WHERE feature_id = s.id) as component_count,
133
- (SELECT COUNT(*) FROM massu_sentinel_procedures WHERE feature_id = s.id) as procedure_count,
134
- (SELECT COUNT(*) FROM massu_sentinel_pages WHERE feature_id = s.id) as page_count
135
- FROM massu_sentinel s
136
- JOIN massu_sentinel_fts fts ON s.id = fts.rowid
137
- WHERE massu_sentinel_fts MATCH ?
133
+ (SELECT COUNT(*) FROM ${t('sentinel_components')} WHERE feature_id = s.id) as component_count,
134
+ (SELECT COUNT(*) FROM ${t('sentinel_procedures')} WHERE feature_id = s.id) as procedure_count,
135
+ (SELECT COUNT(*) FROM ${t('sentinel_pages')} WHERE feature_id = s.id) as page_count
136
+ FROM ${t('sentinel')} s
137
+ JOIN ${t('sentinel_fts')} fts ON s.id = fts.rowid
138
+ WHERE ${t('sentinel_fts')} MATCH ?
138
139
  `;
139
140
  params.push(sanitizeFts5Query(query));
140
141
  } else {
141
142
  sql = `
142
143
  SELECT s.*,
143
- (SELECT COUNT(*) FROM massu_sentinel_components WHERE feature_id = s.id) as component_count,
144
- (SELECT COUNT(*) FROM massu_sentinel_procedures WHERE feature_id = s.id) as procedure_count,
145
- (SELECT COUNT(*) FROM massu_sentinel_pages WHERE feature_id = s.id) as page_count
146
- FROM massu_sentinel s
144
+ (SELECT COUNT(*) FROM ${t('sentinel_components')} WHERE feature_id = s.id) as component_count,
145
+ (SELECT COUNT(*) FROM ${t('sentinel_procedures')} WHERE feature_id = s.id) as procedure_count,
146
+ (SELECT COUNT(*) FROM ${t('sentinel_pages')} WHERE feature_id = s.id) as page_count
147
+ FROM ${t('sentinel')} s
147
148
  WHERE 1=1
148
149
  `;
149
150
  }
@@ -166,7 +167,7 @@ export function searchFeatures(
166
167
  params.push(`%"${escapedPortal}"%`);
167
168
  }
168
169
  if (filters?.page_route) {
169
- sql += ' AND s.id IN (SELECT feature_id FROM massu_sentinel_pages WHERE page_route = ?)';
170
+ sql += ` AND s.id IN (SELECT feature_id FROM ${t('sentinel_pages')} WHERE page_route = ?)`;
170
171
  params.push(filters.page_route);
171
172
  }
172
173
 
@@ -182,14 +183,14 @@ export function searchFeatures(
182
183
  }
183
184
 
184
185
  export function getFeaturesByDomain(db: Database.Database, domain: string): Feature[] {
185
- const rows = db.prepare('SELECT * FROM massu_sentinel WHERE domain = ? ORDER BY subdomain, feature_key').all(domain) as Record<string, unknown>[];
186
+ const rows = db.prepare(`SELECT * FROM ${t('sentinel')} WHERE domain = ? ORDER BY subdomain, feature_key`).all(domain) as Record<string, unknown>[];
186
187
  return rows.map(toFeature);
187
188
  }
188
189
 
189
190
  export function getFeaturesByFile(db: Database.Database, filePath: string): Feature[] {
190
191
  const rows = db.prepare(`
191
- SELECT DISTINCT s.* FROM massu_sentinel s
192
- JOIN massu_sentinel_components c ON c.feature_id = s.id
192
+ SELECT DISTINCT s.* FROM ${t('sentinel')} s
193
+ JOIN ${t('sentinel_components')} c ON c.feature_id = s.id
193
194
  WHERE c.component_file = ?
194
195
  ORDER BY s.feature_key
195
196
  `).all(filePath) as Record<string, unknown>[];
@@ -198,8 +199,8 @@ export function getFeaturesByFile(db: Database.Database, filePath: string): Feat
198
199
 
199
200
  export function getFeaturesByRoute(db: Database.Database, route: string): Feature[] {
200
201
  const rows = db.prepare(`
201
- SELECT DISTINCT s.* FROM massu_sentinel s
202
- JOIN massu_sentinel_pages p ON p.feature_id = s.id
202
+ SELECT DISTINCT s.* FROM ${t('sentinel')} s
203
+ JOIN ${t('sentinel_pages')} p ON p.feature_id = s.id
203
204
  WHERE p.page_route = ?
204
205
  ORDER BY s.feature_key
205
206
  `).all(route) as Record<string, unknown>[];
@@ -219,11 +220,11 @@ export function getFeatureDetail(db: Database.Database, featureKeyOrId: string |
219
220
  }
220
221
  if (!feature) return null;
221
222
 
222
- const components = db.prepare('SELECT * FROM massu_sentinel_components WHERE feature_id = ?').all(feature.id) as FeatureComponent[];
223
- const procedures = db.prepare('SELECT * FROM massu_sentinel_procedures WHERE feature_id = ?').all(feature.id) as FeatureProcedure[];
224
- const pages = db.prepare('SELECT * FROM massu_sentinel_pages WHERE feature_id = ?').all(feature.id) as FeaturePage[];
225
- const dependencies = db.prepare('SELECT * FROM massu_sentinel_deps WHERE feature_id = ?').all(feature.id) as FeatureDep[];
226
- const changelog = db.prepare('SELECT * FROM massu_sentinel_changelog WHERE feature_id = ? ORDER BY created_at DESC LIMIT 50').all(feature.id) as FeatureChangeLog[];
223
+ const components = db.prepare(`SELECT * FROM ${t('sentinel_components')} WHERE feature_id = ?`).all(feature.id) as FeatureComponent[];
224
+ const procedures = db.prepare(`SELECT * FROM ${t('sentinel_procedures')} WHERE feature_id = ?`).all(feature.id) as FeatureProcedure[];
225
+ const pages = db.prepare(`SELECT * FROM ${t('sentinel_pages')} WHERE feature_id = ?`).all(feature.id) as FeaturePage[];
226
+ const dependencies = db.prepare(`SELECT * FROM ${t('sentinel_deps')} WHERE feature_id = ?`).all(feature.id) as FeatureDep[];
227
+ const changelog = db.prepare(`SELECT * FROM ${t('sentinel_changelog')} WHERE feature_id = ? ORDER BY created_at DESC LIMIT 50`).all(feature.id) as FeatureChangeLog[];
227
228
 
228
229
  return { ...feature, components, procedures, pages, dependencies, changelog };
229
230
  }
@@ -235,10 +236,10 @@ export function getFeatureDetail(db: Database.Database, featureKeyOrId: string |
235
236
  export function getOrphanedFeatures(db: Database.Database): Feature[] {
236
237
  // Active features with no living primary component files
237
238
  const features = db.prepare(`
238
- SELECT s.* FROM massu_sentinel s
239
+ SELECT s.* FROM ${t('sentinel')} s
239
240
  WHERE s.status = 'active'
240
241
  AND NOT EXISTS (
241
- SELECT 1 FROM massu_sentinel_components c
242
+ SELECT 1 FROM ${t('sentinel_components')} c
242
243
  WHERE c.feature_id = s.id AND c.is_primary = 1
243
244
  )
244
245
  ORDER BY s.domain, s.feature_key
@@ -253,7 +254,7 @@ export function getFeatureImpact(db: Database.Database, filePaths: string[]): Im
253
254
  // Find all features linked to these files
254
255
  for (const filePath of filePaths) {
255
256
  const links = db.prepare(
256
- 'SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?'
257
+ `SELECT feature_id FROM ${t('sentinel_components')} WHERE component_file = ?`
257
258
  ).all(filePath) as { feature_id: number }[];
258
259
  for (const link of links) {
259
260
  affectedFeatureIds.add(link.feature_id);
@@ -269,7 +270,7 @@ export function getFeatureImpact(db: Database.Database, filePaths: string[]): Im
269
270
  if (!feature || feature.status !== 'active') continue;
270
271
 
271
272
  const allComponents = db.prepare(
272
- 'SELECT component_file, is_primary FROM massu_sentinel_components WHERE feature_id = ?'
273
+ `SELECT component_file, is_primary FROM ${t('sentinel_components')} WHERE feature_id = ?`
273
274
  ).all(featureId) as { component_file: string; is_primary: number }[];
274
275
 
275
276
  const affected = allComponents.filter(c => fileSet.has(c.component_file));
@@ -324,7 +325,7 @@ export function linkComponent(
324
325
  isPrimary: boolean = false
325
326
  ): void {
326
327
  db.prepare(`
327
- INSERT OR REPLACE INTO massu_sentinel_components (feature_id, component_file, component_name, role, is_primary)
328
+ INSERT OR REPLACE INTO ${t('sentinel_components')} (feature_id, component_file, component_name, role, is_primary)
328
329
  VALUES (?, ?, ?, ?, ?)
329
330
  `).run(featureId, filePath, componentName, role, isPrimary ? 1 : 0);
330
331
  }
@@ -337,7 +338,7 @@ export function linkProcedure(
337
338
  procedureType?: string
338
339
  ): void {
339
340
  db.prepare(`
340
- INSERT OR REPLACE INTO massu_sentinel_procedures (feature_id, router_name, procedure_name, procedure_type)
341
+ INSERT OR REPLACE INTO ${t('sentinel_procedures')} (feature_id, router_name, procedure_name, procedure_type)
341
342
  VALUES (?, ?, ?, ?)
342
343
  `).run(featureId, routerName, procedureName, procedureType || null);
343
344
  }
@@ -349,7 +350,7 @@ export function linkPage(
349
350
  portal?: string
350
351
  ): void {
351
352
  db.prepare(`
352
- INSERT OR REPLACE INTO massu_sentinel_pages (feature_id, page_route, portal)
353
+ INSERT OR REPLACE INTO ${t('sentinel_pages')} (feature_id, page_route, portal)
353
354
  VALUES (?, ?, ?)
354
355
  `).run(featureId, route, portal || null);
355
356
  }
@@ -367,7 +368,7 @@ export function logChange(
367
368
  changedBy: string = 'claude-code'
368
369
  ): void {
369
370
  db.prepare(`
370
- INSERT INTO massu_sentinel_changelog (feature_id, change_type, changed_by, change_detail, commit_hash)
371
+ INSERT INTO ${t('sentinel_changelog')} (feature_id, change_type, changed_by, change_detail, commit_hash)
371
372
  VALUES (?, ?, ?, ?, ?)
372
373
  `).run(featureId, changeType, changedBy, detail, commitHash || null);
373
374
  }
@@ -377,7 +378,7 @@ export function logChange(
377
378
  // ============================================================
378
379
 
379
380
  export function validateFeatures(db: Database.Database, domainFilter?: string): ValidationReport {
380
- let sql = `SELECT * FROM massu_sentinel WHERE status = 'active'`;
381
+ let sql = `SELECT * FROM ${t('sentinel')} WHERE status = 'active'`;
381
382
  const params: unknown[] = [];
382
383
  if (domainFilter) {
383
384
  sql += ' AND domain = ?';
@@ -393,9 +394,9 @@ export function validateFeatures(db: Database.Database, domainFilter?: string):
393
394
 
394
395
  for (const row of features) {
395
396
  const feature = toFeature(row);
396
- const components = db.prepare('SELECT * FROM massu_sentinel_components WHERE feature_id = ?').all(feature.id) as FeatureComponent[];
397
- const procedures = db.prepare('SELECT * FROM massu_sentinel_procedures WHERE feature_id = ?').all(feature.id) as FeatureProcedure[];
398
- const pages = db.prepare('SELECT * FROM massu_sentinel_pages WHERE feature_id = ?').all(feature.id) as FeaturePage[];
397
+ const components = db.prepare(`SELECT * FROM ${t('sentinel_components')} WHERE feature_id = ?`).all(feature.id) as FeatureComponent[];
398
+ const procedures = db.prepare(`SELECT * FROM ${t('sentinel_procedures')} WHERE feature_id = ?`).all(feature.id) as FeatureProcedure[];
399
+ const pages = db.prepare(`SELECT * FROM ${t('sentinel_pages')} WHERE feature_id = ?`).all(feature.id) as FeaturePage[];
399
400
 
400
401
  const missingComponents: string[] = [];
401
402
  const missingProcedures: { router: string; procedure: string }[] = [];
@@ -475,7 +476,7 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
475
476
  // Find features linked to old files
476
477
  const oldFeatureIds = new Set<number>();
477
478
  for (const file of oldFiles) {
478
- const links = db.prepare('SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?').all(file) as { feature_id: number }[];
479
+ const links = db.prepare(`SELECT feature_id FROM ${t('sentinel_components')} WHERE component_file = ?`).all(file) as { feature_id: number }[];
479
480
  for (const link of links) {
480
481
  oldFeatureIds.add(link.feature_id);
481
482
  }
@@ -484,7 +485,7 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
484
485
  // Find features linked to new files
485
486
  const newFeatureIds = new Set<number>();
486
487
  for (const file of newFiles) {
487
- const links = db.prepare('SELECT feature_id FROM massu_sentinel_components WHERE component_file = ?').all(file) as { feature_id: number }[];
488
+ const links = db.prepare(`SELECT feature_id FROM ${t('sentinel_components')} WHERE component_file = ?`).all(file) as { feature_id: number }[];
488
489
  for (const link of links) {
489
490
  newFeatureIds.add(link.feature_id);
490
491
  }
@@ -500,8 +501,8 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
500
501
  const feature = getFeatureById(db, fId);
501
502
  if (!feature) continue;
502
503
 
503
- const oldComps = db.prepare('SELECT component_file FROM massu_sentinel_components WHERE feature_id = ? AND component_file IN (' + oldFiles.map(() => '?').join(',') + ')').all(fId, ...oldFiles) as { component_file: string }[];
504
- const newComps = db.prepare('SELECT component_file FROM massu_sentinel_components WHERE feature_id = ? AND component_file IN (' + newFiles.map(() => '?').join(',') + ')').all(fId, ...newFiles) as { component_file: string }[];
504
+ const oldComps = db.prepare(`SELECT component_file FROM ${t('sentinel_components')} WHERE feature_id = ? AND component_file IN (` + oldFiles.map(() => '?').join(',') + ')').all(fId, ...oldFiles) as { component_file: string }[];
505
+ const newComps = db.prepare(`SELECT component_file FROM ${t('sentinel_components')} WHERE feature_id = ? AND component_file IN (` + newFiles.map(() => '?').join(',') + ')').all(fId, ...newFiles) as { component_file: string }[];
505
506
 
506
507
  const item: ParityItem = {
507
508
  feature_key: feature.feature_key,
@@ -524,7 +525,7 @@ export function checkParity(db: Database.Database, oldFiles: string[], newFiles:
524
525
  const feature = getFeatureById(db, fId);
525
526
  if (!feature) continue;
526
527
 
527
- const newComps = db.prepare('SELECT component_file FROM massu_sentinel_components WHERE feature_id = ? AND component_file IN (' + newFiles.map(() => '?').join(',') + ')').all(fId, ...newFiles) as { component_file: string }[];
528
+ const newComps = db.prepare(`SELECT component_file FROM ${t('sentinel_components')} WHERE feature_id = ? AND component_file IN (` + newFiles.map(() => '?').join(',') + ')').all(fId, ...newFiles) as { component_file: string }[];
528
529
 
529
530
  newFeatures.push({
530
531
  feature_key: feature.feature_key,
@@ -549,19 +550,19 @@ export function clearAutoDiscoveredFeatures(db: Database.Database): void {
549
550
  // Only clear features that were auto-discovered (not manually registered)
550
551
  // We use the changelog to distinguish: auto-discovered ones have changed_by = 'scanner'
551
552
  const autoIds = db.prepare(`
552
- SELECT DISTINCT feature_id FROM massu_sentinel_changelog
553
+ SELECT DISTINCT feature_id FROM ${t('sentinel_changelog')}
553
554
  WHERE changed_by = 'scanner' AND change_type = 'created'
554
555
  `).all() as { feature_id: number }[];
555
556
 
556
557
  // Don't delete features that also have manual changes
557
558
  for (const { feature_id } of autoIds) {
558
559
  const hasManualChanges = db.prepare(`
559
- SELECT 1 FROM massu_sentinel_changelog
560
+ SELECT 1 FROM ${t('sentinel_changelog')}
560
561
  WHERE feature_id = ? AND changed_by != 'scanner'
561
562
  `).get(feature_id);
562
563
 
563
564
  if (!hasManualChanges) {
564
- db.prepare('DELETE FROM massu_sentinel WHERE id = ?').run(feature_id);
565
+ db.prepare(`DELETE FROM ${t('sentinel')} WHERE id = ?`).run(feature_id);
565
566
  }
566
567
  }
567
568
  }
@@ -13,6 +13,7 @@ import {
13
13
  } from './sentinel-db.ts';
14
14
  import type { FeatureInput, FeaturePriority } from './sentinel-types.ts';
15
15
  import { getConfig, getProjectRoot } from './config.ts';
16
+ import { t } from './lib/sql-table-names.ts';
16
17
 
17
18
  // ============================================================
18
19
  // Sentinel: Feature Auto-Discovery Scanner
@@ -114,7 +115,7 @@ function scanTrpcProcedures(dataDb: Database.Database): DiscoveredFeature[] {
114
115
 
115
116
  const procedures = dataDb.prepare(`
116
117
  SELECT router_name, procedure_name, procedure_type, router_file
117
- FROM massu_trpc_procedures
118
+ FROM ${t('trpc_procedures')}
118
119
  ORDER BY router_name, procedure_name
119
120
  `).all() as { router_name: string; procedure_name: string; procedure_type: string; router_file: string }[];
120
121
 
@@ -171,7 +172,7 @@ function scanPageRoutes(dataDb: Database.Database): DiscoveredFeature[] {
171
172
 
172
173
  const pages = dataDb.prepare(`
173
174
  SELECT page_file, route, portal, components, hooks, routers
174
- FROM massu_page_deps
175
+ FROM ${t('page_deps')}
175
176
  ORDER BY route
176
177
  `).all() as { page_file: string; route: string; portal: string; components: string; hooks: string; routers: string }[];
177
178
 
@@ -17,6 +17,7 @@ import {
17
17
  } from './sentinel-db.ts';
18
18
  import type { ComponentRole, FeatureStatus, FeaturePriority } from './sentinel-types.ts';
19
19
  import { getConfig } from './config.ts';
20
+ import { t } from './lib/sql-table-names.ts';
20
21
 
21
22
  /** Prefix a base tool name with the configured tool prefix. */
22
23
  function p(baseName: string): string {
@@ -400,7 +401,7 @@ function handleValidate(args: Record<string, unknown>, db: Database.Database): T
400
401
  if (fix) {
401
402
  lines.push('### Auto-Fix Applied');
402
403
  for (const item of issues.filter(i => i.status === 'orphaned')) {
403
- db.prepare("UPDATE massu_sentinel SET status = 'deprecated', updated_at = datetime('now') WHERE id = ?").run(item.feature.id);
404
+ db.prepare(`UPDATE ${t('sentinel')} SET status = 'deprecated', updated_at = datetime('now') WHERE id = ?`).run(item.feature.id);
404
405
  logChange(db, item.feature.id, 'deprecated', 'Auto-deprecated: all primary components missing');
405
406
  lines.push(`- Deprecated: ${item.feature.feature_key}`);
406
407
  }