@claude-flow/cli 3.17.0 → 3.18.1
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/dist/src/commands/neural.d.ts.map +1 -1
- package/dist/src/commands/neural.js +16 -6
- package/dist/src/commands/neural.js.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/agentdb-tools.js +52 -1
- package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
- package/dist/src/mcp-tools/browser-session-tools.d.ts +5 -1
- package/dist/src/mcp-tools/browser-session-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/browser-session-tools.js +92 -18
- package/dist/src/mcp-tools/browser-session-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts +1 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +131 -0
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/neural-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/neural-tools.js +119 -84
- package/dist/src/mcp-tools/neural-tools.js.map +1 -1
- package/dist/src/memory/intelligence.d.ts.map +1 -1
- package/dist/src/memory/intelligence.js +20 -5
- package/dist/src/memory/intelligence.js.map +1 -1
- package/dist/src/memory/memory-bridge.d.ts +15 -2
- package/dist/src/memory/memory-bridge.d.ts.map +1 -1
- package/dist/src/memory/memory-bridge.js +64 -10
- package/dist/src/memory/memory-bridge.js.map +1 -1
- package/dist/src/ruvector/lora-adapter.d.ts +9 -0
- package/dist/src/ruvector/lora-adapter.d.ts.map +1 -1
- package/dist/src/ruvector/lora-adapter.js +21 -3
- package/dist/src/ruvector/lora-adapter.js.map +1 -1
- package/dist/src/ruvector/output-verifier.d.ts +83 -0
- package/dist/src/ruvector/output-verifier.d.ts.map +1 -0
- package/dist/src/ruvector/output-verifier.js +277 -0
- package/dist/src/ruvector/output-verifier.js.map +1 -0
- package/dist/src/ruvector/trajectory-tree.d.ts +113 -0
- package/dist/src/ruvector/trajectory-tree.d.ts.map +1 -0
- package/dist/src/ruvector/trajectory-tree.js +237 -0
- package/dist/src/ruvector/trajectory-tree.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
- package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +5 -3
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +47 -39
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +173 -104
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +231 -0
- package/plugins/ruflo-metaharness/scripts/_redblue.mjs +36 -75
- package/plugins/ruflo-metaharness/scripts/evolve.mjs +163 -3
- package/plugins/ruflo-metaharness/scripts/gepa.mjs +7 -53
- package/plugins/ruflo-metaharness/scripts/smoke.sh +98 -47
- package/plugins/ruflo-metaharness/scripts/test-graceful-degradation.mjs +16 -0
- package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +41 -2
- package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +4 -2
- package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +3 -1
- package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
- package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +3 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claude-flow/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.18.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -38,7 +38,7 @@ needs its own ADR.
|
|
|
38
38
|
|
|
39
39
|
## Tools (subprocess-only, no library imports)
|
|
40
40
|
|
|
41
|
-
- All skills shell out to `
|
|
41
|
+
- All skills shell out to the pinned `metaharness` / `harness` binaries (`metaharness@~0.3.0`, local install or one-time versioned cache — never `@latest`) via the `_harness.mjs` shared helper.
|
|
42
42
|
- 60s hard timeout per subprocess.
|
|
43
43
|
- Output captured + parsed; `--json` flag forced unless the script opts out.
|
|
44
44
|
- No `@metaharness/*` import statement appears outside the optional-router path in `v3/@claude-flow/cli/src/ruvector/neural-router.ts`.
|
|
@@ -3,9 +3,11 @@ name: ruflo-metaharness
|
|
|
3
3
|
description: MetaHarness integration — score, genome, mint, mcp-scan, threat-model — via subprocess invocations honoring ADR-150 architectural constraint
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
MetaHarness integration commands. All shell out to
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
MetaHarness integration commands. All shell out to the PINNED
|
|
7
|
+
`metaharness`/`harness` binaries (`metaharness@~0.3.0` — resolved from a local
|
|
8
|
+
install or a one-time `~/.ruflo/metaharness-cache-<pin>` install, never
|
|
9
|
+
`@latest`) via the `_harness.mjs` shared helper; no library imports on
|
|
10
|
+
ruflo's boot path.
|
|
9
11
|
|
|
10
12
|
**`harness score [--path .] [--alert-on-fit-below N] [--format table|json]`** -- 5-dimension readiness scorecard (harnessFit / compileConfidence / taskCoverage / toolSafety / memoryUsefulness + estCostPerRunUsd + scaffoldReady).
|
|
11
13
|
1. Run `node plugins/ruflo-metaharness/scripts/score.mjs --path <dir>`
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
// but targets the separate darwin binary (`metaharness-darwin`) which is
|
|
5
5
|
// published as its own npm package (`@metaharness/darwin@~0.8.0`).
|
|
6
6
|
//
|
|
7
|
+
// Shared plumbing (degraded classification, --json injection, trailing-JSON
|
|
8
|
+
// parse, degraded emitter) lives in `_invoke.mjs`. What stays here is the
|
|
9
|
+
// genuinely-darwin part: the ASYNC STREAMING variant (`runDarwinAsync`) that
|
|
10
|
+
// long `evolve` runs need for per-generation progress visibility.
|
|
11
|
+
//
|
|
7
12
|
// Three subcommands surfaced (matches darwin 0.8.x — same verbs as 0.3.x,
|
|
8
13
|
// with GEPA-engine evolve flags added upstream: --selection modes,
|
|
9
14
|
// --crossover, --epistasis, --curriculum, --mutator ruvllm, --sandbox):
|
|
@@ -28,15 +33,24 @@
|
|
|
28
33
|
// smoke shapes only.
|
|
29
34
|
|
|
30
35
|
import { spawnSync, spawn } from 'node:child_process';
|
|
36
|
+
import {
|
|
37
|
+
classifyDegraded,
|
|
38
|
+
importOptionalLibrary,
|
|
39
|
+
injectJson,
|
|
40
|
+
makeDegradedEmitter,
|
|
41
|
+
parseTrailingJson,
|
|
42
|
+
} from './_invoke.mjs';
|
|
31
43
|
|
|
32
44
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
33
45
|
|
|
34
46
|
// Pinned semver range. Bump in lock-step with optionalDependencies in
|
|
35
47
|
// @claude-flow/cli/package.json + ruflo/package.json. The `~` allows
|
|
36
48
|
// patch upgrades without re-pinning.
|
|
37
|
-
const
|
|
49
|
+
const DARWIN_PKG = '@metaharness/darwin';
|
|
50
|
+
const DARWIN_PIN_VERSION = '~0.8.0';
|
|
51
|
+
const DARWIN_PIN = `${DARWIN_PKG}@${DARWIN_PIN_VERSION}`;
|
|
38
52
|
|
|
39
|
-
const
|
|
53
|
+
const REASON_PREFIX = 'metaharness-darwin';
|
|
40
54
|
|
|
41
55
|
function buildArgv(args, wantJson) {
|
|
42
56
|
// `metaharness-darwin` historically does NOT take --json on every
|
|
@@ -45,30 +59,14 @@ function buildArgv(args, wantJson) {
|
|
|
45
59
|
// explicitly request it, but never silently inject it on subcommands
|
|
46
60
|
// that don't accept it. The shape that does support --json is the
|
|
47
61
|
// single-verb `evolve` and `bench verify`.
|
|
48
|
-
|
|
49
|
-
return [...args, '--json'];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function classifyDegraded(stderr, exitCode) {
|
|
53
|
-
if (exitCode === null) return { degraded: true, reason: 'metaharness-darwin-timeout' };
|
|
54
|
-
if (DEGRADED_RX.test(stderr)) return { degraded: true, reason: 'metaharness-darwin-not-available' };
|
|
55
|
-
return { degraded: false };
|
|
62
|
+
return injectJson(args, wantJson);
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (matches.length === 0) return null;
|
|
64
|
-
// Try last match first; fall back to greedy single-block match.
|
|
65
|
-
for (let i = matches.length - 1; i >= 0; i--) {
|
|
66
|
-
try { return JSON.parse(matches[i][0]); } catch { /* try previous */ }
|
|
67
|
-
}
|
|
68
|
-
const greedy = /\{[\s\S]*\}/.exec(stdout);
|
|
69
|
-
if (greedy) { try { return JSON.parse(greedy[0]); } catch { return null; } }
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
65
|
+
// darwin's evolve emits a final JSON object at end of stdout when --json
|
|
66
|
+
// is passed; everything before is human-readable progress. Grab the LAST
|
|
67
|
+
// {...} block, not the first — the first may be a per-generation log line.
|
|
68
|
+
// (This last-block parse is now the family-wide shared implementation.)
|
|
69
|
+
const maybeParseJson = parseTrailingJson;
|
|
72
70
|
|
|
73
71
|
/**
|
|
74
72
|
* Sync invocation. Use for short subcommands (`bench verify`, smoke shapes).
|
|
@@ -90,7 +88,7 @@ export function runDarwin(args, opts = {}) {
|
|
|
90
88
|
const durationMs = Date.now() - start;
|
|
91
89
|
const stdout = r.stdout || '';
|
|
92
90
|
const stderr = r.stderr || '';
|
|
93
|
-
const classified = classifyDegraded(stderr, r.status);
|
|
91
|
+
const classified = classifyDegraded(stderr, r.status, REASON_PREFIX);
|
|
94
92
|
if (classified.degraded) {
|
|
95
93
|
return {
|
|
96
94
|
stdout, stderr,
|
|
@@ -142,7 +140,9 @@ export function runDarwinAsync(args, opts = {}) {
|
|
|
142
140
|
for (const line of lines) opts.onProgress(line);
|
|
143
141
|
}
|
|
144
142
|
});
|
|
143
|
+
let timedOut = false;
|
|
145
144
|
const timer = setTimeout(() => {
|
|
145
|
+
timedOut = true;
|
|
146
146
|
try { p.kill('SIGTERM'); } catch { /* ignore */ }
|
|
147
147
|
}, timeoutMs);
|
|
148
148
|
if (opts.signal) {
|
|
@@ -156,13 +156,13 @@ export function runDarwinAsync(args, opts = {}) {
|
|
|
156
156
|
stdout,
|
|
157
157
|
stderr: stderr + String(e?.message ?? e),
|
|
158
158
|
exitCode: 127, json: null, durationMs: Date.now() - start,
|
|
159
|
-
degraded: true, reason:
|
|
159
|
+
degraded: true, reason: `${REASON_PREFIX}-not-available`,
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
162
|
p.on('close', (code) => {
|
|
163
163
|
clearTimeout(timer);
|
|
164
164
|
const durationMs = Date.now() - start;
|
|
165
|
-
const classified = classifyDegraded(stderr, code);
|
|
165
|
+
const classified = classifyDegraded(stderr, timedOut ? null : code, REASON_PREFIX);
|
|
166
166
|
if (classified.degraded) {
|
|
167
167
|
resolve({
|
|
168
168
|
stdout, stderr,
|
|
@@ -185,18 +185,26 @@ export function runDarwinAsync(args, opts = {}) {
|
|
|
185
185
|
/**
|
|
186
186
|
* Match the existing `emitDegradedJsonAndExit` shape used by the
|
|
187
187
|
* metaharness-umbrella scripts so MCP tool consumers see one contract.
|
|
188
|
+
* Exit 0 — ADR-150 architectural constraint: ruflo continues to function
|
|
189
|
+
* when MetaHarness is absent. Same posture as the umbrella scripts.
|
|
188
190
|
*/
|
|
189
|
-
export
|
|
190
|
-
const payload = {
|
|
191
|
-
degraded: true,
|
|
192
|
-
reason,
|
|
193
|
-
hint: 'Install with `npm i -D ' + DARWIN_PIN + '` or run `npx -y ' + DARWIN_PIN + ' --help` to verify network access.',
|
|
194
|
-
generatedAt: new Date().toISOString(),
|
|
195
|
-
};
|
|
196
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
197
|
-
// Exit 0 — ADR-150 architectural constraint: ruflo continues to function
|
|
198
|
-
// when MetaHarness is absent. Same posture as the umbrella scripts.
|
|
199
|
-
process.exit(0);
|
|
200
|
-
}
|
|
191
|
+
export const emitDarwinDegradedJsonAndExit = makeDegradedEmitter(DARWIN_PKG, DARWIN_PIN_VERSION);
|
|
201
192
|
|
|
202
193
|
export const DARWIN_VERSION_PIN = DARWIN_PIN;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Resolver for the darwin GEPA LIBRARY entry (`@metaharness/darwin/gepa`).
|
|
197
|
+
* Lives here (not in gepa.mjs) because gepa.mjs is a CLI script that runs
|
|
198
|
+
* main() on import — this module is the import-safe home for the darwin
|
|
199
|
+
* pin. Used by gepa.mjs (genome/validate/render/analyze ops) and by
|
|
200
|
+
* evolve.mjs --diagnose (failure-class analysis of run transcripts).
|
|
201
|
+
* Returns the module namespace or null. Never throws on absence.
|
|
202
|
+
*/
|
|
203
|
+
export function importGepa() {
|
|
204
|
+
return importOptionalLibrary({
|
|
205
|
+
specifier: '@metaharness/darwin/gepa',
|
|
206
|
+
pkg: DARWIN_PKG,
|
|
207
|
+
pinVersion: DARWIN_PIN_VERSION,
|
|
208
|
+
entryRelPath: 'dist/gepa/index.js',
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -7,41 +7,147 @@
|
|
|
7
7
|
// vetted bridge that every skill imports from.
|
|
8
8
|
//
|
|
9
9
|
// CONTRACT
|
|
10
|
-
// - `runMetaharness(args, opts)` — invoke `
|
|
11
|
-
// - `runHarness(args, opts)` — invoke
|
|
12
|
-
// - both return `{ stdout, stderr, exitCode, json|null, durationMs }`
|
|
10
|
+
// - `runMetaharness(args, opts)` — invoke the `metaharness` binary
|
|
11
|
+
// - `runHarness(args, opts)` — invoke the sibling `harness` binary
|
|
12
|
+
// - both return `{ stdout, stderr, exitCode, json|null, durationMs, degraded, reason? }`
|
|
13
13
|
// - `--json` flag is appended automatically when `opts.json !== false`
|
|
14
14
|
// - subprocess hard timeout (default 60s) — captured in opts.timeoutMs
|
|
15
|
-
// -
|
|
16
|
-
// `degraded: true, reason: 'metaharness-not-available'` — never
|
|
17
|
-
// (ADR-150 graceful-degradation rule #3)
|
|
15
|
+
// - when the package cannot be resolved/installed, returns degraded result
|
|
16
|
+
// with `degraded: true, reason: 'metaharness-not-available'` — never
|
|
17
|
+
// throws (ADR-150 graceful-degradation rule #3). A subprocess killed by
|
|
18
|
+
// the timeout reports `reason: 'metaharness-timeout'` instead (matches
|
|
19
|
+
// the sibling _darwin/_redblue helpers).
|
|
20
|
+
//
|
|
21
|
+
// SECURITY + PERF (supersedes the iter-27 npx-with-@latest-dist-tag path)
|
|
22
|
+
// =========================================================================
|
|
23
|
+
// The pre-consolidation implementation shelled to `npx -y` with the @latest
|
|
24
|
+
// dist-tag. Two problems, both fixed here:
|
|
25
|
+
// 1. SECURITY (HIGH): @latest means a compromised upstream publish executes
|
|
26
|
+
// arbitrary code on user machines on the very next skill invocation.
|
|
27
|
+
// The version is now PINNED to METAHARNESS_PIN_VERSION (tilde range —
|
|
28
|
+
// patch updates only) and only bumped deliberately, in lock-step with
|
|
29
|
+
// optionalDependencies in @claude-flow/cli + ruflo package.json.
|
|
30
|
+
// 2. PERF: @latest forced an npm-registry metadata check on EVERY call.
|
|
31
|
+
// Resolution is now (a) an already-installed local metaharness
|
|
32
|
+
// satisfying the pin (walk-up node_modules — free), then (b) a ONE-TIME
|
|
33
|
+
// `npm install --prefix ~/.ruflo/metaharness-cache-<pin>` versioned
|
|
34
|
+
// cache (same proven pattern as _redblue.mjs / gepa.mjs), after which
|
|
35
|
+
// every call is a plain `node <abs-path-to-cli>` spawn — zero network.
|
|
36
|
+
//
|
|
37
|
+
// BOTH BINARIES: the `metaharness` package ships two bins (`metaharness`
|
|
38
|
+
// and `harness`); their entry paths are read from the resolved package.json
|
|
39
|
+
// bin map rather than hardcoded, so upstream layout changes inside the
|
|
40
|
+
// pinned range cannot silently break us.
|
|
18
41
|
|
|
19
42
|
import { spawnSync, spawn } from 'node:child_process';
|
|
43
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
44
|
+
import { join } from 'node:path';
|
|
45
|
+
import {
|
|
46
|
+
classifyDegraded,
|
|
47
|
+
ensureCachedInstall,
|
|
48
|
+
findLocalPackageDir,
|
|
49
|
+
injectJson,
|
|
50
|
+
makeDegradedEmitter,
|
|
51
|
+
parseTrailingJson,
|
|
52
|
+
} from './_invoke.mjs';
|
|
20
53
|
|
|
21
54
|
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
22
55
|
|
|
56
|
+
// Pinned semver range. Bump in lock-step with optionalDependencies in
|
|
57
|
+
// @claude-flow/cli/package.json + ruflo/package.json. NEVER @latest.
|
|
58
|
+
const METAHARNESS_PKG = 'metaharness';
|
|
59
|
+
const METAHARNESS_PIN_VERSION = '~0.3.0';
|
|
60
|
+
|
|
61
|
+
const REASON_NOT_AVAILABLE = 'metaharness-not-available';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve absolute paths for the two bins shipped by the metaharness
|
|
65
|
+
* package. Memoized per process — the whole point is that after the first
|
|
66
|
+
* resolution (or one-time cache install) every call is local-only.
|
|
67
|
+
*/
|
|
68
|
+
let RESOLVED = null;
|
|
69
|
+
function resolveMetaharnessBins() {
|
|
70
|
+
if (RESOLVED) return RESOLVED;
|
|
71
|
+
// (a) already-installed local copy satisfying the pin — free.
|
|
72
|
+
const localDir = findLocalPackageDir(METAHARNESS_PKG, METAHARNESS_PIN_VERSION);
|
|
73
|
+
if (localDir) {
|
|
74
|
+
const bins = readBinMap(localDir);
|
|
75
|
+
if (bins) return (RESOLVED = { ok: true, bins, source: 'local' });
|
|
76
|
+
}
|
|
77
|
+
// (b) one-time versioned cache install of the pinned range.
|
|
78
|
+
const cached = ensureCachedInstall({ pkg: METAHARNESS_PKG, pinVersion: METAHARNESS_PIN_VERSION });
|
|
79
|
+
if (!cached.ok) {
|
|
80
|
+
return (RESOLVED = {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: REASON_NOT_AVAILABLE,
|
|
83
|
+
stderr: cached.stderr ?? cached.error ?? '',
|
|
84
|
+
stdout: cached.stdout ?? '',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const bins = readBinMap(cached.pkgDir);
|
|
88
|
+
if (!bins) {
|
|
89
|
+
return (RESOLVED = {
|
|
90
|
+
ok: false,
|
|
91
|
+
reason: REASON_NOT_AVAILABLE,
|
|
92
|
+
stderr: `metaharness package at ${cached.pkgDir} is missing the expected bin map (metaharness + harness)`,
|
|
93
|
+
stdout: '',
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return (RESOLVED = { ok: true, bins, source: 'cache' });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Read both bin entry points from the package's bin map. */
|
|
100
|
+
function readBinMap(pkgDir) {
|
|
101
|
+
try {
|
|
102
|
+
const pj = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf-8'));
|
|
103
|
+
const bin = pj.bin || {};
|
|
104
|
+
if (!bin.metaharness || !bin.harness) return null;
|
|
105
|
+
const metaharness = join(pkgDir, bin.metaharness);
|
|
106
|
+
const harness = join(pkgDir, bin.harness);
|
|
107
|
+
if (!existsSync(metaharness) || !existsSync(harness)) return null;
|
|
108
|
+
return { metaharness, harness };
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function degradedResult(resolved, start) {
|
|
115
|
+
return {
|
|
116
|
+
stdout: resolved.stdout ?? '',
|
|
117
|
+
stderr: resolved.stderr ?? '',
|
|
118
|
+
exitCode: 127,
|
|
119
|
+
json: null,
|
|
120
|
+
durationMs: Date.now() - start,
|
|
121
|
+
degraded: true,
|
|
122
|
+
reason: resolved.reason ?? REASON_NOT_AVAILABLE,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
23
126
|
/**
|
|
24
|
-
* iter 56 —
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* sync execCli so callers can swap without ceremony.
|
|
127
|
+
* Async variant (iter 56) — used by oia-audit.mjs to parallelize its 5
|
|
128
|
+
* subprocess calls, dropping worst-case wall-clock from 5×TIMEOUT to
|
|
129
|
+
* 1×TIMEOUT. Identical return shape to the sync path.
|
|
28
130
|
*/
|
|
29
|
-
function
|
|
131
|
+
function execBinAsync(binName, args, opts = {}) {
|
|
132
|
+
const start = Date.now();
|
|
133
|
+
const resolved = resolveMetaharnessBins();
|
|
134
|
+
if (!resolved.ok) return Promise.resolve(degradedResult(resolved, start));
|
|
30
135
|
return new Promise((resolve) => {
|
|
31
|
-
const start = Date.now();
|
|
32
136
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
33
137
|
const wantJson = opts.json !== false;
|
|
34
|
-
const argv =
|
|
35
|
-
const p = spawn('
|
|
138
|
+
const argv = injectJson(args, wantJson);
|
|
139
|
+
const p = spawn('node', [resolved.bins[binName], ...argv], {
|
|
36
140
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
141
|
cwd: opts.cwd,
|
|
38
142
|
env: { ...process.env, ...(opts.env || {}) },
|
|
39
|
-
shell:
|
|
143
|
+
shell: false,
|
|
40
144
|
});
|
|
41
145
|
let stdout = '', stderr = '';
|
|
42
146
|
p.stdout?.on('data', (d) => { stdout += d.toString(); });
|
|
43
147
|
p.stderr?.on('data', (d) => { stderr += d.toString(); });
|
|
148
|
+
let timedOut = false;
|
|
44
149
|
const timer = setTimeout(() => {
|
|
150
|
+
timedOut = true;
|
|
45
151
|
try { p.kill('SIGTERM'); } catch { /* ignore */ }
|
|
46
152
|
}, timeoutMs);
|
|
47
153
|
p.on('error', (e) => {
|
|
@@ -49,123 +155,87 @@ function execCliAsync(npxArgs, opts = {}) {
|
|
|
49
155
|
resolve({
|
|
50
156
|
stdout, stderr: stderr + String(e?.message ?? e),
|
|
51
157
|
exitCode: 127, json: null, durationMs: Date.now() - start,
|
|
52
|
-
degraded: true, reason:
|
|
158
|
+
degraded: true, reason: REASON_NOT_AVAILABLE,
|
|
53
159
|
});
|
|
54
160
|
});
|
|
55
161
|
p.on('close', (code) => {
|
|
56
162
|
clearTimeout(timer);
|
|
57
163
|
const durationMs = Date.now() - start;
|
|
58
|
-
|
|
164
|
+
const classified = classifyDegraded(stderr, timedOut ? null : code, 'metaharness');
|
|
165
|
+
if (classified.degraded) {
|
|
59
166
|
resolve({
|
|
60
167
|
stdout, stderr,
|
|
61
168
|
exitCode: code ?? 127, json: null, durationMs,
|
|
62
|
-
degraded: true, reason:
|
|
169
|
+
degraded: true, reason: classified.reason,
|
|
63
170
|
});
|
|
64
171
|
return;
|
|
65
172
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
173
|
+
resolve({
|
|
174
|
+
stdout, stderr,
|
|
175
|
+
exitCode: code ?? 0,
|
|
176
|
+
json: wantJson ? parseTrailingJson(stdout) : null,
|
|
177
|
+
durationMs,
|
|
178
|
+
degraded: false,
|
|
179
|
+
});
|
|
72
180
|
});
|
|
73
181
|
});
|
|
74
182
|
}
|
|
75
183
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export function runHarnessAsync(args, opts) {
|
|
81
|
-
return execCliAsync(['-y', '-p', 'metaharness@latest', 'harness', ...args], opts);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ITER 27 — npx invocation hardening.
|
|
85
|
-
// The pre-iter-27 implementation passed `'-y metaharness@latest'` as a
|
|
86
|
-
// SINGLE argv element to npx (`spawnSync('npx', [bin, ...argv])` where
|
|
87
|
-
// bin contained two whitespace-separated tokens). spawnSync with
|
|
88
|
-
// shell:false does no word-splitting, so npx received a literal string
|
|
89
|
-
// with an embedded space and either failed silently or treated the
|
|
90
|
-
// whole thing as a package name. The graceful-degradation path then
|
|
91
|
-
// reported `degraded: true` for every skill — masking the bug. Every
|
|
92
|
-
// argv token must now be its own array element.
|
|
93
|
-
function execCli(npxArgs, opts = {}) {
|
|
184
|
+
/** Sync invocation of one of the two resolved bins via `node <abs-path>`. */
|
|
185
|
+
function execBin(binName, args, opts = {}) {
|
|
94
186
|
const start = Date.now();
|
|
187
|
+
const resolved = resolveMetaharnessBins();
|
|
188
|
+
if (!resolved.ok) return degradedResult(resolved, start);
|
|
95
189
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
96
190
|
const wantJson = opts.json !== false;
|
|
97
|
-
const argv =
|
|
98
|
-
const r = spawnSync('
|
|
191
|
+
const argv = injectJson(args, wantJson);
|
|
192
|
+
const r = spawnSync('node', [resolved.bins[binName], ...argv], {
|
|
99
193
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
100
194
|
encoding: 'utf-8',
|
|
101
195
|
timeout: timeoutMs,
|
|
102
196
|
cwd: opts.cwd, // iter 27 — let callers redirect $CWD (mint.mjs needs this)
|
|
103
197
|
env: { ...process.env, ...(opts.env || {}) },
|
|
104
|
-
shell:
|
|
198
|
+
shell: false,
|
|
105
199
|
});
|
|
106
200
|
const durationMs = Date.now() - start;
|
|
107
201
|
const stdout = r.stdout || '';
|
|
108
202
|
const stderr = r.stderr || '';
|
|
109
|
-
|
|
110
|
-
if (
|
|
203
|
+
const classified = classifyDegraded(stderr, r.status, 'metaharness');
|
|
204
|
+
if (classified.degraded) {
|
|
111
205
|
return {
|
|
112
206
|
stdout, stderr,
|
|
113
207
|
exitCode: r.status ?? 127,
|
|
114
208
|
json: null,
|
|
115
209
|
durationMs,
|
|
116
210
|
degraded: true,
|
|
117
|
-
reason:
|
|
211
|
+
reason: classified.reason,
|
|
118
212
|
};
|
|
119
213
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
214
|
+
return {
|
|
215
|
+
stdout, stderr,
|
|
216
|
+
exitCode: r.status ?? 0,
|
|
217
|
+
json: wantJson ? parseTrailingJson(stdout) : null,
|
|
218
|
+
durationMs,
|
|
219
|
+
degraded: false,
|
|
220
|
+
};
|
|
126
221
|
}
|
|
127
222
|
|
|
128
223
|
export function runMetaharness(args, opts) {
|
|
129
|
-
|
|
130
|
-
// string, which silently degraded every skill).
|
|
131
|
-
return execCli(['-y', 'metaharness@latest', ...args], opts);
|
|
224
|
+
return execBin('metaharness', args, opts);
|
|
132
225
|
}
|
|
133
226
|
|
|
134
227
|
export function runHarness(args, opts) {
|
|
135
|
-
// The `harness` binary ships inside the `metaharness` package
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
cwd: opts?.cwd, // iter 27 — same cwd-redirect support as runMetaharness
|
|
147
|
-
env: { ...process.env, ...(opts?.env || {}) },
|
|
148
|
-
shell: process.platform === 'win32',
|
|
149
|
-
});
|
|
150
|
-
const durationMs = Date.now() - start;
|
|
151
|
-
const stdout = r.stdout || '';
|
|
152
|
-
const stderr = r.stderr || '';
|
|
153
|
-
if (r.status === null || /could not determine executable|404|not installed|MODULE_NOT_FOUND|ENOTFOUND|getaddrinfo|ECONNREFUSED|ETIMEDOUT/i.test(stderr)) {
|
|
154
|
-
return {
|
|
155
|
-
stdout, stderr,
|
|
156
|
-
exitCode: r.status ?? 127,
|
|
157
|
-
json: null,
|
|
158
|
-
durationMs,
|
|
159
|
-
degraded: true,
|
|
160
|
-
reason: 'metaharness-not-available',
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
let json = null;
|
|
164
|
-
if (wantJson) {
|
|
165
|
-
const m = /\{[\s\S]*\}/.exec(stdout);
|
|
166
|
-
if (m) { try { json = JSON.parse(m[0]); } catch { /* leave null */ } }
|
|
167
|
-
}
|
|
168
|
-
return { stdout, stderr, exitCode: r.status ?? 0, json, durationMs, degraded: false };
|
|
228
|
+
// The `harness` binary ships inside the same `metaharness` package —
|
|
229
|
+
// resolved from the package.json bin map above.
|
|
230
|
+
return execBin('harness', args, opts);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function runMetaharnessAsync(args, opts) {
|
|
234
|
+
return execBinAsync('metaharness', args, opts);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function runHarnessAsync(args, opts) {
|
|
238
|
+
return execBinAsync('harness', args, opts);
|
|
169
239
|
}
|
|
170
240
|
|
|
171
241
|
/**
|
|
@@ -244,18 +314,17 @@ export function parseMcpScanText(stdout) {
|
|
|
244
314
|
return { findings, summary };
|
|
245
315
|
}
|
|
246
316
|
|
|
247
|
-
// Convenience
|
|
317
|
+
// Convenience emitter for skill scripts — keep the boilerplate out of
|
|
248
318
|
// each skill so they focus on argument parsing + exit-code semantics.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
process.exit(0);
|
|
319
|
+
// Exit 0 — ADR-150 architectural constraint says ruflo continues to
|
|
320
|
+
// function when MetaHarness is absent. Skills emit a structured
|
|
321
|
+
// degraded payload rather than failing.
|
|
322
|
+
export const emitDegradedJsonAndExit = makeDegradedEmitter(METAHARNESS_PKG, METAHARNESS_PIN_VERSION);
|
|
323
|
+
|
|
324
|
+
export const METAHARNESS_VERSION_PIN = `${METAHARNESS_PKG}@${METAHARNESS_PIN_VERSION}`;
|
|
325
|
+
|
|
326
|
+
/** Expose resolution for diagnostics/tests (which path served the bins). */
|
|
327
|
+
export function metaharnessResolution() {
|
|
328
|
+
const r = resolveMetaharnessBins();
|
|
329
|
+
return r.ok ? { ok: true, source: r.source, bins: r.bins } : { ok: false, reason: r.reason };
|
|
261
330
|
}
|