@axiomatic-labs/claudeflow 2.32.4 → 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/lib/panel.js +25 -9
- 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/lib/panel.js
CHANGED
|
@@ -230,16 +230,32 @@ function getSetupContextInfo(cwd) {
|
|
|
230
230
|
try { ctx = JSON.parse(fs.readFileSync(p, 'utf8')); }
|
|
231
231
|
catch { return { exists: false, path: p }; }
|
|
232
232
|
|
|
233
|
-
|
|
233
|
+
// Canonical evidence types per test-tooling-schema.md: the 5 binary `_test`
|
|
234
|
+
// types + the 2 observational `_verification` types. (benchmark /
|
|
235
|
+
// exploratory_test are conditional — triggered only by PERFORMANCE_CHANGE or
|
|
236
|
+
// unmeasurable correctness — so they are NOT required for "complete".)
|
|
237
|
+
// Source of truth: .claude/skills/claudeflow-init/references/test-tooling-schema.md.
|
|
238
|
+
const canonicalToolingTypes = [
|
|
239
|
+
'unit_test',
|
|
240
|
+
'integration_test',
|
|
241
|
+
'api_contract_test',
|
|
242
|
+
'e2e_test',
|
|
243
|
+
'security_test',
|
|
244
|
+
'browser_verification',
|
|
245
|
+
'visual_verification',
|
|
246
|
+
];
|
|
234
247
|
const tooling = ctx.test_tooling || {};
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
248
|
+
// An entry is "defined" when it is shell-backed (`command_pattern`) OR
|
|
249
|
+
// Skill-backed (`invocation`). browser_verification is Skill-backed and
|
|
250
|
+
// legitimately carries NO command_pattern, so a command_pattern-only check
|
|
251
|
+
// would false-flag it as missing.
|
|
252
|
+
const isToolingDefined = (e) =>
|
|
253
|
+
e &&
|
|
254
|
+
typeof e === 'object' &&
|
|
255
|
+
((typeof e.command_pattern === 'string' && e.command_pattern.trim()) ||
|
|
256
|
+
(typeof e.invocation === 'string' && e.invocation.trim()));
|
|
257
|
+
const toolingComplete = canonicalToolingTypes.every((t) => isToolingDefined(tooling[t]));
|
|
258
|
+
const missingTooling = canonicalToolingTypes.filter((t) => !isToolingDefined(tooling[t]));
|
|
243
259
|
|
|
244
260
|
return {
|
|
245
261
|
exists: true,
|
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"
|