bmad-fh 6.0.0-alpha.23.02a963fa → 6.0.0-alpha.23.599980af
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 +116 -13
- package/package.json +1 -1
- package/src/core/lib/scope/scope-manager.js +37 -4
- package/test/test-scope-cli.js +1306 -0
- package/test/test-scope-e2e.js +13 -17
- package/test/test-scope-system.js +115 -95
- package/tools/cli/commands/scope.js +1135 -43
- package/tools/cli/installers/lib/modules/manager.js +6 -2
package/test/test-scope-e2e.js
CHANGED
|
@@ -214,9 +214,9 @@ async function testParallelScopeWorkflow() {
|
|
|
214
214
|
projectRoot: tmpDir,
|
|
215
215
|
});
|
|
216
216
|
|
|
217
|
-
// Payments scope can read auth scope
|
|
217
|
+
// Payments scope can read auth scope - canRead returns {allowed, reason}
|
|
218
218
|
assertTrue(
|
|
219
|
-
resolver.canRead(path.join(tmpDir, '_bmad-output', 'auth', 'planning-artifacts', 'prd.md')),
|
|
219
|
+
resolver.canRead(path.join(tmpDir, '_bmad-output', 'auth', 'planning-artifacts', 'prd.md')).allowed,
|
|
220
220
|
'Should allow cross-scope read',
|
|
221
221
|
);
|
|
222
222
|
});
|
|
@@ -229,9 +229,9 @@ async function testParallelScopeWorkflow() {
|
|
|
229
229
|
isolationMode: 'strict',
|
|
230
230
|
});
|
|
231
231
|
|
|
232
|
-
// Payments scope cannot write to auth scope
|
|
232
|
+
// Payments scope cannot write to auth scope - canWrite returns {allowed, reason, warning}
|
|
233
233
|
assertFalse(
|
|
234
|
-
resolver.canWrite(path.join(tmpDir, '_bmad-output', 'auth', 'planning-artifacts', 'new.md')),
|
|
234
|
+
resolver.canWrite(path.join(tmpDir, '_bmad-output', 'auth', 'planning-artifacts', 'new.md')).allowed,
|
|
235
235
|
'Should block cross-scope write',
|
|
236
236
|
);
|
|
237
237
|
});
|
|
@@ -262,19 +262,16 @@ async function testParallelScopeWorkflow() {
|
|
|
262
262
|
await asyncTest('Sync-up promotes artifacts to shared layer', async () => {
|
|
263
263
|
const sync = new ScopeSync({ projectRoot: tmpDir });
|
|
264
264
|
|
|
265
|
-
// Create a promotable artifact
|
|
266
|
-
const archPath = path.join(tmpDir, '_bmad-output', 'auth', '
|
|
265
|
+
// Create a promotable artifact matching pattern 'architecture/*.md'
|
|
266
|
+
const archPath = path.join(tmpDir, '_bmad-output', 'auth', 'architecture', 'overview.md');
|
|
267
267
|
fs.mkdirSync(path.dirname(archPath), { recursive: true });
|
|
268
268
|
fs.writeFileSync(archPath, '# Auth Architecture\n\nShared auth patterns...');
|
|
269
269
|
|
|
270
|
-
// Create architecture directory in shared
|
|
271
|
-
fs.mkdirSync(path.join(tmpDir, '_bmad-output', '_shared', 'auth'), { recursive: true });
|
|
272
|
-
|
|
273
270
|
await sync.syncUp('auth');
|
|
274
271
|
|
|
275
|
-
// Check artifact was promoted
|
|
272
|
+
// Check artifact was promoted to _shared/auth/architecture/overview.md
|
|
276
273
|
assertFileExists(
|
|
277
|
-
path.join(tmpDir, '_bmad-output', '_shared', 'auth', 'architecture.md'),
|
|
274
|
+
path.join(tmpDir, '_bmad-output', '_shared', 'auth', 'architecture', 'overview.md'),
|
|
278
275
|
'Architecture should be promoted to shared',
|
|
279
276
|
);
|
|
280
277
|
});
|
|
@@ -284,14 +281,13 @@ async function testParallelScopeWorkflow() {
|
|
|
284
281
|
// ========================================
|
|
285
282
|
await asyncTest('Events are logged', async () => {
|
|
286
283
|
const eventLogger = new EventLogger({ projectRoot: tmpDir });
|
|
284
|
+
await eventLogger.initialize();
|
|
287
285
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
scope: 'auth',
|
|
291
|
-
artifact: 'prd.md',
|
|
292
|
-
});
|
|
286
|
+
// EventLogger uses logEvent(type, scopeId, data) not log({...})
|
|
287
|
+
await eventLogger.logEvent('artifact_created', 'auth', { artifact: 'prd.md' });
|
|
293
288
|
|
|
294
|
-
|
|
289
|
+
// getEvents takes (scopeId, options) not ({scope})
|
|
290
|
+
const events = await eventLogger.getEvents('auth');
|
|
295
291
|
assertTrue(events.length > 0, 'Should have logged events');
|
|
296
292
|
assertEqual(events[0].type, 'artifact_created');
|
|
297
293
|
});
|
|
@@ -32,10 +32,10 @@ let passCount = 0;
|
|
|
32
32
|
let failCount = 0;
|
|
33
33
|
const failures = [];
|
|
34
34
|
|
|
35
|
-
function test(name, fn) {
|
|
35
|
+
async function test(name, fn) {
|
|
36
36
|
testCount++;
|
|
37
37
|
try {
|
|
38
|
-
fn();
|
|
38
|
+
await fn();
|
|
39
39
|
passCount++;
|
|
40
40
|
console.log(` ${colors.green}✓${colors.reset} ${name}`);
|
|
41
41
|
} catch (error) {
|
|
@@ -102,103 +102,122 @@ function cleanupTempDir(tmpDir) {
|
|
|
102
102
|
// ScopeValidator Tests
|
|
103
103
|
// ============================================================================
|
|
104
104
|
|
|
105
|
-
function testScopeValidator() {
|
|
105
|
+
async function testScopeValidator() {
|
|
106
106
|
console.log(`\n${colors.blue}ScopeValidator Tests${colors.reset}`);
|
|
107
107
|
|
|
108
108
|
const { ScopeValidator } = require('../src/core/lib/scope/scope-validator');
|
|
109
109
|
const validator = new ScopeValidator();
|
|
110
110
|
|
|
111
|
-
// Valid scope IDs
|
|
112
|
-
test('validates simple scope ID', () => {
|
|
113
|
-
|
|
111
|
+
// Valid scope IDs - using validateScopeId which returns {valid, error}
|
|
112
|
+
await test('validates simple scope ID', () => {
|
|
113
|
+
const result = validator.validateScopeId('auth');
|
|
114
|
+
assertTrue(result.valid, 'auth should be valid');
|
|
114
115
|
});
|
|
115
116
|
|
|
116
|
-
test('validates hyphenated scope ID', () => {
|
|
117
|
-
|
|
117
|
+
await test('validates hyphenated scope ID', () => {
|
|
118
|
+
const result = validator.validateScopeId('user-service');
|
|
119
|
+
assertTrue(result.valid, 'user-service should be valid');
|
|
118
120
|
});
|
|
119
121
|
|
|
120
|
-
test('validates scope ID with numbers', () => {
|
|
121
|
-
|
|
122
|
+
await test('validates scope ID with numbers', () => {
|
|
123
|
+
const result = validator.validateScopeId('api-v2');
|
|
124
|
+
assertTrue(result.valid, 'api-v2 should be valid');
|
|
122
125
|
});
|
|
123
126
|
|
|
124
|
-
test('validates minimum length scope ID', () => {
|
|
125
|
-
|
|
127
|
+
await test('validates minimum length scope ID', () => {
|
|
128
|
+
const result = validator.validateScopeId('ab');
|
|
129
|
+
assertTrue(result.valid, 'ab (2 chars) should be valid');
|
|
126
130
|
});
|
|
127
131
|
|
|
128
132
|
// Invalid scope IDs
|
|
129
|
-
test('rejects single character scope ID', () => {
|
|
130
|
-
|
|
133
|
+
await test('rejects single character scope ID', () => {
|
|
134
|
+
const result = validator.validateScopeId('a');
|
|
135
|
+
assertFalse(result.valid, 'single char should be invalid');
|
|
131
136
|
});
|
|
132
137
|
|
|
133
|
-
test('rejects scope ID starting with number', () => {
|
|
134
|
-
|
|
138
|
+
await test('rejects scope ID starting with number', () => {
|
|
139
|
+
const result = validator.validateScopeId('1auth');
|
|
140
|
+
assertFalse(result.valid, 'starting with number should be invalid');
|
|
135
141
|
});
|
|
136
142
|
|
|
137
|
-
test('rejects scope ID with uppercase', () => {
|
|
138
|
-
|
|
143
|
+
await test('rejects scope ID with uppercase', () => {
|
|
144
|
+
const result = validator.validateScopeId('Auth');
|
|
145
|
+
assertFalse(result.valid, 'uppercase should be invalid');
|
|
139
146
|
});
|
|
140
147
|
|
|
141
|
-
test('rejects scope ID with underscore', () => {
|
|
142
|
-
|
|
148
|
+
await test('rejects scope ID with underscore', () => {
|
|
149
|
+
const result = validator.validateScopeId('user_service');
|
|
150
|
+
assertFalse(result.valid, 'underscore should be invalid');
|
|
143
151
|
});
|
|
144
152
|
|
|
145
|
-
test('rejects scope ID ending with hyphen', () => {
|
|
146
|
-
|
|
153
|
+
await test('rejects scope ID ending with hyphen', () => {
|
|
154
|
+
const result = validator.validateScopeId('auth-');
|
|
155
|
+
assertFalse(result.valid, 'ending with hyphen should be invalid');
|
|
147
156
|
});
|
|
148
157
|
|
|
149
|
-
test('rejects scope ID starting with hyphen', () => {
|
|
150
|
-
|
|
158
|
+
await test('rejects scope ID starting with hyphen', () => {
|
|
159
|
+
const result = validator.validateScopeId('-auth');
|
|
160
|
+
assertFalse(result.valid, 'starting with hyphen should be invalid');
|
|
151
161
|
});
|
|
152
162
|
|
|
153
|
-
test('rejects scope ID with spaces', () => {
|
|
154
|
-
|
|
163
|
+
await test('rejects scope ID with spaces', () => {
|
|
164
|
+
const result = validator.validateScopeId('auth service');
|
|
165
|
+
assertFalse(result.valid, 'spaces should be invalid');
|
|
155
166
|
});
|
|
156
167
|
|
|
157
|
-
// Reserved IDs
|
|
158
|
-
test('rejects reserved ID _shared', () => {
|
|
159
|
-
|
|
168
|
+
// Reserved IDs - note: reserved IDs like _shared start with _ which fails pattern before reserved check
|
|
169
|
+
await test('rejects reserved ID _shared', () => {
|
|
170
|
+
const result = validator.validateScopeId('_shared');
|
|
171
|
+
assertFalse(result.valid, '_shared should be invalid (pattern or reserved)');
|
|
160
172
|
});
|
|
161
173
|
|
|
162
|
-
test('rejects reserved ID _events', () => {
|
|
163
|
-
|
|
174
|
+
await test('rejects reserved ID _events', () => {
|
|
175
|
+
const result = validator.validateScopeId('_events');
|
|
176
|
+
assertFalse(result.valid, '_events should be invalid (pattern or reserved)');
|
|
164
177
|
});
|
|
165
178
|
|
|
166
|
-
test('rejects reserved ID _config', () => {
|
|
167
|
-
|
|
179
|
+
await test('rejects reserved ID _config', () => {
|
|
180
|
+
const result = validator.validateScopeId('_config');
|
|
181
|
+
assertFalse(result.valid, '_config should be invalid (pattern or reserved)');
|
|
168
182
|
});
|
|
169
183
|
|
|
170
|
-
test('rejects reserved ID global', () => {
|
|
171
|
-
|
|
184
|
+
await test('rejects reserved ID global', () => {
|
|
185
|
+
const result = validator.validateScopeId('global');
|
|
186
|
+
// 'global' matches pattern but is reserved
|
|
187
|
+
assertFalse(result.valid, 'global is reserved');
|
|
172
188
|
});
|
|
173
189
|
|
|
174
190
|
// Circular dependency detection
|
|
175
|
-
|
|
191
|
+
// Note: detectCircularDependencies takes (scopeId, dependencies, allScopes) and returns {hasCircular, chain}
|
|
192
|
+
await test('detects direct circular dependency', () => {
|
|
176
193
|
const scopes = {
|
|
177
|
-
auth: { dependencies: ['payments'] },
|
|
178
|
-
payments: { dependencies: ['auth'] },
|
|
194
|
+
auth: { id: 'auth', dependencies: ['payments'] },
|
|
195
|
+
payments: { id: 'payments', dependencies: ['auth'] },
|
|
179
196
|
};
|
|
180
|
-
|
|
197
|
+
// Check from payments perspective - it depends on auth, which depends on payments
|
|
198
|
+
const result = validator.detectCircularDependencies('payments', ['auth'], scopes);
|
|
181
199
|
assertTrue(result.hasCircular, 'Should detect circular dependency');
|
|
182
|
-
assertTrue(result.cycles.length > 0, 'Should report cycles');
|
|
183
200
|
});
|
|
184
201
|
|
|
185
|
-
test('detects indirect circular dependency', () => {
|
|
202
|
+
await test('detects indirect circular dependency', () => {
|
|
186
203
|
const scopes = {
|
|
187
|
-
aa: { dependencies: ['bb'] },
|
|
188
|
-
bb: { dependencies: ['cc'] },
|
|
189
|
-
cc: { dependencies: ['aa'] },
|
|
204
|
+
aa: { id: 'aa', dependencies: ['bb'] },
|
|
205
|
+
bb: { id: 'bb', dependencies: ['cc'] },
|
|
206
|
+
cc: { id: 'cc', dependencies: ['aa'] },
|
|
190
207
|
};
|
|
191
|
-
|
|
208
|
+
// Check from cc perspective - it depends on aa, which eventually leads back to cc
|
|
209
|
+
const result = validator.detectCircularDependencies('cc', ['aa'], scopes);
|
|
192
210
|
assertTrue(result.hasCircular, 'Should detect indirect circular dependency');
|
|
193
211
|
});
|
|
194
212
|
|
|
195
|
-
test('accepts valid dependency graph', () => {
|
|
213
|
+
await test('accepts valid dependency graph', () => {
|
|
196
214
|
const scopes = {
|
|
197
|
-
auth: { dependencies: [] },
|
|
198
|
-
payments: { dependencies: ['auth'] },
|
|
199
|
-
orders: { dependencies: ['auth', 'payments'] },
|
|
215
|
+
auth: { id: 'auth', dependencies: [] },
|
|
216
|
+
payments: { id: 'payments', dependencies: ['auth'] },
|
|
217
|
+
orders: { id: 'orders', dependencies: ['auth', 'payments'] },
|
|
200
218
|
};
|
|
201
|
-
|
|
219
|
+
// Check from orders perspective - no circular deps
|
|
220
|
+
const result = validator.detectCircularDependencies('orders', ['auth', 'payments'], scopes);
|
|
202
221
|
assertFalse(result.hasCircular, 'Should not detect circular dependency');
|
|
203
222
|
});
|
|
204
223
|
}
|
|
@@ -207,7 +226,7 @@ function testScopeValidator() {
|
|
|
207
226
|
// ScopeManager Tests
|
|
208
227
|
// ============================================================================
|
|
209
228
|
|
|
210
|
-
function testScopeManager() {
|
|
229
|
+
async function testScopeManager() {
|
|
211
230
|
console.log(`\n${colors.blue}ScopeManager Tests${colors.reset}`);
|
|
212
231
|
|
|
213
232
|
const { ScopeManager } = require('../src/core/lib/scope/scope-manager');
|
|
@@ -230,7 +249,7 @@ function testScopeManager() {
|
|
|
230
249
|
}
|
|
231
250
|
|
|
232
251
|
// Test initialization
|
|
233
|
-
test('initializes scope system', async () => {
|
|
252
|
+
await test('initializes scope system', async () => {
|
|
234
253
|
const manager = setup();
|
|
235
254
|
try {
|
|
236
255
|
await manager.initialize();
|
|
@@ -242,7 +261,7 @@ function testScopeManager() {
|
|
|
242
261
|
});
|
|
243
262
|
|
|
244
263
|
// Test scope creation
|
|
245
|
-
test('creates new scope', async () => {
|
|
264
|
+
await test('creates new scope', async () => {
|
|
246
265
|
const manager = setup();
|
|
247
266
|
try {
|
|
248
267
|
await manager.initialize();
|
|
@@ -255,7 +274,7 @@ function testScopeManager() {
|
|
|
255
274
|
}
|
|
256
275
|
});
|
|
257
276
|
|
|
258
|
-
test('creates scope directory structure', async () => {
|
|
277
|
+
await test('creates scope directory structure', async () => {
|
|
259
278
|
const manager = setup();
|
|
260
279
|
try {
|
|
261
280
|
await manager.initialize();
|
|
@@ -271,7 +290,7 @@ function testScopeManager() {
|
|
|
271
290
|
}
|
|
272
291
|
});
|
|
273
292
|
|
|
274
|
-
test('rejects invalid scope ID on create', async () => {
|
|
293
|
+
await test('rejects invalid scope ID on create', async () => {
|
|
275
294
|
const manager = setup();
|
|
276
295
|
try {
|
|
277
296
|
await manager.initialize();
|
|
@@ -287,7 +306,7 @@ function testScopeManager() {
|
|
|
287
306
|
}
|
|
288
307
|
});
|
|
289
308
|
|
|
290
|
-
test('rejects duplicate scope ID', async () => {
|
|
309
|
+
await test('rejects duplicate scope ID', async () => {
|
|
291
310
|
const manager = setup();
|
|
292
311
|
try {
|
|
293
312
|
await manager.initialize();
|
|
@@ -305,7 +324,7 @@ function testScopeManager() {
|
|
|
305
324
|
});
|
|
306
325
|
|
|
307
326
|
// Test scope retrieval
|
|
308
|
-
test('retrieves scope by ID', async () => {
|
|
327
|
+
await test('retrieves scope by ID', async () => {
|
|
309
328
|
const manager = setup();
|
|
310
329
|
try {
|
|
311
330
|
await manager.initialize();
|
|
@@ -320,7 +339,7 @@ function testScopeManager() {
|
|
|
320
339
|
}
|
|
321
340
|
});
|
|
322
341
|
|
|
323
|
-
test('returns null for non-existent scope', async () => {
|
|
342
|
+
await test('returns null for non-existent scope', async () => {
|
|
324
343
|
const manager = setup();
|
|
325
344
|
try {
|
|
326
345
|
await manager.initialize();
|
|
@@ -332,7 +351,7 @@ function testScopeManager() {
|
|
|
332
351
|
});
|
|
333
352
|
|
|
334
353
|
// Test scope listing
|
|
335
|
-
test('lists all scopes', async () => {
|
|
354
|
+
await test('lists all scopes', async () => {
|
|
336
355
|
const manager = setup();
|
|
337
356
|
try {
|
|
338
357
|
await manager.initialize();
|
|
@@ -346,7 +365,7 @@ function testScopeManager() {
|
|
|
346
365
|
}
|
|
347
366
|
});
|
|
348
367
|
|
|
349
|
-
test('filters scopes by status', async () => {
|
|
368
|
+
await test('filters scopes by status', async () => {
|
|
350
369
|
const manager = setup();
|
|
351
370
|
try {
|
|
352
371
|
await manager.initialize();
|
|
@@ -363,7 +382,7 @@ function testScopeManager() {
|
|
|
363
382
|
});
|
|
364
383
|
|
|
365
384
|
// Test scope update
|
|
366
|
-
test('updates scope properties', async () => {
|
|
385
|
+
await test('updates scope properties', async () => {
|
|
367
386
|
const manager = setup();
|
|
368
387
|
try {
|
|
369
388
|
await manager.initialize();
|
|
@@ -379,7 +398,7 @@ function testScopeManager() {
|
|
|
379
398
|
});
|
|
380
399
|
|
|
381
400
|
// Test scope archive/activate
|
|
382
|
-
test('archives scope', async () => {
|
|
401
|
+
await test('archives scope', async () => {
|
|
383
402
|
const manager = setup();
|
|
384
403
|
try {
|
|
385
404
|
await manager.initialize();
|
|
@@ -394,7 +413,7 @@ function testScopeManager() {
|
|
|
394
413
|
}
|
|
395
414
|
});
|
|
396
415
|
|
|
397
|
-
test('activates archived scope', async () => {
|
|
416
|
+
await test('activates archived scope', async () => {
|
|
398
417
|
const manager = setup();
|
|
399
418
|
try {
|
|
400
419
|
await manager.initialize();
|
|
@@ -411,7 +430,7 @@ function testScopeManager() {
|
|
|
411
430
|
});
|
|
412
431
|
|
|
413
432
|
// Test path resolution
|
|
414
|
-
test('resolves scope paths', async () => {
|
|
433
|
+
await test('resolves scope paths', async () => {
|
|
415
434
|
const manager = setup();
|
|
416
435
|
try {
|
|
417
436
|
await manager.initialize();
|
|
@@ -428,7 +447,7 @@ function testScopeManager() {
|
|
|
428
447
|
});
|
|
429
448
|
|
|
430
449
|
// Test dependency management
|
|
431
|
-
test('tracks scope dependencies', async () => {
|
|
450
|
+
await test('tracks scope dependencies', async () => {
|
|
432
451
|
const manager = setup();
|
|
433
452
|
try {
|
|
434
453
|
await manager.initialize();
|
|
@@ -442,7 +461,7 @@ function testScopeManager() {
|
|
|
442
461
|
}
|
|
443
462
|
});
|
|
444
463
|
|
|
445
|
-
test('finds dependent scopes', async () => {
|
|
464
|
+
await test('finds dependent scopes', async () => {
|
|
446
465
|
const manager = setup();
|
|
447
466
|
try {
|
|
448
467
|
await manager.initialize();
|
|
@@ -464,51 +483,52 @@ function testScopeManager() {
|
|
|
464
483
|
// ArtifactResolver Tests
|
|
465
484
|
// ============================================================================
|
|
466
485
|
|
|
467
|
-
function testArtifactResolver() {
|
|
486
|
+
async function testArtifactResolver() {
|
|
468
487
|
console.log(`\n${colors.blue}ArtifactResolver Tests${colors.reset}`);
|
|
469
488
|
|
|
470
489
|
const { ArtifactResolver } = require('../src/core/lib/scope/artifact-resolver');
|
|
471
490
|
|
|
472
|
-
|
|
491
|
+
// Note: canRead() and canWrite() return {allowed: boolean, reason: string, warning?: string}
|
|
492
|
+
await test('allows read from any scope', () => {
|
|
473
493
|
const resolver = new ArtifactResolver({
|
|
474
494
|
currentScope: 'auth',
|
|
475
495
|
basePath: '_bmad-output',
|
|
476
496
|
});
|
|
477
497
|
|
|
478
|
-
assertTrue(resolver.canRead('_bmad-output/payments/planning-artifacts/prd.md'), 'Should allow cross-scope read');
|
|
479
|
-
assertTrue(resolver.canRead('_bmad-output/auth/planning-artifacts/prd.md'), 'Should allow own-scope read');
|
|
480
|
-
assertTrue(resolver.canRead('_bmad-output/_shared/project-context.md'), 'Should allow shared read');
|
|
498
|
+
assertTrue(resolver.canRead('_bmad-output/payments/planning-artifacts/prd.md').allowed, 'Should allow cross-scope read');
|
|
499
|
+
assertTrue(resolver.canRead('_bmad-output/auth/planning-artifacts/prd.md').allowed, 'Should allow own-scope read');
|
|
500
|
+
assertTrue(resolver.canRead('_bmad-output/_shared/project-context.md').allowed, 'Should allow shared read');
|
|
481
501
|
});
|
|
482
502
|
|
|
483
|
-
test('allows write to own scope', () => {
|
|
503
|
+
await test('allows write to own scope', () => {
|
|
484
504
|
const resolver = new ArtifactResolver({
|
|
485
505
|
currentScope: 'auth',
|
|
486
506
|
basePath: '_bmad-output',
|
|
487
507
|
});
|
|
488
508
|
|
|
489
|
-
assertTrue(resolver.canWrite('_bmad-output/auth/planning-artifacts/prd.md'), 'Should allow own-scope write');
|
|
509
|
+
assertTrue(resolver.canWrite('_bmad-output/auth/planning-artifacts/prd.md').allowed, 'Should allow own-scope write');
|
|
490
510
|
});
|
|
491
511
|
|
|
492
|
-
test('blocks write to other scope in strict mode', () => {
|
|
512
|
+
await test('blocks write to other scope in strict mode', () => {
|
|
493
513
|
const resolver = new ArtifactResolver({
|
|
494
514
|
currentScope: 'auth',
|
|
495
515
|
basePath: '_bmad-output',
|
|
496
516
|
isolationMode: 'strict',
|
|
497
517
|
});
|
|
498
518
|
|
|
499
|
-
assertFalse(resolver.canWrite('_bmad-output/payments/planning-artifacts/prd.md'), 'Should block cross-scope write');
|
|
519
|
+
assertFalse(resolver.canWrite('_bmad-output/payments/planning-artifacts/prd.md').allowed, 'Should block cross-scope write');
|
|
500
520
|
});
|
|
501
521
|
|
|
502
|
-
test('blocks direct write to _shared', () => {
|
|
522
|
+
await test('blocks direct write to _shared', () => {
|
|
503
523
|
const resolver = new ArtifactResolver({
|
|
504
524
|
currentScope: 'auth',
|
|
505
525
|
basePath: '_bmad-output',
|
|
506
526
|
});
|
|
507
527
|
|
|
508
|
-
assertFalse(resolver.canWrite('_bmad-output/_shared/project-context.md'), 'Should block _shared write');
|
|
528
|
+
assertFalse(resolver.canWrite('_bmad-output/_shared/project-context.md').allowed, 'Should block _shared write');
|
|
509
529
|
});
|
|
510
530
|
|
|
511
|
-
test('extracts scope from path', () => {
|
|
531
|
+
await test('extracts scope from path', () => {
|
|
512
532
|
const resolver = new ArtifactResolver({
|
|
513
533
|
currentScope: 'auth',
|
|
514
534
|
basePath: '_bmad-output',
|
|
@@ -519,7 +539,7 @@ function testArtifactResolver() {
|
|
|
519
539
|
assertEqual(resolver.extractScopeFromPath('_bmad-output/_shared/context.md'), '_shared');
|
|
520
540
|
});
|
|
521
541
|
|
|
522
|
-
test('throws on cross-scope write validation in strict mode', () => {
|
|
542
|
+
await test('throws on cross-scope write validation in strict mode', () => {
|
|
523
543
|
const resolver = new ArtifactResolver({
|
|
524
544
|
currentScope: 'auth',
|
|
525
545
|
basePath: '_bmad-output',
|
|
@@ -529,17 +549,17 @@ function testArtifactResolver() {
|
|
|
529
549
|
assertThrows(() => resolver.validateWrite('_bmad-output/payments/prd.md'), 'Cannot write to scope');
|
|
530
550
|
});
|
|
531
551
|
|
|
532
|
-
test('warns on cross-scope write in warn mode', () => {
|
|
552
|
+
await test('warns on cross-scope write in warn mode', () => {
|
|
533
553
|
const resolver = new ArtifactResolver({
|
|
534
554
|
currentScope: 'auth',
|
|
535
555
|
basePath: '_bmad-output',
|
|
536
556
|
isolationMode: 'warn',
|
|
537
557
|
});
|
|
538
558
|
|
|
539
|
-
//
|
|
559
|
+
// In warn mode, allowed should be true but warning should be set
|
|
540
560
|
const result = resolver.canWrite('_bmad-output/payments/prd.md');
|
|
541
|
-
|
|
542
|
-
|
|
561
|
+
assertTrue(result.allowed, 'Should allow write in warn mode');
|
|
562
|
+
assertTrue(result.warning !== null, 'Should have a warning message');
|
|
543
563
|
});
|
|
544
564
|
}
|
|
545
565
|
|
|
@@ -547,7 +567,7 @@ function testArtifactResolver() {
|
|
|
547
567
|
// StateLock Tests
|
|
548
568
|
// ============================================================================
|
|
549
569
|
|
|
550
|
-
function testStateLock() {
|
|
570
|
+
async function testStateLock() {
|
|
551
571
|
console.log(`\n${colors.blue}StateLock Tests${colors.reset}`);
|
|
552
572
|
|
|
553
573
|
const { StateLock } = require('../src/core/lib/scope/state-lock');
|
|
@@ -565,7 +585,7 @@ function testStateLock() {
|
|
|
565
585
|
}
|
|
566
586
|
}
|
|
567
587
|
|
|
568
|
-
test('acquires and releases lock', async () => {
|
|
588
|
+
await test('acquires and releases lock', async () => {
|
|
569
589
|
const lock = setup();
|
|
570
590
|
try {
|
|
571
591
|
const lockPath = path.join(tmpDir, 'test.lock');
|
|
@@ -580,7 +600,7 @@ function testStateLock() {
|
|
|
580
600
|
}
|
|
581
601
|
});
|
|
582
602
|
|
|
583
|
-
test('prevents concurrent access', async () => {
|
|
603
|
+
await test('prevents concurrent access', async () => {
|
|
584
604
|
const lock = setup();
|
|
585
605
|
try {
|
|
586
606
|
const lockPath = path.join(tmpDir, 'test.lock');
|
|
@@ -610,7 +630,7 @@ function testStateLock() {
|
|
|
610
630
|
}
|
|
611
631
|
});
|
|
612
632
|
|
|
613
|
-
test('detects stale locks', async () => {
|
|
633
|
+
await test('detects stale locks', async () => {
|
|
614
634
|
const lock = setup();
|
|
615
635
|
try {
|
|
616
636
|
const lockPath = path.join(tmpDir, 'test.lock');
|
|
@@ -637,7 +657,7 @@ function testStateLock() {
|
|
|
637
657
|
// ScopeContext Tests
|
|
638
658
|
// ============================================================================
|
|
639
659
|
|
|
640
|
-
function testScopeContext() {
|
|
660
|
+
async function testScopeContext() {
|
|
641
661
|
console.log(`\n${colors.blue}ScopeContext Tests${colors.reset}`);
|
|
642
662
|
|
|
643
663
|
const { ScopeContext } = require('../src/core/lib/scope/scope-context');
|
|
@@ -658,7 +678,7 @@ function testScopeContext() {
|
|
|
658
678
|
}
|
|
659
679
|
}
|
|
660
680
|
|
|
661
|
-
test('sets session scope', async () => {
|
|
681
|
+
await test('sets session scope', async () => {
|
|
662
682
|
const context = setup();
|
|
663
683
|
try {
|
|
664
684
|
// Initialize scope system first
|
|
@@ -675,7 +695,7 @@ function testScopeContext() {
|
|
|
675
695
|
}
|
|
676
696
|
});
|
|
677
697
|
|
|
678
|
-
test('gets current scope from session', async () => {
|
|
698
|
+
await test('gets current scope from session', async () => {
|
|
679
699
|
const context = setup();
|
|
680
700
|
try {
|
|
681
701
|
// Initialize scope system first
|
|
@@ -692,7 +712,7 @@ function testScopeContext() {
|
|
|
692
712
|
}
|
|
693
713
|
});
|
|
694
714
|
|
|
695
|
-
test('clears session scope', async () => {
|
|
715
|
+
await test('clears session scope', async () => {
|
|
696
716
|
const context = setup();
|
|
697
717
|
try {
|
|
698
718
|
const manager = new ScopeManager({ projectRoot: tmpDir });
|
|
@@ -709,7 +729,7 @@ function testScopeContext() {
|
|
|
709
729
|
}
|
|
710
730
|
});
|
|
711
731
|
|
|
712
|
-
test('loads merged project context', async () => {
|
|
732
|
+
await test('loads merged project context', async () => {
|
|
713
733
|
const context = setup();
|
|
714
734
|
try {
|
|
715
735
|
const manager = new ScopeManager({ projectRoot: tmpDir });
|
|
@@ -723,10 +743,10 @@ function testScopeContext() {
|
|
|
723
743
|
fs.mkdirSync(path.join(tmpDir, '_bmad-output', 'auth'), { recursive: true });
|
|
724
744
|
fs.writeFileSync(path.join(tmpDir, '_bmad-output', 'auth', 'project-context.md'), '# Auth Scope\n\nScope-specific content.');
|
|
725
745
|
|
|
726
|
-
const
|
|
746
|
+
const result = await context.loadProjectContext('auth');
|
|
727
747
|
|
|
728
|
-
assertTrue(merged.includes('Global content'), 'Should include global content');
|
|
729
|
-
assertTrue(merged.includes('Scope-specific content'), 'Should include scope content');
|
|
748
|
+
assertTrue(result.merged.includes('Global content'), 'Should include global content');
|
|
749
|
+
assertTrue(result.merged.includes('Scope-specific content'), 'Should include scope content');
|
|
730
750
|
} finally {
|
|
731
751
|
teardown();
|
|
732
752
|
}
|