agileflow 2.94.1 → 2.95.1
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/CHANGELOG.md +20 -0
- package/README.md +3 -3
- package/lib/colors.generated.js +117 -0
- package/lib/colors.js +59 -109
- package/lib/generator-factory.js +333 -0
- package/lib/path-utils.js +49 -0
- package/lib/session-registry.js +25 -15
- package/lib/smart-json-file.js +40 -32
- package/lib/state-machine.js +286 -0
- package/package.json +1 -1
- package/scripts/agileflow-configure.js +7 -6
- package/scripts/archive-completed-stories.sh +86 -11
- package/scripts/babysit-context-restore.js +89 -0
- package/scripts/claude-tmux.sh +111 -5
- package/scripts/damage-control/bash-tool-damage-control.js +11 -247
- package/scripts/damage-control/edit-tool-damage-control.js +9 -249
- package/scripts/damage-control/write-tool-damage-control.js +9 -244
- package/scripts/generate-colors.js +314 -0
- package/scripts/lib/colors.generated.sh +82 -0
- package/scripts/lib/colors.sh +10 -70
- package/scripts/lib/configure-features.js +401 -0
- package/scripts/lib/context-loader.js +181 -52
- package/scripts/precompact-context.sh +54 -17
- package/scripts/session-coordinator.sh +2 -2
- package/scripts/session-manager.js +653 -10
- package/src/core/commands/audit.md +93 -0
- package/src/core/commands/auto.md +73 -0
- package/src/core/commands/babysit.md +169 -13
- package/src/core/commands/baseline.md +73 -0
- package/src/core/commands/batch.md +64 -0
- package/src/core/commands/blockers.md +60 -0
- package/src/core/commands/board.md +66 -0
- package/src/core/commands/choose.md +77 -0
- package/src/core/commands/ci.md +77 -0
- package/src/core/commands/compress.md +27 -1
- package/src/core/commands/configure.md +126 -10
- package/src/core/commands/council.md +74 -0
- package/src/core/commands/debt.md +72 -0
- package/src/core/commands/deploy.md +73 -0
- package/src/core/commands/deps.md +68 -0
- package/src/core/commands/docs.md +60 -0
- package/src/core/commands/feedback.md +68 -0
- package/src/core/commands/ideate.md +74 -0
- package/src/core/commands/impact.md +74 -0
- package/src/core/commands/install.md +529 -0
- package/src/core/commands/maintain.md +558 -0
- package/src/core/commands/metrics.md +75 -0
- package/src/core/commands/multi-expert.md +74 -0
- package/src/core/commands/packages.md +69 -0
- package/src/core/commands/readme-sync.md +64 -0
- package/src/core/commands/research/analyze.md +285 -121
- package/src/core/commands/research/import.md +281 -109
- package/src/core/commands/retro.md +76 -0
- package/src/core/commands/review.md +72 -0
- package/src/core/commands/rlm.md +83 -0
- package/src/core/commands/rpi.md +90 -0
- package/src/core/commands/session/cleanup.md +214 -12
- package/src/core/commands/session/end.md +155 -17
- package/src/core/commands/sprint.md +72 -0
- package/src/core/commands/story-validate.md +68 -0
- package/src/core/commands/template.md +69 -0
- package/src/core/commands/tests.md +83 -0
- package/src/core/commands/update.md +59 -0
- package/src/core/commands/validate-expertise.md +76 -0
- package/src/core/commands/velocity.md +74 -0
- package/src/core/commands/verify.md +91 -0
- package/src/core/commands/whats-new.md +69 -0
- package/src/core/commands/workflow.md +88 -0
- package/src/core/templates/command-documentation.md +187 -0
- package/tools/cli/commands/session.js +1171 -0
- package/tools/cli/commands/setup.js +2 -81
- package/tools/cli/installers/core/installer.js +0 -5
- package/tools/cli/installers/ide/claude-code.js +6 -0
- package/tools/cli/lib/config-manager.js +42 -5
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GeneratorFactory - Dependency Injection for Content Generators
|
|
3
|
+
*
|
|
4
|
+
* Provides a centralized factory for creating content generators with:
|
|
5
|
+
* - Shared PlaceholderRegistry built once
|
|
6
|
+
* - Dependency injection for testability
|
|
7
|
+
* - Generator registration pattern
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const factory = new GeneratorFactory();
|
|
11
|
+
* factory.registerGenerator('help', HelpGenerator);
|
|
12
|
+
* factory.registerGenerator('readme', ReadmeGenerator);
|
|
13
|
+
*
|
|
14
|
+
* // Build registry once, pass to all generators
|
|
15
|
+
* const context = await factory.buildContext();
|
|
16
|
+
* await factory.runAll(context);
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const { PlaceholderRegistry, createDefaultRegistry } = require('./placeholder-registry');
|
|
22
|
+
const { createContainer, createScannerFactory } = require('./registry-di');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Generator interface
|
|
26
|
+
* @typedef {Object} IGenerator
|
|
27
|
+
* @property {string} name - Generator name
|
|
28
|
+
* @property {Function} register - Register placeholders: (registry) => void
|
|
29
|
+
* @property {Function} generate - Generate content: (context) => Promise<void>
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* GeneratorFactory - Factory for creating and running generators
|
|
34
|
+
*/
|
|
35
|
+
class GeneratorFactory {
|
|
36
|
+
/**
|
|
37
|
+
* Create a new GeneratorFactory
|
|
38
|
+
* @param {Object} options - Factory options
|
|
39
|
+
* @param {Object} [options.container] - DI container (from registry-di.js)
|
|
40
|
+
* @param {PlaceholderRegistry} [options.registry] - Pre-built registry
|
|
41
|
+
* @param {Object} [options.paths] - Path configuration
|
|
42
|
+
*/
|
|
43
|
+
constructor(options = {}) {
|
|
44
|
+
this._generators = new Map();
|
|
45
|
+
this._container = options.container || createContainer();
|
|
46
|
+
this._registry = options.registry || null;
|
|
47
|
+
this._paths = options.paths || {};
|
|
48
|
+
this._built = false;
|
|
49
|
+
this._context = null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Register a generator with the factory
|
|
54
|
+
* @param {string} name - Generator name
|
|
55
|
+
* @param {IGenerator|Function} generator - Generator instance or class
|
|
56
|
+
* @returns {GeneratorFactory} this for chaining
|
|
57
|
+
*/
|
|
58
|
+
registerGenerator(name, generator) {
|
|
59
|
+
if (!name || typeof name !== 'string') {
|
|
60
|
+
throw new Error('Generator name must be a non-empty string');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Support both instances and classes
|
|
64
|
+
const instance = typeof generator === 'function' ? new generator() : generator;
|
|
65
|
+
|
|
66
|
+
if (!instance.register || typeof instance.register !== 'function') {
|
|
67
|
+
throw new Error(`Generator "${name}" must have a register(registry) method`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!instance.generate || typeof instance.generate !== 'function') {
|
|
71
|
+
throw new Error(`Generator "${name}" must have a generate(context) method`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this._generators.set(name, instance);
|
|
75
|
+
|
|
76
|
+
// Reset built state when new generator is added
|
|
77
|
+
this._built = false;
|
|
78
|
+
this._context = null;
|
|
79
|
+
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Unregister a generator
|
|
85
|
+
* @param {string} name - Generator name
|
|
86
|
+
* @returns {boolean} True if removed
|
|
87
|
+
*/
|
|
88
|
+
unregisterGenerator(name) {
|
|
89
|
+
const result = this._generators.delete(name);
|
|
90
|
+
if (result) {
|
|
91
|
+
this._built = false;
|
|
92
|
+
this._context = null;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get list of registered generator names
|
|
99
|
+
* @returns {string[]}
|
|
100
|
+
*/
|
|
101
|
+
getGeneratorNames() {
|
|
102
|
+
return Array.from(this._generators.keys());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if a generator is registered
|
|
107
|
+
* @param {string} name - Generator name
|
|
108
|
+
* @returns {boolean}
|
|
109
|
+
*/
|
|
110
|
+
hasGenerator(name) {
|
|
111
|
+
return this._generators.has(name);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get the shared registry instance
|
|
116
|
+
* @returns {PlaceholderRegistry}
|
|
117
|
+
*/
|
|
118
|
+
getRegistry() {
|
|
119
|
+
if (!this._registry) {
|
|
120
|
+
this._registry = createDefaultRegistry();
|
|
121
|
+
}
|
|
122
|
+
return this._registry;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build context by registering all generator placeholders
|
|
127
|
+
* Registry is built once and shared across all generators
|
|
128
|
+
* @param {Object} [baseContext={}] - Base context to merge
|
|
129
|
+
* @returns {Object} Built context
|
|
130
|
+
*/
|
|
131
|
+
buildContext(baseContext = {}) {
|
|
132
|
+
if (this._built && this._context) {
|
|
133
|
+
return { ...this._context, ...baseContext };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const registry = this.getRegistry();
|
|
137
|
+
|
|
138
|
+
// Let each generator register its placeholders
|
|
139
|
+
for (const [name, generator] of this._generators) {
|
|
140
|
+
try {
|
|
141
|
+
generator.register(registry);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error(`Failed to register placeholders for "${name}":`, error.message);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Build context with resolved values
|
|
148
|
+
this._context = {
|
|
149
|
+
registry,
|
|
150
|
+
container: this._container,
|
|
151
|
+
paths: this._paths,
|
|
152
|
+
scanner: createScannerFactory(this._container),
|
|
153
|
+
...baseContext,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
this._built = true;
|
|
157
|
+
|
|
158
|
+
return this._context;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Run a specific generator
|
|
163
|
+
* @param {string} name - Generator name
|
|
164
|
+
* @param {Object} [context] - Context override
|
|
165
|
+
* @returns {Promise<{success: boolean, error?: Error}>}
|
|
166
|
+
*/
|
|
167
|
+
async runGenerator(name, context) {
|
|
168
|
+
const generator = this._generators.get(name);
|
|
169
|
+
|
|
170
|
+
if (!generator) {
|
|
171
|
+
return { success: false, error: new Error(`Generator not found: ${name}`) };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const ctx = context || this.buildContext();
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
await generator.generate(ctx);
|
|
178
|
+
return { success: true };
|
|
179
|
+
} catch (error) {
|
|
180
|
+
return { success: false, error };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Run all registered generators
|
|
186
|
+
* @param {Object} [context] - Context override
|
|
187
|
+
* @returns {Promise<Map<string, {success: boolean, error?: Error}>>}
|
|
188
|
+
*/
|
|
189
|
+
async runAll(context) {
|
|
190
|
+
const ctx = context || this.buildContext();
|
|
191
|
+
const results = new Map();
|
|
192
|
+
|
|
193
|
+
for (const name of this._generators.keys()) {
|
|
194
|
+
results.set(name, await this.runGenerator(name, ctx));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return results;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Run generators in parallel
|
|
202
|
+
* @param {Object} [context] - Context override
|
|
203
|
+
* @returns {Promise<Map<string, {success: boolean, error?: Error}>>}
|
|
204
|
+
*/
|
|
205
|
+
async runParallel(context) {
|
|
206
|
+
const ctx = context || this.buildContext();
|
|
207
|
+
const results = new Map();
|
|
208
|
+
|
|
209
|
+
const promises = Array.from(this._generators.keys()).map(async name => {
|
|
210
|
+
const result = await this.runGenerator(name, ctx);
|
|
211
|
+
return { name, result };
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const settled = await Promise.allSettled(promises);
|
|
215
|
+
|
|
216
|
+
for (const item of settled) {
|
|
217
|
+
if (item.status === 'fulfilled') {
|
|
218
|
+
results.set(item.value.name, item.value.result);
|
|
219
|
+
} else {
|
|
220
|
+
// Shouldn't happen since runGenerator catches errors
|
|
221
|
+
const name = 'unknown';
|
|
222
|
+
results.set(name, { success: false, error: item.reason });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return results;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Reset the factory state
|
|
231
|
+
*/
|
|
232
|
+
reset() {
|
|
233
|
+
this._built = false;
|
|
234
|
+
this._context = null;
|
|
235
|
+
this._registry = null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Get container (for testing)
|
|
240
|
+
* @returns {Object}
|
|
241
|
+
*/
|
|
242
|
+
getContainer() {
|
|
243
|
+
return this._container;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Base generator class with common utilities
|
|
249
|
+
*/
|
|
250
|
+
class BaseGenerator {
|
|
251
|
+
constructor(options = {}) {
|
|
252
|
+
this.name = options.name || this.constructor.name;
|
|
253
|
+
this.placeholders = [];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Register placeholders - override in subclass
|
|
258
|
+
* @param {PlaceholderRegistry} registry
|
|
259
|
+
*/
|
|
260
|
+
register(registry) {
|
|
261
|
+
// Subclasses should override this method
|
|
262
|
+
for (const placeholder of this.placeholders) {
|
|
263
|
+
registry.register(placeholder.name, placeholder.resolver, placeholder.config);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Generate content - override in subclass
|
|
269
|
+
* @param {Object} context
|
|
270
|
+
* @returns {Promise<void>}
|
|
271
|
+
*/
|
|
272
|
+
async generate(context) {
|
|
273
|
+
throw new Error('Subclass must implement generate(context)');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Helper to add a placeholder definition
|
|
278
|
+
* @param {string} name - Placeholder name
|
|
279
|
+
* @param {Function} resolver - Resolver function
|
|
280
|
+
* @param {Object} [config] - Configuration
|
|
281
|
+
*/
|
|
282
|
+
addPlaceholder(name, resolver, config = {}) {
|
|
283
|
+
this.placeholders.push({ name, resolver, config });
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Create a factory with standard generators
|
|
289
|
+
* @param {Object} options - Factory options
|
|
290
|
+
* @returns {GeneratorFactory}
|
|
291
|
+
*/
|
|
292
|
+
function createGeneratorFactory(options = {}) {
|
|
293
|
+
const factory = new GeneratorFactory(options);
|
|
294
|
+
|
|
295
|
+
// Register standard generators if provided
|
|
296
|
+
if (options.generators) {
|
|
297
|
+
for (const [name, generator] of Object.entries(options.generators)) {
|
|
298
|
+
factory.registerGenerator(name, generator);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return factory;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Singleton instance
|
|
306
|
+
let _factoryInstance = null;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Get singleton factory instance
|
|
310
|
+
* @param {Object} [options] - Factory options
|
|
311
|
+
* @returns {GeneratorFactory}
|
|
312
|
+
*/
|
|
313
|
+
function getGeneratorFactory(options = {}) {
|
|
314
|
+
if (!_factoryInstance || options.forceNew) {
|
|
315
|
+
_factoryInstance = createGeneratorFactory(options);
|
|
316
|
+
}
|
|
317
|
+
return _factoryInstance;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Reset singleton (for testing)
|
|
322
|
+
*/
|
|
323
|
+
function resetGeneratorFactory() {
|
|
324
|
+
_factoryInstance = null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = {
|
|
328
|
+
GeneratorFactory,
|
|
329
|
+
BaseGenerator,
|
|
330
|
+
createGeneratorFactory,
|
|
331
|
+
getGeneratorFactory,
|
|
332
|
+
resetGeneratorFactory,
|
|
333
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* path-utils.js - Unified Path Utilities Module
|
|
3
|
+
*
|
|
4
|
+
* This module consolidates all path-related utilities for AgileFlow CLI.
|
|
5
|
+
* It re-exports from validate-paths.js for backwards compatibility and
|
|
6
|
+
* discoverability, while adding path resolution helpers from path-resolver.js.
|
|
7
|
+
*
|
|
8
|
+
* US-0194: Consolidate Path Matching and Validation Logic
|
|
9
|
+
*
|
|
10
|
+
* Features:
|
|
11
|
+
* - Path traversal prevention (validatePath, hasUnsafePathPatterns)
|
|
12
|
+
* - Symlink chain validation (checkSymlinkChainDepth)
|
|
13
|
+
* - Filename sanitization (sanitizeFilename)
|
|
14
|
+
* - Path resolution with project root detection (PathResolver)
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const { validatePath, PathResolver } = require('./path-utils');
|
|
18
|
+
*
|
|
19
|
+
* // Validate a path is safe and within base directory
|
|
20
|
+
* const result = validatePath('./config.yaml', '/project/root', { allowSymlinks: false });
|
|
21
|
+
*
|
|
22
|
+
* // Use PathResolver for project-aware path operations
|
|
23
|
+
* const resolver = new PathResolver('/project/root');
|
|
24
|
+
* const docsPath = resolver.getDocsDir();
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// Re-export all path validation utilities from validate-paths.js
|
|
28
|
+
const validatePaths = require('./validate-paths');
|
|
29
|
+
|
|
30
|
+
// Re-export PathResolver for convenient access
|
|
31
|
+
const { PathResolver, getDefaultResolver, getAllPaths } = require('./path-resolver');
|
|
32
|
+
|
|
33
|
+
module.exports = {
|
|
34
|
+
// Path validation (from validate-paths.js)
|
|
35
|
+
PathValidationError: validatePaths.PathValidationError,
|
|
36
|
+
checkSymlinkChainDepth: validatePaths.checkSymlinkChainDepth,
|
|
37
|
+
validatePath: validatePaths.validatePath,
|
|
38
|
+
validatePathSync: validatePaths.validatePathSync,
|
|
39
|
+
hasUnsafePathPatterns: validatePaths.hasUnsafePathPatterns,
|
|
40
|
+
sanitizeFilename: validatePaths.sanitizeFilename,
|
|
41
|
+
|
|
42
|
+
// Path resolution (from path-resolver.js)
|
|
43
|
+
PathResolver,
|
|
44
|
+
getDefaultResolver,
|
|
45
|
+
getAllPaths,
|
|
46
|
+
|
|
47
|
+
// Convenience: full module access for advanced usage
|
|
48
|
+
paths: validatePaths,
|
|
49
|
+
};
|
package/lib/session-registry.js
CHANGED
|
@@ -32,6 +32,7 @@ const EventEmitter = require('events');
|
|
|
32
32
|
const fs = require('fs');
|
|
33
33
|
const path = require('path');
|
|
34
34
|
const SmartJsonFile = require('./smart-json-file');
|
|
35
|
+
const { success, failure, failureFromError } = require('./result-schema');
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* Session Registry Event Bus
|
|
@@ -286,13 +287,13 @@ class SessionRegistry extends EventEmitter {
|
|
|
286
287
|
/**
|
|
287
288
|
* Unregister a session
|
|
288
289
|
* @param {number|string} sessionId - Session ID
|
|
289
|
-
* @returns {Promise<{
|
|
290
|
+
* @returns {Promise<Result<{found: boolean}>>}
|
|
290
291
|
*/
|
|
291
292
|
async unregisterSession(sessionId) {
|
|
292
293
|
const registry = await this.load(true);
|
|
293
294
|
|
|
294
295
|
if (!registry.sessions || !registry.sessions[sessionId]) {
|
|
295
|
-
return {
|
|
296
|
+
return success({ found: false });
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
delete registry.sessions[sessionId];
|
|
@@ -301,23 +302,25 @@ class SessionRegistry extends EventEmitter {
|
|
|
301
302
|
if (result.ok) {
|
|
302
303
|
this._auditLog('unregister', { sessionId });
|
|
303
304
|
this.emit('unregistered', { sessionId });
|
|
304
|
-
return {
|
|
305
|
+
return success({ found: true });
|
|
305
306
|
}
|
|
306
307
|
|
|
307
|
-
return
|
|
308
|
+
return failure('EUNKNOWN', result.error || 'Failed to save registry', {
|
|
309
|
+
context: { found: true },
|
|
310
|
+
});
|
|
308
311
|
}
|
|
309
312
|
|
|
310
313
|
/**
|
|
311
314
|
* Update a session
|
|
312
315
|
* @param {number|string} sessionId - Session ID
|
|
313
316
|
* @param {Object} updates - Fields to update
|
|
314
|
-
* @returns {Promise<{
|
|
317
|
+
* @returns {Promise<Result<{found: boolean}>>}
|
|
315
318
|
*/
|
|
316
319
|
async updateSession(sessionId, updates) {
|
|
317
320
|
const registry = await this.load(true);
|
|
318
321
|
|
|
319
322
|
if (!registry.sessions || !registry.sessions[sessionId]) {
|
|
320
|
-
return
|
|
323
|
+
return failure('ENOENT', `Session ${sessionId} not found`, { context: { found: false } });
|
|
321
324
|
}
|
|
322
325
|
|
|
323
326
|
registry.sessions[sessionId] = {
|
|
@@ -331,10 +334,12 @@ class SessionRegistry extends EventEmitter {
|
|
|
331
334
|
if (result.ok) {
|
|
332
335
|
this._auditLog('update', { sessionId, updates });
|
|
333
336
|
this.emit('updated', { sessionId, changes: updates });
|
|
334
|
-
return {
|
|
337
|
+
return success({ found: true });
|
|
335
338
|
}
|
|
336
339
|
|
|
337
|
-
return
|
|
340
|
+
return failure('EUNKNOWN', result.error || 'Failed to save registry', {
|
|
341
|
+
context: { found: true },
|
|
342
|
+
});
|
|
338
343
|
}
|
|
339
344
|
|
|
340
345
|
/**
|
|
@@ -359,11 +364,11 @@ class SessionRegistry extends EventEmitter {
|
|
|
359
364
|
|
|
360
365
|
/**
|
|
361
366
|
* Commit all batched changes in one write
|
|
362
|
-
* @returns {Promise<{
|
|
367
|
+
* @returns {Promise<Result<{applied: number}>>}
|
|
363
368
|
*/
|
|
364
369
|
async commitBatch() {
|
|
365
370
|
if (!this._batchMode) {
|
|
366
|
-
return
|
|
371
|
+
return failure('EINVAL', 'Not in batch mode', { context: { applied: 0 } });
|
|
367
372
|
}
|
|
368
373
|
|
|
369
374
|
const registry = await this.load(true);
|
|
@@ -415,10 +420,10 @@ class SessionRegistry extends EventEmitter {
|
|
|
415
420
|
|
|
416
421
|
if (result.ok) {
|
|
417
422
|
this._auditLog('batch', { applied });
|
|
418
|
-
return {
|
|
423
|
+
return success({ applied });
|
|
419
424
|
}
|
|
420
425
|
|
|
421
|
-
return
|
|
426
|
+
return failure('EUNKNOWN', result.error || 'Failed to save registry', { context: { applied } });
|
|
422
427
|
}
|
|
423
428
|
|
|
424
429
|
/**
|
|
@@ -447,7 +452,7 @@ class SessionRegistry extends EventEmitter {
|
|
|
447
452
|
/**
|
|
448
453
|
* Clean up stale sessions
|
|
449
454
|
* @param {Function} isAlive - Function to check if session is alive (sessionId) => boolean
|
|
450
|
-
* @returns {Promise<{
|
|
455
|
+
* @returns {Promise<Result<{cleaned: number}>>}
|
|
451
456
|
*/
|
|
452
457
|
async cleanupStaleSessions(isAlive) {
|
|
453
458
|
const registry = await this.load(true);
|
|
@@ -464,10 +469,15 @@ class SessionRegistry extends EventEmitter {
|
|
|
464
469
|
|
|
465
470
|
if (cleaned > 0) {
|
|
466
471
|
const result = await this.save(registry);
|
|
467
|
-
|
|
472
|
+
if (!result.ok) {
|
|
473
|
+
return failure('EUNKNOWN', result.error || 'Failed to save registry', {
|
|
474
|
+
context: { cleaned },
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
return success({ cleaned });
|
|
468
478
|
}
|
|
469
479
|
|
|
470
|
-
return {
|
|
480
|
+
return success({ cleaned: 0 });
|
|
471
481
|
}
|
|
472
482
|
}
|
|
473
483
|
|