specweave 0.32.10 → 0.33.2
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/CLAUDE.md +106 -1
- package/dist/src/cli/add-child-pid.d.ts +11 -0
- package/dist/src/cli/add-child-pid.d.ts.map +1 -0
- package/dist/src/cli/add-child-pid.js +42 -0
- package/dist/src/cli/add-child-pid.js.map +1 -0
- package/dist/src/cli/add-child-process.d.ts +15 -0
- package/dist/src/cli/add-child-process.d.ts.map +1 -0
- package/dist/src/cli/add-child-process.js +40 -0
- package/dist/src/cli/add-child-process.js.map +1 -0
- package/dist/src/cli/check-watchdog.d.ts +15 -0
- package/dist/src/cli/check-watchdog.d.ts.map +1 -0
- package/dist/src/cli/check-watchdog.js +47 -0
- package/dist/src/cli/check-watchdog.js.map +1 -0
- package/dist/src/cli/cleanup-zombies.d.ts +14 -0
- package/dist/src/cli/cleanup-zombies.d.ts.map +1 -0
- package/dist/src/cli/cleanup-zombies.js +268 -0
- package/dist/src/cli/cleanup-zombies.js.map +1 -0
- package/dist/src/cli/find-session-by-pid.d.ts +14 -0
- package/dist/src/cli/find-session-by-pid.d.ts.map +1 -0
- package/dist/src/cli/find-session-by-pid.js +45 -0
- package/dist/src/cli/find-session-by-pid.js.map +1 -0
- package/dist/src/cli/get-stale-sessions.d.ts +17 -0
- package/dist/src/cli/get-stale-sessions.d.ts.map +1 -0
- package/dist/src/cli/get-stale-sessions.js +36 -0
- package/dist/src/cli/get-stale-sessions.js.map +1 -0
- package/dist/src/cli/register-session.d.ts +16 -0
- package/dist/src/cli/register-session.d.ts.map +1 -0
- package/dist/src/cli/register-session.js +48 -0
- package/dist/src/cli/register-session.js.map +1 -0
- package/dist/src/cli/remove-session.d.ts +11 -0
- package/dist/src/cli/remove-session.d.ts.map +1 -0
- package/dist/src/cli/remove-session.js +36 -0
- package/dist/src/cli/remove-session.js.map +1 -0
- package/dist/src/cli/update-heartbeat.d.ts +11 -0
- package/dist/src/cli/update-heartbeat.d.ts.map +1 -0
- package/dist/src/cli/update-heartbeat.js +36 -0
- package/dist/src/cli/update-heartbeat.js.map +1 -0
- package/dist/src/config/types.d.ts +1208 -203
- package/dist/src/config/types.d.ts.map +1 -1
- package/dist/src/core/background/job-manager.d.ts +16 -0
- package/dist/src/core/background/job-manager.d.ts.map +1 -1
- package/dist/src/core/background/job-manager.js +110 -15
- package/dist/src/core/background/job-manager.js.map +1 -1
- package/dist/src/core/increment/increment-utils.d.ts +26 -1
- package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
- package/dist/src/core/increment/increment-utils.js +66 -4
- package/dist/src/core/increment/increment-utils.js.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts +3 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.js +5 -2
- package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +48 -12
- package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.d.ts +70 -0
- package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.js +188 -0
- package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.d.ts +33 -0
- package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.js +290 -0
- package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +114 -11
- package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.d.ts +23 -0
- package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.js +283 -0
- package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.d.ts +44 -0
- package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.js +61 -0
- package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.d.ts +126 -0
- package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.js +378 -0
- package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +57 -0
- package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.d.ts +82 -0
- package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.js +430 -0
- package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.d.ts +84 -0
- package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.js +387 -0
- package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/report-writer.d.ts +61 -0
- package/dist/src/core/living-docs/intelligent-analyzer/report-writer.d.ts.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/report-writer.js +174 -0
- package/dist/src/core/living-docs/intelligent-analyzer/report-writer.js.map +1 -0
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +1 -1
- package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.d.ts +3 -0
- package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
- package/dist/src/core/living-docs/module-analyzer.js +40 -1
- package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
- package/dist/src/core/qa/qa-runner.js +1 -1
- package/dist/src/core/qa/qa-runner.js.map +1 -1
- package/dist/src/core/scheduler/session-sync-executor.js +1 -1
- package/dist/src/core/scheduler/session-sync-executor.js.map +1 -1
- package/dist/src/core/status-line/status-line-updater.d.ts +1 -1
- package/dist/src/core/status-line/status-line-updater.d.ts.map +1 -1
- package/dist/src/core/status-line/status-line-updater.js +4 -3
- package/dist/src/core/status-line/status-line-updater.js.map +1 -1
- package/dist/src/importers/jira-importer.d.ts.map +1 -1
- package/dist/src/importers/jira-importer.js +18 -9
- package/dist/src/importers/jira-importer.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +140 -33
- package/dist/src/init/architecture/types.d.ts.map +1 -1
- package/dist/src/init/compliance/types.d.ts +27 -30
- package/dist/src/init/compliance/types.d.ts.map +1 -1
- package/dist/src/init/repo/types.d.ts +34 -11
- package/dist/src/init/repo/types.d.ts.map +1 -1
- package/dist/src/init/research/src/config/types.d.ts +82 -15
- package/dist/src/init/research/src/config/types.d.ts.map +1 -1
- package/dist/src/init/research/types.d.ts +93 -38
- package/dist/src/init/research/types.d.ts.map +1 -1
- package/dist/src/init/team/types.d.ts +42 -4
- package/dist/src/init/team/types.d.ts.map +1 -1
- package/dist/src/sync/ado-reconciler.js +1 -1
- package/dist/src/sync/ado-reconciler.js.map +1 -1
- package/dist/src/sync/github-reconciler.js +1 -1
- package/dist/src/sync/github-reconciler.js.map +1 -1
- package/dist/src/sync/jira-reconciler.js +1 -1
- package/dist/src/sync/jira-reconciler.js.map +1 -1
- package/dist/src/types/session.d.ts +65 -0
- package/dist/src/types/session.d.ts.map +1 -0
- package/dist/src/types/session.js +8 -0
- package/dist/src/types/session.js.map +1 -0
- package/dist/src/utils/lock-manager.d.ts +48 -0
- package/dist/src/utils/lock-manager.d.ts.map +1 -0
- package/dist/src/utils/lock-manager.js +195 -0
- package/dist/src/utils/lock-manager.js.map +1 -0
- package/dist/src/utils/notification-manager.d.ts +45 -0
- package/dist/src/utils/notification-manager.d.ts.map +1 -0
- package/dist/src/utils/notification-manager.js +130 -0
- package/dist/src/utils/notification-manager.js.map +1 -0
- package/dist/src/utils/platform-utils.d.ts +136 -0
- package/dist/src/utils/platform-utils.d.ts.map +1 -0
- package/dist/src/utils/platform-utils.js +366 -0
- package/dist/src/utils/platform-utils.js.map +1 -0
- package/dist/src/utils/session-registry.d.ts +142 -0
- package/dist/src/utils/session-registry.d.ts.map +1 -0
- package/dist/src/utils/session-registry.js +480 -0
- package/dist/src/utils/session-registry.js.map +1 -0
- package/package.json +5 -2
- package/plugins/specweave/commands/specweave-living-docs.md +42 -0
- package/plugins/specweave/hooks/hooks.json +10 -0
- package/plugins/specweave/hooks/lib/update-active-increment.sh +2 -2
- package/plugins/specweave/hooks/lib/update-status-line.sh +1 -1
- package/plugins/specweave/hooks/post-increment-status-change.sh +3 -3
- package/plugins/specweave/hooks/post-metadata-change.sh +1 -1
- package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
- package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
- package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
- package/plugins/specweave/hooks/user-prompt-submit.sh +2 -2
- package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +61 -0
- package/plugins/specweave/hooks/v2/session-end.sh +69 -0
- package/plugins/specweave/hooks/v2/session-start.sh +81 -0
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js +1 -1
- package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
- package/plugins/specweave/scripts/heartbeat.sh +110 -0
- package/plugins/specweave/scripts/progress.js +34 -4
- package/plugins/specweave/scripts/read-jobs.sh +1 -1
- package/plugins/specweave/scripts/read-progress.sh +50 -5
- package/plugins/specweave/scripts/read-workflow.sh +1 -1
- package/plugins/specweave/scripts/session-watchdog.sh +65 -0
- package/plugins/specweave/scripts/status.js +28 -11
- package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +738 -0
- package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +1107 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Registry - Central tracking for all Claude Code sessions and child processes
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic file-based operations for tracking active sessions, detecting
|
|
5
|
+
* stale/zombie processes, and coordinating cleanup across multiple sessions.
|
|
6
|
+
*
|
|
7
|
+
* Key Features:
|
|
8
|
+
* - Atomic operations using temp file + rename pattern
|
|
9
|
+
* - File locking via mkdir (cross-platform atomic operation)
|
|
10
|
+
* - Automatic corruption recovery with JSON validation
|
|
11
|
+
* - Concurrent-safe updates from multiple processes
|
|
12
|
+
*
|
|
13
|
+
* Registry Location: `.specweave/state/.session-registry.json`
|
|
14
|
+
*/
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
import * as path from 'path';
|
|
17
|
+
import { consoleLogger } from './logger.js';
|
|
18
|
+
const REGISTRY_FILENAME = '.session-registry.json';
|
|
19
|
+
const LOCK_TIMEOUT_MS = 5000; // 5 seconds max wait for lock
|
|
20
|
+
const LOCK_CHECK_INTERVAL_MS = 50; // Check lock every 50ms
|
|
21
|
+
const REGISTRY_VERSION = '1.0.0';
|
|
22
|
+
export class SessionRegistry {
|
|
23
|
+
constructor(projectRoot, options = {}) {
|
|
24
|
+
this.logger = options.logger ?? consoleLogger;
|
|
25
|
+
const stateDir = path.join(projectRoot, '.specweave', 'state');
|
|
26
|
+
// Ensure state directory exists
|
|
27
|
+
if (!fs.existsSync(stateDir)) {
|
|
28
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
29
|
+
}
|
|
30
|
+
this.registryPath = path.join(stateDir, REGISTRY_FILENAME);
|
|
31
|
+
this.lockPath = path.join(stateDir, `${REGISTRY_FILENAME}.lock`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Acquires an exclusive lock on the registry file
|
|
35
|
+
*
|
|
36
|
+
* Uses mkdir as an atomic operation (works cross-platform)
|
|
37
|
+
* Waits up to LOCK_TIMEOUT_MS before failing
|
|
38
|
+
*
|
|
39
|
+
* @returns true if lock acquired, false if timeout
|
|
40
|
+
*/
|
|
41
|
+
async acquireLock() {
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
while (Date.now() - startTime < LOCK_TIMEOUT_MS) {
|
|
44
|
+
try {
|
|
45
|
+
// mkdir is atomic on all platforms
|
|
46
|
+
fs.mkdirSync(this.lockPath, { recursive: false });
|
|
47
|
+
// Write our PID to lock directory for debugging
|
|
48
|
+
const lockPidFile = path.join(this.lockPath, 'pid');
|
|
49
|
+
fs.writeFileSync(lockPidFile, String(process.pid));
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (err.code !== 'EEXIST') {
|
|
54
|
+
this.logger.error('Failed to acquire lock:', err);
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
// Lock exists, wait and retry
|
|
58
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_CHECK_INTERVAL_MS));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
this.logger.error(`Lock acquisition timeout after ${LOCK_TIMEOUT_MS}ms`);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Releases the lock on the registry file
|
|
66
|
+
*/
|
|
67
|
+
releaseLock() {
|
|
68
|
+
try {
|
|
69
|
+
// Remove PID file first
|
|
70
|
+
const lockPidFile = path.join(this.lockPath, 'pid');
|
|
71
|
+
if (fs.existsSync(lockPidFile)) {
|
|
72
|
+
fs.unlinkSync(lockPidFile);
|
|
73
|
+
}
|
|
74
|
+
// Remove lock directory
|
|
75
|
+
fs.rmdirSync(this.lockPath);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
this.logger.error('Failed to release lock:', err);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reads the registry file with corruption recovery
|
|
83
|
+
*
|
|
84
|
+
* If registry doesn't exist, creates a new empty one
|
|
85
|
+
* If registry is corrupted (invalid JSON), creates backup and reinitializes
|
|
86
|
+
*
|
|
87
|
+
* @returns The current registry state
|
|
88
|
+
*/
|
|
89
|
+
readRegistry() {
|
|
90
|
+
// If registry doesn't exist, create empty one
|
|
91
|
+
if (!fs.existsSync(this.registryPath)) {
|
|
92
|
+
return this.createEmptyRegistry();
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const content = fs.readFileSync(this.registryPath, 'utf-8');
|
|
96
|
+
const registry = JSON.parse(content);
|
|
97
|
+
// Validate basic structure
|
|
98
|
+
if (!registry.sessions || typeof registry.sessions !== 'object') {
|
|
99
|
+
throw new Error('Invalid registry structure: missing sessions object');
|
|
100
|
+
}
|
|
101
|
+
if (!registry.version) {
|
|
102
|
+
// Migrate old registry without version
|
|
103
|
+
registry.version = REGISTRY_VERSION;
|
|
104
|
+
}
|
|
105
|
+
return registry;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
this.logger.error('Registry corrupted, creating backup and reinitializing:', err);
|
|
109
|
+
// Backup corrupted registry
|
|
110
|
+
const backupPath = `${this.registryPath}.corrupted.${Date.now()}`;
|
|
111
|
+
try {
|
|
112
|
+
fs.copyFileSync(this.registryPath, backupPath);
|
|
113
|
+
this.logger.info(`Corrupted registry backed up to: ${backupPath}`);
|
|
114
|
+
}
|
|
115
|
+
catch (backupErr) {
|
|
116
|
+
this.logger.error('Failed to backup corrupted registry:', backupErr);
|
|
117
|
+
}
|
|
118
|
+
// Create new empty registry
|
|
119
|
+
return this.createEmptyRegistry();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Writes registry to disk atomically
|
|
124
|
+
*
|
|
125
|
+
* Uses temp file + rename pattern for atomic write
|
|
126
|
+
* This ensures registry is never left in a corrupted state
|
|
127
|
+
*
|
|
128
|
+
* @param registry - The registry state to write
|
|
129
|
+
*/
|
|
130
|
+
writeRegistry(registry) {
|
|
131
|
+
const tempPath = `${this.registryPath}.tmp.${process.pid}`;
|
|
132
|
+
try {
|
|
133
|
+
// Write to temp file with pretty formatting
|
|
134
|
+
fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2), 'utf-8');
|
|
135
|
+
// Atomic rename (overwrites existing file)
|
|
136
|
+
fs.renameSync(tempPath, this.registryPath);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
this.logger.error('Failed to write registry:', err);
|
|
140
|
+
// Clean up temp file if it exists
|
|
141
|
+
if (fs.existsSync(tempPath)) {
|
|
142
|
+
try {
|
|
143
|
+
fs.unlinkSync(tempPath);
|
|
144
|
+
}
|
|
145
|
+
catch (cleanupErr) {
|
|
146
|
+
// Ignore cleanup errors
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Creates a new empty registry
|
|
154
|
+
*/
|
|
155
|
+
createEmptyRegistry() {
|
|
156
|
+
return {
|
|
157
|
+
sessions: {},
|
|
158
|
+
last_cleanup: new Date().toISOString(),
|
|
159
|
+
version: REGISTRY_VERSION,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Registers a new session in the registry
|
|
164
|
+
*
|
|
165
|
+
* @param sessionId - Unique session identifier
|
|
166
|
+
* @param pid - Process ID of the session
|
|
167
|
+
* @param options - Optional configuration
|
|
168
|
+
* @returns true if registration successful
|
|
169
|
+
*/
|
|
170
|
+
async registerSession(sessionId, pid, options = {}) {
|
|
171
|
+
const lockAcquired = await this.acquireLock();
|
|
172
|
+
if (!lockAcquired) {
|
|
173
|
+
this.logger.error('Failed to acquire lock for session registration');
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const registry = this.readRegistry();
|
|
178
|
+
const now = new Date().toISOString();
|
|
179
|
+
// Check if session already exists
|
|
180
|
+
if (registry.sessions[sessionId]) {
|
|
181
|
+
this.logger.warn(`Session ${sessionId} already registered, updating...`);
|
|
182
|
+
}
|
|
183
|
+
// Create session entry
|
|
184
|
+
const sessionInfo = {
|
|
185
|
+
session_id: sessionId,
|
|
186
|
+
pid,
|
|
187
|
+
type: options.type ?? 'claude-code',
|
|
188
|
+
start_time: now,
|
|
189
|
+
last_heartbeat: now,
|
|
190
|
+
status: 'active',
|
|
191
|
+
child_pids: [],
|
|
192
|
+
parent_session: options.parent_session,
|
|
193
|
+
metadata: options.metadata,
|
|
194
|
+
};
|
|
195
|
+
registry.sessions[sessionId] = sessionInfo;
|
|
196
|
+
this.writeRegistry(registry);
|
|
197
|
+
this.logger.info(`Registered session: ${sessionId} (PID: ${pid})`);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
this.releaseLock();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Updates the heartbeat timestamp for a session
|
|
206
|
+
*
|
|
207
|
+
* @param sessionId - Session to update
|
|
208
|
+
* @returns true if update successful
|
|
209
|
+
*/
|
|
210
|
+
async updateHeartbeat(sessionId) {
|
|
211
|
+
const lockAcquired = await this.acquireLock();
|
|
212
|
+
if (!lockAcquired) {
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
try {
|
|
216
|
+
const registry = this.readRegistry();
|
|
217
|
+
const session = registry.sessions[sessionId];
|
|
218
|
+
if (!session) {
|
|
219
|
+
this.logger.error(`Session not found: ${sessionId}`);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
session.last_heartbeat = new Date().toISOString();
|
|
223
|
+
session.status = 'active'; // Reset to active on heartbeat
|
|
224
|
+
this.writeRegistry(registry);
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
this.releaseLock();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Adds a child process PID to a session
|
|
233
|
+
*
|
|
234
|
+
* @param sessionId - Parent session ID
|
|
235
|
+
* @param childPid - Child process PID to add
|
|
236
|
+
* @returns true if successful
|
|
237
|
+
*/
|
|
238
|
+
async addChildProcess(sessionId, childPid) {
|
|
239
|
+
const lockAcquired = await this.acquireLock();
|
|
240
|
+
if (!lockAcquired) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
const registry = this.readRegistry();
|
|
245
|
+
const session = registry.sessions[sessionId];
|
|
246
|
+
if (!session) {
|
|
247
|
+
this.logger.error(`Session not found: ${sessionId}`);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (!session.child_pids) {
|
|
251
|
+
session.child_pids = [];
|
|
252
|
+
}
|
|
253
|
+
// Avoid duplicates
|
|
254
|
+
if (!session.child_pids.includes(childPid)) {
|
|
255
|
+
session.child_pids.push(childPid);
|
|
256
|
+
this.writeRegistry(registry);
|
|
257
|
+
this.logger.info(`Added child PID ${childPid} to session ${sessionId}`);
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
this.releaseLock();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Removes a session from the registry
|
|
267
|
+
*
|
|
268
|
+
* @param sessionId - Session to remove
|
|
269
|
+
* @returns true if successful
|
|
270
|
+
*/
|
|
271
|
+
async removeSession(sessionId) {
|
|
272
|
+
const lockAcquired = await this.acquireLock();
|
|
273
|
+
if (!lockAcquired) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
const registry = this.readRegistry();
|
|
278
|
+
if (!registry.sessions[sessionId]) {
|
|
279
|
+
this.logger.warn(`Session not found for removal: ${sessionId}`);
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
delete registry.sessions[sessionId];
|
|
283
|
+
this.writeRegistry(registry);
|
|
284
|
+
this.logger.info(`Removed session: ${sessionId}`);
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
finally {
|
|
288
|
+
this.releaseLock();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Gets a session by ID
|
|
293
|
+
*
|
|
294
|
+
* @param sessionId - Session to retrieve
|
|
295
|
+
* @returns Session info or undefined
|
|
296
|
+
*/
|
|
297
|
+
async getSession(sessionId) {
|
|
298
|
+
const lockAcquired = await this.acquireLock();
|
|
299
|
+
if (!lockAcquired) {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
const registry = this.readRegistry();
|
|
304
|
+
return registry.sessions[sessionId];
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
this.releaseLock();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Gets all sessions
|
|
312
|
+
*
|
|
313
|
+
* @returns Array of all session info
|
|
314
|
+
*/
|
|
315
|
+
async getAllSessions() {
|
|
316
|
+
const lockAcquired = await this.acquireLock();
|
|
317
|
+
if (!lockAcquired) {
|
|
318
|
+
return [];
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
const registry = this.readRegistry();
|
|
322
|
+
return Object.values(registry.sessions);
|
|
323
|
+
}
|
|
324
|
+
finally {
|
|
325
|
+
this.releaseLock();
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Updates the status of a session
|
|
330
|
+
*
|
|
331
|
+
* @param sessionId - Session to update
|
|
332
|
+
* @param status - New status
|
|
333
|
+
* @returns true if successful
|
|
334
|
+
*/
|
|
335
|
+
async updateStatus(sessionId, status) {
|
|
336
|
+
const lockAcquired = await this.acquireLock();
|
|
337
|
+
if (!lockAcquired) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
try {
|
|
341
|
+
const registry = this.readRegistry();
|
|
342
|
+
const session = registry.sessions[sessionId];
|
|
343
|
+
if (!session) {
|
|
344
|
+
this.logger.error(`Session not found: ${sessionId}`);
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
session.status = status;
|
|
348
|
+
this.writeRegistry(registry);
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
this.releaseLock();
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Gets all stale sessions based on heartbeat threshold
|
|
357
|
+
*
|
|
358
|
+
* A session is considered stale if its last_heartbeat is older than
|
|
359
|
+
* the specified threshold.
|
|
360
|
+
*
|
|
361
|
+
* @param thresholdSeconds - Max age of last heartbeat before considered stale
|
|
362
|
+
* @returns Array of stale session info with staleness details
|
|
363
|
+
*/
|
|
364
|
+
async getStaleSessions(thresholdSeconds) {
|
|
365
|
+
const lockAcquired = await this.acquireLock();
|
|
366
|
+
if (!lockAcquired) {
|
|
367
|
+
return [];
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const registry = this.readRegistry();
|
|
371
|
+
const now = Date.now();
|
|
372
|
+
const staleSessions = [];
|
|
373
|
+
for (const session of Object.values(registry.sessions)) {
|
|
374
|
+
const lastHeartbeat = new Date(session.last_heartbeat).getTime();
|
|
375
|
+
const ageSec = (now - lastHeartbeat) / 1000;
|
|
376
|
+
if (ageSec > thresholdSeconds) {
|
|
377
|
+
// Check if PID still exists (cross-platform)
|
|
378
|
+
const pidExists = await this.checkPidExists(session.pid);
|
|
379
|
+
staleSessions.push({
|
|
380
|
+
session,
|
|
381
|
+
stale_duration_seconds: ageSec,
|
|
382
|
+
pid_exists: pidExists,
|
|
383
|
+
});
|
|
384
|
+
// Mark session as stale
|
|
385
|
+
session.status = 'stale';
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Update registry with stale statuses
|
|
389
|
+
if (staleSessions.length > 0) {
|
|
390
|
+
this.writeRegistry(registry);
|
|
391
|
+
}
|
|
392
|
+
return staleSessions;
|
|
393
|
+
}
|
|
394
|
+
finally {
|
|
395
|
+
this.releaseLock();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Cleans up old sessions based on retention period
|
|
400
|
+
*
|
|
401
|
+
* Removes sessions that have been completed or stale for longer
|
|
402
|
+
* than the retention period.
|
|
403
|
+
*
|
|
404
|
+
* @param retentionHours - How long to keep old sessions (in hours)
|
|
405
|
+
* @returns Number of sessions removed
|
|
406
|
+
*/
|
|
407
|
+
async cleanupOldSessions(retentionHours) {
|
|
408
|
+
const lockAcquired = await this.acquireLock();
|
|
409
|
+
if (!lockAcquired) {
|
|
410
|
+
return 0;
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
const registry = this.readRegistry();
|
|
414
|
+
const now = Date.now();
|
|
415
|
+
const retentionMs = retentionHours * 60 * 60 * 1000;
|
|
416
|
+
const sessionsToRemove = [];
|
|
417
|
+
for (const [sessionId, session] of Object.entries(registry.sessions)) {
|
|
418
|
+
// Only clean up completed or stale sessions
|
|
419
|
+
if (session.status !== 'completed' && session.status !== 'stale') {
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
// Check age based on last_heartbeat
|
|
423
|
+
const lastActivity = new Date(session.last_heartbeat).getTime();
|
|
424
|
+
const ageMs = now - lastActivity;
|
|
425
|
+
if (ageMs > retentionMs) {
|
|
426
|
+
sessionsToRemove.push(sessionId);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// Remove old sessions
|
|
430
|
+
for (const sessionId of sessionsToRemove) {
|
|
431
|
+
delete registry.sessions[sessionId];
|
|
432
|
+
this.logger.info(`Cleaned up old session: ${sessionId} (age: ${Math.floor((now - new Date(registry.sessions[sessionId]?.last_heartbeat || now).getTime()) / 3600000)}h)`);
|
|
433
|
+
}
|
|
434
|
+
// Update last_cleanup timestamp
|
|
435
|
+
registry.last_cleanup = new Date().toISOString();
|
|
436
|
+
if (sessionsToRemove.length > 0) {
|
|
437
|
+
this.writeRegistry(registry);
|
|
438
|
+
}
|
|
439
|
+
return sessionsToRemove.length;
|
|
440
|
+
}
|
|
441
|
+
finally {
|
|
442
|
+
this.releaseLock();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Checks if a PID exists (cross-platform)
|
|
447
|
+
*
|
|
448
|
+
* Uses kill -0 on Unix-like systems, which doesn't actually send a signal
|
|
449
|
+
* but checks if the process exists.
|
|
450
|
+
*
|
|
451
|
+
* @param pid - Process ID to check
|
|
452
|
+
* @returns true if process exists
|
|
453
|
+
*/
|
|
454
|
+
async checkPidExists(pid) {
|
|
455
|
+
try {
|
|
456
|
+
// On Unix-like systems (macOS, Linux), kill -0 checks process existence
|
|
457
|
+
// without actually sending a signal
|
|
458
|
+
if (process.platform === 'win32') {
|
|
459
|
+
// Windows: use tasklist
|
|
460
|
+
const { execSync } = await import('child_process');
|
|
461
|
+
const output = execSync(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
462
|
+
encoding: 'utf-8',
|
|
463
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
464
|
+
});
|
|
465
|
+
return output.includes(String(pid));
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
// Unix-like (macOS, Linux)
|
|
469
|
+
const { execSync } = await import('child_process');
|
|
470
|
+
execSync(`kill -0 ${pid}`, { stdio: 'ignore' });
|
|
471
|
+
return true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
// Process doesn't exist or we don't have permission
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../../../src/utils/session-registry.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specweave",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.33.2",
|
|
4
4
|
"description": "Spec-driven development framework for Claude Code. AI-native workflow with living documentation, intelligent agents, and multilingual support (9 languages). Enterprise-grade traceability with permanent specs and temporary increments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,9 +19,12 @@
|
|
|
19
19
|
"prepublishOnly": "npm run rebuild",
|
|
20
20
|
"test:unit": "vitest run tests/unit --coverage",
|
|
21
21
|
"test:integration": "vitest run tests/integration --coverage",
|
|
22
|
+
"test:lifecycle": "npm run test:unit && npm run test:integration",
|
|
23
|
+
"test:e2e:lifecycle": "vitest run tests/e2e/normal-session-lifecycle.e2e.ts tests/e2e/crash-recovery.e2e.ts tests/e2e/multiple-sessions.e2e.ts",
|
|
24
|
+
"test:performance": "ts-node tests/performance/session-registry-bench.ts && bash tests/performance/heartbeat-overhead-bench.sh && ts-node tests/performance/compare-to-baseline.ts",
|
|
22
25
|
"test:smoke": "bash tests/smoke/smoke-test.sh",
|
|
23
26
|
"test:e2e": "playwright test tests/e2e/ --grep-invert=\"(should default to claude adapter|should use claude adapter when explicitly requested|should use generic adapter|should create .claude|should initialize project with specweave init|should create correct directory structure|should handle non-interactive mode correctly|should validate config.json structure|should create .specweave directory structure|should create CLAUDE.md and AGENTS.md|should initialize git repository|should install SpecWeave|should scaffold SaaS|should create proper directory|should create required configuration|should install core skills|should install core agents|should have deployment|should have Stripe|ADO Sync|Increment Discipline Blocking|Self-Reflection|Increment Discipline Enforcement)\"",
|
|
24
|
-
"test:all": "npm run test:unit && npm run test:integration && npm run test:e2e",
|
|
27
|
+
"test:all": "npm run test:unit && npm run test:integration && npm run test:e2e && npm run test:e2e:lifecycle",
|
|
25
28
|
"test:coverage": "vitest run --coverage",
|
|
26
29
|
"test": "npm run test:smoke",
|
|
27
30
|
"benchmark": "ts-node tests/performance/run-all-benchmarks.ts",
|
|
@@ -31,6 +31,7 @@ Launch the Living Docs Builder independently of `specweave init`. This is essent
|
|
|
31
31
|
| `--sources <folders>` | Additional doc folders (comma-separated): `docs/,wiki/` |
|
|
32
32
|
| `--depends-on <jobIds>` | Wait for jobs before starting (comma-separated) |
|
|
33
33
|
| `--foreground` | Run in current session instead of background |
|
|
34
|
+
| `--full` | Force full rebuild by clearing cache and checkpoints (bypasses incremental mode) |
|
|
34
35
|
|
|
35
36
|
---
|
|
36
37
|
|
|
@@ -73,6 +74,9 @@ Launch the Living Docs Builder independently of `specweave init`. This is essent
|
|
|
73
74
|
|
|
74
75
|
# AI-powered deep analysis (FREE with MAX subscription)
|
|
75
76
|
/specweave:living-docs --depth deep-native --priority core,api
|
|
77
|
+
|
|
78
|
+
# Force full rebuild (clears cache and checkpoints)
|
|
79
|
+
/specweave:living-docs --full --depth standard
|
|
76
80
|
```
|
|
77
81
|
|
|
78
82
|
---
|
|
@@ -220,6 +224,44 @@ The job will:
|
|
|
220
224
|
|
|
221
225
|
---
|
|
222
226
|
|
|
227
|
+
## Update Summary (v0.33.0+)
|
|
228
|
+
|
|
229
|
+
After completion, you'll see a detailed summary showing:
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
✅ LIVING DOCS UPDATE COMPLETE
|
|
233
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
234
|
+
|
|
235
|
+
📊 SUMMARY:
|
|
236
|
+
|
|
237
|
+
Discovery: Discovered 3 repos (2,845 files)
|
|
238
|
+
Duration: 5s
|
|
239
|
+
|
|
240
|
+
Analysis: Analyzed 3 repos
|
|
241
|
+
Duration: 127s
|
|
242
|
+
|
|
243
|
+
Synthesis: Generated 12 ADRs, 4 teams
|
|
244
|
+
Duration: 43s
|
|
245
|
+
|
|
246
|
+
Files Created: 47
|
|
247
|
+
• .specweave/docs/internal/repos/main/overview.md
|
|
248
|
+
• .specweave/docs/internal/repos/main/api-surface.md
|
|
249
|
+
• .specweave/docs/internal/architecture/system-architecture.md
|
|
250
|
+
• .specweave/docs/internal/architecture/adr/0001-typescript-migration.md
|
|
251
|
+
• .specweave/docs/internal/architecture/adr/0002-plugin-system.md
|
|
252
|
+
... and 42 more
|
|
253
|
+
|
|
254
|
+
Files Updated: 8
|
|
255
|
+
• .specweave/docs/internal/modules/auth.md
|
|
256
|
+
• .specweave/docs/internal/modules/payments.md
|
|
257
|
+
... and 6 more
|
|
258
|
+
|
|
259
|
+
Total Duration: 175s
|
|
260
|
+
Mode: INCREMENTAL (cache used)
|
|
261
|
+
|
|
262
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
263
|
+
```
|
|
264
|
+
|
|
223
265
|
## Output Files
|
|
224
266
|
|
|
225
267
|
After completion:
|
|
@@ -69,6 +69,16 @@
|
|
|
69
69
|
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/guards/increment-duplicate-guard.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"allow\\\"}\")'"
|
|
70
70
|
}
|
|
71
71
|
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"matcher": "Write",
|
|
75
|
+
"matcher_content": "\\.specweave/increments/\\d{3,4}E?-[^/]+/[^/]+$",
|
|
76
|
+
"hooks": [
|
|
77
|
+
{
|
|
78
|
+
"type": "command",
|
|
79
|
+
"command": "bash -c 'W=\"${CLAUDE_PLUGIN_ROOT}/hooks/universal/fail-fast-wrapper.sh\"; S=\"${CLAUDE_PLUGIN_ROOT}/hooks/v2/guards/increment-root-guard.sh\"; [[ -x \"$W\" ]] && exec \"$W\" \"$S\" || (cat >/dev/null && printf \"{\\\"decision\\\":\\\"allow\\\"}\")'"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
72
82
|
}
|
|
73
83
|
],
|
|
74
84
|
"PostToolUse": [
|
|
@@ -37,7 +37,7 @@ mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
|
37
37
|
# ============================================================================
|
|
38
38
|
# SCAN ALL INCREMENTS FOR ACTIVE STATUS
|
|
39
39
|
# ============================================================================
|
|
40
|
-
# Strategy: Scan metadata.json files for status=active|planning|
|
|
40
|
+
# Strategy: Scan metadata.json files for status=active|planning|backlog|ready_for_review
|
|
41
41
|
# This matches what ActiveIncrementManager.smartUpdate() does in TypeScript
|
|
42
42
|
|
|
43
43
|
ACTIVE_IDS=()
|
|
@@ -54,7 +54,7 @@ while IFS= read -r metadata_file; do
|
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
56
|
# Check if status is active-like
|
|
57
|
-
if [[ "$status" == "active" ]] || [[ "$status" == "planning" ]] || [[ "$status" == "
|
|
57
|
+
if [[ "$status" == "active" ]] || [[ "$status" == "planning" ]] || [[ "$status" == "backlog" ]] || [[ "$status" == "ready_for_review" ]]; then
|
|
58
58
|
increment_id=$(basename "$(dirname "$metadata_file")")
|
|
59
59
|
ACTIVE_IDS+=("$increment_id")
|
|
60
60
|
echo "[$(date)] update-active-increment: Found active: $increment_id (status: $status)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
@@ -129,7 +129,7 @@ while IFS= read -r metadata_file; do
|
|
|
129
129
|
fi
|
|
130
130
|
|
|
131
131
|
# Check if active
|
|
132
|
-
if [[ "$status" == "active" || "$status" == "planning" || "$status" == "
|
|
132
|
+
if [[ "$status" == "active" || "$status" == "planning" || "$status" == "backlog" || "$status" == "ready_for_review" ]]; then
|
|
133
133
|
OPEN_COUNT=$((OPEN_COUNT + 1))
|
|
134
134
|
increment_id=$(basename "$(dirname "$metadata_file")")
|
|
135
135
|
|
|
@@ -62,7 +62,7 @@ echo "[$(date)] 📊 Status changed: $NEW_STATUS" >> "$DEBUG_LOG" 2>/dev/null ||
|
|
|
62
62
|
|
|
63
63
|
# Validate status
|
|
64
64
|
case "$NEW_STATUS" in
|
|
65
|
-
paused|
|
|
65
|
+
paused|abandoned|active|planning|backlog|ready_for_review|completed)
|
|
66
66
|
;;
|
|
67
67
|
*)
|
|
68
68
|
echo "[$(date)] ⚠️ Unknown status: $NEW_STATUS (skipping sync)" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
@@ -107,11 +107,11 @@ fi
|
|
|
107
107
|
# ============================================================================
|
|
108
108
|
|
|
109
109
|
case "$NEW_STATUS" in
|
|
110
|
-
|
|
110
|
+
active|planning|backlog|ready_for_review)
|
|
111
111
|
# ========================================================================
|
|
112
112
|
# REOPEN GitHub Issues (NEW)
|
|
113
113
|
# ========================================================================
|
|
114
|
-
# When increment
|
|
114
|
+
# When increment becomes active (including resumed from pause), reopen all closed GitHub issues
|
|
115
115
|
echo "[$(date)] ▶️ Status is $NEW_STATUS - checking if issues need reopening" >> "$DEBUG_LOG" 2>/dev/null || true
|
|
116
116
|
|
|
117
117
|
if command -v node &> /dev/null; then
|
|
@@ -224,7 +224,7 @@ case "$CURRENT_STATUS" in
|
|
|
224
224
|
bash "$HOOK_DIR/lib/update-status-line.sh" --force 2>/dev/null || true
|
|
225
225
|
;;
|
|
226
226
|
|
|
227
|
-
active|planning|
|
|
227
|
+
active|planning|backlog|ready_for_review)
|
|
228
228
|
# Increment became active - MUST register in active-increment.json!
|
|
229
229
|
# CRITICAL FIX (v0.26.15): post-task-completion.sh depends on this file
|
|
230
230
|
# Without registration, ALL sync operations are skipped!
|