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.
@@ -7,12 +7,12 @@ const { StateLock } = require('./state-lock');
7
7
  /**
8
8
  * Handles synchronization between scopes and shared layer
9
9
  * Implements sync-up (promote to shared) and sync-down (pull from shared)
10
- *
10
+ *
11
11
  * @class ScopeSync
12
12
  * @requires fs-extra
13
13
  * @requires yaml
14
14
  * @requires StateLock
15
- *
15
+ *
16
16
  * @example
17
17
  * const sync = new ScopeSync({ projectRoot: '/path/to/project' });
18
18
  * await sync.syncUp('auth', ['architecture.md']);
@@ -24,13 +24,13 @@ class ScopeSync {
24
24
  this.outputPath = path.join(this.projectRoot, this.outputBase);
25
25
  this.sharedPath = path.join(this.outputPath, '_shared');
26
26
  this.stateLock = new StateLock();
27
-
27
+
28
28
  // Default patterns for promotable artifacts
29
29
  this.promotablePatterns = options.promotablePatterns || [
30
30
  'architecture/*.md',
31
31
  'contracts/*.md',
32
32
  'principles/*.md',
33
- 'project-context.md'
33
+ 'project-context.md',
34
34
  ];
35
35
  }
36
36
 
@@ -74,7 +74,7 @@ class ScopeSync {
74
74
  */
75
75
  async loadSyncMeta(scopeId) {
76
76
  const metaPath = this.getSyncMetaPath(scopeId);
77
-
77
+
78
78
  try {
79
79
  if (await fs.pathExists(metaPath)) {
80
80
  const content = await fs.readFile(metaPath, 'utf8');
@@ -89,7 +89,7 @@ class ScopeSync {
89
89
  lastSyncUp: null,
90
90
  lastSyncDown: null,
91
91
  promotedFiles: {},
92
- pulledFiles: {}
92
+ pulledFiles: {},
93
93
  };
94
94
  }
95
95
 
@@ -117,14 +117,14 @@ class ScopeSync {
117
117
  promoted: [],
118
118
  conflicts: [],
119
119
  errors: [],
120
- skipped: []
120
+ skipped: [],
121
121
  };
122
122
 
123
123
  try {
124
124
  const scopePath = path.join(this.outputPath, scopeId);
125
-
125
+
126
126
  // Verify scope exists
127
- if (!await fs.pathExists(scopePath)) {
127
+ if (!(await fs.pathExists(scopePath))) {
128
128
  throw new Error(`Scope '${scopeId}' does not exist`);
129
129
  }
130
130
 
@@ -133,10 +133,10 @@ class ScopeSync {
133
133
 
134
134
  // Determine files to promote
135
135
  let filesToPromote = [];
136
-
136
+
137
137
  if (files && files.length > 0) {
138
138
  // Use specified files
139
- filesToPromote = files.map(f => path.isAbsolute(f) ? f : path.join(scopePath, f));
139
+ filesToPromote = files.map((f) => (path.isAbsolute(f) ? f : path.join(scopePath, f)));
140
140
  } else {
141
141
  // Find promotable files using patterns
142
142
  filesToPromote = await this.findPromotableFiles(scopePath);
@@ -146,7 +146,7 @@ class ScopeSync {
146
146
  for (const sourceFile of filesToPromote) {
147
147
  try {
148
148
  // Verify file exists
149
- if (!await fs.pathExists(sourceFile)) {
149
+ if (!(await fs.pathExists(sourceFile))) {
150
150
  result.skipped.push({ file: sourceFile, reason: 'File not found' });
151
151
  continue;
152
152
  }
@@ -156,7 +156,7 @@ class ScopeSync {
156
156
  const targetPath = path.join(this.sharedPath, scopeId, relativePath);
157
157
 
158
158
  // Check for conflicts
159
- if (await fs.pathExists(targetPath) && !options.force) {
159
+ if ((await fs.pathExists(targetPath)) && !options.force) {
160
160
  const sourceHash = await this.computeHash(sourceFile);
161
161
  const targetHash = await this.computeHash(targetPath);
162
162
 
@@ -165,7 +165,7 @@ class ScopeSync {
165
165
  file: relativePath,
166
166
  source: sourceFile,
167
167
  target: targetPath,
168
- resolution: 'manual'
168
+ resolution: 'manual',
169
169
  });
170
170
  continue;
171
171
  }
@@ -184,7 +184,7 @@ class ScopeSync {
184
184
  promoted_at: new Date().toISOString(),
185
185
  original_path: relativePath,
186
186
  original_hash: await this.computeHash(sourceFile),
187
- version: (meta.promotedFiles[relativePath]?.version || 0) + 1
187
+ version: (meta.promotedFiles[relativePath]?.version || 0) + 1,
188
188
  };
189
189
  await fs.writeFile(metaFilePath, yaml.stringify(fileMeta), 'utf8');
190
190
 
@@ -192,18 +192,17 @@ class ScopeSync {
192
192
  meta.promotedFiles[relativePath] = {
193
193
  promotedAt: fileMeta.promoted_at,
194
194
  hash: fileMeta.original_hash,
195
- version: fileMeta.version
195
+ version: fileMeta.version,
196
196
  };
197
197
 
198
198
  result.promoted.push({
199
199
  file: relativePath,
200
- target: targetPath
200
+ target: targetPath,
201
201
  });
202
-
203
202
  } catch (error) {
204
203
  result.errors.push({
205
204
  file: sourceFile,
206
- error: error.message
205
+ error: error.message,
207
206
  });
208
207
  }
209
208
  }
@@ -213,7 +212,6 @@ class ScopeSync {
213
212
  await this.saveSyncMeta(scopeId, meta);
214
213
 
215
214
  result.success = result.errors.length === 0;
216
-
217
215
  } catch (error) {
218
216
  result.success = false;
219
217
  result.errors.push({ error: error.message });
@@ -234,14 +232,14 @@ class ScopeSync {
234
232
  pulled: [],
235
233
  conflicts: [],
236
234
  errors: [],
237
- upToDate: []
235
+ upToDate: [],
238
236
  };
239
237
 
240
238
  try {
241
239
  const scopePath = path.join(this.outputPath, scopeId);
242
-
240
+
243
241
  // Verify scope exists
244
- if (!await fs.pathExists(scopePath)) {
242
+ if (!(await fs.pathExists(scopePath))) {
245
243
  throw new Error(`Scope '${scopeId}' does not exist`);
246
244
  }
247
245
 
@@ -250,10 +248,10 @@ class ScopeSync {
250
248
 
251
249
  // Find all shared files from any scope
252
250
  const sharedScopeDirs = await fs.readdir(this.sharedPath, { withFileTypes: true });
253
-
251
+
254
252
  for (const dir of sharedScopeDirs) {
255
253
  if (!dir.isDirectory() || dir.name.startsWith('.')) continue;
256
-
254
+
257
255
  const sharedScopePath = path.join(this.sharedPath, dir.name);
258
256
  const files = await this.getAllFiles(sharedScopePath);
259
257
 
@@ -264,7 +262,7 @@ class ScopeSync {
264
262
  try {
265
263
  const relativePath = path.relative(sharedScopePath, sharedFile);
266
264
  const targetPath = path.join(scopePath, 'shared', dir.name, relativePath);
267
-
265
+
268
266
  // Load shared file metadata
269
267
  const metaFilePath = `${sharedFile}.meta`;
270
268
  let fileMeta = null;
@@ -281,7 +279,7 @@ class ScopeSync {
281
279
  }
282
280
 
283
281
  // Check for local conflicts
284
- if (await fs.pathExists(targetPath) && !options.force) {
282
+ if ((await fs.pathExists(targetPath)) && !options.force) {
285
283
  const localHash = await this.computeHash(targetPath);
286
284
  const sharedHash = await this.computeHash(sharedFile);
287
285
 
@@ -294,7 +292,7 @@ class ScopeSync {
294
292
  scope: dir.name,
295
293
  local: targetPath,
296
294
  shared: sharedFile,
297
- resolution: options.resolution || 'prompt'
295
+ resolution: options.resolution || 'prompt',
298
296
  });
299
297
  continue;
300
298
  }
@@ -311,19 +309,18 @@ class ScopeSync {
311
309
  meta.pulledFiles[`${dir.name}/${relativePath}`] = {
312
310
  pulledAt: new Date().toISOString(),
313
311
  version: fileMeta?.version || 1,
314
- hash: await this.computeHash(targetPath)
312
+ hash: await this.computeHash(targetPath),
315
313
  };
316
314
 
317
315
  result.pulled.push({
318
316
  file: relativePath,
319
317
  scope: dir.name,
320
- target: targetPath
318
+ target: targetPath,
321
319
  });
322
-
323
320
  } catch (error) {
324
321
  result.errors.push({
325
322
  file: sharedFile,
326
- error: error.message
323
+ error: error.message,
327
324
  });
328
325
  }
329
326
  }
@@ -334,7 +331,6 @@ class ScopeSync {
334
331
  await this.saveSyncMeta(scopeId, meta);
335
332
 
336
333
  result.success = result.errors.length === 0;
337
-
338
334
  } catch (error) {
339
335
  result.success = false;
340
336
  result.errors.push({ error: error.message });
@@ -350,18 +346,18 @@ class ScopeSync {
350
346
  */
351
347
  async findPromotableFiles(scopePath) {
352
348
  const files = [];
353
-
349
+
354
350
  for (const pattern of this.promotablePatterns) {
355
351
  // Simple glob-like matching
356
352
  const parts = pattern.split('/');
357
353
  const dir = parts.slice(0, -1).join('/');
358
354
  const filePattern = parts.at(-1);
359
-
355
+
360
356
  const searchDir = path.join(scopePath, dir);
361
-
357
+
362
358
  if (await fs.pathExists(searchDir)) {
363
359
  const entries = await fs.readdir(searchDir, { withFileTypes: true });
364
-
360
+
365
361
  for (const entry of entries) {
366
362
  if (entry.isFile() && this.matchPattern(entry.name, filePattern)) {
367
363
  files.push(path.join(searchDir, entry.name));
@@ -380,9 +376,7 @@ class ScopeSync {
380
376
  * @returns {boolean} True if matches
381
377
  */
382
378
  matchPattern(filename, pattern) {
383
- const regexPattern = pattern
384
- .replaceAll('.', String.raw`\.`)
385
- .replaceAll('*', '.*');
379
+ const regexPattern = pattern.replaceAll('.', String.raw`\.`).replaceAll('*', '.*');
386
380
  const regex = new RegExp(`^${regexPattern}$`);
387
381
  return regex.test(filename);
388
382
  }
@@ -394,13 +388,13 @@ class ScopeSync {
394
388
  */
395
389
  async getAllFiles(dir) {
396
390
  const files = [];
397
-
391
+
398
392
  async function walk(currentDir) {
399
393
  const entries = await fs.readdir(currentDir, { withFileTypes: true });
400
-
394
+
401
395
  for (const entry of entries) {
402
396
  const fullPath = path.join(currentDir, entry.name);
403
-
397
+
404
398
  if (entry.isDirectory()) {
405
399
  await walk(fullPath);
406
400
  } else {
@@ -408,7 +402,7 @@ class ScopeSync {
408
402
  }
409
403
  }
410
404
  }
411
-
405
+
412
406
  await walk(dir);
413
407
  return files;
414
408
  }
@@ -420,14 +414,14 @@ class ScopeSync {
420
414
  */
421
415
  async getSyncStatus(scopeId) {
422
416
  const meta = await this.loadSyncMeta(scopeId);
423
-
417
+
424
418
  return {
425
419
  lastSyncUp: meta.lastSyncUp,
426
420
  lastSyncDown: meta.lastSyncDown,
427
421
  promotedCount: Object.keys(meta.promotedFiles).length,
428
422
  pulledCount: Object.keys(meta.pulledFiles).length,
429
423
  promotedFiles: Object.keys(meta.promotedFiles),
430
- pulledFiles: Object.keys(meta.pulledFiles)
424
+ pulledFiles: Object.keys(meta.pulledFiles),
431
425
  };
432
426
  }
433
427
 
@@ -452,7 +446,7 @@ class ScopeSync {
452
446
  case 'keep-shared': {
453
447
  // Overwrite with shared
454
448
  await fs.copy(conflict.shared || conflict.source, conflict.local || conflict.target, {
455
- overwrite: true
449
+ overwrite: true,
456
450
  });
457
451
  result.action = 'kept-shared';
458
452
  result.success = true;
@@ -464,7 +458,7 @@ class ScopeSync {
464
458
  const backupPath = `${conflict.local || conflict.target}.backup.${Date.now()}`;
465
459
  await fs.copy(conflict.local || conflict.target, backupPath);
466
460
  await fs.copy(conflict.shared || conflict.source, conflict.local || conflict.target, {
467
- overwrite: true
461
+ overwrite: true,
468
462
  });
469
463
  result.action = 'backed-up-and-updated';
470
464
  result.backupPath = backupPath;
@@ -7,13 +7,13 @@ const yaml = require('yaml');
7
7
  class ScopeValidator {
8
8
  constructor() {
9
9
  // Scope ID validation pattern: lowercase alphanumeric + hyphens, 2-50 chars
10
-
10
+
11
11
  // Reserved scope IDs that cannot be used
12
12
  this.reservedIds = ['_shared', '_events', '_config', 'global', 'default'];
13
-
13
+
14
14
  // Valid isolation modes
15
15
  this.validIsolationModes = ['strict', 'warn', 'permissive'];
16
-
16
+
17
17
  // Valid scope statuses
18
18
  this.validStatuses = ['active', 'archived'];
19
19
  }
@@ -38,7 +38,8 @@ class ScopeValidator {
38
38
  if (!this.scopeIdPattern.test(scopeId)) {
39
39
  return {
40
40
  valid: false,
41
- error: 'Scope ID must start with lowercase letter, contain only lowercase letters, numbers, and hyphens, and end with letter or number'
41
+ error:
42
+ 'Scope ID must start with lowercase letter, contain only lowercase letters, numbers, and hyphens, and end with letter or number',
42
43
  };
43
44
  }
44
45
 
@@ -46,7 +47,7 @@ class ScopeValidator {
46
47
  if (this.reservedIds.includes(scopeId)) {
47
48
  return {
48
49
  valid: false,
49
- error: `Scope ID '${scopeId}' is reserved and cannot be used`
50
+ error: `Scope ID '${scopeId}' is reserved and cannot be used`,
50
51
  };
51
52
  }
52
53
 
@@ -132,8 +133,8 @@ class ScopeValidator {
132
133
  }
133
134
  }
134
135
  if (scope._meta.artifact_count !== undefined && (!Number.isInteger(scope._meta.artifact_count) || scope._meta.artifact_count < 0)) {
135
- errors.push('_meta.artifact_count must be a non-negative integer');
136
- }
136
+ errors.push('_meta.artifact_count must be a non-negative integer');
137
+ }
137
138
  } else {
138
139
  errors.push('Scope _meta must be an object');
139
140
  }
@@ -141,7 +142,7 @@ class ScopeValidator {
141
142
 
142
143
  return {
143
144
  valid: errors.length === 0,
144
- errors
145
+ errors,
145
146
  };
146
147
  }
147
148
 
@@ -172,13 +173,7 @@ class ScopeValidator {
172
173
  // Recursively check this dependency's dependencies
173
174
  const depScope = allScopes[dep];
174
175
  if (depScope && depScope.dependencies) {
175
- const result = this.detectCircularDependencies(
176
- dep,
177
- depScope.dependencies,
178
- allScopes,
179
- new Set(visited),
180
- [...chain]
181
- );
176
+ const result = this.detectCircularDependencies(dep, depScope.dependencies, allScopes, new Set(visited), [...chain]);
182
177
  if (result.hasCircular) {
183
178
  return result;
184
179
  }
@@ -249,7 +244,7 @@ class ScopeValidator {
249
244
 
250
245
  return {
251
246
  valid: errors.length === 0,
252
- errors
247
+ errors,
253
248
  };
254
249
  }
255
250
 
@@ -262,17 +257,17 @@ class ScopeValidator {
262
257
  try {
263
258
  const config = yaml.parse(yamlContent);
264
259
  const validation = this.validateConfig(config);
265
-
260
+
266
261
  return {
267
262
  valid: validation.valid,
268
263
  errors: validation.errors,
269
- config: validation.valid ? config : null
264
+ config: validation.valid ? config : null,
270
265
  };
271
266
  } catch (error) {
272
267
  return {
273
268
  valid: false,
274
269
  errors: [`Failed to parse YAML: ${error.message}`],
275
- config: null
270
+ config: null,
276
271
  };
277
272
  }
278
273
  }
@@ -288,9 +283,9 @@ class ScopeValidator {
288
283
  allow_adhoc_scopes: true,
289
284
  isolation_mode: 'strict',
290
285
  default_output_base: '_bmad-output',
291
- default_shared_path: '_bmad-output/_shared'
286
+ default_shared_path: '_bmad-output/_shared',
292
287
  },
293
- scopes: {}
288
+ scopes: {},
294
289
  };
295
290
  }
296
291
  scopeIdPattern = /^[a-z][a-z0-9-]*[a-z0-9]$/;
@@ -5,11 +5,11 @@ const yaml = require('yaml');
5
5
  /**
6
6
  * File locking utilities for safe concurrent access to state files
7
7
  * Uses file-based locking for cross-process synchronization
8
- *
8
+ *
9
9
  * @class StateLock
10
10
  * @requires fs-extra
11
11
  * @requires yaml
12
- *
12
+ *
13
13
  * @example
14
14
  * const lock = new StateLock();
15
15
  * const result = await lock.withLock('/path/to/state.yaml', async () => {
@@ -57,25 +57,22 @@ class StateLock {
57
57
  */
58
58
  async acquireLock(filePath) {
59
59
  const lockPath = this.getLockPath(filePath);
60
-
60
+
61
61
  for (let attempt = 0; attempt < this.retries; attempt++) {
62
62
  try {
63
63
  // Check if lock exists
64
64
  const lockExists = await fs.pathExists(lockPath);
65
-
65
+
66
66
  if (lockExists) {
67
67
  // Check if lock is stale
68
68
  const isStale = await this.isLockStale(lockPath);
69
-
69
+
70
70
  if (isStale) {
71
71
  // Remove stale lock
72
72
  await fs.remove(lockPath);
73
73
  } else {
74
74
  // Lock is active, wait and retry
75
- const waitTime = Math.min(
76
- this.minTimeout * Math.pow(2, attempt),
77
- this.maxTimeout
78
- );
75
+ const waitTime = Math.min(this.minTimeout * Math.pow(2, attempt), this.maxTimeout);
79
76
  await this.sleep(waitTime);
80
77
  continue;
81
78
  }
@@ -85,22 +82,19 @@ class StateLock {
85
82
  const lockContent = {
86
83
  pid: process.pid,
87
84
  hostname: require('node:os').hostname(),
88
- created: new Date().toISOString()
85
+ created: new Date().toISOString(),
89
86
  };
90
87
 
91
88
  // Use exclusive flag for atomic creation
92
89
  await fs.writeFile(lockPath, JSON.stringify(lockContent), {
93
- flag: 'wx' // Exclusive create
90
+ flag: 'wx', // Exclusive create
94
91
  });
95
92
 
96
93
  return { success: true, lockPath };
97
94
  } catch (error) {
98
95
  if (error.code === 'EEXIST') {
99
96
  // Lock was created by another process, retry
100
- const waitTime = Math.min(
101
- this.minTimeout * Math.pow(2, attempt),
102
- this.maxTimeout
103
- );
97
+ const waitTime = Math.min(this.minTimeout * Math.pow(2, attempt), this.maxTimeout);
104
98
  await this.sleep(waitTime);
105
99
  continue;
106
100
  }
@@ -118,7 +112,7 @@ class StateLock {
118
112
  */
119
113
  async releaseLock(filePath) {
120
114
  const lockPath = this.getLockPath(filePath);
121
-
115
+
122
116
  try {
123
117
  await fs.remove(lockPath);
124
118
  return true;
@@ -138,7 +132,7 @@ class StateLock {
138
132
  */
139
133
  async withLock(filePath, operation) {
140
134
  const lockResult = await this.acquireLock(filePath);
141
-
135
+
142
136
  if (!lockResult.success) {
143
137
  throw new Error(`Failed to acquire lock on ${filePath}: ${lockResult.reason}`);
144
138
  }
@@ -159,12 +153,12 @@ class StateLock {
159
153
  try {
160
154
  const content = await fs.readFile(filePath, 'utf8');
161
155
  const data = yaml.parse(content);
162
-
156
+
163
157
  // Ensure version field exists
164
158
  if (!data._version) {
165
159
  data._version = 0;
166
160
  }
167
-
161
+
168
162
  return data;
169
163
  } catch (error) {
170
164
  if (error.code === 'ENOENT') {
@@ -183,17 +177,17 @@ class StateLock {
183
177
  async writeYaml(filePath, data) {
184
178
  // Ensure directory exists
185
179
  await fs.ensureDir(path.dirname(filePath));
186
-
180
+
187
181
  // Update version and timestamp
188
182
  const versionedData = {
189
183
  ...data,
190
184
  _version: (data._version || 0) + 1,
191
- _lastModified: new Date().toISOString()
185
+ _lastModified: new Date().toISOString(),
192
186
  };
193
187
 
194
188
  const yamlContent = yaml.stringify(versionedData, { indent: 2 });
195
189
  await fs.writeFile(filePath, yamlContent, 'utf8');
196
-
190
+
197
191
  return versionedData;
198
192
  }
199
193
 
@@ -208,17 +202,17 @@ class StateLock {
208
202
  // Read current data
209
203
  const data = await this.readYaml(filePath);
210
204
  const currentVersion = data._version || 0;
211
-
205
+
212
206
  // Apply modifications
213
207
  const modified = await modifier(data);
214
-
208
+
215
209
  // Update version
216
210
  modified._version = currentVersion + 1;
217
211
  modified._lastModified = new Date().toISOString();
218
-
212
+
219
213
  // Write back
220
214
  await this.writeYaml(filePath, modified);
221
-
215
+
222
216
  return modified;
223
217
  });
224
218
  }
@@ -233,30 +227,30 @@ class StateLock {
233
227
  async optimisticUpdate(filePath, expectedVersion, newData) {
234
228
  return this.withLock(filePath, async () => {
235
229
  const current = await this.readYaml(filePath);
236
-
230
+
237
231
  // Check version
238
232
  if (current._version !== expectedVersion) {
239
233
  return {
240
234
  success: false,
241
235
  data: current,
242
236
  conflict: true,
243
- message: `Version conflict: expected ${expectedVersion}, found ${current._version}`
237
+ message: `Version conflict: expected ${expectedVersion}, found ${current._version}`,
244
238
  };
245
239
  }
246
-
240
+
247
241
  // Update with new version
248
242
  const updated = {
249
243
  ...newData,
250
244
  _version: expectedVersion + 1,
251
- _lastModified: new Date().toISOString()
245
+ _lastModified: new Date().toISOString(),
252
246
  };
253
-
247
+
254
248
  await this.writeYaml(filePath, updated);
255
-
249
+
256
250
  return {
257
251
  success: true,
258
252
  data: updated,
259
- conflict: false
253
+ conflict: false,
260
254
  };
261
255
  });
262
256
  }
@@ -267,7 +261,7 @@ class StateLock {
267
261
  * @returns {Promise<void>}
268
262
  */
269
263
  sleep(ms) {
270
- return new Promise(resolve => setTimeout(resolve, ms));
264
+ return new Promise((resolve) => setTimeout(resolve, ms));
271
265
  }
272
266
 
273
267
  /**
@@ -277,14 +271,14 @@ class StateLock {
277
271
  */
278
272
  async isLocked(filePath) {
279
273
  const lockPath = this.getLockPath(filePath);
280
-
274
+
281
275
  try {
282
276
  const exists = await fs.pathExists(lockPath);
283
-
277
+
284
278
  if (!exists) {
285
279
  return false;
286
280
  }
287
-
281
+
288
282
  // Check if lock is stale
289
283
  const isStale = await this.isLockStale(lockPath);
290
284
  return !isStale;
@@ -300,22 +294,22 @@ class StateLock {
300
294
  */
301
295
  async getLockInfo(filePath) {
302
296
  const lockPath = this.getLockPath(filePath);
303
-
297
+
304
298
  try {
305
299
  const exists = await fs.pathExists(lockPath);
306
-
300
+
307
301
  if (!exists) {
308
302
  return null;
309
303
  }
310
-
304
+
311
305
  const content = await fs.readFile(lockPath, 'utf8');
312
306
  const info = JSON.parse(content);
313
307
  const stat = await fs.stat(lockPath);
314
-
308
+
315
309
  return {
316
310
  ...info,
317
311
  age: Date.now() - stat.mtimeMs,
318
- isStale: Date.now() - stat.mtimeMs > this.staleTimeout
312
+ isStale: Date.now() - stat.mtimeMs > this.staleTimeout,
319
313
  };
320
314
  } catch {
321
315
  return null;
@@ -329,7 +323,7 @@ class StateLock {
329
323
  */
330
324
  async forceRelease(filePath) {
331
325
  const lockPath = this.getLockPath(filePath);
332
-
326
+
333
327
  try {
334
328
  await fs.remove(lockPath);
335
329
  return true;
@@ -29,12 +29,12 @@ output_folder:
29
29
  scope_settings:
30
30
  header: "Scope System Settings"
31
31
  subheader: "Configure multi-scope artifact isolation"
32
-
32
+
33
33
  allow_adhoc_scopes:
34
34
  prompt: "Allow creating scopes on-demand during workflows?"
35
35
  default: true
36
36
  result: "{value}"
37
-
37
+
38
38
  isolation_mode:
39
39
  prompt: "Scope isolation mode"
40
40
  default: "strict"