@massu/core 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/hooks/cost-tracker.js +23 -35
- package/dist/hooks/post-edit-context.js +2 -2
- package/dist/hooks/post-tool-use.js +43 -58
- package/dist/hooks/pre-compact.js +23 -38
- package/dist/hooks/pre-delete-check.js +18 -31
- package/dist/hooks/quality-event.js +23 -35
- package/dist/hooks/session-end.js +62 -78
- package/dist/hooks/session-start.js +33 -42
- package/dist/hooks/user-prompt.js +23 -38
- package/package.json +8 -14
- package/src/adr-generator.ts +9 -2
- package/src/analytics.ts +9 -3
- package/src/audit-trail.ts +10 -3
- package/src/cloud-sync.ts +14 -18
- package/src/commands/init.ts +1 -5
- package/src/cost-tracker.ts +11 -6
- package/src/dependency-scorer.ts +9 -2
- package/src/docs-tools.ts +13 -10
- package/src/hooks/post-edit-context.ts +3 -3
- package/src/hooks/session-end.ts +3 -3
- package/src/hooks/session-start.ts +2 -2
- package/src/memory-db.ts +1351 -23
- package/src/memory-tools.ts +14 -15
- package/src/observability-tools.ts +13 -2
- package/src/prompt-analyzer.ts +9 -2
- package/src/regression-detector.ts +9 -3
- package/src/security-scorer.ts +9 -2
- package/src/sentinel-db.ts +43 -88
- package/src/sentinel-tools.ts +8 -11
- package/src/server.ts +1 -2
- package/src/team-knowledge.ts +9 -2
- package/src/tools.ts +771 -35
- package/src/validate-features-runner.ts +0 -1
- package/src/validation-engine.ts +9 -2
- package/dist/cli.js +0 -7890
- package/dist/server.js +0 -7008
- package/src/__tests__/adr-generator.test.ts +0 -260
- package/src/__tests__/analytics.test.ts +0 -282
- package/src/__tests__/audit-trail.test.ts +0 -382
- package/src/__tests__/backfill-sessions.test.ts +0 -690
- package/src/__tests__/cli.test.ts +0 -290
- package/src/__tests__/cloud-sync.test.ts +0 -261
- package/src/__tests__/config-sections.test.ts +0 -359
- package/src/__tests__/config.test.ts +0 -732
- package/src/__tests__/cost-tracker.test.ts +0 -348
- package/src/__tests__/db.test.ts +0 -177
- package/src/__tests__/dependency-scorer.test.ts +0 -325
- package/src/__tests__/docs-integration.test.ts +0 -178
- package/src/__tests__/docs-tools.test.ts +0 -199
- package/src/__tests__/domains.test.ts +0 -236
- package/src/__tests__/hooks.test.ts +0 -221
- package/src/__tests__/import-resolver.test.ts +0 -95
- package/src/__tests__/integration/path-traversal.test.ts +0 -134
- package/src/__tests__/integration/pricing-consistency.test.ts +0 -88
- package/src/__tests__/integration/tool-registration.test.ts +0 -146
- package/src/__tests__/memory-db.test.ts +0 -404
- package/src/__tests__/memory-enhancements.test.ts +0 -316
- package/src/__tests__/memory-tools.test.ts +0 -199
- package/src/__tests__/middleware-tree.test.ts +0 -177
- package/src/__tests__/observability-tools.test.ts +0 -595
- package/src/__tests__/observability.test.ts +0 -437
- package/src/__tests__/observation-extractor.test.ts +0 -167
- package/src/__tests__/page-deps.test.ts +0 -60
- package/src/__tests__/prompt-analyzer.test.ts +0 -298
- package/src/__tests__/regression-detector.test.ts +0 -295
- package/src/__tests__/rules.test.ts +0 -87
- package/src/__tests__/schema-mapper.test.ts +0 -29
- package/src/__tests__/security-scorer.test.ts +0 -238
- package/src/__tests__/security-utils.test.ts +0 -175
- package/src/__tests__/sentinel-db.test.ts +0 -491
- package/src/__tests__/sentinel-scanner.test.ts +0 -750
- package/src/__tests__/sentinel-tools.test.ts +0 -324
- package/src/__tests__/sentinel-types.test.ts +0 -750
- package/src/__tests__/server.test.ts +0 -452
- package/src/__tests__/session-archiver.test.ts +0 -524
- package/src/__tests__/session-state-generator.test.ts +0 -900
- package/src/__tests__/team-knowledge.test.ts +0 -327
- package/src/__tests__/tools.test.ts +0 -340
- package/src/__tests__/transcript-parser.test.ts +0 -195
- package/src/__tests__/trpc-index.test.ts +0 -25
- package/src/__tests__/validate-features-runner.test.ts +0 -517
- package/src/__tests__/validation-engine.test.ts +0 -300
- package/src/core-tools.ts +0 -685
- package/src/memory-queries.ts +0 -804
- package/src/memory-schema.ts +0 -546
- package/src/tool-helpers.ts +0 -41
|
@@ -1,491 +0,0 @@
|
|
|
1
|
-
// Copyright (c) 2026 Massu. All rights reserved.
|
|
2
|
-
// Licensed under BSL 1.1 - see LICENSE file for details.
|
|
3
|
-
|
|
4
|
-
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
5
|
-
import Database from 'better-sqlite3';
|
|
6
|
-
import {
|
|
7
|
-
upsertFeature,
|
|
8
|
-
getFeature,
|
|
9
|
-
getFeatureById,
|
|
10
|
-
searchFeatures,
|
|
11
|
-
getFeatureImpact,
|
|
12
|
-
linkComponent,
|
|
13
|
-
linkProcedure,
|
|
14
|
-
linkPage,
|
|
15
|
-
logChange,
|
|
16
|
-
validateFeatures,
|
|
17
|
-
checkParity,
|
|
18
|
-
clearAutoDiscoveredFeatures,
|
|
19
|
-
bulkUpsertFeatures,
|
|
20
|
-
} from '../sentinel-db.ts';
|
|
21
|
-
import type { FeatureInput } from '../sentinel-types.ts';
|
|
22
|
-
|
|
23
|
-
function createTestDb(): Database.Database {
|
|
24
|
-
const db = new Database(':memory:');
|
|
25
|
-
db.pragma('journal_mode = WAL');
|
|
26
|
-
db.pragma('foreign_keys = ON');
|
|
27
|
-
|
|
28
|
-
// Initialize sentinel schema
|
|
29
|
-
db.exec(`
|
|
30
|
-
CREATE TABLE IF NOT EXISTS massu_sentinel (
|
|
31
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
-
feature_key TEXT UNIQUE NOT NULL,
|
|
33
|
-
domain TEXT NOT NULL,
|
|
34
|
-
subdomain TEXT,
|
|
35
|
-
title TEXT NOT NULL,
|
|
36
|
-
description TEXT,
|
|
37
|
-
status TEXT NOT NULL DEFAULT 'active'
|
|
38
|
-
CHECK(status IN ('planned', 'active', 'deprecated', 'removed')),
|
|
39
|
-
priority TEXT DEFAULT 'standard'
|
|
40
|
-
CHECK(priority IN ('critical', 'standard', 'nice-to-have')),
|
|
41
|
-
portal_scope TEXT DEFAULT '[]',
|
|
42
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
43
|
-
updated_at TEXT DEFAULT (datetime('now')),
|
|
44
|
-
removed_at TEXT,
|
|
45
|
-
removed_reason TEXT
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
CREATE INDEX IF NOT EXISTS idx_sentinel_domain ON massu_sentinel(domain);
|
|
49
|
-
CREATE INDEX IF NOT EXISTS idx_sentinel_status ON massu_sentinel(status);
|
|
50
|
-
CREATE INDEX IF NOT EXISTS idx_sentinel_key ON massu_sentinel(feature_key);
|
|
51
|
-
|
|
52
|
-
CREATE TABLE IF NOT EXISTS massu_sentinel_components (
|
|
53
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
54
|
-
feature_id INTEGER NOT NULL REFERENCES massu_sentinel(id) ON DELETE CASCADE,
|
|
55
|
-
component_file TEXT NOT NULL,
|
|
56
|
-
component_name TEXT,
|
|
57
|
-
role TEXT DEFAULT 'implementation'
|
|
58
|
-
CHECK(role IN ('implementation', 'ui', 'data', 'utility')),
|
|
59
|
-
is_primary BOOLEAN DEFAULT 0,
|
|
60
|
-
UNIQUE(feature_id, component_file, component_name)
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
CREATE TABLE IF NOT EXISTS massu_sentinel_procedures (
|
|
64
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
65
|
-
feature_id INTEGER NOT NULL REFERENCES massu_sentinel(id) ON DELETE CASCADE,
|
|
66
|
-
router_name TEXT NOT NULL,
|
|
67
|
-
procedure_name TEXT NOT NULL,
|
|
68
|
-
procedure_type TEXT,
|
|
69
|
-
UNIQUE(feature_id, router_name, procedure_name)
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
CREATE TABLE IF NOT EXISTS massu_sentinel_pages (
|
|
73
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
74
|
-
feature_id INTEGER NOT NULL REFERENCES massu_sentinel(id) ON DELETE CASCADE,
|
|
75
|
-
page_route TEXT NOT NULL,
|
|
76
|
-
portal TEXT,
|
|
77
|
-
UNIQUE(feature_id, page_route, portal)
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
CREATE TABLE IF NOT EXISTS massu_sentinel_deps (
|
|
81
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
82
|
-
feature_id INTEGER NOT NULL REFERENCES massu_sentinel(id) ON DELETE CASCADE,
|
|
83
|
-
depends_on_feature_id INTEGER NOT NULL REFERENCES massu_sentinel(id) ON DELETE CASCADE,
|
|
84
|
-
dependency_type TEXT DEFAULT 'requires'
|
|
85
|
-
CHECK(dependency_type IN ('requires', 'enhances', 'replaces')),
|
|
86
|
-
UNIQUE(feature_id, depends_on_feature_id)
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
CREATE TABLE IF NOT EXISTS massu_sentinel_changelog (
|
|
90
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
91
|
-
feature_id INTEGER NOT NULL REFERENCES massu_sentinel(id) ON DELETE CASCADE,
|
|
92
|
-
change_type TEXT NOT NULL,
|
|
93
|
-
changed_by TEXT,
|
|
94
|
-
change_detail TEXT,
|
|
95
|
-
commit_hash TEXT,
|
|
96
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
CREATE INDEX IF NOT EXISTS idx_sentinel_components_file ON massu_sentinel_components(component_file);
|
|
100
|
-
CREATE INDEX IF NOT EXISTS idx_sentinel_procedures_router ON massu_sentinel_procedures(router_name);
|
|
101
|
-
CREATE INDEX IF NOT EXISTS idx_sentinel_pages_route ON massu_sentinel_pages(page_route);
|
|
102
|
-
CREATE INDEX IF NOT EXISTS idx_sentinel_changelog_feature ON massu_sentinel_changelog(feature_id);
|
|
103
|
-
`);
|
|
104
|
-
|
|
105
|
-
// FTS5 table
|
|
106
|
-
db.exec(`
|
|
107
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS massu_sentinel_fts USING fts5(
|
|
108
|
-
feature_key, title, description, domain, subdomain,
|
|
109
|
-
content=massu_sentinel, content_rowid=id
|
|
110
|
-
);
|
|
111
|
-
`);
|
|
112
|
-
|
|
113
|
-
// FTS5 triggers
|
|
114
|
-
db.exec(`
|
|
115
|
-
CREATE TRIGGER IF NOT EXISTS massu_sentinel_ai AFTER INSERT ON massu_sentinel BEGIN
|
|
116
|
-
INSERT INTO massu_sentinel_fts(rowid, feature_key, title, description, domain, subdomain)
|
|
117
|
-
VALUES (new.id, new.feature_key, new.title, new.description, new.domain, new.subdomain);
|
|
118
|
-
END;
|
|
119
|
-
|
|
120
|
-
CREATE TRIGGER IF NOT EXISTS massu_sentinel_ad AFTER DELETE ON massu_sentinel BEGIN
|
|
121
|
-
INSERT INTO massu_sentinel_fts(massu_sentinel_fts, rowid, feature_key, title, description, domain, subdomain)
|
|
122
|
-
VALUES ('delete', old.id, old.feature_key, old.title, old.description, old.domain, old.subdomain);
|
|
123
|
-
END;
|
|
124
|
-
|
|
125
|
-
CREATE TRIGGER IF NOT EXISTS massu_sentinel_au AFTER UPDATE ON massu_sentinel BEGIN
|
|
126
|
-
INSERT INTO massu_sentinel_fts(massu_sentinel_fts, rowid, feature_key, title, description, domain, subdomain)
|
|
127
|
-
VALUES ('delete', old.id, old.feature_key, old.title, old.description, old.domain, old.subdomain);
|
|
128
|
-
INSERT INTO massu_sentinel_fts(rowid, feature_key, title, description, domain, subdomain)
|
|
129
|
-
VALUES (new.id, new.feature_key, new.title, new.description, new.domain, new.subdomain);
|
|
130
|
-
END;
|
|
131
|
-
`);
|
|
132
|
-
|
|
133
|
-
return db;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
describe('Sentinel Database', () => {
|
|
137
|
-
let db: Database.Database;
|
|
138
|
-
|
|
139
|
-
beforeEach(() => {
|
|
140
|
-
db = createTestDb();
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
afterEach(() => {
|
|
144
|
-
db.close();
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe('upsertFeature', () => {
|
|
148
|
-
it('inserts a new feature', () => {
|
|
149
|
-
const input: FeatureInput = {
|
|
150
|
-
feature_key: 'test.feature-1',
|
|
151
|
-
domain: 'testing',
|
|
152
|
-
subdomain: 'unit',
|
|
153
|
-
title: 'Test Feature 1',
|
|
154
|
-
description: 'A test feature',
|
|
155
|
-
status: 'active',
|
|
156
|
-
priority: 'standard',
|
|
157
|
-
portal_scope: ['internal'],
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const id = upsertFeature(db, input);
|
|
161
|
-
expect(id).toBeGreaterThan(0);
|
|
162
|
-
|
|
163
|
-
const feature = getFeature(db, 'test.feature-1');
|
|
164
|
-
expect(feature).toBeTruthy();
|
|
165
|
-
expect(feature?.title).toBe('Test Feature 1');
|
|
166
|
-
expect(feature?.domain).toBe('testing');
|
|
167
|
-
expect(feature?.portal_scope).toEqual(['internal']);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('updates an existing feature', () => {
|
|
171
|
-
const input: FeatureInput = {
|
|
172
|
-
feature_key: 'test.feature-1',
|
|
173
|
-
domain: 'testing',
|
|
174
|
-
title: 'Original Title',
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const id1 = upsertFeature(db, input);
|
|
178
|
-
|
|
179
|
-
const updateInput: FeatureInput = {
|
|
180
|
-
feature_key: 'test.feature-1',
|
|
181
|
-
domain: 'testing',
|
|
182
|
-
title: 'Updated Title',
|
|
183
|
-
status: 'deprecated',
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const id2 = upsertFeature(db, updateInput);
|
|
187
|
-
expect(id2).toBe(id1);
|
|
188
|
-
|
|
189
|
-
const feature = getFeature(db, 'test.feature-1');
|
|
190
|
-
expect(feature?.title).toBe('Updated Title');
|
|
191
|
-
expect(feature?.status).toBe('deprecated');
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('preserves existing status if not provided', () => {
|
|
195
|
-
const input1: FeatureInput = {
|
|
196
|
-
feature_key: 'test.feature-1',
|
|
197
|
-
domain: 'testing',
|
|
198
|
-
title: 'Feature 1',
|
|
199
|
-
status: 'deprecated',
|
|
200
|
-
};
|
|
201
|
-
upsertFeature(db, input1);
|
|
202
|
-
|
|
203
|
-
const input2: FeatureInput = {
|
|
204
|
-
feature_key: 'test.feature-1',
|
|
205
|
-
domain: 'testing',
|
|
206
|
-
title: 'Feature 1 Updated',
|
|
207
|
-
};
|
|
208
|
-
upsertFeature(db, input2);
|
|
209
|
-
|
|
210
|
-
const feature = getFeature(db, 'test.feature-1');
|
|
211
|
-
expect(feature?.status).toBe('deprecated');
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
describe('getFeature and getFeatureById', () => {
|
|
216
|
-
it('retrieves feature by key', () => {
|
|
217
|
-
const input: FeatureInput = {
|
|
218
|
-
feature_key: 'test.feature-1',
|
|
219
|
-
domain: 'testing',
|
|
220
|
-
title: 'Test Feature 1',
|
|
221
|
-
};
|
|
222
|
-
upsertFeature(db, input);
|
|
223
|
-
|
|
224
|
-
const feature = getFeature(db, 'test.feature-1');
|
|
225
|
-
expect(feature).toBeTruthy();
|
|
226
|
-
expect(feature?.feature_key).toBe('test.feature-1');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('retrieves feature by id', () => {
|
|
230
|
-
const input: FeatureInput = {
|
|
231
|
-
feature_key: 'test.feature-1',
|
|
232
|
-
domain: 'testing',
|
|
233
|
-
title: 'Test Feature 1',
|
|
234
|
-
};
|
|
235
|
-
const id = upsertFeature(db, input);
|
|
236
|
-
|
|
237
|
-
const feature = getFeatureById(db, id);
|
|
238
|
-
expect(feature).toBeTruthy();
|
|
239
|
-
expect(feature?.id).toBe(id);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('returns null for non-existent feature', () => {
|
|
243
|
-
expect(getFeature(db, 'nonexistent')).toBeNull();
|
|
244
|
-
expect(getFeatureById(db, 9999)).toBeNull();
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe('searchFeatures', () => {
|
|
249
|
-
beforeEach(() => {
|
|
250
|
-
upsertFeature(db, {
|
|
251
|
-
feature_key: 'auth.login',
|
|
252
|
-
domain: 'auth',
|
|
253
|
-
title: 'User Login',
|
|
254
|
-
description: 'Login feature with email',
|
|
255
|
-
status: 'active',
|
|
256
|
-
});
|
|
257
|
-
upsertFeature(db, {
|
|
258
|
-
feature_key: 'auth.register',
|
|
259
|
-
domain: 'auth',
|
|
260
|
-
title: 'User Registration',
|
|
261
|
-
description: 'Registration with validation',
|
|
262
|
-
status: 'active',
|
|
263
|
-
});
|
|
264
|
-
upsertFeature(db, {
|
|
265
|
-
feature_key: 'product.list',
|
|
266
|
-
domain: 'product',
|
|
267
|
-
title: 'Product Listing',
|
|
268
|
-
status: 'deprecated',
|
|
269
|
-
});
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
it('searches with FTS5 query', () => {
|
|
273
|
-
const results = searchFeatures(db, 'login');
|
|
274
|
-
expect(results.length).toBe(1);
|
|
275
|
-
expect(results[0].feature_key).toBe('auth.login');
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
it('filters by domain', () => {
|
|
279
|
-
const results = searchFeatures(db, '', { domain: 'auth' });
|
|
280
|
-
expect(results.length).toBe(2);
|
|
281
|
-
expect(results.every(f => f.domain === 'auth')).toBe(true);
|
|
282
|
-
});
|
|
283
|
-
|
|
284
|
-
it('filters by status', () => {
|
|
285
|
-
const results = searchFeatures(db, '', { status: 'deprecated' });
|
|
286
|
-
expect(results.length).toBe(1);
|
|
287
|
-
expect(results[0].feature_key).toBe('product.list');
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it('returns features with counts', () => {
|
|
291
|
-
const id = upsertFeature(db, {
|
|
292
|
-
feature_key: 'test.with-links',
|
|
293
|
-
domain: 'test',
|
|
294
|
-
title: 'Feature with links',
|
|
295
|
-
});
|
|
296
|
-
linkComponent(db, id, 'src/test.ts', 'TestComponent', 'implementation', true);
|
|
297
|
-
linkProcedure(db, id, 'testRouter', 'testProc', 'query');
|
|
298
|
-
linkPage(db, id, '/test', 'internal');
|
|
299
|
-
|
|
300
|
-
const results = searchFeatures(db, '', { domain: 'test' });
|
|
301
|
-
expect(results.length).toBe(1);
|
|
302
|
-
expect(results[0].component_count).toBe(1);
|
|
303
|
-
expect(results[0].procedure_count).toBe(1);
|
|
304
|
-
expect(results[0].page_count).toBe(1);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
describe('linkComponent, linkProcedure, linkPage', () => {
|
|
309
|
-
let featureId: number;
|
|
310
|
-
|
|
311
|
-
beforeEach(() => {
|
|
312
|
-
featureId = upsertFeature(db, {
|
|
313
|
-
feature_key: 'test.feature-1',
|
|
314
|
-
domain: 'testing',
|
|
315
|
-
title: 'Test Feature',
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
it('links a component to a feature', () => {
|
|
320
|
-
linkComponent(db, featureId, 'src/components/Test.tsx', 'TestComponent', 'ui', true);
|
|
321
|
-
|
|
322
|
-
const components = db.prepare('SELECT * FROM massu_sentinel_components WHERE feature_id = ?').all(featureId);
|
|
323
|
-
expect(components.length).toBe(1);
|
|
324
|
-
expect(components[0]).toMatchObject({
|
|
325
|
-
component_file: 'src/components/Test.tsx',
|
|
326
|
-
component_name: 'TestComponent',
|
|
327
|
-
role: 'ui',
|
|
328
|
-
is_primary: 1,
|
|
329
|
-
});
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
it('links a procedure to a feature', () => {
|
|
333
|
-
linkProcedure(db, featureId, 'orders', 'getOrder', 'query');
|
|
334
|
-
|
|
335
|
-
const procedures = db.prepare('SELECT * FROM massu_sentinel_procedures WHERE feature_id = ?').all(featureId);
|
|
336
|
-
expect(procedures.length).toBe(1);
|
|
337
|
-
expect(procedures[0]).toMatchObject({
|
|
338
|
-
router_name: 'orders',
|
|
339
|
-
procedure_name: 'getOrder',
|
|
340
|
-
procedure_type: 'query',
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
it('links a page to a feature', () => {
|
|
345
|
-
linkPage(db, featureId, '/orders/[id]', 'internal');
|
|
346
|
-
|
|
347
|
-
const pages = db.prepare('SELECT * FROM massu_sentinel_pages WHERE feature_id = ?').all(featureId);
|
|
348
|
-
expect(pages.length).toBe(1);
|
|
349
|
-
expect(pages[0]).toMatchObject({
|
|
350
|
-
page_route: '/orders/[id]',
|
|
351
|
-
portal: 'internal',
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
describe('logChange', () => {
|
|
357
|
-
it('logs a change to the changelog', () => {
|
|
358
|
-
const featureId = upsertFeature(db, {
|
|
359
|
-
feature_key: 'test.feature-1',
|
|
360
|
-
domain: 'testing',
|
|
361
|
-
title: 'Test Feature',
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
logChange(db, featureId, 'created', 'Initial creation', 'abc123', 'test-user');
|
|
365
|
-
|
|
366
|
-
const changes = db.prepare('SELECT * FROM massu_sentinel_changelog WHERE feature_id = ?').all(featureId);
|
|
367
|
-
expect(changes.length).toBe(1);
|
|
368
|
-
expect(changes[0]).toMatchObject({
|
|
369
|
-
change_type: 'created',
|
|
370
|
-
changed_by: 'test-user',
|
|
371
|
-
change_detail: 'Initial creation',
|
|
372
|
-
commit_hash: 'abc123',
|
|
373
|
-
});
|
|
374
|
-
});
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
describe('getFeatureImpact', () => {
|
|
378
|
-
it('identifies orphaned features', () => {
|
|
379
|
-
const featureId = upsertFeature(db, {
|
|
380
|
-
feature_key: 'test.feature-1',
|
|
381
|
-
domain: 'testing',
|
|
382
|
-
title: 'Test Feature',
|
|
383
|
-
status: 'active',
|
|
384
|
-
priority: 'critical',
|
|
385
|
-
});
|
|
386
|
-
linkComponent(db, featureId, 'src/test.ts', 'TestComponent', 'implementation', true);
|
|
387
|
-
|
|
388
|
-
const report = getFeatureImpact(db, ['src/test.ts']);
|
|
389
|
-
expect(report.orphaned.length).toBe(1);
|
|
390
|
-
expect(report.orphaned[0].feature.feature_key).toBe('test.feature-1');
|
|
391
|
-
expect(report.blocked).toBe(true);
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
it('identifies degraded features', () => {
|
|
395
|
-
const featureId = upsertFeature(db, {
|
|
396
|
-
feature_key: 'test.feature-1',
|
|
397
|
-
domain: 'testing',
|
|
398
|
-
title: 'Test Feature',
|
|
399
|
-
status: 'active',
|
|
400
|
-
});
|
|
401
|
-
linkComponent(db, featureId, 'src/primary.ts', 'Primary', 'implementation', true);
|
|
402
|
-
linkComponent(db, featureId, 'src/helper.ts', 'Helper', 'utility', false);
|
|
403
|
-
|
|
404
|
-
const report = getFeatureImpact(db, ['src/helper.ts']);
|
|
405
|
-
expect(report.degraded.length).toBe(1);
|
|
406
|
-
expect(report.degraded[0].feature.feature_key).toBe('test.feature-1');
|
|
407
|
-
expect(report.degraded[0].affected_files).toEqual(['src/helper.ts']);
|
|
408
|
-
expect(report.degraded[0].remaining_files).toEqual(['src/primary.ts']);
|
|
409
|
-
});
|
|
410
|
-
});
|
|
411
|
-
|
|
412
|
-
describe('validateFeatures', () => {
|
|
413
|
-
it('validates features against filesystem', () => {
|
|
414
|
-
upsertFeature(db, {
|
|
415
|
-
feature_key: 'test.feature-1',
|
|
416
|
-
domain: 'testing',
|
|
417
|
-
title: 'Test Feature',
|
|
418
|
-
status: 'active',
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
const report = validateFeatures(db);
|
|
422
|
-
expect(report.alive).toBeGreaterThanOrEqual(0);
|
|
423
|
-
expect(report.orphaned).toBeGreaterThanOrEqual(0);
|
|
424
|
-
expect(report.degraded).toBeGreaterThanOrEqual(0);
|
|
425
|
-
expect(report.details).toBeTruthy();
|
|
426
|
-
});
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
describe('checkParity', () => {
|
|
430
|
-
it('compares old and new file sets', () => {
|
|
431
|
-
const featureId = upsertFeature(db, {
|
|
432
|
-
feature_key: 'test.feature-1',
|
|
433
|
-
domain: 'testing',
|
|
434
|
-
title: 'Test Feature',
|
|
435
|
-
});
|
|
436
|
-
linkComponent(db, featureId, 'src/old.ts', null, 'implementation', true);
|
|
437
|
-
linkComponent(db, featureId, 'src/new.ts', null, 'implementation', true);
|
|
438
|
-
|
|
439
|
-
const report = checkParity(db, ['src/old.ts'], ['src/new.ts']);
|
|
440
|
-
expect(report.done.length).toBe(1);
|
|
441
|
-
expect(report.parity_percentage).toBeGreaterThan(0);
|
|
442
|
-
});
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
describe('clearAutoDiscoveredFeatures', () => {
|
|
446
|
-
it('clears auto-discovered features', () => {
|
|
447
|
-
const featureId = upsertFeature(db, {
|
|
448
|
-
feature_key: 'test.auto-feature',
|
|
449
|
-
domain: 'testing',
|
|
450
|
-
title: 'Auto Feature',
|
|
451
|
-
});
|
|
452
|
-
logChange(db, featureId, 'created', null, undefined, 'scanner');
|
|
453
|
-
|
|
454
|
-
clearAutoDiscoveredFeatures(db);
|
|
455
|
-
|
|
456
|
-
const feature = getFeature(db, 'test.auto-feature');
|
|
457
|
-
expect(feature).toBeNull();
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
it('preserves manually changed features', () => {
|
|
461
|
-
const featureId = upsertFeature(db, {
|
|
462
|
-
feature_key: 'test.manual-feature',
|
|
463
|
-
domain: 'testing',
|
|
464
|
-
title: 'Manual Feature',
|
|
465
|
-
});
|
|
466
|
-
logChange(db, featureId, 'created', null, undefined, 'scanner');
|
|
467
|
-
logChange(db, featureId, 'updated', null, undefined, 'user');
|
|
468
|
-
|
|
469
|
-
clearAutoDiscoveredFeatures(db);
|
|
470
|
-
|
|
471
|
-
const feature = getFeature(db, 'test.manual-feature');
|
|
472
|
-
expect(feature).toBeTruthy();
|
|
473
|
-
});
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
describe('bulkUpsertFeatures', () => {
|
|
477
|
-
it('inserts multiple features in a transaction', () => {
|
|
478
|
-
const features: FeatureInput[] = [
|
|
479
|
-
{ feature_key: 'bulk.1', domain: 'bulk', title: 'Bulk 1' },
|
|
480
|
-
{ feature_key: 'bulk.2', domain: 'bulk', title: 'Bulk 2' },
|
|
481
|
-
{ feature_key: 'bulk.3', domain: 'bulk', title: 'Bulk 3' },
|
|
482
|
-
];
|
|
483
|
-
|
|
484
|
-
const count = bulkUpsertFeatures(db, features);
|
|
485
|
-
expect(count).toBe(3);
|
|
486
|
-
|
|
487
|
-
const results = searchFeatures(db, '', { domain: 'bulk' });
|
|
488
|
-
expect(results.length).toBe(3);
|
|
489
|
-
});
|
|
490
|
-
});
|
|
491
|
-
});
|