bmad-fh 6.0.0-alpha.23 → 6.0.0-alpha.23.3b00cb36
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/.github/workflows/publish.yaml +68 -0
- package/.husky/pre-commit +17 -2
- package/.husky/pre-push +10 -0
- package/README.md +117 -14
- package/eslint.config.mjs +2 -2
- package/package.json +1 -2
- package/src/bmm/module.yaml +2 -2
- package/src/core/lib/scope/artifact-resolver.js +26 -26
- package/src/core/lib/scope/event-logger.js +34 -45
- package/src/core/lib/scope/index.js +3 -3
- package/src/core/lib/scope/scope-context.js +22 -28
- package/src/core/lib/scope/scope-initializer.js +29 -31
- package/src/core/lib/scope/scope-manager.js +57 -24
- package/src/core/lib/scope/scope-migrator.js +44 -52
- package/src/core/lib/scope/scope-sync.js +42 -48
- package/src/core/lib/scope/scope-validator.js +16 -21
- package/src/core/lib/scope/state-lock.js +37 -43
- package/src/core/module.yaml +2 -2
- package/test/test-scope-cli.js +1306 -0
- package/test/test-scope-e2e.js +682 -92
- package/test/test-scope-system.js +973 -169
- package/tools/cli/bmad-cli.js +5 -0
- package/tools/cli/commands/scope.js +1250 -115
- package/tools/cli/installers/lib/modules/manager.js +6 -2
- package/tools/cli/scripts/migrate-workflows.js +43 -51
- package/.github/workflows/publish-multi-artifact.yaml +0 -50
|
@@ -6,12 +6,12 @@ const { StateLock } = require('./state-lock');
|
|
|
6
6
|
/**
|
|
7
7
|
* Logs and tracks events across scopes
|
|
8
8
|
* Handles event logging and subscription notifications
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
10
|
* @class EventLogger
|
|
11
11
|
* @requires fs-extra
|
|
12
12
|
* @requires yaml
|
|
13
13
|
* @requires StateLock
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* @example
|
|
16
16
|
* const logger = new EventLogger({ projectRoot: '/path/to/project' });
|
|
17
17
|
* await logger.logEvent('artifact_created', 'auth', { artifact: 'prd.md' });
|
|
@@ -47,19 +47,19 @@ class EventLogger {
|
|
|
47
47
|
await fs.ensureDir(this.eventsPath);
|
|
48
48
|
|
|
49
49
|
// Create event-log.yaml if not exists
|
|
50
|
-
if (!await fs.pathExists(this.eventLogPath)) {
|
|
50
|
+
if (!(await fs.pathExists(this.eventLogPath))) {
|
|
51
51
|
const eventLog = {
|
|
52
52
|
version: 1,
|
|
53
|
-
events: []
|
|
53
|
+
events: [],
|
|
54
54
|
};
|
|
55
55
|
await fs.writeFile(this.eventLogPath, yaml.stringify(eventLog), 'utf8');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// Create subscriptions.yaml if not exists
|
|
59
|
-
if (!await fs.pathExists(this.subscriptionsPath)) {
|
|
59
|
+
if (!(await fs.pathExists(this.subscriptionsPath))) {
|
|
60
60
|
const subscriptions = {
|
|
61
61
|
version: 1,
|
|
62
|
-
subscriptions: {}
|
|
62
|
+
subscriptions: {},
|
|
63
63
|
};
|
|
64
64
|
await fs.writeFile(this.subscriptionsPath, yaml.stringify(subscriptions), 'utf8');
|
|
65
65
|
}
|
|
@@ -88,7 +88,7 @@ class EventLogger {
|
|
|
88
88
|
type,
|
|
89
89
|
scope: scopeId,
|
|
90
90
|
timestamp: new Date().toISOString(),
|
|
91
|
-
data
|
|
91
|
+
data,
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
return this.stateLock.withLock(this.eventLogPath, async () => {
|
|
@@ -123,23 +123,23 @@ class EventLogger {
|
|
|
123
123
|
|
|
124
124
|
// Filter by scope
|
|
125
125
|
if (scopeId) {
|
|
126
|
-
events = events.filter(e => e.scope === scopeId);
|
|
126
|
+
events = events.filter((e) => e.scope === scopeId);
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Filter by type
|
|
130
130
|
if (options.type) {
|
|
131
|
-
events = events.filter(e => e.type === options.type);
|
|
131
|
+
events = events.filter((e) => e.type === options.type);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// Filter by time range
|
|
135
135
|
if (options.since) {
|
|
136
136
|
const sinceDate = new Date(options.since);
|
|
137
|
-
events = events.filter(e => new Date(e.timestamp) >= sinceDate);
|
|
137
|
+
events = events.filter((e) => new Date(e.timestamp) >= sinceDate);
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
if (options.until) {
|
|
141
141
|
const untilDate = new Date(options.until);
|
|
142
|
-
events = events.filter(e => new Date(e.timestamp) <= untilDate);
|
|
142
|
+
events = events.filter((e) => new Date(e.timestamp) <= untilDate);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// Limit results
|
|
@@ -169,21 +169,19 @@ class EventLogger {
|
|
|
169
169
|
if (!subs.subscriptions[subscriberScope]) {
|
|
170
170
|
subs.subscriptions[subscriberScope] = {
|
|
171
171
|
watch: [],
|
|
172
|
-
notify: true
|
|
172
|
+
notify: true,
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// Add or update watch entry
|
|
177
|
-
const existingWatch = subs.subscriptions[subscriberScope].watch.find(
|
|
178
|
-
w => w.scope === watchScope
|
|
179
|
-
);
|
|
177
|
+
const existingWatch = subs.subscriptions[subscriberScope].watch.find((w) => w.scope === watchScope);
|
|
180
178
|
|
|
181
179
|
if (existingWatch) {
|
|
182
180
|
existingWatch.patterns = patterns;
|
|
183
181
|
} else {
|
|
184
182
|
subs.subscriptions[subscriberScope].watch.push({
|
|
185
183
|
scope: watchScope,
|
|
186
|
-
patterns
|
|
184
|
+
patterns,
|
|
187
185
|
});
|
|
188
186
|
}
|
|
189
187
|
|
|
@@ -206,8 +204,7 @@ class EventLogger {
|
|
|
206
204
|
const subs = yaml.parse(content);
|
|
207
205
|
|
|
208
206
|
if (subs.subscriptions[subscriberScope]) {
|
|
209
|
-
subs.subscriptions[subscriberScope].watch =
|
|
210
|
-
subs.subscriptions[subscriberScope].watch.filter(w => w.scope !== watchScope);
|
|
207
|
+
subs.subscriptions[subscriberScope].watch = subs.subscriptions[subscriberScope].watch.filter((w) => w.scope !== watchScope);
|
|
211
208
|
}
|
|
212
209
|
|
|
213
210
|
await fs.writeFile(this.subscriptionsPath, yaml.stringify(subs), 'utf8');
|
|
@@ -239,38 +236,34 @@ class EventLogger {
|
|
|
239
236
|
async getPendingNotifications(scopeId, since = null) {
|
|
240
237
|
try {
|
|
241
238
|
const subs = await this.getSubscriptions(scopeId);
|
|
242
|
-
|
|
239
|
+
|
|
243
240
|
if (!subs.notify || subs.watch.length === 0) {
|
|
244
241
|
return [];
|
|
245
242
|
}
|
|
246
243
|
|
|
247
244
|
const notifications = [];
|
|
248
|
-
|
|
245
|
+
|
|
249
246
|
for (const watch of subs.watch) {
|
|
250
247
|
const events = await this.getEvents(watch.scope, {
|
|
251
|
-
since: since || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString() // Last 24h default
|
|
248
|
+
since: since || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // Last 24h default
|
|
252
249
|
});
|
|
253
250
|
|
|
254
251
|
for (const event of events) {
|
|
255
252
|
// Check if event matches any pattern
|
|
256
|
-
const matches = watch.patterns.some(pattern =>
|
|
257
|
-
this.matchesPattern(event.data?.artifact, pattern)
|
|
258
|
-
);
|
|
253
|
+
const matches = watch.patterns.some((pattern) => this.matchesPattern(event.data?.artifact, pattern));
|
|
259
254
|
|
|
260
255
|
if (matches || watch.patterns.includes('*')) {
|
|
261
256
|
notifications.push({
|
|
262
257
|
...event,
|
|
263
258
|
watchedBy: scopeId,
|
|
264
|
-
pattern: watch.patterns
|
|
259
|
+
pattern: watch.patterns,
|
|
265
260
|
});
|
|
266
261
|
}
|
|
267
262
|
}
|
|
268
263
|
}
|
|
269
264
|
|
|
270
265
|
// Sort by timestamp
|
|
271
|
-
notifications.sort((a, b) =>
|
|
272
|
-
new Date(a.timestamp) - new Date(b.timestamp)
|
|
273
|
-
);
|
|
266
|
+
notifications.sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
|
|
274
267
|
|
|
275
268
|
return notifications;
|
|
276
269
|
} catch {
|
|
@@ -287,10 +280,8 @@ class EventLogger {
|
|
|
287
280
|
matchesPattern(artifact, pattern) {
|
|
288
281
|
if (!artifact) return false;
|
|
289
282
|
if (pattern === '*') return true;
|
|
290
|
-
|
|
291
|
-
const regexPattern = pattern
|
|
292
|
-
.replaceAll('.', String.raw`\.`)
|
|
293
|
-
.replaceAll('*', '.*');
|
|
283
|
+
|
|
284
|
+
const regexPattern = pattern.replaceAll('.', String.raw`\.`).replaceAll('*', '.*');
|
|
294
285
|
const regex = new RegExp(regexPattern);
|
|
295
286
|
return regex.test(artifact);
|
|
296
287
|
}
|
|
@@ -309,7 +300,7 @@ class EventLogger {
|
|
|
309
300
|
SYNC_UP: 'sync_up',
|
|
310
301
|
SYNC_DOWN: 'sync_down',
|
|
311
302
|
WORKFLOW_STARTED: 'workflow_started',
|
|
312
|
-
WORKFLOW_COMPLETED: 'workflow_completed'
|
|
303
|
+
WORKFLOW_COMPLETED: 'workflow_completed',
|
|
313
304
|
};
|
|
314
305
|
|
|
315
306
|
/**
|
|
@@ -321,7 +312,7 @@ class EventLogger {
|
|
|
321
312
|
async logArtifactCreated(scopeId, artifact, metadata = {}) {
|
|
322
313
|
return this.logEvent(EventLogger.EventTypes.ARTIFACT_CREATED, scopeId, {
|
|
323
314
|
artifact,
|
|
324
|
-
...metadata
|
|
315
|
+
...metadata,
|
|
325
316
|
});
|
|
326
317
|
}
|
|
327
318
|
|
|
@@ -334,7 +325,7 @@ class EventLogger {
|
|
|
334
325
|
async logArtifactUpdated(scopeId, artifact, metadata = {}) {
|
|
335
326
|
return this.logEvent(EventLogger.EventTypes.ARTIFACT_UPDATED, scopeId, {
|
|
336
327
|
artifact,
|
|
337
|
-
...metadata
|
|
328
|
+
...metadata,
|
|
338
329
|
});
|
|
339
330
|
}
|
|
340
331
|
|
|
@@ -347,7 +338,7 @@ class EventLogger {
|
|
|
347
338
|
async logArtifactPromoted(scopeId, artifact, sharedPath) {
|
|
348
339
|
return this.logEvent(EventLogger.EventTypes.ARTIFACT_PROMOTED, scopeId, {
|
|
349
340
|
artifact,
|
|
350
|
-
shared_path: sharedPath
|
|
341
|
+
shared_path: sharedPath,
|
|
351
342
|
});
|
|
352
343
|
}
|
|
353
344
|
|
|
@@ -358,14 +349,12 @@ class EventLogger {
|
|
|
358
349
|
* @param {object} result - Sync result
|
|
359
350
|
*/
|
|
360
351
|
async logSync(type, scopeId, result) {
|
|
361
|
-
const eventType = type === 'up'
|
|
362
|
-
|
|
363
|
-
: EventLogger.EventTypes.SYNC_DOWN;
|
|
364
|
-
|
|
352
|
+
const eventType = type === 'up' ? EventLogger.EventTypes.SYNC_UP : EventLogger.EventTypes.SYNC_DOWN;
|
|
353
|
+
|
|
365
354
|
return this.logEvent(eventType, scopeId, {
|
|
366
355
|
files_count: result.promoted?.length || result.pulled?.length || 0,
|
|
367
356
|
conflicts_count: result.conflicts?.length || 0,
|
|
368
|
-
errors_count: result.errors?.length || 0
|
|
357
|
+
errors_count: result.errors?.length || 0,
|
|
369
358
|
});
|
|
370
359
|
}
|
|
371
360
|
|
|
@@ -376,13 +365,13 @@ class EventLogger {
|
|
|
376
365
|
*/
|
|
377
366
|
async getStats(scopeId = null) {
|
|
378
367
|
const events = await this.getEvents(scopeId);
|
|
379
|
-
|
|
368
|
+
|
|
380
369
|
const stats = {
|
|
381
370
|
total: events.length,
|
|
382
371
|
byType: {},
|
|
383
372
|
byScope: {},
|
|
384
373
|
last24h: 0,
|
|
385
|
-
lastEvent: null
|
|
374
|
+
lastEvent: null,
|
|
386
375
|
};
|
|
387
376
|
|
|
388
377
|
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
|
@@ -390,10 +379,10 @@ class EventLogger {
|
|
|
390
379
|
for (const event of events) {
|
|
391
380
|
// Count by type
|
|
392
381
|
stats.byType[event.type] = (stats.byType[event.type] || 0) + 1;
|
|
393
|
-
|
|
382
|
+
|
|
394
383
|
// Count by scope
|
|
395
384
|
stats.byScope[event.scope] = (stats.byScope[event.scope] || 0) + 1;
|
|
396
|
-
|
|
385
|
+
|
|
397
386
|
// Count recent
|
|
398
387
|
if (new Date(event.timestamp) >= oneDayAgo) {
|
|
399
388
|
stats.last24h++;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Scope Management Module
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Provides multi-scope parallel artifact system functionality
|
|
5
5
|
* for isolated development workflows.
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* @module scope
|
|
8
8
|
*/
|
|
9
9
|
|
|
@@ -26,5 +26,5 @@ module.exports = {
|
|
|
26
26
|
ArtifactResolver,
|
|
27
27
|
StateLock,
|
|
28
28
|
ScopeSync,
|
|
29
|
-
EventLogger
|
|
29
|
+
EventLogger,
|
|
30
30
|
};
|
|
@@ -5,11 +5,11 @@ const yaml = require('yaml');
|
|
|
5
5
|
/**
|
|
6
6
|
* Manages session-sticky scope context
|
|
7
7
|
* Tracks the current active scope for workflows and agents
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* @class ScopeContext
|
|
10
10
|
* @requires fs-extra
|
|
11
11
|
* @requires yaml
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
13
|
* @example
|
|
14
14
|
* const context = new ScopeContext({ projectRoot: '/path/to/project' });
|
|
15
15
|
* await context.setScope('auth');
|
|
@@ -40,13 +40,13 @@ class ScopeContext {
|
|
|
40
40
|
*/
|
|
41
41
|
async getCurrentScope() {
|
|
42
42
|
try {
|
|
43
|
-
if (!await fs.pathExists(this.contextFilePath)) {
|
|
43
|
+
if (!(await fs.pathExists(this.contextFilePath))) {
|
|
44
44
|
return null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const content = await fs.readFile(this.contextFilePath, 'utf8');
|
|
48
48
|
const context = yaml.parse(content);
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
return context?.active_scope || null;
|
|
51
51
|
} catch {
|
|
52
52
|
return null;
|
|
@@ -63,7 +63,7 @@ class ScopeContext {
|
|
|
63
63
|
const context = {
|
|
64
64
|
active_scope: scopeId,
|
|
65
65
|
set_at: new Date().toISOString(),
|
|
66
|
-
set_by: process.env.USER || 'unknown'
|
|
66
|
+
set_by: process.env.USER || 'unknown',
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
await fs.writeFile(this.contextFilePath, yaml.stringify(context), 'utf8');
|
|
@@ -94,7 +94,7 @@ class ScopeContext {
|
|
|
94
94
|
*/
|
|
95
95
|
async getContext() {
|
|
96
96
|
try {
|
|
97
|
-
if (!await fs.pathExists(this.contextFilePath)) {
|
|
97
|
+
if (!(await fs.pathExists(this.contextFilePath))) {
|
|
98
98
|
return null;
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -121,11 +121,11 @@ class ScopeContext {
|
|
|
121
121
|
* @returns {Promise<object>} Merged context object
|
|
122
122
|
*/
|
|
123
123
|
async loadProjectContext(scopeId = null) {
|
|
124
|
-
const scope = scopeId || await this.getCurrentScope();
|
|
124
|
+
const scope = scopeId || (await this.getCurrentScope());
|
|
125
125
|
const context = {
|
|
126
126
|
global: null,
|
|
127
127
|
scope: null,
|
|
128
|
-
merged: ''
|
|
128
|
+
merged: '',
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
try {
|
|
@@ -137,12 +137,7 @@ class ScopeContext {
|
|
|
137
137
|
|
|
138
138
|
// Load scope-specific context if scope is set
|
|
139
139
|
if (scope) {
|
|
140
|
-
const scopeContextPath = path.join(
|
|
141
|
-
this.projectRoot,
|
|
142
|
-
this.outputBase,
|
|
143
|
-
scope,
|
|
144
|
-
'project-context.md'
|
|
145
|
-
);
|
|
140
|
+
const scopeContextPath = path.join(this.projectRoot, this.outputBase, scope, 'project-context.md');
|
|
146
141
|
if (await fs.pathExists(scopeContextPath)) {
|
|
147
142
|
context.scope = await fs.readFile(scopeContextPath, 'utf8');
|
|
148
143
|
}
|
|
@@ -156,7 +151,6 @@ class ScopeContext {
|
|
|
156
151
|
} else if (context.scope) {
|
|
157
152
|
context.merged = context.scope;
|
|
158
153
|
}
|
|
159
|
-
|
|
160
154
|
} catch (error) {
|
|
161
155
|
throw new Error(`Failed to load project context: ${error.message}`);
|
|
162
156
|
}
|
|
@@ -204,26 +198,26 @@ class ScopeContext {
|
|
|
204
198
|
* @returns {Promise<object>} Variables object
|
|
205
199
|
*/
|
|
206
200
|
async getScopeVariables(scopeId) {
|
|
207
|
-
const scope = scopeId || await this.getCurrentScope();
|
|
208
|
-
|
|
201
|
+
const scope = scopeId || (await this.getCurrentScope());
|
|
202
|
+
|
|
209
203
|
if (!scope) {
|
|
210
204
|
return {
|
|
211
205
|
scope: '',
|
|
212
206
|
scope_path: '',
|
|
213
207
|
scope_planning: '',
|
|
214
208
|
scope_implementation: '',
|
|
215
|
-
scope_tests: ''
|
|
209
|
+
scope_tests: '',
|
|
216
210
|
};
|
|
217
211
|
}
|
|
218
212
|
|
|
219
213
|
const basePath = path.join(this.outputBase, scope);
|
|
220
|
-
|
|
214
|
+
|
|
221
215
|
return {
|
|
222
216
|
scope: scope,
|
|
223
217
|
scope_path: basePath,
|
|
224
218
|
scope_planning: path.join(basePath, 'planning-artifacts'),
|
|
225
219
|
scope_implementation: path.join(basePath, 'implementation-artifacts'),
|
|
226
|
-
scope_tests: path.join(basePath, 'tests')
|
|
220
|
+
scope_tests: path.join(basePath, 'tests'),
|
|
227
221
|
};
|
|
228
222
|
}
|
|
229
223
|
|
|
@@ -234,8 +228,8 @@ class ScopeContext {
|
|
|
234
228
|
* @returns {Promise<string>} Context snippet
|
|
235
229
|
*/
|
|
236
230
|
async createContextSnippet(scopeId) {
|
|
237
|
-
const scope = scopeId || await this.getCurrentScope();
|
|
238
|
-
|
|
231
|
+
const scope = scopeId || (await this.getCurrentScope());
|
|
232
|
+
|
|
239
233
|
if (!scope) {
|
|
240
234
|
return '<!-- No scope context active -->';
|
|
241
235
|
}
|
|
@@ -264,14 +258,14 @@ ${context.merged || 'No project context loaded.'}
|
|
|
264
258
|
* @returns {Promise<string>} Shell export statements
|
|
265
259
|
*/
|
|
266
260
|
async exportForShell(scopeId) {
|
|
267
|
-
const scope = scopeId || await this.getCurrentScope();
|
|
268
|
-
|
|
261
|
+
const scope = scopeId || (await this.getCurrentScope());
|
|
262
|
+
|
|
269
263
|
if (!scope) {
|
|
270
264
|
return '# No scope set';
|
|
271
265
|
}
|
|
272
266
|
|
|
273
267
|
const vars = await this.getScopeVariables(scope);
|
|
274
|
-
|
|
268
|
+
|
|
275
269
|
return `
|
|
276
270
|
export BMAD_SCOPE="${vars.scope}"
|
|
277
271
|
export BMAD_SCOPE_PATH="${vars.scope_path}"
|
|
@@ -288,12 +282,12 @@ export BMAD_SCOPE_TESTS="${vars.scope_tests}"
|
|
|
288
282
|
*/
|
|
289
283
|
async updateMetadata(metadata) {
|
|
290
284
|
try {
|
|
291
|
-
const context = await this.getContext() || {};
|
|
292
|
-
|
|
285
|
+
const context = (await this.getContext()) || {};
|
|
286
|
+
|
|
293
287
|
const updated = {
|
|
294
288
|
...context,
|
|
295
289
|
...metadata,
|
|
296
|
-
updated_at: new Date().toISOString()
|
|
290
|
+
updated_at: new Date().toISOString(),
|
|
297
291
|
};
|
|
298
292
|
|
|
299
293
|
await fs.writeFile(this.contextFilePath, yaml.stringify(updated), 'utf8');
|
|
@@ -5,11 +5,11 @@ const yaml = require('yaml');
|
|
|
5
5
|
/**
|
|
6
6
|
* Initializes directory structure for scopes
|
|
7
7
|
* Creates scope directories, shared layer, and event system
|
|
8
|
-
*
|
|
8
|
+
*
|
|
9
9
|
* @class ScopeInitializer
|
|
10
10
|
* @requires fs-extra
|
|
11
11
|
* @requires yaml
|
|
12
|
-
*
|
|
12
|
+
*
|
|
13
13
|
* @example
|
|
14
14
|
* const initializer = new ScopeInitializer({ projectRoot: '/path/to/project' });
|
|
15
15
|
* await initializer.initializeScope('auth');
|
|
@@ -70,14 +70,14 @@ class ScopeInitializer {
|
|
|
70
70
|
|
|
71
71
|
// Create README in shared directory
|
|
72
72
|
const sharedReadmePath = path.join(this.sharedPath, 'README.md');
|
|
73
|
-
if (!await fs.pathExists(sharedReadmePath)) {
|
|
73
|
+
if (!(await fs.pathExists(sharedReadmePath))) {
|
|
74
74
|
const readmeContent = this.generateSharedReadme();
|
|
75
75
|
await fs.writeFile(sharedReadmePath, readmeContent, 'utf8');
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// Create global project-context.md template
|
|
79
79
|
const contextPath = path.join(this.sharedPath, 'project-context.md');
|
|
80
|
-
if (!await fs.pathExists(contextPath)) {
|
|
80
|
+
if (!(await fs.pathExists(contextPath))) {
|
|
81
81
|
const contextContent = this.generateGlobalContextTemplate();
|
|
82
82
|
await fs.writeFile(contextPath, contextContent, 'utf8');
|
|
83
83
|
}
|
|
@@ -100,20 +100,20 @@ class ScopeInitializer {
|
|
|
100
100
|
|
|
101
101
|
// Create event-log.yaml
|
|
102
102
|
const eventLogPath = path.join(this.eventsPath, 'event-log.yaml');
|
|
103
|
-
if (!await fs.pathExists(eventLogPath)) {
|
|
103
|
+
if (!(await fs.pathExists(eventLogPath))) {
|
|
104
104
|
const eventLog = {
|
|
105
105
|
version: 1,
|
|
106
|
-
events: []
|
|
106
|
+
events: [],
|
|
107
107
|
};
|
|
108
108
|
await fs.writeFile(eventLogPath, yaml.stringify(eventLog), 'utf8');
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Create subscriptions.yaml
|
|
112
112
|
const subscriptionsPath = path.join(this.eventsPath, 'subscriptions.yaml');
|
|
113
|
-
if (!await fs.pathExists(subscriptionsPath)) {
|
|
113
|
+
if (!(await fs.pathExists(subscriptionsPath))) {
|
|
114
114
|
const subscriptions = {
|
|
115
115
|
version: 1,
|
|
116
|
-
subscriptions: {}
|
|
116
|
+
subscriptions: {},
|
|
117
117
|
};
|
|
118
118
|
await fs.writeFile(subscriptionsPath, yaml.stringify(subscriptions), 'utf8');
|
|
119
119
|
}
|
|
@@ -135,10 +135,8 @@ class ScopeInitializer {
|
|
|
135
135
|
const scopePath = path.join(this.outputPath, scopeId);
|
|
136
136
|
|
|
137
137
|
// Check if scope directory already exists
|
|
138
|
-
if (await fs.pathExists(scopePath)) {
|
|
139
|
-
|
|
140
|
-
throw new Error(`Scope directory '${scopeId}' already exists. Use force option to recreate.`);
|
|
141
|
-
}
|
|
138
|
+
if ((await fs.pathExists(scopePath)) && !options.force) {
|
|
139
|
+
throw new Error(`Scope directory '${scopeId}' already exists. Use force option to recreate.`);
|
|
142
140
|
}
|
|
143
141
|
|
|
144
142
|
// Create scope directory structure
|
|
@@ -147,7 +145,7 @@ class ScopeInitializer {
|
|
|
147
145
|
planning: path.join(scopePath, 'planning-artifacts'),
|
|
148
146
|
implementation: path.join(scopePath, 'implementation-artifacts'),
|
|
149
147
|
tests: path.join(scopePath, 'tests'),
|
|
150
|
-
meta: path.join(scopePath, '.scope-meta.yaml')
|
|
148
|
+
meta: path.join(scopePath, '.scope-meta.yaml'),
|
|
151
149
|
};
|
|
152
150
|
|
|
153
151
|
// Create directories
|
|
@@ -163,14 +161,14 @@ class ScopeInitializer {
|
|
|
163
161
|
structure: {
|
|
164
162
|
planning_artifacts: 'planning-artifacts/',
|
|
165
163
|
implementation_artifacts: 'implementation-artifacts/',
|
|
166
|
-
tests: 'tests/'
|
|
167
|
-
}
|
|
164
|
+
tests: 'tests/',
|
|
165
|
+
},
|
|
168
166
|
};
|
|
169
167
|
await fs.writeFile(paths.meta, yaml.stringify(metadata), 'utf8');
|
|
170
168
|
|
|
171
169
|
// Create README in scope directory
|
|
172
170
|
const readmePath = path.join(scopePath, 'README.md');
|
|
173
|
-
if (!await fs.pathExists(readmePath)) {
|
|
171
|
+
if (!(await fs.pathExists(readmePath))) {
|
|
174
172
|
const readmeContent = this.generateScopeReadme(scopeId, options);
|
|
175
173
|
await fs.writeFile(readmePath, readmeContent, 'utf8');
|
|
176
174
|
}
|
|
@@ -199,7 +197,7 @@ class ScopeInitializer {
|
|
|
199
197
|
const scopePath = path.join(this.outputPath, scopeId);
|
|
200
198
|
|
|
201
199
|
// Check if scope exists
|
|
202
|
-
if (!await fs.pathExists(scopePath)) {
|
|
200
|
+
if (!(await fs.pathExists(scopePath))) {
|
|
203
201
|
throw new Error(`Scope directory '${scopeId}' does not exist`);
|
|
204
202
|
}
|
|
205
203
|
|
|
@@ -251,7 +249,7 @@ class ScopeInitializer {
|
|
|
251
249
|
implementation: path.join(scopePath, 'implementation-artifacts'),
|
|
252
250
|
tests: path.join(scopePath, 'tests'),
|
|
253
251
|
meta: path.join(scopePath, '.scope-meta.yaml'),
|
|
254
|
-
context: path.join(scopePath, 'project-context.md')
|
|
252
|
+
context: path.join(scopePath, 'project-context.md'),
|
|
255
253
|
};
|
|
256
254
|
}
|
|
257
255
|
|
|
@@ -281,9 +279,9 @@ The shared layer enables:
|
|
|
281
279
|
|
|
282
280
|
## Usage
|
|
283
281
|
|
|
284
|
-
1. **Reading**: All scopes can read from
|
|
285
|
-
2. **Writing**: Use
|
|
286
|
-
3. **Syncing**: Use
|
|
282
|
+
1. **Reading**: All scopes can read from \`_shared/\`
|
|
283
|
+
2. **Writing**: Use \`bmad scope sync-up <scope>\` to promote artifacts
|
|
284
|
+
3. **Syncing**: Use \`bmad scope sync-down <scope>\` to pull updates
|
|
287
285
|
|
|
288
286
|
## Best Practices
|
|
289
287
|
|
|
@@ -368,16 +366,14 @@ ${description}
|
|
|
368
366
|
|
|
369
367
|
## Scope Information
|
|
370
368
|
|
|
371
|
-
- **ID:**
|
|
369
|
+
- **ID:** ${scopeId}
|
|
372
370
|
- **Name:** ${scopeName}
|
|
373
371
|
- **Status:** ${options.status || 'active'}
|
|
374
372
|
- **Created:** ${new Date().toISOString().split('T')[0]}
|
|
375
373
|
|
|
376
374
|
## Dependencies
|
|
377
375
|
|
|
378
|
-
${options.dependencies && options.dependencies.length > 0
|
|
379
|
-
? options.dependencies.map(dep => `- \\`${dep}\\``).join('\n')
|
|
380
|
-
: 'No dependencies'}
|
|
376
|
+
${options.dependencies && options.dependencies.length > 0 ? options.dependencies.map((dep) => `- ${dep}`).join('\n') : 'No dependencies'}
|
|
381
377
|
|
|
382
378
|
## Usage
|
|
383
379
|
|
|
@@ -403,8 +399,8 @@ bmad scope sync-down ${scopeId}
|
|
|
403
399
|
|
|
404
400
|
## Related Documentation
|
|
405
401
|
|
|
406
|
-
- Global context:
|
|
407
|
-
- Contracts:
|
|
402
|
+
- Global context: ../_shared/project-context.md
|
|
403
|
+
- Contracts: ../_shared/contracts/
|
|
408
404
|
`;
|
|
409
405
|
}
|
|
410
406
|
|
|
@@ -419,7 +415,7 @@ bmad scope sync-down ${scopeId}
|
|
|
419
415
|
|
|
420
416
|
return `# Scope Context: ${scopeName}
|
|
421
417
|
|
|
422
|
-
> This context extends the global project context in
|
|
418
|
+
> This context extends the global project context in ../_shared/project-context.md
|
|
423
419
|
|
|
424
420
|
## Scope Purpose
|
|
425
421
|
|
|
@@ -436,9 +432,11 @@ bmad scope sync-down ${scopeId}
|
|
|
436
432
|
## Integration Points
|
|
437
433
|
|
|
438
434
|
### Dependencies
|
|
439
|
-
${
|
|
440
|
-
|
|
441
|
-
|
|
435
|
+
${
|
|
436
|
+
options.dependencies && options.dependencies.length > 0
|
|
437
|
+
? options.dependencies.map((dep) => `- **${dep}**: [Describe dependency relationship]`).join('\n')
|
|
438
|
+
: 'No dependencies'
|
|
439
|
+
}
|
|
442
440
|
|
|
443
441
|
### Provides
|
|
444
442
|
[What this scope provides to other scopes]
|