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,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Utilities - Cross-platform OS operations
|
|
3
|
+
*
|
|
4
|
+
* Abstracts platform-specific operations for macOS, Linux, and Windows
|
|
5
|
+
* Enhanced for process lifecycle management with async API, logger injection,
|
|
6
|
+
* and system notifications
|
|
7
|
+
*/
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import { stat, mkdir, rmdir } from 'fs/promises';
|
|
10
|
+
import { consoleLogger } from './logger.js';
|
|
11
|
+
/**
|
|
12
|
+
* Gets the current platform
|
|
13
|
+
*/
|
|
14
|
+
export function getCurrentPlatform() {
|
|
15
|
+
return process.platform;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Checks if a process exists (cross-platform)
|
|
19
|
+
*
|
|
20
|
+
* @param pid - Process ID to check
|
|
21
|
+
* @returns true if process exists
|
|
22
|
+
*/
|
|
23
|
+
export function checkProcessExists(pid) {
|
|
24
|
+
try {
|
|
25
|
+
if (process.platform === 'win32') {
|
|
26
|
+
const output = execSync(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
27
|
+
encoding: 'utf-8',
|
|
28
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
29
|
+
});
|
|
30
|
+
return output.includes(String(pid));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
execSync(`kill -0 ${pid}`, { stdio: 'ignore' });
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Gets file modification time in seconds since epoch (cross-platform)
|
|
43
|
+
*
|
|
44
|
+
* @param filePath - Path to file
|
|
45
|
+
* @returns Modification time in seconds since epoch
|
|
46
|
+
*/
|
|
47
|
+
export function getFileMtime(filePath) {
|
|
48
|
+
try {
|
|
49
|
+
if (process.platform === 'darwin') {
|
|
50
|
+
const output = execSync(`stat -f %m "${filePath}"`, { encoding: 'utf-8' });
|
|
51
|
+
return parseInt(output.trim(), 10);
|
|
52
|
+
}
|
|
53
|
+
else if (process.platform === 'win32') {
|
|
54
|
+
const output = execSync(`powershell -Command "(Get-Item '${filePath}').LastWriteTime.ToFileTimeUtc()"`, { encoding: 'utf-8' });
|
|
55
|
+
const fileTime = parseInt(output.trim(), 10);
|
|
56
|
+
return Math.floor((fileTime - 116444736000000000) / 10000000);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const output = execSync(`stat -c %Y "${filePath}"`, { encoding: 'utf-8' });
|
|
60
|
+
return parseInt(output.trim(), 10);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Kills a process (cross-platform)
|
|
69
|
+
*
|
|
70
|
+
* @param pid - Process ID to kill
|
|
71
|
+
* @param signal - Signal to send (default: SIGTERM)
|
|
72
|
+
*/
|
|
73
|
+
export function killProcess(pid, signal = 'SIGTERM') {
|
|
74
|
+
try {
|
|
75
|
+
if (process.platform === 'win32') {
|
|
76
|
+
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
process.kill(pid, signal);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Process may not exist, ignore
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Acquires a file lock using atomic directory creation (cross-platform)
|
|
88
|
+
*
|
|
89
|
+
* @param lockPath - Path to lock directory
|
|
90
|
+
* @returns true if lock acquired
|
|
91
|
+
*/
|
|
92
|
+
export function acquireFileLock(lockPath) {
|
|
93
|
+
const fs = require('fs');
|
|
94
|
+
try {
|
|
95
|
+
fs.mkdirSync(lockPath, { recursive: false });
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
if (err.code === 'EEXIST') {
|
|
100
|
+
return false; // Lock already exists
|
|
101
|
+
}
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Enhanced cross-platform utility functions with async API and logger injection
|
|
107
|
+
*/
|
|
108
|
+
export class PlatformUtils {
|
|
109
|
+
constructor(options = {}) {
|
|
110
|
+
this.platform = process.platform;
|
|
111
|
+
this.logger = options.logger ?? consoleLogger;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Get current platform
|
|
115
|
+
*/
|
|
116
|
+
getPlatform() {
|
|
117
|
+
return this.platform;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check if current platform is Windows
|
|
121
|
+
*/
|
|
122
|
+
isWindows() {
|
|
123
|
+
return this.platform === 'win32';
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if current platform is POSIX (macOS or Linux)
|
|
127
|
+
*/
|
|
128
|
+
isPosix() {
|
|
129
|
+
return this.platform === 'darwin' || this.platform === 'linux';
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Check if a process exists by PID (async)
|
|
133
|
+
*
|
|
134
|
+
* - macOS/Linux: kill -0 $pid (signal 0 = existence check)
|
|
135
|
+
* - Windows: tasklist /FI "PID eq $pid"
|
|
136
|
+
*
|
|
137
|
+
* @param pid Process ID to check
|
|
138
|
+
* @returns true if process exists, false otherwise
|
|
139
|
+
*/
|
|
140
|
+
async checkProcessExistsAsync(pid) {
|
|
141
|
+
try {
|
|
142
|
+
if (this.isWindows()) {
|
|
143
|
+
const output = execSync(`tasklist /FI "PID eq ${pid}" /NH`, {
|
|
144
|
+
encoding: 'utf-8',
|
|
145
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
146
|
+
windowsHide: true
|
|
147
|
+
});
|
|
148
|
+
return output.includes(pid.toString());
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Signal 0 = check existence without actually sending a signal
|
|
152
|
+
process.kill(pid, 0);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (err.code === 'ESRCH') {
|
|
158
|
+
// No such process
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
if (err.code === 'EPERM') {
|
|
162
|
+
// Process exists but we don't have permission to signal it
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
this.logger.error(`Error checking process ${pid}:`, err);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Get file modification time (async, uses fs.stat for cross-platform)
|
|
171
|
+
*
|
|
172
|
+
* @param path File path
|
|
173
|
+
* @returns Modification time in seconds since epoch
|
|
174
|
+
*/
|
|
175
|
+
async getFileMtimeAsync(path) {
|
|
176
|
+
try {
|
|
177
|
+
const stats = await stat(path);
|
|
178
|
+
return Math.floor(stats.mtimeMs / 1000);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
this.logger.error(`Error getting mtime for ${path}:`, err);
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Acquire file lock using atomic mkdir (async)
|
|
187
|
+
*
|
|
188
|
+
* @param lockPath Path to lock directory
|
|
189
|
+
* @param timeoutMs Maximum time to wait for lock (default: 5000ms)
|
|
190
|
+
* @returns true if lock acquired, false if timeout
|
|
191
|
+
*/
|
|
192
|
+
async acquireFileLockAsync(lockPath, timeoutMs = 5000) {
|
|
193
|
+
const startTime = Date.now();
|
|
194
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
195
|
+
try {
|
|
196
|
+
await mkdir(lockPath, { recursive: false });
|
|
197
|
+
return true; // Lock acquired
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
if (err.code === 'EEXIST') {
|
|
201
|
+
// Lock exists, wait and retry
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
this.logger.error(`Error acquiring lock at ${lockPath}:`, err);
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this.logger.warn(`Lock acquisition timeout after ${timeoutMs}ms: ${lockPath}`);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Release file lock (async)
|
|
214
|
+
*
|
|
215
|
+
* @param lockPath Path to lock directory
|
|
216
|
+
*/
|
|
217
|
+
async releaseFileLockAsync(lockPath) {
|
|
218
|
+
try {
|
|
219
|
+
await rmdir(lockPath);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
if (err.code !== 'ENOENT') {
|
|
223
|
+
this.logger.error(`Error releasing lock at ${lockPath}:`, err);
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Kill process with signal (async)
|
|
230
|
+
*
|
|
231
|
+
* @param pid Process ID to kill
|
|
232
|
+
* @param signal Signal to send (default: SIGTERM)
|
|
233
|
+
*/
|
|
234
|
+
async killProcessAsync(pid, signal = 'SIGTERM') {
|
|
235
|
+
try {
|
|
236
|
+
if (this.isWindows()) {
|
|
237
|
+
execSync(`taskkill /PID ${pid} /F`, {
|
|
238
|
+
stdio: 'ignore',
|
|
239
|
+
windowsHide: true
|
|
240
|
+
});
|
|
241
|
+
this.logger.debug(`Killed process ${pid} (Windows taskkill /F)`);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
process.kill(pid, signal);
|
|
245
|
+
this.logger.debug(`Killed process ${pid} with signal ${signal}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
if (err.code === 'ESRCH') {
|
|
250
|
+
this.logger.debug(`Process ${pid} doesn't exist (already terminated)`);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.logger.error(`Error killing process ${pid}:`, err);
|
|
254
|
+
throw err;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Kill process with graceful fallback (SIGTERM → SIGKILL)
|
|
259
|
+
*
|
|
260
|
+
* @param pid Process ID to kill
|
|
261
|
+
* @param gracePeriodMs Time to wait after SIGTERM before SIGKILL (default: 2000ms)
|
|
262
|
+
*/
|
|
263
|
+
async killProcessGracefully(pid, gracePeriodMs = 2000) {
|
|
264
|
+
const exists = await this.checkProcessExistsAsync(pid);
|
|
265
|
+
if (!exists) {
|
|
266
|
+
this.logger.debug(`Process ${pid} doesn't exist, skipping kill`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
// Step 1: Send SIGTERM (graceful shutdown)
|
|
271
|
+
await this.killProcessAsync(pid, 'SIGTERM');
|
|
272
|
+
this.logger.debug(`Sent SIGTERM to process ${pid}, waiting ${gracePeriodMs}ms`);
|
|
273
|
+
// Step 2: Wait for grace period
|
|
274
|
+
await new Promise(resolve => setTimeout(resolve, gracePeriodMs));
|
|
275
|
+
// Step 3: Check if process still exists
|
|
276
|
+
const stillExists = await this.checkProcessExistsAsync(pid);
|
|
277
|
+
if (!stillExists) {
|
|
278
|
+
this.logger.debug(`Process ${pid} terminated gracefully after SIGTERM`);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Step 4: Force kill with SIGKILL
|
|
282
|
+
this.logger.warn(`Process ${pid} didn't terminate after SIGTERM, sending SIGKILL`);
|
|
283
|
+
await this.killProcessAsync(pid, 'SIGKILL');
|
|
284
|
+
this.logger.debug(`Sent SIGKILL to process ${pid}`);
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
this.logger.error(`Error during graceful kill of process ${pid}:`, err);
|
|
288
|
+
throw err;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Send system notification
|
|
293
|
+
*
|
|
294
|
+
* - macOS: osascript (AppleScript)
|
|
295
|
+
* - Linux: notify-send (if available)
|
|
296
|
+
* - Windows: PowerShell toast notification
|
|
297
|
+
*
|
|
298
|
+
* Fails gracefully if notification system is unavailable
|
|
299
|
+
*
|
|
300
|
+
* @param title Notification title
|
|
301
|
+
* @param body Notification body text
|
|
302
|
+
*/
|
|
303
|
+
async sendNotification(title, body) {
|
|
304
|
+
try {
|
|
305
|
+
if (this.platform === 'darwin') {
|
|
306
|
+
await this.sendNotificationMacOS(title, body);
|
|
307
|
+
}
|
|
308
|
+
else if (this.platform === 'linux') {
|
|
309
|
+
await this.sendNotificationLinux(title, body);
|
|
310
|
+
}
|
|
311
|
+
else if (this.platform === 'win32') {
|
|
312
|
+
await this.sendNotificationWindows(title, body);
|
|
313
|
+
}
|
|
314
|
+
this.logger.debug(`Sent notification: ${title}`);
|
|
315
|
+
}
|
|
316
|
+
catch (err) {
|
|
317
|
+
this.logger.warn(`Failed to send notification "${title}": ${err}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* macOS notification using osascript
|
|
322
|
+
*/
|
|
323
|
+
async sendNotificationMacOS(title, body) {
|
|
324
|
+
const escapedTitle = title.replace(/"/g, '\\"');
|
|
325
|
+
const escapedBody = body.replace(/"/g, '\\"');
|
|
326
|
+
const cmd = `osascript -e 'display notification "${escapedBody}" with title "${escapedTitle}"'`;
|
|
327
|
+
execSync(cmd, { stdio: 'ignore' });
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Linux notification using notify-send
|
|
331
|
+
*/
|
|
332
|
+
async sendNotificationLinux(title, body) {
|
|
333
|
+
try {
|
|
334
|
+
execSync('command -v notify-send', { stdio: 'ignore' });
|
|
335
|
+
const escapedTitle = title.replace(/"/g, '\\"');
|
|
336
|
+
const escapedBody = body.replace(/"/g, '\\"');
|
|
337
|
+
execSync(`notify-send "${escapedTitle}" "${escapedBody}"`, { stdio: 'ignore' });
|
|
338
|
+
}
|
|
339
|
+
catch (err) {
|
|
340
|
+
throw new Error('notify-send not available on this system');
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Windows notification using PowerShell toast
|
|
345
|
+
*/
|
|
346
|
+
async sendNotificationWindows(title, body) {
|
|
347
|
+
const escapedTitle = title.replace(/'/g, "''");
|
|
348
|
+
const escapedBody = body.replace(/'/g, "''");
|
|
349
|
+
const psScript = `
|
|
350
|
+
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null;
|
|
351
|
+
$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02);
|
|
352
|
+
$xml = [xml]$template.GetXml();
|
|
353
|
+
$xml.GetElementsByTagName('text')[0].AppendChild($xml.CreateTextNode('${escapedTitle}')) | Out-Null;
|
|
354
|
+
$xml.GetElementsByTagName('text')[1].AppendChild($xml.CreateTextNode('${escapedBody}')) | Out-Null;
|
|
355
|
+
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml);
|
|
356
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('SpecWeave').Show($toast);
|
|
357
|
+
`;
|
|
358
|
+
execSync(`powershell -Command "${psScript}"`, {
|
|
359
|
+
stdio: 'ignore',
|
|
360
|
+
windowsHide: true
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Export singleton instance for convenience
|
|
365
|
+
export const platformUtils = new PlatformUtils();
|
|
366
|
+
//# sourceMappingURL=platform-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform-utils.js","sourceRoot":"","sources":["../../../src/utils/platform-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
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 { SessionInfo, RegisterSessionOptions, StaleSessionInfo, SessionStatus } from '../types/session.js';
|
|
16
|
+
import { Logger } from './logger.js';
|
|
17
|
+
export declare class SessionRegistry {
|
|
18
|
+
private registryPath;
|
|
19
|
+
private lockPath;
|
|
20
|
+
private logger;
|
|
21
|
+
constructor(projectRoot: string, options?: {
|
|
22
|
+
logger?: Logger;
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Acquires an exclusive lock on the registry file
|
|
26
|
+
*
|
|
27
|
+
* Uses mkdir as an atomic operation (works cross-platform)
|
|
28
|
+
* Waits up to LOCK_TIMEOUT_MS before failing
|
|
29
|
+
*
|
|
30
|
+
* @returns true if lock acquired, false if timeout
|
|
31
|
+
*/
|
|
32
|
+
private acquireLock;
|
|
33
|
+
/**
|
|
34
|
+
* Releases the lock on the registry file
|
|
35
|
+
*/
|
|
36
|
+
private releaseLock;
|
|
37
|
+
/**
|
|
38
|
+
* Reads the registry file with corruption recovery
|
|
39
|
+
*
|
|
40
|
+
* If registry doesn't exist, creates a new empty one
|
|
41
|
+
* If registry is corrupted (invalid JSON), creates backup and reinitializes
|
|
42
|
+
*
|
|
43
|
+
* @returns The current registry state
|
|
44
|
+
*/
|
|
45
|
+
private readRegistry;
|
|
46
|
+
/**
|
|
47
|
+
* Writes registry to disk atomically
|
|
48
|
+
*
|
|
49
|
+
* Uses temp file + rename pattern for atomic write
|
|
50
|
+
* This ensures registry is never left in a corrupted state
|
|
51
|
+
*
|
|
52
|
+
* @param registry - The registry state to write
|
|
53
|
+
*/
|
|
54
|
+
private writeRegistry;
|
|
55
|
+
/**
|
|
56
|
+
* Creates a new empty registry
|
|
57
|
+
*/
|
|
58
|
+
private createEmptyRegistry;
|
|
59
|
+
/**
|
|
60
|
+
* Registers a new session in the registry
|
|
61
|
+
*
|
|
62
|
+
* @param sessionId - Unique session identifier
|
|
63
|
+
* @param pid - Process ID of the session
|
|
64
|
+
* @param options - Optional configuration
|
|
65
|
+
* @returns true if registration successful
|
|
66
|
+
*/
|
|
67
|
+
registerSession(sessionId: string, pid: number, options?: RegisterSessionOptions): Promise<boolean>;
|
|
68
|
+
/**
|
|
69
|
+
* Updates the heartbeat timestamp for a session
|
|
70
|
+
*
|
|
71
|
+
* @param sessionId - Session to update
|
|
72
|
+
* @returns true if update successful
|
|
73
|
+
*/
|
|
74
|
+
updateHeartbeat(sessionId: string): Promise<boolean>;
|
|
75
|
+
/**
|
|
76
|
+
* Adds a child process PID to a session
|
|
77
|
+
*
|
|
78
|
+
* @param sessionId - Parent session ID
|
|
79
|
+
* @param childPid - Child process PID to add
|
|
80
|
+
* @returns true if successful
|
|
81
|
+
*/
|
|
82
|
+
addChildProcess(sessionId: string, childPid: number): Promise<boolean>;
|
|
83
|
+
/**
|
|
84
|
+
* Removes a session from the registry
|
|
85
|
+
*
|
|
86
|
+
* @param sessionId - Session to remove
|
|
87
|
+
* @returns true if successful
|
|
88
|
+
*/
|
|
89
|
+
removeSession(sessionId: string): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* Gets a session by ID
|
|
92
|
+
*
|
|
93
|
+
* @param sessionId - Session to retrieve
|
|
94
|
+
* @returns Session info or undefined
|
|
95
|
+
*/
|
|
96
|
+
getSession(sessionId: string): Promise<SessionInfo | undefined>;
|
|
97
|
+
/**
|
|
98
|
+
* Gets all sessions
|
|
99
|
+
*
|
|
100
|
+
* @returns Array of all session info
|
|
101
|
+
*/
|
|
102
|
+
getAllSessions(): Promise<SessionInfo[]>;
|
|
103
|
+
/**
|
|
104
|
+
* Updates the status of a session
|
|
105
|
+
*
|
|
106
|
+
* @param sessionId - Session to update
|
|
107
|
+
* @param status - New status
|
|
108
|
+
* @returns true if successful
|
|
109
|
+
*/
|
|
110
|
+
updateStatus(sessionId: string, status: SessionStatus): Promise<boolean>;
|
|
111
|
+
/**
|
|
112
|
+
* Gets all stale sessions based on heartbeat threshold
|
|
113
|
+
*
|
|
114
|
+
* A session is considered stale if its last_heartbeat is older than
|
|
115
|
+
* the specified threshold.
|
|
116
|
+
*
|
|
117
|
+
* @param thresholdSeconds - Max age of last heartbeat before considered stale
|
|
118
|
+
* @returns Array of stale session info with staleness details
|
|
119
|
+
*/
|
|
120
|
+
getStaleSessions(thresholdSeconds: number): Promise<StaleSessionInfo[]>;
|
|
121
|
+
/**
|
|
122
|
+
* Cleans up old sessions based on retention period
|
|
123
|
+
*
|
|
124
|
+
* Removes sessions that have been completed or stale for longer
|
|
125
|
+
* than the retention period.
|
|
126
|
+
*
|
|
127
|
+
* @param retentionHours - How long to keep old sessions (in hours)
|
|
128
|
+
* @returns Number of sessions removed
|
|
129
|
+
*/
|
|
130
|
+
cleanupOldSessions(retentionHours: number): Promise<number>;
|
|
131
|
+
/**
|
|
132
|
+
* Checks if a PID exists (cross-platform)
|
|
133
|
+
*
|
|
134
|
+
* Uses kill -0 on Unix-like systems, which doesn't actually send a signal
|
|
135
|
+
* but checks if the process exists.
|
|
136
|
+
*
|
|
137
|
+
* @param pid - Process ID to check
|
|
138
|
+
* @returns true if process exists
|
|
139
|
+
*/
|
|
140
|
+
private checkPidExists;
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=session-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../../../src/utils/session-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EACL,WAAW,EAEX,sBAAsB,EACtB,gBAAgB,EAChB,aAAa,EACd,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAiB,MAAM,aAAa,CAAC;AAOpD,qBAAa,eAAe;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,MAAM,CAAS;gBAEX,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO;IAclE;;;;;;;OAOG;YACW,WAAW;IA4BzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAenB;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY;IAsCpB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IAyBrB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;OAOG;IACG,eAAe,CACnB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,OAAO,CAAC;IAuCnB;;;;;OAKG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAyB1D;;;;;;OAMG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgC5E;;;;;OAKG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwBxD;;;;;OAKG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAcrE;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAc9C;;;;;;OAMG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAwB9E;;;;;;;;OAQG;IACG,gBAAgB,CAAC,gBAAgB,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAyC7E;;;;;;;;OAQG;IACG,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgDjE;;;;;;;;OAQG;YACW,cAAc;CAuB7B"}
|