@axiomatic-labs/claudeflow 2.32.5 → 2.32.6
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/lib/doctor.js +42 -14
- package/package.json +1 -1
package/lib/doctor.js
CHANGED
|
@@ -146,9 +146,43 @@ function checkStaleLockfiles(cwd) {
|
|
|
146
146
|
// Detection strategy (in order of preference):
|
|
147
147
|
// 1. error-observer.json exists → read its `port` + `pid_path`.
|
|
148
148
|
// 2. If pidfile exists and PID is alive → running=true.
|
|
149
|
-
// 3. Else, TCP
|
|
149
|
+
// 3. Else, TCP-probe the observer port with a short `net.connect` (via a
|
|
150
|
+
// one-shot `node -e` child, so the synchronous status path stays sync).
|
|
150
151
|
// The pidfile may be missing (race) but if SOMETHING is listening on
|
|
151
|
-
// the configured port, the daemon is up.
|
|
152
|
+
// the configured port, the daemon is up. This replaces a prior `lsof`
|
|
153
|
+
// probe that was slow on macOS (often >1.5s — the timeout — so a live
|
|
154
|
+
// observer read as stopped) and absent on Windows (no `lsof` → the
|
|
155
|
+
// fallback never ran). `net.connect` is a Node built-in: fast and
|
|
156
|
+
// cross-platform. Trade-off: it proves a listener EXISTS but cannot
|
|
157
|
+
// attribute the owning PID, so a probe hit returns `pid: null`.
|
|
158
|
+
//
|
|
159
|
+
// Synchronous TCP liveness check WITHOUT lsof. Node has no sync net.connect,
|
|
160
|
+
// so we run the connect in a one-shot `node -e` child via spawnSync — keeping
|
|
161
|
+
// readObserverState (and its sync callers: getMcpInfo/getObserverInfo/
|
|
162
|
+
// collectStatus) synchronous. Probes both IPv4 and IPv6 loopback (the daemon
|
|
163
|
+
// may bind either); exit 0 = a listener answered. Reached only when the
|
|
164
|
+
// pidfile path already failed, so the ~node-startup cost is off the hot path.
|
|
165
|
+
function portHasListenerSync(port) {
|
|
166
|
+
if (!Number.isFinite(port) || port <= 0) return false;
|
|
167
|
+
const script =
|
|
168
|
+
'const net=require("net");' +
|
|
169
|
+
'const port=' + Number(port) + ';' +
|
|
170
|
+
'const hosts=["127.0.0.1","::1"];' +
|
|
171
|
+
'let pending=hosts.length,done=false;' +
|
|
172
|
+
'const finish=(ok)=>{if(done)return;if(ok){done=true;process.exit(0);}if(--pending===0){done=true;process.exit(1);}};' +
|
|
173
|
+
'for(const h of hosts){const s=net.connect({host:h,port});s.setTimeout(250);' +
|
|
174
|
+
's.once("connect",()=>{s.destroy();finish(true);});' +
|
|
175
|
+
's.once("error",()=>finish(false));' +
|
|
176
|
+
's.once("timeout",()=>{s.destroy();finish(false);});}';
|
|
177
|
+
try {
|
|
178
|
+
const { spawnSync } = require('child_process');
|
|
179
|
+
const r = spawnSync(process.execPath, ['-e', script], { timeout: 1000 });
|
|
180
|
+
return r.status === 0;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
152
186
|
function readObserverState(cwd) {
|
|
153
187
|
const observerJsonPath = path.join(cwd, '.claudeflow', 'tmp', 'error-observer.json');
|
|
154
188
|
// Defense-in-depth retry: even though the daemon now writes atomically
|
|
@@ -200,18 +234,12 @@ function readObserverState(cwd) {
|
|
|
200
234
|
if (alive) return { running: true, pid, port, source: 'pidfile', observerJsonPath };
|
|
201
235
|
}
|
|
202
236
|
|
|
203
|
-
// Fallback:
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const tcpPid = parseInt(r.stdout.trim().split('\n')[0], 10);
|
|
210
|
-
if (Number.isFinite(tcpPid) && tcpPid > 0) {
|
|
211
|
-
return { running: true, pid: tcpPid, port, source: 'tcp-probe', observerJsonPath };
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
} catch {}
|
|
237
|
+
// Fallback: cross-platform net.connect probe. Catches the case where the
|
|
238
|
+
// pidfile is missing (race / external start) but the daemon is still
|
|
239
|
+
// listening on the port. Can't attribute the PID, so report pid: null.
|
|
240
|
+
if (portHasListenerSync(port)) {
|
|
241
|
+
return { running: true, pid: null, port, source: 'tcp-probe', observerJsonPath };
|
|
242
|
+
}
|
|
215
243
|
|
|
216
244
|
return { running: false, pid: pid, port, source: 'no-listener', observerJsonPath };
|
|
217
245
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.32.
|
|
3
|
+
"version": "2.32.6",
|
|
4
4
|
"description": "Claudeflow — AI-powered development toolkit for Claude Code. Skills, agents, hooks, and quality gates that ship production apps.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claudeflow": "./bin/cli.js"
|