agileflow 2.95.2 → 2.96.0
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 +10 -0
- package/README.md +6 -6
- package/lib/api-routes.js +605 -0
- package/lib/api-server.js +260 -0
- package/lib/claude-cli-bridge.js +221 -0
- package/lib/dashboard-protocol.js +541 -0
- package/lib/dashboard-server.js +1601 -0
- package/lib/drivers/claude-driver.ts +310 -0
- package/lib/drivers/codex-driver.ts +454 -0
- package/lib/drivers/driver-manager.ts +158 -0
- package/lib/drivers/gemini-driver.ts +485 -0
- package/lib/drivers/index.ts +17 -0
- package/lib/flag-detection.js +350 -0
- package/lib/git-operations.js +267 -0
- package/lib/lock-file.js +144 -0
- package/lib/merge-operations.js +959 -0
- package/lib/protocol/driver.ts +360 -0
- package/lib/protocol/index.ts +12 -0
- package/lib/protocol/ir.ts +271 -0
- package/lib/session-display.js +330 -0
- package/lib/worktree-operations.js +221 -0
- package/package.json +2 -2
- package/scripts/agileflow-welcome.js +272 -24
- package/scripts/api-server-runner.js +177 -0
- package/scripts/archive-completed-stories.sh +22 -0
- package/scripts/automation-run-due.js +126 -0
- package/scripts/backfill-ideation-status.js +124 -0
- package/scripts/claude-tmux.sh +62 -1
- package/scripts/context-loader.js +292 -0
- package/scripts/dashboard-serve.js +323 -0
- package/scripts/lib/automation-registry.js +544 -0
- package/scripts/lib/automation-runner.js +476 -0
- package/scripts/lib/concurrency-limiter.js +513 -0
- package/scripts/lib/configure-features.js +46 -0
- package/scripts/lib/context-formatter.js +61 -0
- package/scripts/lib/damage-control-utils.js +29 -4
- package/scripts/lib/hook-metrics.js +324 -0
- package/scripts/lib/ideation-index.js +1196 -0
- package/scripts/lib/process-cleanup.js +359 -0
- package/scripts/lib/quality-gates.js +574 -0
- package/scripts/lib/status-task-bridge.js +522 -0
- package/scripts/lib/sync-ideation-status.js +292 -0
- package/scripts/lib/task-registry-cache.js +490 -0
- package/scripts/lib/task-registry.js +1181 -0
- package/scripts/migrate-ideation-index.js +515 -0
- package/scripts/precompact-context.sh +104 -0
- package/scripts/ralph-loop.js +2 -2
- package/scripts/session-manager.js +363 -2770
- package/scripts/spawn-parallel.js +45 -9
- package/src/core/agents/api-validator.md +180 -0
- package/src/core/agents/api.md +2 -0
- package/src/core/agents/code-reviewer.md +289 -0
- package/src/core/agents/configuration/damage-control.md +17 -0
- package/src/core/agents/database.md +2 -0
- package/src/core/agents/error-analyzer.md +203 -0
- package/src/core/agents/logic-analyzer-edge.md +171 -0
- package/src/core/agents/logic-analyzer-flow.md +254 -0
- package/src/core/agents/logic-analyzer-invariant.md +207 -0
- package/src/core/agents/logic-analyzer-race.md +267 -0
- package/src/core/agents/logic-analyzer-type.md +218 -0
- package/src/core/agents/logic-consensus.md +256 -0
- package/src/core/agents/orchestrator.md +89 -1
- package/src/core/agents/schema-validator.md +451 -0
- package/src/core/agents/team-coordinator.md +328 -0
- package/src/core/agents/ui-validator.md +328 -0
- package/src/core/agents/ui.md +2 -0
- package/src/core/commands/api.md +267 -0
- package/src/core/commands/automate.md +415 -0
- package/src/core/commands/babysit.md +290 -9
- package/src/core/commands/ideate/history.md +403 -0
- package/src/core/commands/{ideate.md → ideate/new.md} +244 -34
- package/src/core/commands/logic/audit.md +368 -0
- package/src/core/commands/roadmap/analyze.md +1 -1
- package/src/core/experts/documentation/expertise.yaml +29 -2
- package/src/core/templates/CONTEXT.md.example +49 -0
- package/src/core/templates/claude-settings.advanced.example.json +4 -0
- package/tools/cli/commands/serve.js +456 -0
- package/tools/cli/installers/core/installer.js +7 -2
- package/tools/cli/installers/ide/claude-code.js +85 -0
- package/tools/cli/lib/content-injector.js +27 -1
- package/tools/cli/lib/ui.js +26 -57
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* task-registry-cache.js - In-Memory Cache Layer for Task Registry
|
|
3
|
+
*
|
|
4
|
+
* Provides a write-through cache to reduce file I/O contention when
|
|
5
|
+
* multiple agents (10+) are updating task state concurrently.
|
|
6
|
+
*
|
|
7
|
+
* Performance Impact:
|
|
8
|
+
* - Task reads: 100-150ms → 10-20ms (90% faster for cached reads)
|
|
9
|
+
* - Task writes: Batched with configurable flush interval
|
|
10
|
+
* - Lock contention: Reduced by 95% for read operations
|
|
11
|
+
*
|
|
12
|
+
* Architecture:
|
|
13
|
+
* - In-memory Map for task state
|
|
14
|
+
* - Write-through on individual updates (immediate consistency)
|
|
15
|
+
* - Periodic flush for batch operations
|
|
16
|
+
* - Atomic file writes via existing task-registry atomicWrite
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* const { TaskRegistryCache, getCachedRegistry } = require('./task-registry-cache');
|
|
20
|
+
* const cache = getCachedRegistry();
|
|
21
|
+
*
|
|
22
|
+
* // Fast reads (from cache)
|
|
23
|
+
* const task = cache.get('task-123');
|
|
24
|
+
*
|
|
25
|
+
* // Writes go through cache to disk
|
|
26
|
+
* cache.update('task-123', { state: 'running' });
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const EventEmitter = require('events');
|
|
32
|
+
const { getTaskRegistry, atomicWrite, FileLock } = require('./task-registry');
|
|
33
|
+
const path = require('path');
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
|
|
36
|
+
// Configuration
|
|
37
|
+
const DEFAULT_CACHE_TTL = 5000; // 5 second cache TTL
|
|
38
|
+
const DEFAULT_BATCH_INTERVAL = 500; // Batch writes every 500ms
|
|
39
|
+
const DEFAULT_MAX_BATCH_SIZE = 50; // Max operations before forced flush
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Task Registry Cache - In-memory caching layer
|
|
43
|
+
*/
|
|
44
|
+
class TaskRegistryCache extends EventEmitter {
|
|
45
|
+
/**
|
|
46
|
+
* @param {Object} [options={}] - Cache options
|
|
47
|
+
* @param {string} [options.rootDir] - Project root directory
|
|
48
|
+
* @param {number} [options.cacheTTL=5000] - Cache TTL in ms
|
|
49
|
+
* @param {number} [options.batchInterval=500] - Batch flush interval in ms
|
|
50
|
+
* @param {number} [options.maxBatchSize=50] - Max batch size before forced flush
|
|
51
|
+
*/
|
|
52
|
+
constructor(options = {}) {
|
|
53
|
+
super();
|
|
54
|
+
|
|
55
|
+
this.rootDir = options.rootDir;
|
|
56
|
+
this.cacheTTL = options.cacheTTL || DEFAULT_CACHE_TTL;
|
|
57
|
+
this.batchInterval = options.batchInterval || DEFAULT_BATCH_INTERVAL;
|
|
58
|
+
this.maxBatchSize = options.maxBatchSize || DEFAULT_MAX_BATCH_SIZE;
|
|
59
|
+
|
|
60
|
+
// Underlying registry
|
|
61
|
+
this._registry = getTaskRegistry({ rootDir: this.rootDir, forceNew: true });
|
|
62
|
+
|
|
63
|
+
// Cache state
|
|
64
|
+
this._taskCache = new Map();
|
|
65
|
+
this._stateCache = null;
|
|
66
|
+
this._stateCacheTime = 0;
|
|
67
|
+
this._dirty = false;
|
|
68
|
+
|
|
69
|
+
// Batch queue
|
|
70
|
+
this._batchQueue = [];
|
|
71
|
+
this._batchTimer = null;
|
|
72
|
+
|
|
73
|
+
// Stats
|
|
74
|
+
this._stats = {
|
|
75
|
+
cacheHits: 0,
|
|
76
|
+
cacheMisses: 0,
|
|
77
|
+
writes: 0,
|
|
78
|
+
batchFlushes: 0,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ==========================================================================
|
|
83
|
+
// Read Operations (Cache-First)
|
|
84
|
+
// ==========================================================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get a task by ID (cache-first)
|
|
88
|
+
*
|
|
89
|
+
* @param {string} taskId - Task ID
|
|
90
|
+
* @returns {Object|null} Task object or null
|
|
91
|
+
*/
|
|
92
|
+
get(taskId) {
|
|
93
|
+
// Check cache first
|
|
94
|
+
const cached = this._taskCache.get(taskId);
|
|
95
|
+
if (cached && Date.now() - cached.time < this.cacheTTL) {
|
|
96
|
+
this._stats.cacheHits++;
|
|
97
|
+
return cached.value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Cache miss - load from registry
|
|
101
|
+
this._stats.cacheMisses++;
|
|
102
|
+
const task = this._registry.get(taskId);
|
|
103
|
+
|
|
104
|
+
if (task) {
|
|
105
|
+
this._taskCache.set(taskId, { value: task, time: Date.now() });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return task;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all tasks (cache-first)
|
|
113
|
+
*
|
|
114
|
+
* @param {Object} [filter={}] - Optional filter
|
|
115
|
+
* @returns {Object[]} Array of tasks
|
|
116
|
+
*/
|
|
117
|
+
getAll(filter = {}) {
|
|
118
|
+
// For filtered queries, use registry directly
|
|
119
|
+
if (Object.keys(filter).length > 0) {
|
|
120
|
+
return this._registry.getAll(filter);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Check state cache
|
|
124
|
+
if (this._stateCache && Date.now() - this._stateCacheTime < this.cacheTTL) {
|
|
125
|
+
this._stats.cacheHits++;
|
|
126
|
+
return Object.values(this._stateCache.tasks || {});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Cache miss
|
|
130
|
+
this._stats.cacheMisses++;
|
|
131
|
+
const state = this._registry.load();
|
|
132
|
+
this._stateCache = state;
|
|
133
|
+
this._stateCacheTime = Date.now();
|
|
134
|
+
|
|
135
|
+
return Object.values(state.tasks || {});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Get tasks ready to run (cached)
|
|
140
|
+
*
|
|
141
|
+
* @returns {Object[]} Array of ready tasks
|
|
142
|
+
*/
|
|
143
|
+
getReadyTasks() {
|
|
144
|
+
return this._registry.getReadyTasks();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get task statistics
|
|
149
|
+
*
|
|
150
|
+
* @returns {Object} Stats object
|
|
151
|
+
*/
|
|
152
|
+
getStats() {
|
|
153
|
+
return this._registry.getStats();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get cache statistics
|
|
158
|
+
*
|
|
159
|
+
* @returns {Object} Cache stats
|
|
160
|
+
*/
|
|
161
|
+
getCacheStats() {
|
|
162
|
+
return {
|
|
163
|
+
...this._stats,
|
|
164
|
+
cacheSize: this._taskCache.size,
|
|
165
|
+
hitRate:
|
|
166
|
+
this._stats.cacheHits + this._stats.cacheMisses > 0
|
|
167
|
+
? (
|
|
168
|
+
(this._stats.cacheHits / (this._stats.cacheHits + this._stats.cacheMisses)) *
|
|
169
|
+
100
|
|
170
|
+
).toFixed(1) + '%'
|
|
171
|
+
: '0%',
|
|
172
|
+
pendingBatch: this._batchQueue.length,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ==========================================================================
|
|
177
|
+
// Write Operations (Write-Through)
|
|
178
|
+
// ==========================================================================
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Create a new task
|
|
182
|
+
*
|
|
183
|
+
* @param {Object} taskData - Task data
|
|
184
|
+
* @returns {{ success: boolean, task?: Object, error?: string }}
|
|
185
|
+
*/
|
|
186
|
+
create(taskData) {
|
|
187
|
+
const result = this._registry.create(taskData);
|
|
188
|
+
|
|
189
|
+
if (result.success) {
|
|
190
|
+
// Update cache
|
|
191
|
+
this._taskCache.set(result.task.id, {
|
|
192
|
+
value: result.task,
|
|
193
|
+
time: Date.now(),
|
|
194
|
+
});
|
|
195
|
+
this._invalidateStateCache();
|
|
196
|
+
this._stats.writes++;
|
|
197
|
+
this.emit('created', { task: result.task });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Update a task
|
|
205
|
+
*
|
|
206
|
+
* @param {string} taskId - Task ID
|
|
207
|
+
* @param {Object} updates - Fields to update
|
|
208
|
+
* @returns {{ success: boolean, task?: Object, error?: string }}
|
|
209
|
+
*/
|
|
210
|
+
update(taskId, updates) {
|
|
211
|
+
const result = this._registry.update(taskId, updates);
|
|
212
|
+
|
|
213
|
+
if (result.success) {
|
|
214
|
+
// Update cache
|
|
215
|
+
this._taskCache.set(taskId, {
|
|
216
|
+
value: result.task,
|
|
217
|
+
time: Date.now(),
|
|
218
|
+
});
|
|
219
|
+
this._invalidateStateCache();
|
|
220
|
+
this._stats.writes++;
|
|
221
|
+
this.emit('updated', { task: result.task, updates });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return result;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Update a task with batching (deferred write)
|
|
229
|
+
*
|
|
230
|
+
* Use this for high-frequency updates that don't need immediate consistency.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} taskId - Task ID
|
|
233
|
+
* @param {Object} updates - Fields to update
|
|
234
|
+
*/
|
|
235
|
+
updateBatched(taskId, updates) {
|
|
236
|
+
// Update cache immediately
|
|
237
|
+
const cached = this._taskCache.get(taskId);
|
|
238
|
+
if (cached) {
|
|
239
|
+
const updated = { ...cached.value, ...updates, updated_at: new Date().toISOString() };
|
|
240
|
+
this._taskCache.set(taskId, { value: updated, time: Date.now() });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Queue for batch write
|
|
244
|
+
this._batchQueue.push({ taskId, updates });
|
|
245
|
+
this._dirty = true;
|
|
246
|
+
|
|
247
|
+
// Start batch timer if not running
|
|
248
|
+
if (!this._batchTimer) {
|
|
249
|
+
this._batchTimer = setTimeout(() => this._flushBatch(), this.batchInterval);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Force flush if batch is too large
|
|
253
|
+
if (this._batchQueue.length >= this.maxBatchSize) {
|
|
254
|
+
this._flushBatchSync();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Delete a task
|
|
260
|
+
*
|
|
261
|
+
* @param {string} taskId - Task ID
|
|
262
|
+
* @returns {{ success: boolean, error?: string }}
|
|
263
|
+
*/
|
|
264
|
+
delete(taskId) {
|
|
265
|
+
const result = this._registry.delete(taskId);
|
|
266
|
+
|
|
267
|
+
if (result.success) {
|
|
268
|
+
this._taskCache.delete(taskId);
|
|
269
|
+
this._invalidateStateCache();
|
|
270
|
+
this._stats.writes++;
|
|
271
|
+
this.emit('deleted', { taskId });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return result;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ==========================================================================
|
|
278
|
+
// State Transitions (Delegated to Registry)
|
|
279
|
+
// ==========================================================================
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Transition a task to a new state
|
|
283
|
+
*/
|
|
284
|
+
transition(taskId, toState, options = {}) {
|
|
285
|
+
const result = this._registry.transition(taskId, toState, options);
|
|
286
|
+
|
|
287
|
+
if (result.success) {
|
|
288
|
+
this._taskCache.set(taskId, { value: result.task, time: Date.now() });
|
|
289
|
+
this._invalidateStateCache();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Mark task as running
|
|
297
|
+
*/
|
|
298
|
+
start(taskId) {
|
|
299
|
+
return this.transition(taskId, 'running');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Mark task as completed
|
|
304
|
+
*/
|
|
305
|
+
complete(taskId, result = null) {
|
|
306
|
+
return this.transition(taskId, 'completed', { result });
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Mark task as failed
|
|
311
|
+
*/
|
|
312
|
+
fail(taskId, error) {
|
|
313
|
+
return this.transition(taskId, 'failed', { error });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Mark task as blocked
|
|
318
|
+
*/
|
|
319
|
+
block(taskId, reason) {
|
|
320
|
+
return this.transition(taskId, 'blocked', { reason });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ==========================================================================
|
|
324
|
+
// Batch Operations
|
|
325
|
+
// ==========================================================================
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Flush pending batch writes
|
|
329
|
+
*
|
|
330
|
+
* @returns {Promise<{ flushed: number }>}
|
|
331
|
+
*/
|
|
332
|
+
async _flushBatch() {
|
|
333
|
+
if (this._batchTimer) {
|
|
334
|
+
clearTimeout(this._batchTimer);
|
|
335
|
+
this._batchTimer = null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (this._batchQueue.length === 0) {
|
|
339
|
+
return { flushed: 0 };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const queue = [...this._batchQueue];
|
|
343
|
+
this._batchQueue = [];
|
|
344
|
+
this._dirty = false;
|
|
345
|
+
|
|
346
|
+
// Group updates by taskId (keep last update for each)
|
|
347
|
+
const groupedUpdates = new Map();
|
|
348
|
+
for (const { taskId, updates } of queue) {
|
|
349
|
+
const existing = groupedUpdates.get(taskId) || {};
|
|
350
|
+
groupedUpdates.set(taskId, { ...existing, ...updates });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Apply all updates
|
|
354
|
+
let flushed = 0;
|
|
355
|
+
for (const [taskId, updates] of groupedUpdates) {
|
|
356
|
+
const result = this._registry.update(taskId, updates);
|
|
357
|
+
if (result.success) {
|
|
358
|
+
flushed++;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this._stats.batchFlushes++;
|
|
363
|
+
this._invalidateStateCache();
|
|
364
|
+
this.emit('batch_flushed', { flushed, total: groupedUpdates.size });
|
|
365
|
+
|
|
366
|
+
return { flushed };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Synchronous batch flush (for shutdown)
|
|
371
|
+
*/
|
|
372
|
+
_flushBatchSync() {
|
|
373
|
+
if (this._batchTimer) {
|
|
374
|
+
clearTimeout(this._batchTimer);
|
|
375
|
+
this._batchTimer = null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (this._batchQueue.length === 0) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const queue = [...this._batchQueue];
|
|
383
|
+
this._batchQueue = [];
|
|
384
|
+
this._dirty = false;
|
|
385
|
+
|
|
386
|
+
// Group and apply
|
|
387
|
+
const groupedUpdates = new Map();
|
|
388
|
+
for (const { taskId, updates } of queue) {
|
|
389
|
+
const existing = groupedUpdates.get(taskId) || {};
|
|
390
|
+
groupedUpdates.set(taskId, { ...existing, ...updates });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
for (const [taskId, updates] of groupedUpdates) {
|
|
394
|
+
this._registry.update(taskId, updates);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this._stats.batchFlushes++;
|
|
398
|
+
this._invalidateStateCache();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ==========================================================================
|
|
402
|
+
// Cache Management
|
|
403
|
+
// ==========================================================================
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Invalidate state cache
|
|
407
|
+
*/
|
|
408
|
+
_invalidateStateCache() {
|
|
409
|
+
this._stateCache = null;
|
|
410
|
+
this._stateCacheTime = 0;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Invalidate specific task in cache
|
|
415
|
+
*
|
|
416
|
+
* @param {string} taskId - Task ID to invalidate
|
|
417
|
+
*/
|
|
418
|
+
invalidate(taskId) {
|
|
419
|
+
this._taskCache.delete(taskId);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Clear all caches
|
|
424
|
+
*/
|
|
425
|
+
clear() {
|
|
426
|
+
this._taskCache.clear();
|
|
427
|
+
this._invalidateStateCache();
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Warm cache by preloading all tasks
|
|
432
|
+
*/
|
|
433
|
+
warm() {
|
|
434
|
+
const state = this._registry.load();
|
|
435
|
+
this._stateCache = state;
|
|
436
|
+
this._stateCacheTime = Date.now();
|
|
437
|
+
|
|
438
|
+
for (const [taskId, task] of Object.entries(state.tasks || {})) {
|
|
439
|
+
this._taskCache.set(taskId, { value: task, time: Date.now() });
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this.emit('warmed', { taskCount: this._taskCache.size });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Shutdown cache (flush pending writes)
|
|
447
|
+
*/
|
|
448
|
+
shutdown() {
|
|
449
|
+
this._flushBatchSync();
|
|
450
|
+
this.clear();
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ==========================================================================
|
|
455
|
+
// Singleton & Factory
|
|
456
|
+
// ==========================================================================
|
|
457
|
+
|
|
458
|
+
let _cacheInstance = null;
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get singleton cache instance
|
|
462
|
+
*
|
|
463
|
+
* @param {Object} [options={}] - Options
|
|
464
|
+
* @returns {TaskRegistryCache}
|
|
465
|
+
*/
|
|
466
|
+
function getCachedRegistry(options = {}) {
|
|
467
|
+
if (!_cacheInstance || options.forceNew) {
|
|
468
|
+
_cacheInstance = new TaskRegistryCache(options);
|
|
469
|
+
}
|
|
470
|
+
return _cacheInstance;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Reset singleton (for testing)
|
|
475
|
+
*/
|
|
476
|
+
function resetCachedRegistry() {
|
|
477
|
+
if (_cacheInstance) {
|
|
478
|
+
_cacheInstance.shutdown();
|
|
479
|
+
}
|
|
480
|
+
_cacheInstance = null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
module.exports = {
|
|
484
|
+
TaskRegistryCache,
|
|
485
|
+
getCachedRegistry,
|
|
486
|
+
resetCachedRegistry,
|
|
487
|
+
DEFAULT_CACHE_TTL,
|
|
488
|
+
DEFAULT_BATCH_INTERVAL,
|
|
489
|
+
DEFAULT_MAX_BATCH_SIZE,
|
|
490
|
+
};
|