@massu/core 1.10.6 → 1.10.7
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.
- package/dist/cli.js +363 -334
- package/dist/hooks/post-edit-context.js +6 -1
- package/dist/hooks/pre-delete-check.js +12 -7
- package/package.json +1 -1
- package/src/db.ts +63 -62
- package/src/domains.ts +2 -1
- package/src/hooks/pre-delete-check.ts +7 -5
- package/src/import-resolver.ts +3 -2
- package/src/knowledge-tools.ts +3 -2
- package/src/lib/sql-table-names.ts +71 -0
- package/src/middleware-tree.ts +6 -5
- package/src/page-deps.ts +9 -8
- package/src/python/coupling-detector.ts +4 -3
- package/src/python/domain-enforcer.ts +3 -2
- package/src/python/impact-analyzer.ts +7 -6
- package/src/python/import-resolver.ts +3 -2
- package/src/python/migration-indexer.ts +5 -4
- package/src/python/model-indexer.ts +5 -4
- package/src/python/route-indexer.ts +4 -3
- package/src/python-tools.ts +30 -29
- package/src/security/license-pubkey.generated.ts +1 -1
- package/src/security/registry-pubkey.generated.ts +1 -1
- package/src/sentinel-db.ts +47 -46
- package/src/sentinel-scanner.ts +3 -2
- package/src/sentinel-tools.ts +2 -1
- package/src/tools.ts +14 -13
- package/src/trpc-index.ts +5 -4
- package/src/validate-features-runner.ts +5 -4
package/src/sentinel-db.ts
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
133
|
-
(SELECT COUNT(*) FROM
|
|
134
|
-
(SELECT COUNT(*) FROM
|
|
135
|
-
FROM
|
|
136
|
-
JOIN
|
|
137
|
-
WHERE
|
|
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
|
|
144
|
-
(SELECT COUNT(*) FROM
|
|
145
|
-
(SELECT COUNT(*) FROM
|
|
146
|
-
FROM
|
|
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 +=
|
|
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(
|
|
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
|
|
192
|
-
JOIN
|
|
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
|
|
202
|
-
JOIN
|
|
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(
|
|
223
|
-
const procedures = db.prepare(
|
|
224
|
-
const pages = db.prepare(
|
|
225
|
-
const dependencies = db.prepare(
|
|
226
|
-
const changelog = db.prepare(
|
|
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
|
|
239
|
+
SELECT s.* FROM ${t('sentinel')} s
|
|
239
240
|
WHERE s.status = 'active'
|
|
240
241
|
AND NOT EXISTS (
|
|
241
|
-
SELECT 1 FROM
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
397
|
-
const procedures = db.prepare(
|
|
398
|
-
const pages = db.prepare(
|
|
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(
|
|
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(
|
|
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(
|
|
504
|
-
const newComps = db.prepare(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
565
|
+
db.prepare(`DELETE FROM ${t('sentinel')} WHERE id = ?`).run(feature_id);
|
|
565
566
|
}
|
|
566
567
|
}
|
|
567
568
|
}
|
package/src/sentinel-scanner.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
package/src/sentinel-tools.ts
CHANGED
|
@@ -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(
|
|
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
|
}
|
package/src/tools.ts
CHANGED
|
@@ -39,6 +39,7 @@ import { getKnowledgeDb } from './knowledge-db.ts';
|
|
|
39
39
|
import { getPythonToolDefinitions, isPythonTool, handlePythonToolCall } from './python-tools.ts';
|
|
40
40
|
import { getConfig, getProjectRoot, getResolvedPaths } from './config.ts';
|
|
41
41
|
import { getCurrentTier, getToolTier, isToolAllowed, annotateToolDefinitions, getLicenseToolDefinitions, isLicenseTool, handleLicenseToolCall, isCloudFeatureAvailable } from './license.ts';
|
|
42
|
+
import { t } from './lib/sql-table-names.ts';
|
|
42
43
|
|
|
43
44
|
export interface ToolDefinition {
|
|
44
45
|
name: string;
|
|
@@ -572,7 +573,7 @@ function handleContext(file: string, dataDb: Database.Database, codegraphDb: Dat
|
|
|
572
573
|
|
|
573
574
|
// 5. Import edges
|
|
574
575
|
const imports = dataDb.prepare(
|
|
575
|
-
|
|
576
|
+
`SELECT target_file, imported_names FROM ${t('imports')} WHERE source_file = ? LIMIT 20`
|
|
576
577
|
).all(file) as { target_file: string; imported_names: string }[];
|
|
577
578
|
|
|
578
579
|
if (imports.length > 0) {
|
|
@@ -586,7 +587,7 @@ function handleContext(file: string, dataDb: Database.Database, codegraphDb: Dat
|
|
|
586
587
|
|
|
587
588
|
// 6. Imported BY
|
|
588
589
|
const importedBy = dataDb.prepare(
|
|
589
|
-
|
|
590
|
+
`SELECT source_file FROM ${t('imports')} WHERE target_file = ? LIMIT 20`
|
|
590
591
|
).all(file) as { source_file: string }[];
|
|
591
592
|
|
|
592
593
|
if (importedBy.length > 0) {
|
|
@@ -786,7 +787,7 @@ function handleTrpcMap(args: Record<string, unknown>, dataDb: Database.Database)
|
|
|
786
787
|
|
|
787
788
|
if (args.uncoupled) {
|
|
788
789
|
const uncoupled = dataDb.prepare(
|
|
789
|
-
|
|
790
|
+
`SELECT router_name, procedure_name, procedure_type, router_file FROM ${t('trpc_procedures')} WHERE has_ui_caller = 0 ORDER BY router_name, procedure_name`
|
|
790
791
|
).all() as { router_name: string; procedure_name: string; procedure_type: string; router_file: string }[];
|
|
791
792
|
|
|
792
793
|
lines.push(`## Uncoupled Procedures (${uncoupled.length} total)`);
|
|
@@ -803,7 +804,7 @@ function handleTrpcMap(args: Record<string, unknown>, dataDb: Database.Database)
|
|
|
803
804
|
}
|
|
804
805
|
} else if (args.router) {
|
|
805
806
|
const procs = dataDb.prepare(
|
|
806
|
-
|
|
807
|
+
`SELECT id, procedure_name, procedure_type, has_ui_caller FROM ${t('trpc_procedures')} WHERE router_name = ? ORDER BY procedure_name`
|
|
807
808
|
).all(args.router as string) as { id: number; procedure_name: string; procedure_type: string; has_ui_caller: number }[];
|
|
808
809
|
|
|
809
810
|
lines.push(`## Router: ${args.router} (${procs.length} procedures)`);
|
|
@@ -814,7 +815,7 @@ function handleTrpcMap(args: Record<string, unknown>, dataDb: Database.Database)
|
|
|
814
815
|
lines.push(`### ${args.router}.${proc.procedure_name} (${proc.procedure_type})${status}`);
|
|
815
816
|
|
|
816
817
|
const callSites = dataDb.prepare(
|
|
817
|
-
|
|
818
|
+
`SELECT file, line, call_pattern FROM ${t('trpc_call_sites')} WHERE procedure_id = ?`
|
|
818
819
|
).all(proc.id) as { file: string; line: number; call_pattern: string }[];
|
|
819
820
|
|
|
820
821
|
if (callSites.length > 0) {
|
|
@@ -829,7 +830,7 @@ function handleTrpcMap(args: Record<string, unknown>, dataDb: Database.Database)
|
|
|
829
830
|
}
|
|
830
831
|
} else if (args.procedure) {
|
|
831
832
|
const procs = dataDb.prepare(
|
|
832
|
-
|
|
833
|
+
`SELECT id, router_name, router_file, procedure_type, has_ui_caller FROM ${t('trpc_procedures')} WHERE procedure_name = ? ORDER BY router_name`
|
|
833
834
|
).all(args.procedure as string) as { id: number; router_name: string; router_file: string; procedure_type: string; has_ui_caller: number }[];
|
|
834
835
|
|
|
835
836
|
lines.push(`## Procedure "${args.procedure}" found in ${procs.length} routers`);
|
|
@@ -840,7 +841,7 @@ function handleTrpcMap(args: Record<string, unknown>, dataDb: Database.Database)
|
|
|
840
841
|
lines.push(`File: ${proc.router_file}`);
|
|
841
842
|
|
|
842
843
|
const callSites = dataDb.prepare(
|
|
843
|
-
|
|
844
|
+
`SELECT file, line, call_pattern FROM ${t('trpc_call_sites')} WHERE procedure_id = ?`
|
|
844
845
|
).all(proc.id) as { file: string; line: number; call_pattern: string }[];
|
|
845
846
|
|
|
846
847
|
if (callSites.length > 0) {
|
|
@@ -854,8 +855,8 @@ function handleTrpcMap(args: Record<string, unknown>, dataDb: Database.Database)
|
|
|
854
855
|
lines.push('');
|
|
855
856
|
}
|
|
856
857
|
} else {
|
|
857
|
-
const total = dataDb.prepare(
|
|
858
|
-
const coupled = dataDb.prepare(
|
|
858
|
+
const total = dataDb.prepare(`SELECT COUNT(*) as count FROM ${t('trpc_procedures')}`).get() as { count: number };
|
|
859
|
+
const coupled = dataDb.prepare(`SELECT COUNT(*) as count FROM ${t('trpc_procedures')} WHERE has_ui_caller = 1`).get() as { count: number };
|
|
859
860
|
const uncoupled = total.count - coupled.count;
|
|
860
861
|
|
|
861
862
|
// P-H009 (plan-stage-c-high-batch): when the index is empty, return an
|
|
@@ -893,11 +894,11 @@ function handleCouplingCheck(args: Record<string, unknown>, dataDb: Database.Dat
|
|
|
893
894
|
let uncoupledProcs;
|
|
894
895
|
if (stagedFiles) {
|
|
895
896
|
uncoupledProcs = dataDb.prepare(
|
|
896
|
-
`SELECT router_name, procedure_name, procedure_type, router_file FROM
|
|
897
|
+
`SELECT router_name, procedure_name, procedure_type, router_file FROM ${t('trpc_procedures')} WHERE has_ui_caller = 0 AND router_file IN (${stagedFiles.map(() => '?').join(',')})`
|
|
897
898
|
).all(...stagedFiles) as { router_name: string; procedure_name: string; procedure_type: string; router_file: string }[];
|
|
898
899
|
} else {
|
|
899
900
|
uncoupledProcs = dataDb.prepare(
|
|
900
|
-
|
|
901
|
+
`SELECT router_name, procedure_name, procedure_type, router_file FROM ${t('trpc_procedures')} WHERE has_ui_caller = 0`
|
|
901
902
|
).all() as { router_name: string; procedure_name: string; procedure_type: string; router_file: string }[];
|
|
902
903
|
}
|
|
903
904
|
|
|
@@ -922,7 +923,7 @@ function handleCouplingCheck(args: Record<string, unknown>, dataDb: Database.Dat
|
|
|
922
923
|
const pageImports = new Set<string>();
|
|
923
924
|
for (const page of allPages) {
|
|
924
925
|
const imports = dataDb.prepare(
|
|
925
|
-
|
|
926
|
+
`SELECT target_file FROM ${t('imports')} WHERE source_file = ?`
|
|
926
927
|
).all(page.path) as { target_file: string }[];
|
|
927
928
|
for (const imp of imports) {
|
|
928
929
|
pageImports.add(imp.target_file);
|
|
@@ -1009,7 +1010,7 @@ function handleImpact(file: string, dataDb: Database.Database, codegraphDb: Data
|
|
|
1009
1010
|
lines.push(`### Domain: ${fileDomain}`);
|
|
1010
1011
|
|
|
1011
1012
|
const imports = dataDb.prepare(
|
|
1012
|
-
|
|
1013
|
+
`SELECT target_file FROM ${t('imports')} WHERE source_file = ?`
|
|
1013
1014
|
).all(file) as { target_file: string }[];
|
|
1014
1015
|
|
|
1015
1016
|
const crossings: string[] = [];
|
package/src/trpc-index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
|
5
5
|
import { resolve, join } from 'path';
|
|
6
6
|
import type Database from 'better-sqlite3';
|
|
7
7
|
import { getConfig, getResolvedPaths, getProjectRoot } from './config.ts';
|
|
8
|
+
import { t } from './lib/sql-table-names.ts';
|
|
8
9
|
|
|
9
10
|
interface RouterMapping {
|
|
10
11
|
key: string; // e.g., "orders" (used as api.orders.*)
|
|
@@ -170,16 +171,16 @@ function escapeRegex(str: string): string {
|
|
|
170
171
|
*/
|
|
171
172
|
export function buildTrpcIndex(dataDb: Database.Database): { totalProcedures: number; withCallers: number; withoutCallers: number } {
|
|
172
173
|
// Clear existing data
|
|
173
|
-
dataDb.exec(
|
|
174
|
-
dataDb.exec(
|
|
174
|
+
dataDb.exec(`DELETE FROM ${t('trpc_call_sites')}`);
|
|
175
|
+
dataDb.exec(`DELETE FROM ${t('trpc_procedures')}`);
|
|
175
176
|
|
|
176
177
|
const routerMappings = parseRootRouter();
|
|
177
178
|
|
|
178
179
|
const insertProc = dataDb.prepare(
|
|
179
|
-
|
|
180
|
+
`INSERT INTO ${t('trpc_procedures')} (router_file, router_name, procedure_name, procedure_type, has_ui_caller) VALUES (?, ?, ?, ?, ?)`
|
|
180
181
|
);
|
|
181
182
|
const insertCallSite = dataDb.prepare(
|
|
182
|
-
|
|
183
|
+
`INSERT INTO ${t('trpc_call_sites')} (procedure_id, file, line, call_pattern) VALUES (?, ?, ?, ?)`
|
|
183
184
|
);
|
|
184
185
|
|
|
185
186
|
let totalProcedures = 0;
|
|
@@ -12,6 +12,7 @@ import Database from 'better-sqlite3';
|
|
|
12
12
|
import { resolve } from 'path';
|
|
13
13
|
import { existsSync } from 'fs';
|
|
14
14
|
import { getProjectRoot, getResolvedPaths } from './config.ts';
|
|
15
|
+
import { t } from './lib/sql-table-names.ts';
|
|
15
16
|
|
|
16
17
|
const PROJECT_ROOT = getProjectRoot();
|
|
17
18
|
|
|
@@ -35,7 +36,7 @@ function main(): void {
|
|
|
35
36
|
try {
|
|
36
37
|
// Check if sentinel tables exist
|
|
37
38
|
const tableExists = db.prepare(
|
|
38
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name
|
|
39
|
+
"SELECT name FROM sqlite_master WHERE type='table' AND name=`${t('sentinel')}`"
|
|
39
40
|
).get();
|
|
40
41
|
|
|
41
42
|
if (!tableExists) {
|
|
@@ -45,7 +46,7 @@ function main(): void {
|
|
|
45
46
|
|
|
46
47
|
// Count active features
|
|
47
48
|
const totalActive = db.prepare(
|
|
48
|
-
|
|
49
|
+
`SELECT COUNT(*) as count FROM ${t('sentinel')} WHERE status = 'active'`
|
|
49
50
|
).get() as { count: number };
|
|
50
51
|
|
|
51
52
|
if (totalActive.count === 0) {
|
|
@@ -56,8 +57,8 @@ function main(): void {
|
|
|
56
57
|
// Check for orphaned features (active features with missing primary component files)
|
|
57
58
|
const orphaned = db.prepare(`
|
|
58
59
|
SELECT s.feature_key, s.title, s.priority, c.component_file
|
|
59
|
-
FROM
|
|
60
|
-
JOIN
|
|
60
|
+
FROM ${t('sentinel')} s
|
|
61
|
+
JOIN ${t('sentinel_components')} c ON c.feature_id = s.id AND c.is_primary = 1
|
|
61
62
|
WHERE s.status = 'active'
|
|
62
63
|
ORDER BY s.priority DESC, s.domain, s.feature_key
|
|
63
64
|
`).all() as { feature_key: string; title: string; priority: string; component_file: string }[];
|