@axiomatic-labs/claudeflow 2.13.72 → 2.13.73
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 -0
- package/lib/install.js +34 -1
- package/package.json +1 -1
package/lib/doctor.js
CHANGED
|
@@ -251,6 +251,46 @@ function checkObserverState(cwd) {
|
|
|
251
251
|
};
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
// Detects more than one process listening on the observer port. macOS allows
|
|
255
|
+
// distinct sockets on the same port for IPv4 and IPv6, so a stale daemon
|
|
256
|
+
// bound to "localhost" (which old code resolved to ::1) could coexist with a
|
|
257
|
+
// fresh daemon bound to 127.0.0.1. Snapshots routed via `localhost` then
|
|
258
|
+
// landed on the stale daemon, which carried the pre-v2.13.71 auto-resolve
|
|
259
|
+
// bug — panel saw the new daemon's empty state, hook saw 1 incident. This
|
|
260
|
+
// check surfaces that condition explicitly.
|
|
261
|
+
function checkObserverDualBind(cwd) {
|
|
262
|
+
const observer = readObserverState(cwd);
|
|
263
|
+
if (!observer.port) {
|
|
264
|
+
return { id: 'observer-dual-bind', severity: 'info', message: 'No observer port known — skipping dual-bind check.' };
|
|
265
|
+
}
|
|
266
|
+
const port = observer.port;
|
|
267
|
+
let listeners = [];
|
|
268
|
+
try {
|
|
269
|
+
const { spawnSync } = require('child_process');
|
|
270
|
+
const r = spawnSync('lsof', [`-iTCP:${port}`, '-sTCP:LISTEN', '-P', '-n', '-F', 'pn'], { encoding: 'utf8', timeout: 1500 });
|
|
271
|
+
if (r.status === 0 && r.stdout) {
|
|
272
|
+
let currentPid = null;
|
|
273
|
+
for (const line of r.stdout.split('\n')) {
|
|
274
|
+
if (line.startsWith('p')) currentPid = Number(line.slice(1));
|
|
275
|
+
else if (line.startsWith('n') && currentPid) listeners.push({ pid: currentPid, name: line.slice(1) });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} catch {
|
|
279
|
+
return { id: 'observer-dual-bind', severity: 'info', message: 'lsof unavailable — cannot verify single-bind on observer port.' };
|
|
280
|
+
}
|
|
281
|
+
const uniquePids = new Set(listeners.map((l) => l.pid));
|
|
282
|
+
if (uniquePids.size <= 1) {
|
|
283
|
+
return { id: 'observer-dual-bind', severity: 'ok', message: `Observer port ${port} has a single listener.` };
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
id: 'observer-dual-bind',
|
|
287
|
+
severity: 'error',
|
|
288
|
+
message: `Observer port ${port} has ${uniquePids.size} distinct processes listening (PIDs: ${[...uniquePids].join(', ')}). One is likely a stale daemon from a previous version. Browser snapshots may route to the wrong instance, causing the panel and the Stop hook to disagree. Fix: stop the older PID (\`kill <pid>\`) and let the SessionStart hook respawn a single daemon.`,
|
|
289
|
+
detail: listeners.map((l) => ({ file: `pid ${l.pid}`, reason: l.name })),
|
|
290
|
+
fix: { kind: 'dual-bind', listeners },
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
254
294
|
function applyStaleLockfileFix(check) {
|
|
255
295
|
for (const { file } of check.fix.stale) {
|
|
256
296
|
try { fs.unlinkSync(file); } catch {}
|
|
@@ -286,6 +326,7 @@ async function run(argv = []) {
|
|
|
286
326
|
checkCdpPortMismatch(cwd),
|
|
287
327
|
checkStaleLockfiles(cwd),
|
|
288
328
|
checkObserverState(cwd),
|
|
329
|
+
checkObserverDualBind(cwd),
|
|
289
330
|
];
|
|
290
331
|
|
|
291
332
|
for (const check of checks) printCheck(check);
|
|
@@ -326,6 +367,7 @@ module.exports.deriveCdpPort = deriveCdpPort;
|
|
|
326
367
|
module.exports.checkCdpPortMismatch = checkCdpPortMismatch;
|
|
327
368
|
module.exports.checkStaleLockfiles = checkStaleLockfiles;
|
|
328
369
|
module.exports.checkObserverState = checkObserverState;
|
|
370
|
+
module.exports.checkObserverDualBind = checkObserverDualBind;
|
|
329
371
|
module.exports.readObserverState = readObserverState;
|
|
330
372
|
module.exports.readPlaywrightCdpEndpoint = readPlaywrightCdpEndpoint;
|
|
331
373
|
module.exports.applyCdpPortFix = applyCdpPortFix;
|
package/lib/install.js
CHANGED
|
@@ -117,7 +117,7 @@ async function run() {
|
|
|
117
117
|
command: "npx",
|
|
118
118
|
args: [
|
|
119
119
|
"@playwright/mcp@latest",
|
|
120
|
-
"--cdp-endpoint", `http://
|
|
120
|
+
"--cdp-endpoint", `http://127.0.0.1:${cdpPort}`,
|
|
121
121
|
"--caps", "vision,devtools",
|
|
122
122
|
],
|
|
123
123
|
};
|
|
@@ -220,6 +220,12 @@ async function run() {
|
|
|
220
220
|
fs.rmSync(tmpExtract, { recursive: true, force: true });
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
+
// Kill any running observer daemon so the next request spawns a fresh one
|
|
224
|
+
// with the updated runtime code. Pre-fix: a running daemon held the old
|
|
225
|
+
// version in memory until manually killed, causing the bridge to talk to
|
|
226
|
+
// stale logic (see v2.13.71→v2.13.72 dual-daemon incident).
|
|
227
|
+
killStaleObserverDaemon(cwd);
|
|
228
|
+
|
|
223
229
|
// Install analysis tools (ast-grep + Serena MCP)
|
|
224
230
|
installAnalysisTools();
|
|
225
231
|
|
|
@@ -282,6 +288,33 @@ async function run() {
|
|
|
282
288
|
showGettingStarted(cwd, cliStatus);
|
|
283
289
|
}
|
|
284
290
|
|
|
291
|
+
function killStaleObserverDaemon(cwd) {
|
|
292
|
+
const pidPath = path.join(cwd, '.claudeflow', 'tmp', 'error-observer.pid');
|
|
293
|
+
if (!fs.existsSync(pidPath)) return;
|
|
294
|
+
let pid;
|
|
295
|
+
try {
|
|
296
|
+
pid = Number(fs.readFileSync(pidPath, 'utf8').trim());
|
|
297
|
+
} catch {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (!Number.isFinite(pid) || pid <= 1) return;
|
|
301
|
+
try {
|
|
302
|
+
// signal 0 = "is this PID alive and ours to signal?"
|
|
303
|
+
process.kill(pid, 0);
|
|
304
|
+
} catch {
|
|
305
|
+
// Not running or not ours. Clean up the stale pidfile and move on.
|
|
306
|
+
try { fs.unlinkSync(pidPath); } catch {}
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
process.kill(pid, 'SIGTERM');
|
|
311
|
+
ui.success(`Stopped running observer daemon (pid ${pid}) so the update takes effect`);
|
|
312
|
+
} catch {
|
|
313
|
+
// Swallow — non-fatal. The user can restart manually if needed.
|
|
314
|
+
}
|
|
315
|
+
try { fs.unlinkSync(pidPath); } catch {}
|
|
316
|
+
}
|
|
317
|
+
|
|
285
318
|
function ensureGlobalCli(version) {
|
|
286
319
|
// Returns { available: boolean, autoInstalled: boolean, upgraded: boolean, fromVersion?: string, error?: string }
|
|
287
320
|
// `commandExists('claudeflow')` yields a false positive when install.js runs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiomatic-labs/claudeflow",
|
|
3
|
-
"version": "2.13.
|
|
3
|
+
"version": "2.13.73",
|
|
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"
|