bmad-fh 6.0.0-alpha.23 → 6.0.0-alpha.23.e9c0f978

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.
@@ -28,10 +28,14 @@ jobs:
28
28
  - name: Install dependencies
29
29
  run: npm ci
30
30
 
31
- - name: Get version
31
+ - name: Set version with commit hash
32
32
  id: version
33
33
  run: |
34
- echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
34
+ BASE_VERSION=$(node -p "require('./package.json').version")
35
+ SHORT_SHA=$(git rev-parse --short HEAD)
36
+ NEW_VERSION="${BASE_VERSION}.${SHORT_SHA}"
37
+ npm version "${NEW_VERSION}" --no-git-tag-version
38
+ echo "version=${NEW_VERSION}" >> $GITHUB_OUTPUT
35
39
 
36
40
  - name: Publish to NPM
37
41
  env:
package/eslint.config.mjs CHANGED
@@ -81,9 +81,9 @@ export default [
81
81
  },
82
82
  },
83
83
 
84
- // CLI scripts under tools/** and test/**
84
+ // CLI scripts under tools/**, test/**, and src/core/lib/**
85
85
  {
86
- files: ['tools/**/*.js', 'tools/**/*.mjs', 'test/**/*.js'],
86
+ files: ['tools/**/*.js', 'tools/**/*.mjs', 'test/**/*.js', 'src/core/lib/**/*.js'],
87
87
  rules: {
88
88
  // Allow CommonJS patterns for Node CLI scripts
89
89
  'unicorn/prefer-module': 'off',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "bmad-fh",
4
- "version": "6.0.0-alpha.23",
4
+ "version": "6.0.0-alpha.23.e9c0f978",
5
5
  "description": "Breakthrough Method of Agile AI-driven Development",
6
6
  "keywords": [
7
7
  "agile",
@@ -24,7 +24,6 @@
24
24
  },
25
25
  "scripts": {
26
26
  "bmad:install": "node tools/cli/bmad-cli.js install",
27
- "publish:multi-artifact": "npm publish --tag multi-artifact",
28
27
  "bundle": "node tools/cli/bundlers/bundle-web.js all",
29
28
  "docs:build": "node tools/build-docs.js",
30
29
  "docs:dev": "astro dev --root website",
@@ -40,6 +39,7 @@
40
39
  "lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
41
40
  "lint:md": "markdownlint-cli2 \"**/*.md\"",
42
41
  "prepare": "husky",
42
+ "publish:multi-artifact": "npm publish --tag multi-artifact",
43
43
  "rebundle": "node tools/cli/bundlers/bundle-web.js rebundle",
44
44
  "release:major": "gh workflow run \"Manual Release\" -f version_bump=major",
45
45
  "release:minor": "gh workflow run \"Manual Release\" -f version_bump=minor",
@@ -34,13 +34,13 @@ user_skill_level:
34
34
  scope:
35
35
  default: ""
36
36
  result: "{value}"
37
- runtime: true # Indicates this is resolved at runtime, not during install
37
+ runtime: true # Indicates this is resolved at runtime, not during install
38
38
 
39
39
  # Scope-aware path helper - resolves to output_folder/scope or just output_folder
40
40
  scope_path:
41
41
  default: "{output_folder}"
42
42
  result: "{value}"
43
- runtime: true # Updated at runtime when scope is active
43
+ runtime: true # Updated at runtime when scope is active
44
44
 
45
45
  planning_artifacts: # Phase 1-3 artifacts
46
46
  prompt: "Where should planning artifacts be stored? (Brainstorming, Briefs, PRDs, UX Designs, Architecture, Epics)"
@@ -3,15 +3,15 @@ const path = require('node:path');
3
3
  /**
4
4
  * Resolves and enforces scope-based artifact access
5
5
  * Implements read-any/write-own access model
6
- *
6
+ *
7
7
  * @class ArtifactResolver
8
- *
8
+ *
9
9
  * @example
10
10
  * const resolver = new ArtifactResolver({
11
11
  * currentScope: 'auth',
12
12
  * basePath: '/path/to/_bmad-output'
13
13
  * });
14
- *
14
+ *
15
15
  * if (resolver.canWrite('/path/to/_bmad-output/auth/file.md')) {
16
16
  * // Write operation allowed
17
17
  * }
@@ -52,7 +52,7 @@ class ArtifactResolver {
52
52
  extractScopeFromPath(filePath) {
53
53
  // Normalize path
54
54
  const normalizedPath = path.normalize(filePath);
55
-
55
+
56
56
  // Find the base path in the file path
57
57
  const baseIndex = normalizedPath.indexOf(this.basePath);
58
58
  if (baseIndex === -1) {
@@ -61,16 +61,16 @@ class ArtifactResolver {
61
61
 
62
62
  // Get the relative path from base
63
63
  const relativePath = normalizedPath.slice(Math.max(0, baseIndex + this.basePath.length + 1));
64
-
64
+
65
65
  // Split to get the first segment (scope name)
66
66
  const segments = relativePath.split(path.sep).filter(Boolean);
67
-
67
+
68
68
  if (segments.length === 0) {
69
69
  return null;
70
70
  }
71
71
 
72
72
  const firstSegment = segments[0];
73
-
73
+
74
74
  // Check if it's a reserved path
75
75
  if (this.reservedPaths.includes(firstSegment)) {
76
76
  return firstSegment; // Return the reserved path name
@@ -109,7 +109,7 @@ class ArtifactResolver {
109
109
  // Read is always allowed for all paths
110
110
  return {
111
111
  allowed: true,
112
- reason: 'Read access is always allowed in read-any model'
112
+ reason: 'Read access is always allowed in read-any model',
113
113
  };
114
114
  }
115
115
 
@@ -124,7 +124,7 @@ class ArtifactResolver {
124
124
  return {
125
125
  allowed: true,
126
126
  reason: 'No scope active, operating in legacy mode',
127
- warning: null
127
+ warning: null,
128
128
  };
129
129
  }
130
130
 
@@ -135,7 +135,7 @@ class ArtifactResolver {
135
135
  return {
136
136
  allowed: false,
137
137
  reason: `Cannot write directly to '${this.sharedPath}'. Use: bmad scope sync-up`,
138
- warning: null
138
+ warning: null,
139
139
  };
140
140
  }
141
141
 
@@ -144,7 +144,7 @@ class ArtifactResolver {
144
144
  return {
145
145
  allowed: false,
146
146
  reason: `Cannot write to reserved path '${targetScope}'`,
147
- warning: null
147
+ warning: null,
148
148
  };
149
149
  }
150
150
 
@@ -153,7 +153,7 @@ class ArtifactResolver {
153
153
  return {
154
154
  allowed: true,
155
155
  reason: `Write allowed to current scope '${this.currentScope}'`,
156
- warning: null
156
+ warning: null,
157
157
  };
158
158
  }
159
159
 
@@ -164,31 +164,31 @@ class ArtifactResolver {
164
164
  return {
165
165
  allowed: false,
166
166
  reason: `Cannot write to scope '${targetScope}' while in scope '${this.currentScope}'`,
167
- warning: null
167
+ warning: null,
168
168
  };
169
169
  }
170
-
170
+
171
171
  case 'warn': {
172
172
  return {
173
173
  allowed: true,
174
174
  reason: 'Write allowed with warning in warn mode',
175
- warning: `Warning: Writing to scope '${targetScope}' from scope '${this.currentScope}'`
175
+ warning: `Warning: Writing to scope '${targetScope}' from scope '${this.currentScope}'`,
176
176
  };
177
177
  }
178
-
178
+
179
179
  case 'permissive': {
180
180
  return {
181
181
  allowed: true,
182
182
  reason: 'Write allowed in permissive mode',
183
- warning: null
183
+ warning: null,
184
184
  };
185
185
  }
186
-
186
+
187
187
  default: {
188
188
  return {
189
189
  allowed: false,
190
190
  reason: 'Unknown isolation mode',
191
- warning: null
191
+ warning: null,
192
192
  };
193
193
  }
194
194
  }
@@ -198,7 +198,7 @@ class ArtifactResolver {
198
198
  return {
199
199
  allowed: true,
200
200
  reason: 'Path is outside scope system',
201
- warning: null
201
+ warning: null,
202
202
  };
203
203
  }
204
204
 
@@ -209,11 +209,11 @@ class ArtifactResolver {
209
209
  */
210
210
  validateWrite(filePath) {
211
211
  const result = this.canWrite(filePath);
212
-
212
+
213
213
  if (!result.allowed) {
214
214
  throw new Error(result.reason);
215
215
  }
216
-
216
+
217
217
  if (result.warning) {
218
218
  console.warn(result.warning);
219
219
  }
@@ -227,7 +227,7 @@ class ArtifactResolver {
227
227
  */
228
228
  resolveScopePath(relativePath, scopeId = null) {
229
229
  const scope = scopeId || this.currentScope;
230
-
230
+
231
231
  if (!scope) {
232
232
  // No scope - return path relative to base
233
233
  return path.join(this.basePath, relativePath);
@@ -254,7 +254,7 @@ class ArtifactResolver {
254
254
  currentScope: this.currentScope ? path.join(this.basePath, this.currentScope) : null,
255
255
  shared: path.join(this.basePath, this.sharedPath),
256
256
  allScopes: `${this.basePath}/*`,
257
- description: 'Read access is allowed to all scopes and shared directories'
257
+ description: 'Read access is allowed to all scopes and shared directories',
258
258
  };
259
259
  }
260
260
 
@@ -266,13 +266,13 @@ class ArtifactResolver {
266
266
  if (!this.currentScope) {
267
267
  return {
268
268
  all: this.basePath,
269
- description: 'No scope active - all paths writable (legacy mode)'
269
+ description: 'No scope active - all paths writable (legacy mode)',
270
270
  };
271
271
  }
272
272
 
273
273
  return {
274
274
  currentScope: path.join(this.basePath, this.currentScope),
275
- description: `Write access limited to scope '${this.currentScope}'`
275
+ description: `Write access limited to scope '${this.currentScope}'`,
276
276
  };
277
277
  }
278
278
 
@@ -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
- ? EventLogger.EventTypes.SYNC_UP
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');