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.
Files changed (172) hide show
  1. package/CLAUDE.md +106 -1
  2. package/dist/src/cli/add-child-pid.d.ts +11 -0
  3. package/dist/src/cli/add-child-pid.d.ts.map +1 -0
  4. package/dist/src/cli/add-child-pid.js +42 -0
  5. package/dist/src/cli/add-child-pid.js.map +1 -0
  6. package/dist/src/cli/add-child-process.d.ts +15 -0
  7. package/dist/src/cli/add-child-process.d.ts.map +1 -0
  8. package/dist/src/cli/add-child-process.js +40 -0
  9. package/dist/src/cli/add-child-process.js.map +1 -0
  10. package/dist/src/cli/check-watchdog.d.ts +15 -0
  11. package/dist/src/cli/check-watchdog.d.ts.map +1 -0
  12. package/dist/src/cli/check-watchdog.js +47 -0
  13. package/dist/src/cli/check-watchdog.js.map +1 -0
  14. package/dist/src/cli/cleanup-zombies.d.ts +14 -0
  15. package/dist/src/cli/cleanup-zombies.d.ts.map +1 -0
  16. package/dist/src/cli/cleanup-zombies.js +268 -0
  17. package/dist/src/cli/cleanup-zombies.js.map +1 -0
  18. package/dist/src/cli/find-session-by-pid.d.ts +14 -0
  19. package/dist/src/cli/find-session-by-pid.d.ts.map +1 -0
  20. package/dist/src/cli/find-session-by-pid.js +45 -0
  21. package/dist/src/cli/find-session-by-pid.js.map +1 -0
  22. package/dist/src/cli/get-stale-sessions.d.ts +17 -0
  23. package/dist/src/cli/get-stale-sessions.d.ts.map +1 -0
  24. package/dist/src/cli/get-stale-sessions.js +36 -0
  25. package/dist/src/cli/get-stale-sessions.js.map +1 -0
  26. package/dist/src/cli/register-session.d.ts +16 -0
  27. package/dist/src/cli/register-session.d.ts.map +1 -0
  28. package/dist/src/cli/register-session.js +48 -0
  29. package/dist/src/cli/register-session.js.map +1 -0
  30. package/dist/src/cli/remove-session.d.ts +11 -0
  31. package/dist/src/cli/remove-session.d.ts.map +1 -0
  32. package/dist/src/cli/remove-session.js +36 -0
  33. package/dist/src/cli/remove-session.js.map +1 -0
  34. package/dist/src/cli/update-heartbeat.d.ts +11 -0
  35. package/dist/src/cli/update-heartbeat.d.ts.map +1 -0
  36. package/dist/src/cli/update-heartbeat.js +36 -0
  37. package/dist/src/cli/update-heartbeat.js.map +1 -0
  38. package/dist/src/config/types.d.ts +1208 -203
  39. package/dist/src/config/types.d.ts.map +1 -1
  40. package/dist/src/core/background/job-manager.d.ts +16 -0
  41. package/dist/src/core/background/job-manager.d.ts.map +1 -1
  42. package/dist/src/core/background/job-manager.js +110 -15
  43. package/dist/src/core/background/job-manager.js.map +1 -1
  44. package/dist/src/core/increment/increment-utils.d.ts +26 -1
  45. package/dist/src/core/increment/increment-utils.d.ts.map +1 -1
  46. package/dist/src/core/increment/increment-utils.js +66 -4
  47. package/dist/src/core/increment/increment-utils.js.map +1 -1
  48. package/dist/src/core/increment/status-change-sync-trigger.d.ts +3 -1
  49. package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
  50. package/dist/src/core/increment/status-change-sync-trigger.js +5 -2
  51. package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
  52. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.d.ts.map +1 -1
  53. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js +48 -12
  54. package/dist/src/core/living-docs/intelligent-analyzer/architecture-generator.js.map +1 -1
  55. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.d.ts +70 -0
  56. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.d.ts.map +1 -0
  57. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.js +188 -0
  58. package/dist/src/core/living-docs/intelligent-analyzer/cache-manager.js.map +1 -0
  59. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.d.ts +33 -0
  60. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.d.ts.map +1 -0
  61. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.js +290 -0
  62. package/dist/src/core/living-docs/intelligent-analyzer/dashboard-generator.js.map +1 -0
  63. package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.d.ts.map +1 -1
  64. package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js +114 -11
  65. package/dist/src/core/living-docs/intelligent-analyzer/deep-repo-analyzer.js.map +1 -1
  66. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.d.ts +23 -0
  67. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.d.ts.map +1 -0
  68. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.js +283 -0
  69. package/dist/src/core/living-docs/intelligent-analyzer/graph-visualizer.js.map +1 -0
  70. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.d.ts +44 -0
  71. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.d.ts.map +1 -0
  72. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.js +61 -0
  73. package/dist/src/core/living-docs/intelligent-analyzer/mermaid-generator.js.map +1 -0
  74. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.d.ts +126 -0
  75. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.d.ts.map +1 -0
  76. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.js +378 -0
  77. package/dist/src/core/living-docs/intelligent-analyzer/orchestrator.js.map +1 -0
  78. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.d.ts.map +1 -1
  79. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +57 -0
  80. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js.map +1 -1
  81. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.d.ts +82 -0
  82. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.d.ts.map +1 -0
  83. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.js +430 -0
  84. package/dist/src/core/living-docs/intelligent-analyzer/pattern-analyzer.js.map +1 -0
  85. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.d.ts +84 -0
  86. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.d.ts.map +1 -0
  87. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.js +387 -0
  88. package/dist/src/core/living-docs/intelligent-analyzer/repo-scanner.js.map +1 -0
  89. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.d.ts +61 -0
  90. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.d.ts.map +1 -0
  91. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.js +174 -0
  92. package/dist/src/core/living-docs/intelligent-analyzer/report-writer.js.map +1 -0
  93. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts +1 -1
  94. package/dist/src/core/living-docs/intelligent-analyzer/types.d.ts.map +1 -1
  95. package/dist/src/core/living-docs/module-analyzer.d.ts +3 -0
  96. package/dist/src/core/living-docs/module-analyzer.d.ts.map +1 -1
  97. package/dist/src/core/living-docs/module-analyzer.js +40 -1
  98. package/dist/src/core/living-docs/module-analyzer.js.map +1 -1
  99. package/dist/src/core/qa/qa-runner.js +1 -1
  100. package/dist/src/core/qa/qa-runner.js.map +1 -1
  101. package/dist/src/core/scheduler/session-sync-executor.js +1 -1
  102. package/dist/src/core/scheduler/session-sync-executor.js.map +1 -1
  103. package/dist/src/core/status-line/status-line-updater.d.ts +1 -1
  104. package/dist/src/core/status-line/status-line-updater.d.ts.map +1 -1
  105. package/dist/src/core/status-line/status-line-updater.js +4 -3
  106. package/dist/src/core/status-line/status-line-updater.js.map +1 -1
  107. package/dist/src/importers/jira-importer.d.ts.map +1 -1
  108. package/dist/src/importers/jira-importer.js +18 -9
  109. package/dist/src/importers/jira-importer.js.map +1 -1
  110. package/dist/src/init/architecture/types.d.ts +140 -33
  111. package/dist/src/init/architecture/types.d.ts.map +1 -1
  112. package/dist/src/init/compliance/types.d.ts +27 -30
  113. package/dist/src/init/compliance/types.d.ts.map +1 -1
  114. package/dist/src/init/repo/types.d.ts +34 -11
  115. package/dist/src/init/repo/types.d.ts.map +1 -1
  116. package/dist/src/init/research/src/config/types.d.ts +82 -15
  117. package/dist/src/init/research/src/config/types.d.ts.map +1 -1
  118. package/dist/src/init/research/types.d.ts +93 -38
  119. package/dist/src/init/research/types.d.ts.map +1 -1
  120. package/dist/src/init/team/types.d.ts +42 -4
  121. package/dist/src/init/team/types.d.ts.map +1 -1
  122. package/dist/src/sync/ado-reconciler.js +1 -1
  123. package/dist/src/sync/ado-reconciler.js.map +1 -1
  124. package/dist/src/sync/github-reconciler.js +1 -1
  125. package/dist/src/sync/github-reconciler.js.map +1 -1
  126. package/dist/src/sync/jira-reconciler.js +1 -1
  127. package/dist/src/sync/jira-reconciler.js.map +1 -1
  128. package/dist/src/types/session.d.ts +65 -0
  129. package/dist/src/types/session.d.ts.map +1 -0
  130. package/dist/src/types/session.js +8 -0
  131. package/dist/src/types/session.js.map +1 -0
  132. package/dist/src/utils/lock-manager.d.ts +48 -0
  133. package/dist/src/utils/lock-manager.d.ts.map +1 -0
  134. package/dist/src/utils/lock-manager.js +195 -0
  135. package/dist/src/utils/lock-manager.js.map +1 -0
  136. package/dist/src/utils/notification-manager.d.ts +45 -0
  137. package/dist/src/utils/notification-manager.d.ts.map +1 -0
  138. package/dist/src/utils/notification-manager.js +130 -0
  139. package/dist/src/utils/notification-manager.js.map +1 -0
  140. package/dist/src/utils/platform-utils.d.ts +136 -0
  141. package/dist/src/utils/platform-utils.d.ts.map +1 -0
  142. package/dist/src/utils/platform-utils.js +366 -0
  143. package/dist/src/utils/platform-utils.js.map +1 -0
  144. package/dist/src/utils/session-registry.d.ts +142 -0
  145. package/dist/src/utils/session-registry.d.ts.map +1 -0
  146. package/dist/src/utils/session-registry.js +480 -0
  147. package/dist/src/utils/session-registry.js.map +1 -0
  148. package/package.json +5 -2
  149. package/plugins/specweave/commands/specweave-living-docs.md +42 -0
  150. package/plugins/specweave/hooks/hooks.json +10 -0
  151. package/plugins/specweave/hooks/lib/update-active-increment.sh +2 -2
  152. package/plugins/specweave/hooks/lib/update-status-line.sh +1 -1
  153. package/plugins/specweave/hooks/post-increment-status-change.sh +3 -3
  154. package/plugins/specweave/hooks/post-metadata-change.sh +1 -1
  155. package/plugins/specweave/hooks/universal/hook-wrapper.cmd +26 -26
  156. package/plugins/specweave/hooks/universal/session-start.cmd +16 -16
  157. package/plugins/specweave/hooks/universal/session-start.ps1 +16 -16
  158. package/plugins/specweave/hooks/user-prompt-submit.sh +2 -2
  159. package/plugins/specweave/hooks/v2/guards/increment-root-guard.sh +61 -0
  160. package/plugins/specweave/hooks/v2/session-end.sh +69 -0
  161. package/plugins/specweave/hooks/v2/session-start.sh +81 -0
  162. package/plugins/specweave/lib/vendor/sync/github-reconciler.js +1 -1
  163. package/plugins/specweave/lib/vendor/sync/github-reconciler.js.map +1 -1
  164. package/plugins/specweave/scripts/heartbeat.sh +110 -0
  165. package/plugins/specweave/scripts/progress.js +34 -4
  166. package/plugins/specweave/scripts/read-jobs.sh +1 -1
  167. package/plugins/specweave/scripts/read-progress.sh +50 -5
  168. package/plugins/specweave/scripts/read-workflow.sh +1 -1
  169. package/plugins/specweave/scripts/session-watchdog.sh +65 -0
  170. package/plugins/specweave/scripts/status.js +28 -11
  171. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +738 -0
  172. 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.32.10",
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|in-progress
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" == "in-progress" ]]; then
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" == "in-progress" ]]; then
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|resumed|abandoned|active|in-progress)
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
- resumed|active|in-progress)
110
+ active|planning|backlog|ready_for_review)
111
111
  # ========================================================================
112
112
  # REOPEN GitHub Issues (NEW)
113
113
  # ========================================================================
114
- # When increment is resumed, reopen all closed GitHub issues
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|in-progress)
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!