@claude-flow/cli 3.32.34 → 3.32.36
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/.claude/helpers/helpers.manifest.json +5 -5
- package/.claude/helpers/hook-handler.cjs +30 -0
- package/.claude/helpers/intelligence.cjs +47 -4
- package/.claude/helpers/statusline.cjs +169 -6
- package/bin/cli.js +25 -1
- package/catalog-manifest.json +4 -4
- package/dist/src/commands/agent.js +50 -2
- package/dist/src/commands/doctor.js +159 -11
- package/dist/src/commands/hooks.js +63 -4
- package/dist/src/commands/mcp.js +1 -1
- package/dist/src/commands/memory-distill.js +1 -0
- package/dist/src/commands/metaharness.js +4 -0
- package/dist/src/commands/swarm.js +89 -5
- package/dist/src/config-adapter.js +1 -0
- package/dist/src/index.js +3 -2
- package/dist/src/init/executor.js +16 -10
- package/dist/src/init/helpers-generator.js +15 -4
- package/dist/src/init/statusline-generator.js +18 -5
- package/dist/src/mcp-server.d.ts +25 -0
- package/dist/src/mcp-server.js +57 -2
- package/dist/src/mcp-tools/agent-tools.js +10 -0
- package/dist/src/mcp-tools/embeddings-tools.js +51 -10
- package/dist/src/mcp-tools/hooks-tools.js +120 -60
- package/dist/src/mcp-tools/memory-tools.js +14 -2
- package/dist/src/mcp-tools/metaharness-tools.js +7 -0
- package/dist/src/mcp-tools/swarm-tools.d.ts +16 -0
- package/dist/src/mcp-tools/swarm-tools.js +172 -7
- package/dist/src/memory/memory-bridge.d.ts +2 -0
- package/dist/src/memory/memory-bridge.js +5 -1
- package/dist/src/memory/memory-initializer.d.ts +9 -1
- package/dist/src/memory/memory-initializer.js +66 -19
- package/dist/src/services/daemon-autostart.js +7 -4
- package/dist/src/services/flywheel-receipt.d.ts +3 -0
- package/dist/src/services/flywheel-receipt.js +1 -0
- package/dist/src/services/harness-flywheel-runtime.d.ts +6 -0
- package/dist/src/services/harness-flywheel-runtime.js +19 -19
- package/dist/src/services/harness-flywheel.d.ts +2 -0
- package/dist/src/services/harness-flywheel.js +1 -0
- package/dist/src/services/harness-project-anchor.d.ts +23 -0
- package/dist/src/services/harness-project-anchor.js +129 -0
- package/dist/src/services/learned-routing.d.ts +34 -0
- package/dist/src/services/learned-routing.js +85 -0
- package/dist/src/services/memory-distillation.d.ts +1 -0
- package/dist/src/services/memory-distillation.js +81 -5
- package/dist/src/services/pheromone-adaptive.d.ts +71 -0
- package/dist/src/services/pheromone-adaptive.js +214 -0
- package/dist/src/services/worker-daemon.js +5 -1
- package/node_modules/@claude-flow/codex/.agents/skills/github-automation/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/.agents/skills/performance-analysis/SKILL.md +32 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js +20 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/cli.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +18 -2
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts +11 -5
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js +84 -849
- package/node_modules/@claude-flow/codex/dist/generators/skill-md.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/initializer.js +9 -13
- package/node_modules/@claude-flow/codex/dist/initializer.js.map +1 -1
- package/package.json +1 -1
- package/plugins/ruflo-metaharness/scripts/_harness.mjs +5 -1
- package/plugins/ruflo-metaharness/scripts/_invoke.mjs +12 -13
- package/plugins/ruflo-metaharness/scripts/mcp-scan.mjs +7 -8
- package/plugins/ruflo-metaharness/scripts/smoke.sh +58 -2
- package/plugins/ruflo-metaharness/scripts/test-similarity.mjs +38 -0
- package/plugins/ruflo-metaharness/scripts/threat-model.mjs +4 -1
|
@@ -120,8 +120,8 @@ async function checkConfigFile() {
|
|
|
120
120
|
}
|
|
121
121
|
// Check daemon status
|
|
122
122
|
/**
|
|
123
|
-
* #2448 — Detect
|
|
124
|
-
*
|
|
123
|
+
* #2448 / #2677 — Detect runaway `npx @claude-flow/cli@latest` commands
|
|
124
|
+
* left over in `.claude/settings.json` from pre-#2337 installs.
|
|
125
125
|
*
|
|
126
126
|
* These fire on every Claude Code event (statusLine refires every few hundred
|
|
127
127
|
* ms, hooks fire per tool-use), each spawning a cold Node process + npm
|
|
@@ -138,7 +138,9 @@ async function checkStaleSettingsNpx() {
|
|
|
138
138
|
// Same regex pattern the executor migration uses — kept in sync. Flag-list
|
|
139
139
|
// repetition bounded at 10 (CodeQL js/redos — unbounded `*` here is
|
|
140
140
|
// exponential-backtracking-prone on a crafted settings.json).
|
|
141
|
-
|
|
141
|
+
// Every subcommand incurs the same cold npm process/registry cost. The
|
|
142
|
+
// previous `hooks` literal missed `memory`, `daemon`, `swarm`, etc.
|
|
143
|
+
const BROKEN_RE = /npx\s+(?:--?\S+\s+){0,10}@?claude-flow\/cli@latest\s+\S+/;
|
|
142
144
|
// Look in both project-local and home-dir settings.
|
|
143
145
|
const candidates = [
|
|
144
146
|
join(process.cwd(), '.claude', 'settings.json'),
|
|
@@ -263,18 +265,21 @@ async function checkMemoryDatabase() {
|
|
|
263
265
|
// The existing `checkMemoryDatabase` above asserts existence + statability
|
|
264
266
|
// only, so it CANNOT distinguish a healthy DB from a 99.97%-empty or
|
|
265
267
|
// SQLite-malformed one. Stuinfla reported both cases live (81-store fleet).
|
|
266
|
-
// The
|
|
268
|
+
// The checks below layer functional assertions on top, ordered so
|
|
267
269
|
// the earliest chain-break is always the first red the user sees:
|
|
268
270
|
// 1. Integrity — can sql.js open it AND does PRAGMA integrity_check pass?
|
|
269
271
|
// 2. Content — do most memory_entries rows carry non-empty content?
|
|
270
272
|
// 3. Embedding coverage — do most rows have a vector? (unembedded rows are
|
|
271
273
|
// both unrecallable AND undistillable per ADR-174)
|
|
274
|
+
// 6. Reflexion coverage — can episodes participate in retrieveRelevant's
|
|
275
|
+
// required episode_embeddings INNER JOIN?
|
|
276
|
+
// Feedback critiques — do execution-tier episodes carry a real lesson?
|
|
272
277
|
// Ordering matters: content ratio is meaningless on a DB that can't open;
|
|
273
278
|
// embedding coverage is meaningless on rows with no content. First red wins.
|
|
274
279
|
//
|
|
275
|
-
// Recall probe (stuinfla check 4) requires
|
|
276
|
-
//
|
|
277
|
-
//
|
|
280
|
+
// Recall probe (stuinfla check 4) still requires an isolated write+search+
|
|
281
|
+
// delete round trip through the CLI's own memory pipeline. It remains separate
|
|
282
|
+
// because doctor otherwise stays read-only.
|
|
278
283
|
//
|
|
279
284
|
// Design rules (also from stuinfla's report):
|
|
280
285
|
// - "A check that cannot fail protects nothing" — every check has a
|
|
@@ -731,6 +736,102 @@ async function checkMemoryEmbeddingCoverage() {
|
|
|
731
736
|
catch { /* best-effort */ }
|
|
732
737
|
}
|
|
733
738
|
}
|
|
739
|
+
// #2677 check 6 — ReflexionMemory.retrieveRelevant() INNER JOINs episodes to
|
|
740
|
+
// episode_embeddings. A populated episodes table with an empty/missing partner
|
|
741
|
+
// table is therefore not degraded recall; it is structurally zero recall.
|
|
742
|
+
async function checkMemoryReflexionCoverage() {
|
|
743
|
+
const dbPath = await resolveMemoryDbPath();
|
|
744
|
+
if (!dbPath)
|
|
745
|
+
return { name: 'Memory Reflexion Coverage', status: 'warn', message: 'no memory.db found' };
|
|
746
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
747
|
+
if (!db)
|
|
748
|
+
return { name: 'Memory Reflexion Coverage', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
|
|
749
|
+
try {
|
|
750
|
+
const tables = new Set(db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name IN ('episodes','episode_embeddings')")[0]?.values?.map((v) => String(v[0])) ?? []);
|
|
751
|
+
if (!tables.has('episodes')) {
|
|
752
|
+
return { name: 'Memory Reflexion Coverage', status: 'warn', message: 'episodes table absent — AgentDB Reflexion schema not initialized' };
|
|
753
|
+
}
|
|
754
|
+
const episodes = Number(db.exec('SELECT count(*) FROM episodes')[0]?.values?.[0]?.[0] ?? 0);
|
|
755
|
+
if (episodes === 0) {
|
|
756
|
+
return { name: 'Memory Reflexion Coverage', status: 'warn', message: `${dbPath} — 0 episodes (Reflexion has not been exercised)` };
|
|
757
|
+
}
|
|
758
|
+
if (!tables.has('episode_embeddings')) {
|
|
759
|
+
return {
|
|
760
|
+
name: 'Memory Reflexion Coverage',
|
|
761
|
+
status: 'fail',
|
|
762
|
+
message: `${dbPath} — episode embeddings 0/${episodes} (0.00%); retrieveRelevant() cannot return rows`,
|
|
763
|
+
fix: 'run `claude-flow memory distill run`; current distillation creates/backfills episode_embeddings',
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
const embedded = Number(db.exec('SELECT count(*) FROM episodes e JOIN episode_embeddings ee ON ee.episode_id=e.id')[0]?.values?.[0]?.[0] ?? 0);
|
|
767
|
+
const ratio = embedded / episodes;
|
|
768
|
+
const detail = `episode embeddings ${embedded}/${episodes} (${(ratio * 100).toFixed(2)}%)`;
|
|
769
|
+
if (ratio < 0.95) {
|
|
770
|
+
return {
|
|
771
|
+
name: 'Memory Reflexion Coverage',
|
|
772
|
+
status: 'fail',
|
|
773
|
+
message: `${dbPath} — ${detail} below 95% floor; retrieveRelevant() cannot see uncovered episodes`,
|
|
774
|
+
fix: 'run `claude-flow memory distill run` to populate retrievable episodes',
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
return { name: 'Memory Reflexion Coverage', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
778
|
+
}
|
|
779
|
+
catch (e) {
|
|
780
|
+
return { name: 'Memory Reflexion Coverage', status: 'warn', message: `${dbPath} — probe threw: ${e.message || String(e)}` };
|
|
781
|
+
}
|
|
782
|
+
finally {
|
|
783
|
+
try {
|
|
784
|
+
db.close();
|
|
785
|
+
}
|
|
786
|
+
catch { /* best-effort */ }
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// Execution-tier feedback should carry a lesson, not just reward/success bits.
|
|
790
|
+
// Proxy episodes intentionally remain critique-free to avoid laundering a
|
|
791
|
+
// structural summary into an observed failure explanation.
|
|
792
|
+
async function checkMemoryCritiqueCoverage() {
|
|
793
|
+
const dbPath = await resolveMemoryDbPath();
|
|
794
|
+
if (!dbPath)
|
|
795
|
+
return { name: 'Memory Feedback Critiques', status: 'warn', message: 'no memory.db found' };
|
|
796
|
+
const db = await tryOpenSqlJs(dbPath);
|
|
797
|
+
if (!db)
|
|
798
|
+
return { name: 'Memory Feedback Critiques', status: 'warn', message: 'DB unreadable (see Memory Integrity)' };
|
|
799
|
+
try {
|
|
800
|
+
const hasEpisodes = (db.exec("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='episodes'")[0]?.values?.[0]?.[0] ?? 0) > 0;
|
|
801
|
+
if (!hasEpisodes)
|
|
802
|
+
return { name: 'Memory Feedback Critiques', status: 'warn', message: 'episodes table absent' };
|
|
803
|
+
const row = db.exec(`
|
|
804
|
+
SELECT count(*),
|
|
805
|
+
sum(CASE WHEN length(trim(coalesce(critique,''))) > 0 THEN 1 ELSE 0 END)
|
|
806
|
+
FROM episodes WHERE session_id LIKE 'distill:feedback%'
|
|
807
|
+
`)[0]?.values?.[0] ?? [0, 0];
|
|
808
|
+
const feedback = Number(row[0] ?? 0);
|
|
809
|
+
const withCritique = Number(row[1] ?? 0);
|
|
810
|
+
if (feedback === 0) {
|
|
811
|
+
return { name: 'Memory Feedback Critiques', status: 'warn', message: `${dbPath} — 0 execution-feedback episodes to assess` };
|
|
812
|
+
}
|
|
813
|
+
const ratio = withCritique / feedback;
|
|
814
|
+
const detail = `feedback critiques ${withCritique}/${feedback} (${(ratio * 100).toFixed(2)}%)`;
|
|
815
|
+
if (ratio < 0.95) {
|
|
816
|
+
return {
|
|
817
|
+
name: 'Memory Feedback Critiques',
|
|
818
|
+
status: 'fail',
|
|
819
|
+
message: `${dbPath} — ${detail} below 95% floor`,
|
|
820
|
+
fix: 'run current `claude-flow memory distill run`; execution-tier episodes preserve observed critique/error text',
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
return { name: 'Memory Feedback Critiques', status: 'pass', message: `${dbPath} — ${detail}` };
|
|
824
|
+
}
|
|
825
|
+
catch (e) {
|
|
826
|
+
return { name: 'Memory Feedback Critiques', status: 'warn', message: `${dbPath} — probe threw: ${e.message || String(e)}` };
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
try {
|
|
830
|
+
db.close();
|
|
831
|
+
}
|
|
832
|
+
catch { /* best-effort */ }
|
|
833
|
+
}
|
|
834
|
+
}
|
|
734
835
|
// #2545: Check that the self-learning bridge can actually load @claude-flow/memory
|
|
735
836
|
// the SAME way the SessionStart auto-memory hook does. On the documented `npx ruflo`
|
|
736
837
|
// path the package lands in the npx cache — unreachable from the project — so the
|
|
@@ -1027,6 +1128,48 @@ async function checkMcpServers() {
|
|
|
1027
1128
|
fix: 'claude mcp add ruflo -- npx -y ruflo@latest mcp start',
|
|
1028
1129
|
};
|
|
1029
1130
|
}
|
|
1131
|
+
// #2726 — tools/list is fixed prompt overhead on clients/backends that do not
|
|
1132
|
+
// defer MCP schemas. Measure the actual live registry and warn before a small
|
|
1133
|
+
// context window becomes unrecoverable even after compaction.
|
|
1134
|
+
async function checkMcpSchemaOverhead() {
|
|
1135
|
+
try {
|
|
1136
|
+
const [{ listMCPTools }, { assessMcpSchemaOverhead, filterAdvertisedMcpTools, parseMcpToolSelection, }] = await Promise.all([
|
|
1137
|
+
import('../mcp-client.js'),
|
|
1138
|
+
import('../mcp-server.js'),
|
|
1139
|
+
]);
|
|
1140
|
+
// The `mcp start --tools` CLI flag takes precedence when the server is
|
|
1141
|
+
// constructed. Doctor intentionally inspects the environment fallback so
|
|
1142
|
+
// its estimate describes the configuration inherited by MCP clients.
|
|
1143
|
+
const selection = parseMcpToolSelection(process.env.CLAUDE_FLOW_MCP_TOOLS);
|
|
1144
|
+
const tools = filterAdvertisedMcpTools(listMCPTools(), selection);
|
|
1145
|
+
// This is diagnostic metadata about the external client's context window,
|
|
1146
|
+
// not Ruflo command configuration, so there is no corresponding CLI flag.
|
|
1147
|
+
const rawWindow = process.env.CLAUDE_FLOW_CONTEXT_WINDOW_TOKENS;
|
|
1148
|
+
const contextWindow = rawWindow ? Number.parseInt(rawWindow, 10) : undefined;
|
|
1149
|
+
const assessment = assessMcpSchemaOverhead(tools, contextWindow);
|
|
1150
|
+
const ratio = assessment.ratio === undefined
|
|
1151
|
+
? ''
|
|
1152
|
+
: `, ${(assessment.ratio * 100).toFixed(1)}% of ${assessment.contextWindowTokens}-token window`;
|
|
1153
|
+
const selectionLabel = selection === 'all' ? 'all tools' : `filtered: ${selection.join(',')}`;
|
|
1154
|
+
const message = `${assessment.toolCount} advertised tools ≈ ${assessment.estimatedTokens} schema tokens (${selectionLabel}${ratio})`;
|
|
1155
|
+
if (assessment.risk === 'high') {
|
|
1156
|
+
return {
|
|
1157
|
+
name: 'MCP Schema Overhead',
|
|
1158
|
+
status: 'warn',
|
|
1159
|
+
message,
|
|
1160
|
+
fix: 'Set CLAUDE_FLOW_MCP_TOOLS to required categories/tool names (for example: memory,swarm,agent,hooks) and optionally CLAUDE_FLOW_CONTEXT_WINDOW_TOKENS to your backend limit.',
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
return { name: 'MCP Schema Overhead', status: 'pass', message };
|
|
1164
|
+
}
|
|
1165
|
+
catch (error) {
|
|
1166
|
+
return {
|
|
1167
|
+
name: 'MCP Schema Overhead',
|
|
1168
|
+
status: 'warn',
|
|
1169
|
+
message: `Unable to measure: ${error instanceof Error ? error.message : String(error)}`,
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1030
1173
|
// Check disk space (async with proper env inheritance)
|
|
1031
1174
|
async function checkDiskSpace() {
|
|
1032
1175
|
try {
|
|
@@ -1843,7 +1986,7 @@ export const doctorCommand = {
|
|
|
1843
1986
|
{
|
|
1844
1987
|
name: 'component',
|
|
1845
1988
|
short: 'c',
|
|
1846
|
-
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, metaharness)',
|
|
1989
|
+
description: 'Check specific component (version, node, npm, config, daemon, memory, api, git, mcp, mcp-overhead, claude, disk, typescript, agentic-flow, encryption, federation, funnel, proxy, auth, metaharness)',
|
|
1847
1990
|
type: 'string'
|
|
1848
1991
|
},
|
|
1849
1992
|
{
|
|
@@ -1958,13 +2101,14 @@ export const doctorCommand = {
|
|
|
1958
2101
|
checkGit,
|
|
1959
2102
|
checkGitRepo,
|
|
1960
2103
|
checkConfigFile,
|
|
1961
|
-
checkStaleSettingsNpx, // #2448 — runaway `npx @latest` in
|
|
2104
|
+
checkStaleSettingsNpx, // #2448/#2677 — runaway `npx @latest` in settings
|
|
1962
2105
|
checkDaemonStatus,
|
|
1963
2106
|
checkMemoryDatabase,
|
|
1964
2107
|
checkMemoryStructuralIntegrity, // #2737 — bounded, native quick_check on every default run
|
|
1965
2108
|
checkLearningBridge, // #2545 — can the auto-memory hook actually load @claude-flow/memory?
|
|
1966
2109
|
checkApiKeys,
|
|
1967
2110
|
checkMcpServers,
|
|
2111
|
+
checkMcpSchemaOverhead, // #2726 — fixed tools/list prompt cost
|
|
1968
2112
|
checkAIDefence, // #1807
|
|
1969
2113
|
checkDiskSpace,
|
|
1970
2114
|
checkBuildTools,
|
|
@@ -1982,8 +2126,9 @@ export const doctorCommand = {
|
|
|
1982
2126
|
// array — expanded at execution time. Stuinfla's report showed the
|
|
1983
2127
|
// existence-only check reporting PASS on a 99.97%-empty and even a
|
|
1984
2128
|
// SQLite-malformed DB; the array here layers integrity → content →
|
|
1985
|
-
// embedding coverage over the existing existence probe
|
|
1986
|
-
//
|
|
2129
|
+
// embedding coverage over the existing existence probe; check 6 then
|
|
2130
|
+
// verifies that distilled episodes are actually Reflexion-retrievable and
|
|
2131
|
+
// execution feedback carries a lesson.
|
|
1987
2132
|
const componentMap = {
|
|
1988
2133
|
'version': checkVersionFreshness,
|
|
1989
2134
|
'freshness': checkVersionFreshness,
|
|
@@ -1998,12 +2143,15 @@ export const doctorCommand = {
|
|
|
1998
2143
|
checkMemoryIntegrity, // #2677 check 1: sql.js open + PRAGMA integrity_check
|
|
1999
2144
|
checkMemoryContent, // #2677 check 2: memory_entries content coverage
|
|
2000
2145
|
checkMemoryEmbeddingCoverage, // #2677 check 3: vector coverage on populated rows
|
|
2146
|
+
checkMemoryReflexionCoverage, // #2677 check 6: episodes are retrievable
|
|
2147
|
+
checkMemoryCritiqueCoverage, // #2677 check 6: feedback carries lessons
|
|
2001
2148
|
],
|
|
2002
2149
|
'learning': checkLearningBridge, // #2545
|
|
2003
2150
|
'learning-bridge': checkLearningBridge, // #2545
|
|
2004
2151
|
'api': checkApiKeys,
|
|
2005
2152
|
'git': checkGit,
|
|
2006
2153
|
'mcp': checkMcpServers,
|
|
2154
|
+
'mcp-overhead': checkMcpSchemaOverhead,
|
|
2007
2155
|
'aidefence': checkAIDefence, // #1807
|
|
2008
2156
|
'disk': checkDiskSpace,
|
|
2009
2157
|
'typescript': checkBuildTools,
|
|
@@ -1218,8 +1218,20 @@ const metricsCommand = {
|
|
|
1218
1218
|
const totalPatterns = safeNum(rawMetrics.patterns?.total ?? rawMetrics.summary?.patternsLearned);
|
|
1219
1219
|
const successfulPatterns = safeNum(rawMetrics.patterns?.successful ?? Math.round(safeNum(rawMetrics.summary?.successRate) * totalPatterns));
|
|
1220
1220
|
const failedPatterns = Math.max(0, safeNum(rawMetrics.patterns?.failed ?? totalPatterns - successfulPatterns));
|
|
1221
|
+
const unknownPatterns = Math.max(0, safeNum(rawMetrics.patterns?.unknown));
|
|
1222
|
+
const describedPatterns = Math.max(0, safeNum(rawMetrics.patterns?.described));
|
|
1223
|
+
const descriptionCoverage = safeNum(rawMetrics.patterns?.descriptionCoverage ??
|
|
1224
|
+
(totalPatterns > 0 ? describedPatterns / totalPatterns : 0));
|
|
1221
1225
|
const avgConfidence = safeNum(rawMetrics.patterns?.avgConfidence ?? rawMetrics.summary?.avgQuality);
|
|
1222
|
-
const routingAccuracy =
|
|
1226
|
+
const routingAccuracy = rawMetrics.agents?.routingAccuracy == null
|
|
1227
|
+
? null
|
|
1228
|
+
: safeNum(rawMetrics.agents.routingAccuracy);
|
|
1229
|
+
const averageRoutingConfidence = safeNum(rawMetrics.agents?.averageConfidence ??
|
|
1230
|
+
rawMetrics.routing?.avgConfidence ??
|
|
1231
|
+
rawMetrics.patterns?.avgConfidence);
|
|
1232
|
+
const routingOutcomeSuccessRate = rawMetrics.agents?.outcomeSuccessRate == null
|
|
1233
|
+
? null
|
|
1234
|
+
: safeNum(rawMetrics.agents.outcomeSuccessRate);
|
|
1223
1235
|
const totalRoutes = safeNum(rawMetrics.agents?.totalRoutes ?? rawMetrics.routing?.totalRoutes);
|
|
1224
1236
|
const topAgent = rawMetrics.agents?.topAgent ?? rawMetrics.routing?.topAgents?.[0]?.agent ?? 'n/a';
|
|
1225
1237
|
const totalCommands = safeNum(rawMetrics.commands?.totalExecuted ?? rawMetrics.commands?.totalCommands);
|
|
@@ -1227,8 +1239,22 @@ const metricsCommand = {
|
|
|
1227
1239
|
const avgRiskScore = safeNum(rawMetrics.commands?.avgRiskScore ?? rawMetrics.commands?.avgExecutionTime);
|
|
1228
1240
|
const result = {
|
|
1229
1241
|
...rawMetrics,
|
|
1230
|
-
patterns: {
|
|
1231
|
-
|
|
1242
|
+
patterns: {
|
|
1243
|
+
total: totalPatterns,
|
|
1244
|
+
successful: successfulPatterns,
|
|
1245
|
+
failed: failedPatterns,
|
|
1246
|
+
unknown: unknownPatterns,
|
|
1247
|
+
described: describedPatterns,
|
|
1248
|
+
descriptionCoverage,
|
|
1249
|
+
avgConfidence,
|
|
1250
|
+
},
|
|
1251
|
+
agents: {
|
|
1252
|
+
routingAccuracy,
|
|
1253
|
+
averageConfidence: averageRoutingConfidence,
|
|
1254
|
+
outcomeSuccessRate: routingOutcomeSuccessRate,
|
|
1255
|
+
totalRoutes,
|
|
1256
|
+
topAgent,
|
|
1257
|
+
},
|
|
1232
1258
|
commands: { totalExecuted: totalCommands, successRate: commandSuccessRate, avgRiskScore },
|
|
1233
1259
|
};
|
|
1234
1260
|
if (ctx.flags.format === 'json') {
|
|
@@ -1246,6 +1272,8 @@ const metricsCommand = {
|
|
|
1246
1272
|
{ metric: 'Total Patterns', value: totalPatterns },
|
|
1247
1273
|
{ metric: 'Successful', value: output.success(String(successfulPatterns)) },
|
|
1248
1274
|
{ metric: 'Failed', value: output.error(String(failedPatterns)) },
|
|
1275
|
+
...(unknownPatterns > 0 ? [{ metric: 'Unclassified', value: String(unknownPatterns) }] : []),
|
|
1276
|
+
{ metric: 'With Task Context', value: `${(descriptionCoverage * 100).toFixed(1)}%` },
|
|
1249
1277
|
{ metric: 'Avg Confidence', value: `${(avgConfidence * 100).toFixed(1)}%` }
|
|
1250
1278
|
]
|
|
1251
1279
|
});
|
|
@@ -1258,7 +1286,14 @@ const metricsCommand = {
|
|
|
1258
1286
|
{ key: 'value', header: 'Value', width: 20, align: 'right' }
|
|
1259
1287
|
],
|
|
1260
1288
|
data: [
|
|
1261
|
-
{
|
|
1289
|
+
{
|
|
1290
|
+
metric: routingAccuracy === null ? 'Avg Confidence' : 'Routing Accuracy',
|
|
1291
|
+
value: `${((routingAccuracy ?? averageRoutingConfidence) * 100).toFixed(1)}%`,
|
|
1292
|
+
},
|
|
1293
|
+
...(routingOutcomeSuccessRate === null ? [] : [{
|
|
1294
|
+
metric: 'Outcome Success Rate',
|
|
1295
|
+
value: `${(routingOutcomeSuccessRate * 100).toFixed(1)}%`,
|
|
1296
|
+
}]),
|
|
1262
1297
|
{ metric: 'Total Routes', value: totalRoutes },
|
|
1263
1298
|
{ metric: 'Top Agent', value: output.highlight(topAgent) }
|
|
1264
1299
|
]
|
|
@@ -1672,6 +1707,26 @@ const postTaskCommand = {
|
|
|
1672
1707
|
description: 'Agent that executed the task',
|
|
1673
1708
|
type: 'string'
|
|
1674
1709
|
},
|
|
1710
|
+
{
|
|
1711
|
+
name: 'agent-role',
|
|
1712
|
+
description: 'Stable agent role used for role-aware pheromone comparison (for example coder, tester, coordinator)',
|
|
1713
|
+
type: 'string'
|
|
1714
|
+
},
|
|
1715
|
+
{
|
|
1716
|
+
name: 'duration',
|
|
1717
|
+
description: 'Observed task duration in milliseconds for latency fitness',
|
|
1718
|
+
type: 'number'
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
name: 'latency-budget-ms',
|
|
1722
|
+
description: 'Expected task duration in milliseconds; paired with --duration to normalize latency fitness',
|
|
1723
|
+
type: 'number'
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
name: 'consensus-alignment',
|
|
1727
|
+
description: 'Consensus alignment score from 0 to 1',
|
|
1728
|
+
type: 'number'
|
|
1729
|
+
},
|
|
1675
1730
|
{
|
|
1676
1731
|
name: 'task',
|
|
1677
1732
|
short: 't',
|
|
@@ -1716,6 +1771,10 @@ const postTaskCommand = {
|
|
|
1716
1771
|
success,
|
|
1717
1772
|
quality: ctx.flags.quality,
|
|
1718
1773
|
agent: ctx.flags.agent,
|
|
1774
|
+
agentRole: ctx.flags.agentRole,
|
|
1775
|
+
duration: ctx.flags.duration,
|
|
1776
|
+
latencyBudgetMs: ctx.flags.latencyBudgetMs,
|
|
1777
|
+
consensusAlignment: ctx.flags.consensusAlignment,
|
|
1719
1778
|
// #2785: forward the task description so routing outcomes actually persist
|
|
1720
1779
|
// (hooks_post-task requires taskText + agent to write the outcome row that
|
|
1721
1780
|
// hooks_metrics reads via getIntelligenceStatsFromMemory)
|
package/dist/src/commands/mcp.js
CHANGED
|
@@ -182,6 +182,7 @@ const runCommand = {
|
|
|
182
182
|
data: [
|
|
183
183
|
{ metric: 'Processed', value: String(report.processed) },
|
|
184
184
|
{ metric: 'Episodes', value: String(report.episodes) },
|
|
185
|
+
{ metric: 'Episode Embeddings', value: String(report.episodeEmbeddings) },
|
|
185
186
|
{ metric: 'Patterns', value: String(report.patterns) },
|
|
186
187
|
{ metric: 'Pattern Embeddings', value: String(report.patternEmbeddings) },
|
|
187
188
|
{ metric: 'Causal Edges', value: String(report.causalEdges) },
|
|
@@ -102,12 +102,16 @@ async function dispatchFlywheel(operation, positional, flags) {
|
|
|
102
102
|
receiptPublicKeyPem: publicKeyPath ? readFileSync(resolve(publicKeyPath), 'utf8') : undefined,
|
|
103
103
|
maxConcurrency: Number(flywheelFlag(flags, 'maxConcurrency', 2)),
|
|
104
104
|
evaluationTimeoutMs: Number(flywheelFlag(flags, 'timeoutMs', 120_000)),
|
|
105
|
+
anchorPath: flywheelFlag(flags, 'anchorPath'),
|
|
106
|
+
anchorHash: flywheelFlag(flags, 'anchorHash'),
|
|
107
|
+
anchorManifestPath: flywheelFlag(flags, 'anchorManifest'),
|
|
105
108
|
});
|
|
106
109
|
}
|
|
107
110
|
}
|
|
108
111
|
else if (operation === 'receipts') {
|
|
109
112
|
data = listFlywheelReceipts(projectRoot).map(({ receipt, state }) => ({
|
|
110
113
|
receiptId: receipt.payload.receiptId,
|
|
114
|
+
anchorRef: receipt.payload.anchorRef,
|
|
111
115
|
candidateId: receipt.payload.candidateId,
|
|
112
116
|
baselineRef: receipt.payload.baselineRef,
|
|
113
117
|
decision: receipt.payload.decision,
|
|
@@ -50,6 +50,34 @@ function getSwarmStatus(swarmId) {
|
|
|
50
50
|
// Ignore
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
// The canonical agent registry is the same source used by `agent list`.
|
|
54
|
+
// Prefer it over the swarm-level coordination boolean so idle agents are
|
|
55
|
+
// not reported as active (#2808). Hive agents are merged additively.
|
|
56
|
+
if (totalAgents === 0) {
|
|
57
|
+
try {
|
|
58
|
+
const canonicalPath = path.join(process.cwd(), '.claude-flow', 'agents', 'store.json');
|
|
59
|
+
const hivePath = path.join(process.cwd(), '.claude-flow', 'agents.json');
|
|
60
|
+
const merged = {};
|
|
61
|
+
for (const storePath of [hivePath, canonicalPath]) {
|
|
62
|
+
if (!fs.existsSync(storePath))
|
|
63
|
+
continue;
|
|
64
|
+
const parsed = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
|
|
65
|
+
if (parsed?.agents && typeof parsed.agents === 'object') {
|
|
66
|
+
Object.assign(merged, parsed.agents);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const agents = Object.values(merged).filter(agent => agent.status !== 'terminated');
|
|
70
|
+
if (agents.length > 0) {
|
|
71
|
+
totalAgents = agents.length;
|
|
72
|
+
activeAgents = agents.filter(agent => agent.status === 'active' ||
|
|
73
|
+
agent.status === 'running' ||
|
|
74
|
+
agent.status === 'busy').length;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Ignore — the count-only activity file remains the final fallback.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
53
81
|
// #2799 — `agent spawn` never writes `.swarm/agents/*.json`; it records
|
|
54
82
|
// the count in `.claude-flow/metrics/swarm-activity.json` (via
|
|
55
83
|
// updateSwarmActivityMetrics in commands/agent.ts). So when the agents
|
|
@@ -271,7 +299,8 @@ const TOPOLOGIES = [
|
|
|
271
299
|
{ value: 'ring', label: 'Ring', hint: 'Circular communication pattern' },
|
|
272
300
|
{ value: 'star', label: 'Star', hint: 'Central coordinator with spoke agents' },
|
|
273
301
|
{ value: 'hybrid', label: 'Hybrid', hint: 'Hierarchical mesh for maximum flexibility' },
|
|
274
|
-
{ value: 'hierarchical-mesh', label: 'Hierarchical Mesh', hint: 'V3 15-agent queen + peer communication (recommended)' }
|
|
302
|
+
{ value: 'hierarchical-mesh', label: 'Hierarchical Mesh', hint: 'V3 15-agent queen + peer communication (recommended)' },
|
|
303
|
+
{ value: 'pheromone-adaptive', label: 'Pheromone Adaptive', hint: 'Role-aware dynamic eligibility with quorum safety (ADR-330)' }
|
|
275
304
|
];
|
|
276
305
|
// Swarm strategies
|
|
277
306
|
const STRATEGIES = [
|
|
@@ -324,6 +353,19 @@ const initCommand = {
|
|
|
324
353
|
type: 'boolean',
|
|
325
354
|
default: false
|
|
326
355
|
},
|
|
356
|
+
{
|
|
357
|
+
name: 'apsc-live',
|
|
358
|
+
description: 'Apply APSC suspension decisions (default is calibration-only dry run)',
|
|
359
|
+
type: 'boolean',
|
|
360
|
+
default: false
|
|
361
|
+
},
|
|
362
|
+
{ name: 'apsc-alpha', description: 'Task-success score weight', type: 'number', default: 0.5 },
|
|
363
|
+
{ name: 'apsc-beta', description: 'Latency score weight', type: 'number', default: 0.2 },
|
|
364
|
+
{ name: 'apsc-gamma', description: 'Consensus-alignment score weight', type: 'number', default: 0.3 },
|
|
365
|
+
{ name: 'apsc-pruning-factor', description: 'Fraction of adaptive threshold below which agents are eligible for suspension', type: 'number', default: 0.6 },
|
|
366
|
+
{ name: 'apsc-reactivation-threshold', description: 'Fraction of adaptive threshold required for recovery', type: 'number', default: 0.75 },
|
|
367
|
+
{ name: 'apsc-min-active-agents', description: 'Hard quorum floor', type: 'number', default: 3 },
|
|
368
|
+
{ name: 'apsc-min-samples', description: 'Warm-up observations before pruning', type: 'number', default: 3 },
|
|
327
369
|
{
|
|
328
370
|
// #2768 — dream-cycle SubagentPermissionDelegate. Ships a per-role
|
|
329
371
|
// capability manifest to `.swarm/permissions.jsonl` + an append-only
|
|
@@ -366,6 +408,18 @@ const initCommand = {
|
|
|
366
408
|
failureHandling: 'retry',
|
|
367
409
|
loadBalancing: true,
|
|
368
410
|
autoScaling: ctx.flags.autoScale ?? true,
|
|
411
|
+
...(topology === 'pheromone-adaptive' ? {
|
|
412
|
+
apsc: {
|
|
413
|
+
alpha: Number(ctx.flags.apscAlpha ?? 0.5),
|
|
414
|
+
beta: Number(ctx.flags.apscBeta ?? 0.2),
|
|
415
|
+
gamma: Number(ctx.flags.apscGamma ?? 0.3),
|
|
416
|
+
pruningFactor: Number(ctx.flags.apscPruningFactor ?? 0.6),
|
|
417
|
+
reactivationThreshold: Number(ctx.flags.apscReactivationThreshold ?? 0.75),
|
|
418
|
+
minActiveAgents: Number(ctx.flags.apscMinActiveAgents ?? 3),
|
|
419
|
+
minSamples: Number(ctx.flags.apscMinSamples ?? 3),
|
|
420
|
+
dryRun: ctx.flags.apscLive !== true,
|
|
421
|
+
},
|
|
422
|
+
} : {}),
|
|
369
423
|
},
|
|
370
424
|
metadata: {
|
|
371
425
|
v3Mode,
|
|
@@ -393,7 +447,10 @@ const initCommand = {
|
|
|
393
447
|
{ property: 'Max Agents', value: result.config.maxAgents },
|
|
394
448
|
{ property: 'Auto Scale', value: result.config.autoScaling ? 'Enabled' : 'Disabled' },
|
|
395
449
|
{ property: 'Protocol', value: result.config.communicationProtocol || 'N/A' },
|
|
396
|
-
{ property: 'V3 Mode', value: v3Mode ? 'Enabled' : 'Disabled' }
|
|
450
|
+
{ property: 'V3 Mode', value: v3Mode ? 'Enabled' : 'Disabled' },
|
|
451
|
+
...(topology === 'pheromone-adaptive'
|
|
452
|
+
? [{ property: 'APSC Mode', value: ctx.flags.apscLive === true ? 'Live' : 'Dry run' }]
|
|
453
|
+
: [])
|
|
397
454
|
]
|
|
398
455
|
});
|
|
399
456
|
output.writeln();
|
|
@@ -924,15 +981,41 @@ const compressMessageCommand = {
|
|
|
924
981
|
return { success: true, data: result };
|
|
925
982
|
},
|
|
926
983
|
};
|
|
984
|
+
const pheromoneCommand = {
|
|
985
|
+
name: 'pheromone',
|
|
986
|
+
description: 'Inspect or update ADR-330 pheromone-adaptive scheduling state',
|
|
987
|
+
options: [
|
|
988
|
+
{ name: 'agent-id', description: 'Agent identifier; omit to show status', type: 'string' },
|
|
989
|
+
{ name: 'role', description: 'Agent role for role-local normalization', type: 'string' },
|
|
990
|
+
{ name: 'task-success', description: 'Task success in [0,1]', type: 'number' },
|
|
991
|
+
{ name: 'normalized-latency', description: 'Latency divided by budget in [0,1]', type: 'number' },
|
|
992
|
+
{ name: 'consensus-alignment', description: 'Consensus alignment in [0,1]', type: 'number' },
|
|
993
|
+
],
|
|
994
|
+
action: async (ctx) => {
|
|
995
|
+
const agentId = ctx.flags.agentId;
|
|
996
|
+
const data = agentId
|
|
997
|
+
? await callMCPTool('swarm_pheromone_update', {
|
|
998
|
+
agentId,
|
|
999
|
+
role: String(ctx.flags.role ?? 'worker'),
|
|
1000
|
+
taskSuccess: Number(ctx.flags.taskSuccess ?? 1),
|
|
1001
|
+
normalizedLatency: Number(ctx.flags.normalizedLatency ?? 0),
|
|
1002
|
+
consensusAlignment: Number(ctx.flags.consensusAlignment ?? 1),
|
|
1003
|
+
})
|
|
1004
|
+
: await callMCPTool('swarm_pheromone_status', {});
|
|
1005
|
+
output.writeln(JSON.stringify(data, null, 2));
|
|
1006
|
+
return { success: data.active !== false, data };
|
|
1007
|
+
},
|
|
1008
|
+
};
|
|
927
1009
|
export const swarmCommand = {
|
|
928
1010
|
name: 'swarm',
|
|
929
1011
|
description: 'Swarm coordination commands',
|
|
930
|
-
subcommands: [initCommand, startCommand, statusCommand, stopCommand, scaleCommand, coordinateCommand, compressMessageCommand],
|
|
1012
|
+
subcommands: [initCommand, startCommand, statusCommand, stopCommand, scaleCommand, coordinateCommand, compressMessageCommand, pheromoneCommand],
|
|
931
1013
|
options: [],
|
|
932
1014
|
examples: [
|
|
933
1015
|
{ command: 'claude-flow swarm init --v3-mode', description: 'Initialize V3 swarm' },
|
|
934
1016
|
{ command: 'claude-flow swarm start -o "Build API" -s development', description: 'Start development swarm' },
|
|
935
|
-
{ command: 'claude-flow swarm coordinate --agents 15', description: 'V3 coordination' }
|
|
1017
|
+
{ command: 'claude-flow swarm coordinate --agents 15', description: 'V3 coordination' },
|
|
1018
|
+
{ command: 'claude-flow swarm pheromone', description: 'Inspect pheromone-adaptive scheduling state' }
|
|
936
1019
|
],
|
|
937
1020
|
action: async (ctx) => {
|
|
938
1021
|
output.writeln();
|
|
@@ -947,7 +1030,8 @@ export const swarmCommand = {
|
|
|
947
1030
|
`${output.highlight('status')} - Show swarm status`,
|
|
948
1031
|
`${output.highlight('stop')} - Stop swarm execution`,
|
|
949
1032
|
`${output.highlight('scale')} - Scale swarm agent count`,
|
|
950
|
-
`${output.highlight('coordinate')} - V3 15-agent coordination
|
|
1033
|
+
`${output.highlight('coordinate')} - V3 15-agent coordination`,
|
|
1034
|
+
`${output.highlight('pheromone')} - Inspect/update adaptive pheromone state`
|
|
951
1035
|
]);
|
|
952
1036
|
return { success: true };
|
|
953
1037
|
}
|
package/dist/src/index.js
CHANGED
|
@@ -196,8 +196,9 @@ export class CLI {
|
|
|
196
196
|
try {
|
|
197
197
|
const { ensureDaemonRunning } = await import('./services/daemon-autostart.js');
|
|
198
198
|
const d = ensureDaemonRunning(process.cwd());
|
|
199
|
-
if (d.started
|
|
200
|
-
this.output.
|
|
199
|
+
if (d.started) {
|
|
200
|
+
this.output.printInfo(`Started Ruflo background daemon for ${process.cwd()} (stop: ruflo daemon stop)`);
|
|
201
|
+
}
|
|
201
202
|
}
|
|
202
203
|
catch { /* silent */ }
|
|
203
204
|
}
|
|
@@ -370,18 +370,19 @@ function mergeSettingsForUpgrade(existing) {
|
|
|
370
370
|
// js/redos): a crafted settings.json command string with dozens of
|
|
371
371
|
// dash-token repetitions can hang this check for minutes.
|
|
372
372
|
const BROKEN_STATUSLINE_RE = /(?:npx\s+(?:--?\S+\s+){0,10})?@?claude-flow(?:\/cli)?(?:@\S+)?\s+hooks\s+statusline/;
|
|
373
|
+
const BROKEN_NPX_LATEST_RE = /npx\s+(?:--?\S+\s+){0,10}@?claude-flow\/cli@latest\s+\S+/;
|
|
373
374
|
const existingStatusLine = existing.statusLine;
|
|
374
375
|
if (existingStatusLine) {
|
|
375
376
|
const existingCmd = typeof existingStatusLine.command === 'string' ? existingStatusLine.command : '';
|
|
376
|
-
const isBroken = BROKEN_STATUSLINE_RE.test(existingCmd);
|
|
377
|
+
const isBroken = BROKEN_STATUSLINE_RE.test(existingCmd) || BROKEN_NPX_LATEST_RE.test(existingCmd);
|
|
377
378
|
merged.statusLine = {
|
|
378
379
|
type: 'command',
|
|
379
380
|
command: isBroken || !existingCmd ? NEW_STATUSLINE_CMD : existingCmd,
|
|
380
381
|
// Remove invalid fields: refreshMs, enabled (not supported by Claude Code)
|
|
381
382
|
};
|
|
382
383
|
}
|
|
383
|
-
// #2448 — Detect and REGENERATE per-action hook commands that still
|
|
384
|
-
// the runaway `npx @claude-flow/cli@latest hooks <sub>` form. These fire
|
|
384
|
+
// #2448 / #2677 — Detect and REGENERATE per-action hook commands that still
|
|
385
|
+
// use the runaway `npx @claude-flow/cli@latest hooks <sub>` form. These fire
|
|
385
386
|
// on every PreToolUse/PostToolUse/UserPromptSubmit, each spawning ~130 MB
|
|
386
387
|
// of cold Node + npm registry resolution; the storm is what kernel-paniced
|
|
387
388
|
// the reporter's machine in #2448.
|
|
@@ -409,15 +410,20 @@ function mergeSettingsForUpgrade(existing) {
|
|
|
409
410
|
for (const group of groups) {
|
|
410
411
|
if (!Array.isArray(group.hooks))
|
|
411
412
|
continue;
|
|
412
|
-
|
|
413
|
+
group.hooks = group.hooks.filter((h) => {
|
|
413
414
|
if (typeof h?.command !== 'string')
|
|
414
|
-
|
|
415
|
+
return true;
|
|
415
416
|
const m = BROKEN_HOOK_RE.exec(h.command);
|
|
416
|
-
if (
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
417
|
+
if (m) {
|
|
418
|
+
// Subcommand captured (e.g. "pre-bash", "post-edit", "route") — keep it.
|
|
419
|
+
h.command = localHookCmd(m[1]);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
// Sibling CLI invocations (memory/daemon/swarm/...) have no
|
|
423
|
+
// hook-handler equivalent. Drop the stale extra; the merge above
|
|
424
|
+
// has already installed current local-helper hooks for this event.
|
|
425
|
+
return !BROKEN_NPX_LATEST_RE.test(h.command);
|
|
426
|
+
});
|
|
421
427
|
}
|
|
422
428
|
}
|
|
423
429
|
}
|