@claude-flow/cli 3.17.0 → 3.18.0

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.
Files changed (43) hide show
  1. package/dist/src/mcp-tools/agentdb-tools.d.ts.map +1 -1
  2. package/dist/src/mcp-tools/agentdb-tools.js +52 -1
  3. package/dist/src/mcp-tools/agentdb-tools.js.map +1 -1
  4. package/dist/src/mcp-tools/browser-session-tools.d.ts +5 -1
  5. package/dist/src/mcp-tools/browser-session-tools.d.ts.map +1 -1
  6. package/dist/src/mcp-tools/browser-session-tools.js +92 -18
  7. package/dist/src/mcp-tools/browser-session-tools.js.map +1 -1
  8. package/dist/src/mcp-tools/hooks-tools.d.ts +1 -0
  9. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  10. package/dist/src/mcp-tools/hooks-tools.js +131 -0
  11. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  12. package/dist/src/mcp-tools/neural-tools.d.ts.map +1 -1
  13. package/dist/src/mcp-tools/neural-tools.js +119 -84
  14. package/dist/src/mcp-tools/neural-tools.js.map +1 -1
  15. package/dist/src/memory/memory-bridge.d.ts +15 -2
  16. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  17. package/dist/src/memory/memory-bridge.js +64 -10
  18. package/dist/src/memory/memory-bridge.js.map +1 -1
  19. package/dist/src/ruvector/output-verifier.d.ts +83 -0
  20. package/dist/src/ruvector/output-verifier.d.ts.map +1 -0
  21. package/dist/src/ruvector/output-verifier.js +277 -0
  22. package/dist/src/ruvector/output-verifier.js.map +1 -0
  23. package/dist/src/ruvector/trajectory-tree.d.ts +113 -0
  24. package/dist/src/ruvector/trajectory-tree.d.ts.map +1 -0
  25. package/dist/src/ruvector/trajectory-tree.js +237 -0
  26. package/dist/src/ruvector/trajectory-tree.js.map +1 -0
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +1 -1
  29. package/plugins/ruflo-metaharness/agents/metaharness-architect.md +1 -1
  30. package/plugins/ruflo-metaharness/commands/ruflo-metaharness.md +5 -3
  31. package/plugins/ruflo-metaharness/scripts/_darwin.mjs +47 -39
  32. package/plugins/ruflo-metaharness/scripts/_harness.mjs +173 -104
  33. package/plugins/ruflo-metaharness/scripts/_invoke.mjs +231 -0
  34. package/plugins/ruflo-metaharness/scripts/_redblue.mjs +36 -75
  35. package/plugins/ruflo-metaharness/scripts/evolve.mjs +163 -3
  36. package/plugins/ruflo-metaharness/scripts/gepa.mjs +7 -53
  37. package/plugins/ruflo-metaharness/scripts/smoke.sh +98 -47
  38. package/plugins/ruflo-metaharness/scripts/test-graceful-degradation.mjs +16 -0
  39. package/plugins/ruflo-metaharness/skills/harness-evolve/SKILL.md +41 -2
  40. package/plugins/ruflo-metaharness/skills/harness-learn/SKILL.md +4 -2
  41. package/plugins/ruflo-metaharness/skills/harness-mcp-scan/SKILL.md +3 -1
  42. package/plugins/ruflo-metaharness/skills/harness-score/SKILL.md +1 -1
  43. package/plugins/ruflo-metaharness/skills/harness-threat-model/SKILL.md +3 -1
@@ -22,11 +22,12 @@
22
22
  //
23
23
  // MODULE RESOLUTION (ADR-150 graceful degradation)
24
24
  // ================================================
25
+ // Delegated to _invoke.importOptionalLibrary (family-wide consolidation):
25
26
  // 1. Try bare `import('@metaharness/darwin/gepa')` — free when the optional
26
27
  // dep is installed in an ancestor node_modules.
27
28
  // 2. Fall back to a ruflo-owned versioned cache install
28
- // (~/.ruflo/darwin-cache-<pin>) — same pattern as _redblue.mjs; the
29
- // versioned dir means pin bumps invalidate stale caches automatically.
29
+ // (~/.ruflo/darwin-cache-<pin>) — the versioned dir means pin bumps
30
+ // invalidate stale caches automatically.
30
31
  // 3. Both fail → `{degraded: true}` exit 0. Never throws.
31
32
  //
32
33
  // EXIT CODES
@@ -34,20 +35,11 @@
34
35
  // 1 --alert-on-invalid and validate found errors
35
36
  // 2 config error (bad op / missing file)
36
37
 
37
- import { readFileSync, existsSync, mkdirSync } from 'node:fs';
38
- import { spawnSync } from 'node:child_process';
39
- import { homedir } from 'node:os';
40
- import { join } from 'node:path';
41
- import { pathToFileURL } from 'node:url';
38
+ import { readFileSync, existsSync } from 'node:fs';
39
+ import { importGepa, DARWIN_VERSION_PIN } from './_darwin.mjs';
42
40
 
43
- // Bump in lock-step with DARWIN_PIN in _darwin.mjs + optionalDependencies.
44
- const DARWIN_PIN_VERSION = '~0.8.0';
45
- const CACHE_DIR = join(
46
- homedir(), '.ruflo', `darwin-cache-${DARWIN_PIN_VERSION.replace(/[~^]/g, '')}`,
47
- );
48
- const CACHED_GEPA = join(
49
- CACHE_DIR, 'node_modules', '@metaharness', 'darwin', 'dist', 'gepa', 'index.js',
50
- );
41
+ // Pin lives in _darwin.mjs (DARWIN_VERSION_PIN) — single source of truth.
42
+ const DARWIN_PIN_VERSION = DARWIN_VERSION_PIN.split('@').pop();
51
43
 
52
44
  const ARGS = (() => {
53
45
  const a = {
@@ -83,44 +75,6 @@ function emitDegradedAndExit(reason) {
83
75
  process.exit(0); // ADR-150 — ruflo stays operational without MetaHarness
84
76
  }
85
77
 
86
- async function importGepa() {
87
- try {
88
- return await import('@metaharness/darwin/gepa');
89
- } catch (e) {
90
- const msg = String(e?.message ?? e);
91
- // Fall back on absence AND on stale installs: a pre-0.8.0 darwin in an
92
- // ancestor node_modules has no './gepa' subpath, which throws
93
- // ERR_PACKAGE_PATH_NOT_EXPORTED rather than MODULE_NOT_FOUND.
94
- const recoverable = /Cannot find (module|package)|ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND|ERR_PACKAGE_PATH_NOT_EXPORTED|is not defined by "exports"/i;
95
- if (!recoverable.test(msg)) throw e;
96
- }
97
- // Cached-install fallback.
98
- if (!existsSync(CACHED_GEPA)) {
99
- try {
100
- mkdirSync(CACHE_DIR, { recursive: true });
101
- } catch {
102
- return null;
103
- }
104
- const r = spawnSync('npm', [
105
- 'install',
106
- '--no-audit', '--no-fund', '--no-package-lock',
107
- '--prefix', CACHE_DIR,
108
- `@metaharness/darwin@${DARWIN_PIN_VERSION}`,
109
- ], {
110
- stdio: ['ignore', 'pipe', 'pipe'],
111
- encoding: 'utf-8',
112
- timeout: 180_000,
113
- shell: process.platform === 'win32',
114
- });
115
- if (r.status !== 0 || !existsSync(CACHED_GEPA)) return null;
116
- }
117
- try {
118
- return await import(pathToFileURL(CACHED_GEPA).href);
119
- } catch {
120
- return null;
121
- }
122
- }
123
-
124
78
  function readJsonFile(path, label) {
125
79
  if (!path || !existsSync(path)) {
126
80
  console.error(`gepa: ${label} file not found: ${path}`);
@@ -8,6 +8,29 @@ step() { printf "→ %s ... " "$1"; }
8
8
  ok() { printf "PASS\n"; PASS=$((PASS+1)); }
9
9
  bad() { printf "FAIL: %s\n" "$1"; FAIL=$((FAIL+1)); }
10
10
 
11
+ # ---------------------------------------------------------------------------
12
+ # Derived surface counts (converged arch review, 2026-07).
13
+ # The MCP-tool count (was hardcoded 15 in 4 places) and the CLI-subcommand
14
+ # count (was hardcoded 13 in 2 places, plus the 16 footnote) are derived ONCE
15
+ # from the source-of-truth files here. Every assertion below compares an
16
+ # INDEPENDENT surface (CLAUDE.md catalog, test-mcp-tools literal, footnote
17
+ # occurrences, runScript refs) against these derived values — never a surface
18
+ # against itself. Adding a tool/subcommand upstream now only requires updating
19
+ # the OTHER surfaces, not this script.
20
+ TOOLS_SRC="$ROOT/../../v3/@claude-flow/cli/src/mcp-tools/metaharness-tools.ts"
21
+ SUBS_SRC="$ROOT/../../v3/@claude-flow/cli/src/commands/metaharness.ts"
22
+ EXPECTED_TOOLS=$(grep -cE "name: 'metaharness_" "$TOOLS_SRC" 2>/dev/null; true)
23
+ EXPECTED_SUBS=$(grep -cE "^[[:space:]]+'?[a-z-]+'?:[[:space:]]*'[a-z-]+\.mjs'" "$SUBS_SRC" 2>/dev/null; true)
24
+ : "${EXPECTED_TOOLS:=0}"
25
+ : "${EXPECTED_SUBS:=0}"
26
+
27
+ step "0. derived surface counts extracted (tools >= 15, subcommands >= 13 — regex-rot floor)"
28
+ if [[ "$EXPECTED_TOOLS" -ge 15 && "$EXPECTED_SUBS" -ge 13 ]]; then
29
+ ok
30
+ else
31
+ bad "extraction-regex-rot: EXPECTED_TOOLS=$EXPECTED_TOOLS EXPECTED_SUBS=$EXPECTED_SUBS"
32
+ fi
33
+
11
34
  step "1. plugin.json declares 0.1.0 with adr-150 keywords"
12
35
  v=$(grep -E '"version"' "$ROOT/.claude-plugin/plugin.json" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
13
36
  if [[ "$v" != "0.1.0" ]]; then
@@ -45,6 +68,26 @@ grep -q "metaharness-not-available" "$F" || miss="$miss no-degraded-reason"
45
68
  grep -q "degraded: true" "$F" || miss="$miss no-degraded-flag"
46
69
  [[ -z "$miss" ]] && ok || bad "$miss"
47
70
 
71
+ step "3b. _invoke.mjs shared plumbing layer (consolidation, 2026-07)"
72
+ F="$ROOT/scripts/_invoke.mjs"
73
+ miss=""
74
+ [[ -f "$F" ]] || miss="$miss missing"
75
+ node --check "$F" 2>/dev/null || miss="$miss syntax-error"
76
+ # The consolidated helpers every adapter (_harness/_darwin/_redblue/gepa) uses
77
+ for sym in DEGRADED_RX classifyDegraded injectJson parseTrailingJson ensureCachedInstall makeDegradedEmitter importOptionalLibrary findLocalPackageDir; do
78
+ grep -q "export.*${sym}" "$F" || miss="$miss no-${sym}"
79
+ done
80
+ # Superset degraded regex must include the npm-level failure marker
81
+ grep -q "npm ERR" "$F" || miss="$miss degraded-rx-missing-npm-err"
82
+ # The -timeout vs -not-available distinction is load-bearing
83
+ grep -q -- "-timeout" "$F" || miss="$miss no-timeout-reason"
84
+ grep -q -- "-not-available" "$F" || miss="$miss no-not-available-reason"
85
+ # All four adapters import from it
86
+ for a in _harness _darwin _redblue gepa; do
87
+ grep -q "from './_invoke.mjs'\|from './_darwin.mjs'" "$ROOT/scripts/${a}.mjs" || miss="$miss ${a}-not-adapting"
88
+ done
89
+ [[ -z "$miss" ]] && ok || bad "$miss"
90
+
48
91
  step "4. score.mjs harness present + parses + uses _harness.mjs + alert"
49
92
  F="$ROOT/scripts/score.mjs"
50
93
  miss=""
@@ -170,24 +213,29 @@ if (!od.metaharness) { console.error('missing metaharness in optionalDependencie
170
213
  if (j.dependencies && j.dependencies.metaharness) { console.error('metaharness leaked into dependencies'); process.exit(1); }
171
214
  " 2>/dev/null && ok || bad "ruflo wrapper missing metaharness optionalDep"
172
215
 
173
- step "17r. _harness.mjs npx-argv regression guard (iter 27 fix lock)"
216
+ step "17r. _harness.mjs pinned-version + no-@latest regression guard (supersedes iter 27)"
174
217
  F="$ROOT/scripts/_harness.mjs"
175
218
  miss=""
176
- # THE BUG WAS: passing '-y metaharness@latest' as a single argv token
177
- # to spawnSync. Lock the array-form invocation so it can't regress.
178
- # A correct invocation looks like:
179
- # spawnSync('npx', ['-y', 'metaharness@latest', ...], ...)
180
- # A broken one looks like:
181
- # spawnSync('npx', ['-y metaharness@latest', ...], ...)
182
- # OR:
183
- # execCli('-y metaharness@latest', args, opts)
184
- if grep -qE "execCli\(\s*['\"]-y metaharness@latest['\"]" "$F" 2>/dev/null; then
185
- miss="$miss bug-regressed-string-form"
219
+ # SECURITY (HIGH, converged review 2026-07): the pre-consolidation helper
220
+ # shelled to `npx -y metaharness@latest` a compromised upstream publish
221
+ # would execute arbitrary code on user machines, and @latest forced a
222
+ # registry check per call. Lock the fix: NO @latest anywhere in the loader,
223
+ # a pinned tilde range, and node-direct invocation of resolved bin paths
224
+ # (local walk-up install or one-time versioned cache).
225
+ if grep -q "metaharness@latest" "$F" 2>/dev/null; then
226
+ miss="$miss at-latest-regressed"
186
227
  fi
187
- # Confirm the fix is in place
188
- grep -q "execCli(\[\s*'-y'\s*,\s*'metaharness@latest'" "$F" 2>/dev/null || \
189
- grep -q "execCli(\[ *'-y', 'metaharness@latest'" "$F" 2>/dev/null || miss="$miss no-array-form-fix"
190
- # cwd + env pass-through (added by iter 27)
228
+ grep -q "METAHARNESS_PIN_VERSION = '~" "$F" 2>/dev/null || miss="$miss no-pinned-range"
229
+ # node-direct spawn of the resolved bin (not an npx shim)
230
+ grep -qE "spawnSync\('node'" "$F" 2>/dev/null || miss="$miss no-node-direct-sync"
231
+ grep -qE "spawn\('node'" "$F" 2>/dev/null || miss="$miss no-node-direct-async"
232
+ # resolution order: local install first, then versioned cache install
233
+ grep -q "findLocalPackageDir" "$F" 2>/dev/null || miss="$miss no-local-resolution"
234
+ grep -q "ensureCachedInstall" "$F" 2>/dev/null || miss="$miss no-cache-install"
235
+ # BOTH bins resolved from the package.json bin map (metaharness + harness)
236
+ grep -q "bin.metaharness" "$F" 2>/dev/null || miss="$miss no-metaharness-bin"
237
+ grep -q "bin.harness" "$F" 2>/dev/null || miss="$miss no-harness-bin"
238
+ # cwd + env pass-through (iter 27 behavior retained)
191
239
  grep -q "cwd: opts" "$F" || miss="$miss no-cwd-passthrough"
192
240
  [[ -z "$miss" ]] && ok || bad "$miss"
193
241
 
@@ -722,7 +770,7 @@ for k in $KEYS; do
722
770
  grep -qE "npx ruflo metaharness ${k}([ \\\\]|$)" "$CMD" 2>/dev/null \
723
771
  || miss="$miss subcommand-${k}-not-in-claude-md"
724
772
  done
725
- [[ "$COUNT" == "13" ]] || miss="$miss subcommand-count-stale:$COUNT-expected-13"
773
+ [[ "$COUNT" == "$EXPECTED_SUBS" ]] || miss="$miss subcommand-count-stale:$COUNT-expected-$EXPECTED_SUBS"
726
774
  [[ -z "$miss" ]] && ok || bad "$miss"
727
775
 
728
776
  step "17z56. every MCP tool documented in CLAUDE.md (iter 93)"
@@ -742,12 +790,12 @@ for t in $TOOLS; do
742
790
  grep -q "mcp__claude-flow__${t}" "$CMD" 2>/dev/null \
743
791
  || miss="$miss ${t}-not-in-claude-md"
744
792
  done
745
- # Lock count: 15 MCP tools (mint deliberately excluded — see iter 73).
746
- # Bumped from 9 12 after ADR-153 added metaharness_bench,
747
- # metaharness_evolve, and metaharness_security_bench. Bumped from 12 13
748
- # after @metaharness/redblue integration added metaharness_redblue.
749
- # Bumped from 13 15 after metaharness@0.3.0 learn + darwin@0.8.0 gepa.
750
- [[ "$COUNT" == "15" ]] || miss="$miss mcp-tool-count-stale:$COUNT-expected-15"
793
+ # Count derived from the wrapper source (mint deliberately excluded — see
794
+ # iter 73). The historical bump ladder (9→12→13→15) is gone: the extraction
795
+ # loop above and $EXPECTED_TOOLS both come from metaharness-tools.ts, so this
796
+ # equality only catches loop/derivation divergence; the REAL cross-surface
797
+ # check is the per-tool CLAUDE.md grep in the loop.
798
+ [[ "$COUNT" == "$EXPECTED_TOOLS" ]] || miss="$miss mcp-tool-count-stale:$COUNT-expected-$EXPECTED_TOOLS"
751
799
  [[ -z "$miss" ]] && ok || bad "$miss"
752
800
 
753
801
  step "17z55. MCP enum + SEVERITY_RANK vocabulary aligned (iter 92)"
@@ -822,11 +870,11 @@ for f in $REFS; do
822
870
  COUNT=$((COUNT + 1))
823
871
  [[ -f "$SCRIPTS_DIR/$f" ]] || miss="$miss mcp-script-${f}-missing"
824
872
  done
825
- # Should be 15 unique scripts (one per MCP tool; mint deliberately excluded).
826
- # Bumped from 9 12 after ADR-153 added bench/evolve/security_bench tools.
827
- # Bumped from 12 13 after @metaharness/redblue integration added redblue.mjs.
828
- # Bumped from 13 15 after metaharness@0.3.0 learn + darwin@0.8.0 gepa.
829
- [[ "$COUNT" == "15" ]] || miss="$miss mcp-script-count-stale:$COUNT-expected-15"
873
+ # One unique runScript() ref per MCP tool (mint deliberately excluded).
874
+ # Cross-aspect check: runScript('*.mjs') call sites vs the derived
875
+ # `name: 'metaharness_*'` declaration count in the same file a handler
876
+ # added without a script ref (or vice-versa) diverges these.
877
+ [[ "$COUNT" == "$EXPECTED_TOOLS" ]] || miss="$miss mcp-script-count-stale:$COUNT-expected-$EXPECTED_TOOLS"
830
878
  [[ -z "$miss" ]] && ok || bad "$miss"
831
879
 
832
880
  step "17z52. SUBCOMMANDS map entries point at existing script files (iter 89)"
@@ -846,9 +894,10 @@ for f in $MAPPINGS; do
846
894
  COUNT=$((COUNT + 1))
847
895
  [[ -f "$SCRIPTS_DIR/$f" ]] || miss="$miss script-${f}-missing"
848
896
  done
849
- # Iter 73's description string + redblue + learn/gepa lists 13 subcommands.
850
- # SUBCOMMANDS map should have the same 13 entries. Lock the count at exactly 13.
851
- [[ "$COUNT" == "13" ]] || miss="$miss mapping-count-stale:$COUNT-expected-13"
897
+ # SUBCOMMANDS map entry count vs the derived $EXPECTED_SUBS (same source,
898
+ # so this catches loop/derivation divergence; the real check is the per-file
899
+ # existence assertion in the loop above).
900
+ [[ "$COUNT" == "$EXPECTED_SUBS" ]] || miss="$miss mapping-count-stale:$COUNT-expected-$EXPECTED_SUBS"
852
901
  [[ -z "$miss" ]] && ok || bad "$miss"
853
902
 
854
903
  step "17z51. all metaharness scripts produce parseable JSON in --format json mode (iter 88)"
@@ -1401,10 +1450,13 @@ grep -q "iter 58 — reuse auditResult from the parallel batch" "$F" 2>/dev/null
1401
1450
 
1402
1451
  step "17z20. iter-55 gaps B + C closed (iter 57)"
1403
1452
  miss=""
1404
- # Gap B: _harness.mjs regex catches ENOTFOUND-class network errors
1405
- HARNESS="$ROOT/scripts/_harness.mjs"
1406
- grep -q "ENOTFOUND" "$HARNESS" 2>/dev/null || miss="$miss no-enotfound-regex"
1407
- grep -q "getaddrinfo\|ECONNREFUSED\|ETIMEDOUT" "$HARNESS" 2>/dev/null || miss="$miss no-network-error-regex"
1453
+ # Gap B: the degraded-classification regex catches ENOTFOUND-class network
1454
+ # errors. Consolidation (2026-07) moved DEGRADED_RX to _invoke.mjs — check it
1455
+ # there, plus confirm _harness.mjs actually routes through the shared classifier.
1456
+ INVOKE="$ROOT/scripts/_invoke.mjs"
1457
+ grep -q "ENOTFOUND" "$INVOKE" 2>/dev/null || miss="$miss no-enotfound-regex"
1458
+ grep -q "getaddrinfo\|ECONNREFUSED\|ETIMEDOUT" "$INVOKE" 2>/dev/null || miss="$miss no-network-error-regex"
1459
+ grep -q "classifyDegraded" "$ROOT/scripts/_harness.mjs" 2>/dev/null || miss="$miss harness-not-using-shared-classifier"
1408
1460
  # Gap C: drift-from-history probes oia-audit to disambiguate no-history vs dep-absent
1409
1461
  DRIFT="$ROOT/scripts/drift-from-history.mjs"
1410
1462
  grep -q "disambiguate" "$DRIFT" 2>/dev/null || miss="$miss no-disambiguate-comment"
@@ -1630,13 +1682,11 @@ CODE=$?
1630
1682
  step "17z9. MCP success-semantic footnote + audit_trend file inputs (iter 46)"
1631
1683
  miss=""
1632
1684
  WRAPPER="$ROOT/../../v3/@claude-flow/cli/src/mcp-tools/metaharness-tools.ts"
1633
- # Success-semantic constant declared + appended to N descriptions = N+1 occurrences.
1634
- # Iter 46 set this at 9 (8 tools); iter 54 added the 9th → 10. ADR-153
1635
- # added 3 more tools (bench/evolve/security_bench) 1 + 12 = 13.
1636
- # @metaharness/redblue integration added 1 more → 1 + 13 = 14.
1637
- # learn + gepa (metaharness@0.3.0 / darwin@0.8.0) added 2 more → 1 + 15 = 16.
1685
+ # Success-semantic constant declared + appended to N tool descriptions =
1686
+ # N+1 occurrences. N is the derived tool count cross-aspect check within
1687
+ # the wrapper: footnote appends vs `name:` declarations.
1638
1688
  COUNT=$(grep -c "MCP_SUCCESS_SEMANTIC" "$WRAPPER" 2>/dev/null; true)
1639
- [[ "$COUNT" == "16" ]] || miss="$miss footnote-count:$COUNT-expected-16"
1689
+ [[ "$COUNT" == "$((EXPECTED_TOOLS + 1))" ]] || miss="$miss footnote-count:$COUNT-expected-$((EXPECTED_TOOLS + 1))"
1640
1690
  # audit_trend now exposes baselineFile / currentFile
1641
1691
  grep -q "baselineFile" "$WRAPPER" 2>/dev/null || miss="$miss no-baseline-file"
1642
1692
  grep -q "currentFile" "$WRAPPER" 2>/dev/null || miss="$miss no-current-file"
@@ -1674,10 +1724,9 @@ grep -q "success = exitCode === 0" "$WRAPPER" 2>/dev/null || miss="$miss no-exit
1674
1724
  COUNT_OLD=$(grep -c "success: !r.degraded" "$WRAPPER" 2>/dev/null; true)
1675
1725
  [[ "$COUNT_OLD" == "0" ]] || miss="$miss old-pattern-still-present:$COUNT_OLD"
1676
1726
  COUNT_NEW=$(grep -c "success: r.success" "$WRAPPER" 2>/dev/null; true)
1677
- # Iter 54 added a 9th tool → 9. ADR-153 added 3 more (bench, evolve,
1678
- # security_bench) 12. @metaharness/redblue integration added 1 more → 13.
1679
- # learn + gepa added 2 more → 15. Future iters that add tools should bump this.
1680
- [[ "$COUNT_NEW" == "15" ]] || miss="$miss new-pattern-count:$COUNT_NEW-expected-15"
1727
+ # One `success: r.success` per handler = the derived tool count. Cross-aspect
1728
+ # check within the wrapper: handler success-wiring vs `name:` declarations.
1729
+ [[ "$COUNT_NEW" == "$EXPECTED_TOOLS" ]] || miss="$miss new-pattern-count:$COUNT_NEW-expected-$EXPECTED_TOOLS"
1681
1730
  # Runtime anchors: iter 44 success assertions present
1682
1731
  T="$ROOT/scripts/test-mcp-tools.mjs"
1683
1732
  grep -q "iter 44 fix" "$T" 2>/dev/null || miss="$miss no-iter44-anchors"
@@ -2041,9 +2090,11 @@ grep -q "result has 'exitCode'" "$F" || miss="$miss no-exitcode-assertion"
2041
2090
  for tool in metaharness_score metaharness_genome metaharness_mcp_scan metaharness_threat_model metaharness_oia_audit metaharness_audit_list metaharness_audit_trend metaharness_similarity metaharness_drift_from_history metaharness_bench metaharness_evolve metaharness_security_bench metaharness_redblue metaharness_learn metaharness_gepa; do
2042
2091
  grep -q "${tool}" "$F" || miss="$miss missing-${tool}"
2043
2092
  done
2044
- # Count assertion must match iter-54 (8 9), then ADR-153 (9 12), then
2045
- # redblue (12 13), then learn/gepa (13 15)
2046
- grep -q "tools.length === 15" "$F" || miss="$miss tool-count-assertion-stale"
2093
+ # test-mcp-tools.mjs keeps its own hardcoded `tools.length === N` literal
2094
+ # it is the RUNTIME side of the cross-check. This grep derives N from the
2095
+ # wrapper source, so adding a tool without bumping the test literal fails
2096
+ # here (independent surfaces compared, per the derived-counts contract).
2097
+ grep -q "tools.length === $EXPECTED_TOOLS" "$F" || miss="$miss tool-count-assertion-stale:expected-tools.length===$EXPECTED_TOOLS"
2047
2098
  # Graceful skip when dist absent (so the script is smoke-runnable pre-build)
2048
2099
  grep -q "SKIPPED" "$F" || miss="$miss no-skip-doc"
2049
2100
  [[ -z "$miss" ]] && ok || bad "$miss"
@@ -31,11 +31,21 @@
31
31
  // 2 setup error
32
32
 
33
33
  import { spawnSync } from 'node:child_process';
34
+ import { mkdtempSync, rmSync } from 'node:fs';
35
+ import { tmpdir } from 'node:os';
34
36
  import { dirname, join } from 'node:path';
35
37
  import { fileURLToPath } from 'node:url';
36
38
 
37
39
  const SCRIPTS_DIR = dirname(fileURLToPath(import.meta.url));
38
40
 
41
+ // Since the versioned-cache consolidation (_invoke.mjs), a warm
42
+ // ~/.ruflo/<pkg>-cache-<pin> or a locally installed metaharness would satisfy
43
+ // resolution WITHOUT touching the (unreachable) registry — making this drill
44
+ // vacuous on developer machines. Point the cache base at an empty temp dir
45
+ // and disable local walk-up resolution so the install path (and therefore
46
+ // the degraded contract) is actually exercised.
47
+ const EMPTY_CACHE_BASE = mkdtempSync(join(tmpdir(), 'ruflo-degradation-drill-'));
48
+
39
49
  const ARGS = (() => {
40
50
  const a = { keep: false };
41
51
  for (let i = 2; i < process.argv.length; i++) {
@@ -61,6 +71,8 @@ function runWithUnreachableRegistry(scriptName, extraArgs) {
61
71
  ...process.env,
62
72
  npm_config_registry: UNREACHABLE_REGISTRY,
63
73
  NPM_CONFIG_REGISTRY: UNREACHABLE_REGISTRY,
74
+ RUFLO_METAHARNESS_CACHE_BASE: EMPTY_CACHE_BASE,
75
+ RUFLO_METAHARNESS_SKIP_LOCAL: '1',
64
76
  },
65
77
  stdio: ['ignore', 'pipe', 'pipe'],
66
78
  encoding: 'utf-8',
@@ -137,6 +149,10 @@ function main() {
137
149
  assert(/"degraded"\s*:\s*true/.test(r.stdout), `${s.name} emits "degraded": true`);
138
150
  }
139
151
 
152
+ if (!ARGS.keep) {
153
+ try { rmSync(EMPTY_CACHE_BASE, { recursive: true, force: true }); } catch { /* best-effort */ }
154
+ }
155
+
140
156
  console.log(`\n${passed} passed, ${failed} failed`);
141
157
  if (failed > 0) {
142
158
  console.log('\nFailures:');
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: harness-evolve
3
3
  description: Run `@metaharness/darwin evolve <repo>` to mutate a harness's seven policy surfaces (planner/contextBuilder/reviewer/retryPolicy/toolPolicy/memoryPolicy/scorePolicy), sandbox-score each variant, and promote only measured wins. The model is frozen; the harness evolves. Closes the loop ADR-150 opens (score+genome describe; evolve changes). Degrades gracefully when @metaharness/darwin is absent (ADR-150 + ADR-153 architectural constraints).
4
- argument-hint: "--repo <path> [--generations 3] [--children 3] [--concurrency 2] [--sandbox real|mock|agent] [--selection pareto|quality-diversity|...] [--mutator deterministic|ruvllm] [--confirm]"
4
+ argument-hint: "--repo <path> [--generations 3] [--children 3] [--concurrency 2] [--sandbox real|mock|agent] [--selection pareto|quality-diversity|...] [--mutator deterministic|ruvllm] [--diagnose] [--confirm]"
5
5
  allowed-tools: Bash
6
6
  ---
7
7
 
@@ -73,7 +73,46 @@ Reports land under `<repo>/.metaharness/`:
73
73
  reports/winner.json # final champion + score delta vs parent
74
74
  ```
75
75
 
76
- Skill stdout = JSON `{success, data: {champion, plan, durationMs, improved}}`.
76
+ Skill stdout = JSON `{success, data: {champion, plan, durationMs, improved}}`
77
+ (plus `data.diagnosis` when `--diagnose` is passed — see below).
78
+
79
+ ## Failure diagnosis (`--diagnose`)
80
+
81
+ GEPA's key trick is natural-language failure diagnosis from execution traces
82
+ feeding the next mutation — not just scalar fitness. `--diagnose` adds a
83
+ modest slice of that: after the evolution completes, the losing / failed
84
+ variants' transcripts are run through darwin's GEPA library ops
85
+ (`analyzeTranscript` + `classifyFailure`, via the shared `importGepa`
86
+ resolver in `scripts/_darwin.mjs`) and a `diagnosis` section is appended to
87
+ the emitted JSON:
88
+
89
+ ```json
90
+ "diagnosis": {
91
+ "available": true,
92
+ "scope": "losing-variants",
93
+ "variants": [
94
+ { "id": "g1_v0", "transcripts": 2,
95
+ "failureClasses": { "exploration-loop": 1, "edit-mechanics": 1 },
96
+ "dominantClass": "exploration-loop" }
97
+ ],
98
+ "totals": { "exploration-loop": 1, "edit-mechanics": 1 }
99
+ }
100
+ ```
101
+
102
+ Upstream shape caveats (verified against `@metaharness/darwin@0.8.0`):
103
+
104
+ - `metaharness-darwin evolve --json` prints a TEXT leaderboard — the stdout
105
+ carries no JSON and no transcripts. Per-variant run records live at
106
+ `<repo>/.metaharness/runs/<id>.json`.
107
+ - Those run records hold sandbox exec traces (`{taskId, exitCode, stdout,
108
+ stderr}`), which are NOT GEPA `{actionRaw, obs}` transcripts. Diagnosis
109
+ therefore uses GEPA-shaped transcripts when a run record embeds them
110
+ (agent sandbox / future upstream), falls back to the champion's transcript,
111
+ and otherwise emits `diagnosis: {available: false, reason, traceSummary}`
112
+ where `traceSummary` is a mechanical per-variant tally (tasks / failed /
113
+ timedOut / blockedActions).
114
+ - `--diagnose` NEVER fails the run — any internal error degrades to
115
+ `{available: false, reason: "diagnosis-failed: ..."}`.
77
116
 
78
117
  ## Exit codes
79
118
 
@@ -41,8 +41,10 @@ ADR-235 follow-up and not available yet.
41
41
  Implementation: [`scripts/learn.mjs`](../../scripts/learn.mjs).
42
42
 
43
43
  1. Validate `--repo` exists when given; export it as `$METAHARNESS_REPO`.
44
- 2. Shell to `npx -y metaharness@latest learn --host <h> --model <m> --slice <s> [--run]`
45
- via `_harness.mjs` (graceful degradation, hard timeout).
44
+ 2. Invoke the pinned `metaharness` binary (`metaharness@~0.3.0`, local install
45
+ or one-time versioned cache — never `@latest`): `metaharness learn --host <h>
46
+ --model <m> --slice <s> [--run]` via `_harness.mjs` (graceful degradation,
47
+ hard timeout).
46
48
  3. Default timeouts: 120s dry-run, 600s with `--run` — real runs on larger
47
49
  slices need an explicit `--timeout-ms` matched to slice size × model cost.
48
50
  4. Detect the checkout-required message → structured payload, exit 0.
@@ -13,7 +13,9 @@ tool; pure static analysis.
13
13
 
14
14
  Implementation: [`scripts/mcp-scan.mjs`](../../scripts/mcp-scan.mjs).
15
15
 
16
- 1. Shell `npx -p metaharness@latest harness mcp-scan <path> --json`.
16
+ 1. Invoke the pinned `harness` binary (`metaharness@~0.3.0`, resolved from a
17
+ local install or the one-time `~/.ruflo/metaharness-cache-<pin>` cache —
18
+ never `@latest`): `harness mcp-scan <path> --json`.
17
19
  2. Parse `findings[]` with `{ severity, id, server, tool, message }`.
18
20
  3. `--fail-on <severity>`: exit 1 when any finding is at or above that
19
21
  level. Default `high`.
@@ -58,7 +58,7 @@ no network, registry unreachable), the script emits:
58
58
  {
59
59
  "degraded": true,
60
60
  "reason": "metaharness-not-available",
61
- "hint": "Install metaharness manually with `npm i -D metaharness` or run `npx metaharness@latest --version` to verify network access."
61
+ "hint": "Install with `npm i -D metaharness@~0.3.0` (pinned range this plugin never fetches @latest) or verify network access for the one-time cache install."
62
62
  }
63
63
  ```
64
64
 
@@ -13,7 +13,9 @@ categorized report suitable for sharing with an InfoSec team.
13
13
 
14
14
  Implementation: [`scripts/threat-model.mjs`](../../scripts/threat-model.mjs).
15
15
 
16
- 1. Shell `npx -p metaharness@latest harness threat-model <path> --json`.
16
+ 1. Invoke the pinned `harness` binary (`metaharness@~0.3.0`, resolved from a
17
+ local install or the one-time `~/.ruflo/metaharness-cache-<pin>` cache —
18
+ never `@latest`): `harness threat-model <path> --json`.
17
19
  2. Parse `{ worst, findings[] }`.
18
20
  3. `--fail-on <severity>`: exit 1 when `worst >= fail-on`. Default `high`.
19
21