@claude-flow/cli 3.56.9 → 3.56.10
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-version +1 -1
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/helpers/router.cjs +0 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/autopilot-state.d.ts +2 -0
- package/dist/src/autopilot-state.js +36 -8
- package/dist/src/command-resolution.d.ts +15 -0
- package/dist/src/command-resolution.js +25 -0
- package/dist/src/commands/agent.js +17 -17
- package/dist/src/commands/claims.js +6 -8
- package/dist/src/commands/daemon.js +86 -31
- package/dist/src/commands/memory.js +14 -2
- package/dist/src/commands/security-code-patterns.d.ts +2 -2
- package/dist/src/commands/security-code-patterns.js +61 -3
- package/dist/src/commands/security.d.ts +24 -0
- package/dist/src/commands/security.js +81 -35
- package/dist/src/commands/tui.d.ts +9 -0
- package/dist/src/commands/tui.js +29 -3
- package/dist/src/config/proven-config-refresh.d.ts +9 -0
- package/dist/src/config/proven-config-refresh.js +24 -3
- package/dist/src/funnel/local-signals.js +2 -1
- package/dist/src/index.js +6 -42
- package/dist/src/init/types.js +1 -1
- package/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
- package/dist/src/mcp-tools/agent-execute-core.js +15 -0
- package/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
- package/dist/src/mcp-tools/agent-health-metrics.js +33 -0
- package/dist/src/mcp-tools/agent-tools.js +45 -22
- package/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
- package/dist/src/mcp-tools/agentbbs-federation.js +7 -2
- package/dist/src/mcp-tools/agentdb-tools.js +25 -2
- package/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
- package/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
- package/dist/src/mcp-tools/analyze-tools.js +10 -11
- package/dist/src/mcp-tools/autopilot-tools.js +2 -2
- package/dist/src/mcp-tools/browser-intent-tools.js +6 -2
- package/dist/src/mcp-tools/embeddings-tools.js +29 -7
- package/dist/src/mcp-tools/hooks-tools.js +4 -0
- package/dist/src/mcp-tools/memory-tools.js +70 -20
- package/dist/src/mcp-tools/neural-tools.js +28 -4
- package/dist/src/mcp-tools/ruvllm-tools.js +6 -0
- package/dist/src/mcp-tools/security-tools.js +55 -17
- package/dist/src/mcp-tools/workflow-tools.js +66 -17
- package/dist/src/memory/intelligence.d.ts +5 -1
- package/dist/src/memory/intelligence.js +105 -29
- package/dist/src/memory/memory-bridge.js +10 -3
- package/dist/src/memory/memory-initializer.js +2 -1
- package/dist/src/memory/search-defaults.d.ts +54 -0
- package/dist/src/memory/search-defaults.js +37 -0
- package/dist/src/memory/sona-optimizer.d.ts +28 -0
- package/dist/src/memory/sona-optimizer.js +59 -2
- package/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/dist/src/ruvector/agent-wasm.js +27 -1
- package/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/dist/src/ruvector/diff-classifier.js +202 -69
- package/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/dist/src/services/policy-runtime.d.ts +1 -1
- package/dist/src/services/policy-runtime.js +7 -1
- package/dist/src/services/worker-daemon.d.ts +11 -0
- package/dist/src/services/worker-daemon.js +37 -0
- package/dist/src/transfer/store/discovery.js +16 -17
- package/dist/src/transfer/store/types.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
- package/package.json +4 -4
- package/plugins/ruflo-console/dist/tui.mjs +162 -161
- package/plugins/ruflo-metaharness/.claude-flow/daemon-state.json +179 -0
- package/plugins/ruflo-metaharness/.claude-flow/daemon.pid +1 -0
- package/plugins/ruflo-metaharness/.claude-flow/logs/daemon.log +32 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/backup.json +23 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/codebase-map.json +11 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/consolidation.json +16 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/harness-loop.json +91 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/performance.json +17 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/security-audit.json +12 -0
- package/plugins/ruflo-metaharness/.claude-flow/metrics/test-gaps.json +8 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/patterns.json +2809 -0
- package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +6 -0
- package/plugins/ruflo-metaharness/.claude-flow/policy/state.json +11 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-shm +0 -0
- package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-wal +0 -0
- package/plugins/ruflo-metaharness/.swarm/backups/memory-2026-10-14T16-09-38-164Z.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/backups/store-agentdb-memory.db-2026-10-14T16-09-38-166Z.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/ewc-fisher.json +397 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db +0 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db-shm +0 -0
- package/plugins/ruflo-metaharness/.swarm/memory.db-wal +0 -0
- package/plugins/ruflo-metaharness/agentdb.rvf +0 -0
- package/plugins/ruflo-metaharness/agentdb.rvf.lock +0 -0
- package/plugins/ruflo-metaharness/ruvector.db +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.56.9
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.56.
|
|
3
|
+
"version": "3.56.10",
|
|
4
4
|
"files": {
|
|
5
5
|
"auto-memory-hook.mjs": "631fac9d518835e2852a406888316bbbddddb49e7ed8e406d2e971f46833df9a",
|
|
6
6
|
"hook-handler.cjs": "bc12b3cae92d5a7c8deef699fa00d0ef07b57625c18dbeac4387f15725b8e09f",
|
|
7
|
-
"intelligence.cjs": "
|
|
7
|
+
"intelligence.cjs": "a95ac9ee2759c340b14f9ff656765bcb05e0cefe2f018116bd26b3ba9987f3e4",
|
|
8
8
|
"statusline.cjs": "d72417ecb38634011a19f095a30e07b9929e6f97663520172ece9be7491b2eaa",
|
|
9
9
|
"router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"signature": "
|
|
12
|
+
"signature": "hZ0iy15VIKpZOjC2iG2fPsV68csqXrMhn2mifCIvzsjngaB/wmP0P4iMF38nAo57MeANEM6wW1nhK7YvteUXCg==",
|
|
13
13
|
"algorithm": "ed25519"
|
|
14
14
|
}
|
|
@@ -739,27 +739,69 @@ function appendRecall(detailed, meta) {
|
|
|
739
739
|
* getContext(prompt) — Called from route. Budget: <15ms.
|
|
740
740
|
* Matches prompt to ranked entries, returns top-5 formatted context.
|
|
741
741
|
*/
|
|
742
|
+
/**
|
|
743
|
+
* Inflection variants of a prompt term (plural, -ed, -ing, -ion and their
|
|
744
|
+
* stems), so "migrations" finds an entry that says "migration" and "deploying"
|
|
745
|
+
* finds "deploy". Exact term matching alone lost what the old trigram overlap
|
|
746
|
+
* caught (#4136). The stem keeps at least 4 characters so short words stay exact.
|
|
747
|
+
*/
|
|
748
|
+
function termVariants(word) {
|
|
749
|
+
const out = new Set([word]);
|
|
750
|
+
for (const suffix of ['s', 'es', 'ed', 'ing', 'ion', 'ions']) out.add(word + suffix);
|
|
751
|
+
for (const suffix of ['ions', 'ion', 'ing', 'ed', 'es', 's']) {
|
|
752
|
+
if (word.length - suffix.length >= 4 && word.endsWith(suffix)) {
|
|
753
|
+
const stem = word.slice(0, -suffix.length);
|
|
754
|
+
out.add(stem); out.add(stem + 'e'); out.add(stem + 's');
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return [...out];
|
|
758
|
+
}
|
|
759
|
+
|
|
742
760
|
function getContextDetailed(prompt) {
|
|
743
761
|
if (!prompt) return null;
|
|
744
762
|
|
|
745
763
|
const ranked = readJSON(RANKED_PATH);
|
|
746
764
|
if (!ranked || !ranked.entries || ranked.entries.length === 0) return null;
|
|
747
765
|
|
|
748
|
-
const promptWords = tokenize(prompt);
|
|
766
|
+
const promptWords = [...new Set(tokenize(prompt))];
|
|
749
767
|
if (promptWords.length === 0) return null;
|
|
750
|
-
const promptTrigrams = trigrams(promptWords);
|
|
751
768
|
|
|
752
|
-
|
|
753
|
-
|
|
769
|
+
// Query coverage is independent of entry length: a short diagnostic prompt
|
|
770
|
+
// must still find its exact terms inside a long memory section (#4136).
|
|
771
|
+
// IDF downweights generic terms shared by many entries. Terms absent from
|
|
772
|
+
// every entry still count against coverage, so two incidental words cannot
|
|
773
|
+
// hide the rest of an unrelated prompt.
|
|
774
|
+
const candidates = ranked.entries.map(entry => ({ entry, words: new Set(entry.words || []) }));
|
|
775
|
+
const variants = promptWords.map(termVariants);
|
|
776
|
+
const hasTerm = (words, i) => variants[i].some(v => words.has(v));
|
|
777
|
+
const weights = promptWords.map((word, i) => {
|
|
778
|
+
const frequency = candidates.reduce((count, candidate) => count + Number(hasTerm(candidate.words, i)), 0);
|
|
779
|
+
return Math.log1p((candidates.length - frequency + 0.5) / (frequency + 0.5));
|
|
780
|
+
});
|
|
781
|
+
const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
|
|
782
|
+
if (totalWeight === 0) return null;
|
|
783
|
+
|
|
784
|
+
const MIN_COVERAGE = 0.15;
|
|
754
785
|
const TOP_K = 5;
|
|
755
786
|
|
|
756
787
|
// Score each entry
|
|
757
788
|
const scored = [];
|
|
758
|
-
for (const entry of
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
789
|
+
for (const { entry, words } of candidates) {
|
|
790
|
+
let matchedWeight = 0;
|
|
791
|
+
let matchedTerms = 0;
|
|
792
|
+
for (let i = 0; i < promptWords.length; i++) {
|
|
793
|
+
if (weights[i] > 0 && hasTerm(words, i)) {
|
|
794
|
+
matchedWeight += weights[i];
|
|
795
|
+
matchedTerms++;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
// A single common word in a long prompt is not evidence of relevance;
|
|
799
|
+
// PageRank can order relevant entries but cannot make one relevant.
|
|
800
|
+
if (matchedTerms < Math.min(2, promptWords.length)) continue;
|
|
801
|
+
const coverage = matchedWeight / totalWeight;
|
|
802
|
+
if (coverage >= MIN_COVERAGE) {
|
|
803
|
+
const pageRank = Math.max(0, Math.min(1, Number(entry.pageRank) || 0));
|
|
804
|
+
const score = 0.9 * coverage + 0.1 * pageRank;
|
|
763
805
|
scored.push({ ...entry, score });
|
|
764
806
|
}
|
|
765
807
|
}
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: "Verification & Quality Assurance"
|
|
3
3
|
description: |
|
|
4
|
-
|
|
4
|
+
Check code quality and change risk with ruflo's shipped analysis commands (analyze diff, analyze complexity, analyze code), and run or extend ruflo's own CI regression guards. Use when assessing a change's risk, finding complexity hot spots, or adding a regression guard to ruflo. The truth-scoring and auto-rollback commands described later in this file are design notes, not shipped CLI commands.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Verification & Quality Assurance Skill
|
|
@@ -62,31 +62,34 @@ node plugins/ruflo-core/scripts/witness/history.mjs --history verification/macos
|
|
|
62
62
|
|
|
63
63
|
## Prerequisites
|
|
64
64
|
|
|
65
|
-
- Ruflo installed (`npx ruflo@
|
|
65
|
+
- Ruflo installed (`npx ruflo@latest`)
|
|
66
66
|
- Git repository (for rollback features)
|
|
67
67
|
- Node.js 18+ (for dashboard features)
|
|
68
68
|
- `@noble/ed25519` (for the witness verifier — a single runtime dep, `npm i @noble/ed25519`)
|
|
69
69
|
|
|
70
70
|
## Quick Start
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
# View current truth scores
|
|
74
|
-
npx ruflo@alpha truth
|
|
72
|
+
These commands ship in the current CLI:
|
|
75
73
|
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
```bash
|
|
75
|
+
# Change risk since HEAD~1 (without --risk it also shows classification and reviewers)
|
|
76
|
+
npx ruflo@latest analyze diff HEAD~1 --risk
|
|
78
77
|
|
|
79
|
-
#
|
|
80
|
-
npx ruflo@
|
|
78
|
+
# Complexity hot spots in a directory
|
|
79
|
+
npx ruflo@latest analyze complexity src/ --threshold 15
|
|
81
80
|
|
|
82
|
-
#
|
|
83
|
-
npx ruflo@
|
|
81
|
+
# Size, functions and TODOs per file
|
|
82
|
+
npx ruflo@latest analyze code -p ./src
|
|
84
83
|
```
|
|
85
84
|
|
|
85
|
+
There is no truth-scoring, `verify check` or `verify rollback` command (`ruflo verify` only checks an installed ruflo package against its signed witness manifest). To verify agent output, run the project's own tests, linters and type checks, and roll back with git (`git restore <file>`, `git revert <commit>`). Inside the ruflo repo, run the guards in [Run the guards locally](#run-the-guards-locally).
|
|
86
|
+
|
|
86
87
|
---
|
|
87
88
|
|
|
88
89
|
## Complete Guide
|
|
89
90
|
|
|
91
|
+
> **Design notes, not shipped.** Everything below describes a planned truth-scoring, verification and rollback surface. `ruflo truth`, `ruflo verify check` and `ruflo verify rollback` are not commands in the current CLI, so do not run the examples below; use the commands in [Quick Start](#quick-start) instead.
|
|
92
|
+
|
|
90
93
|
### Truth Scoring System
|
|
91
94
|
|
|
92
95
|
#### View Truth Metrics
|
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ MCP exposes Ruflo tools to your client. The console and mods above are Claude Co
|
|
|
51
51
|
|
|
52
52
|
<img src="docs/assets/readme/icons/terminal.svg" width="28" height="28" alt=""> **Prefer a plain terminal? Optional.** The console also runs on its own, with no Claude Code: `npx ruflo tui` (add `--plain` to print one frame for scripts). It shows Overview, Cost, Swarm, Events and Missions, plus an optional AI page. [Terminal UI guide](docs/USERGUIDE.md#terminal-ui-optional)
|
|
53
53
|
|
|
54
|
-
<p align="center"><img src="docs/assets/ruflo-tui-compact.svg" width="640" alt="Looping recording of the optional ruflo terminal UI
|
|
54
|
+
<p align="center"><img src="docs/assets/ruflo-tui-compact.svg" width="640" alt="Looping recording of the optional ruflo terminal UI with its pictures: Overview in the Classic BBS look, the Orbital look with its moving globe, and the Swarm graph, in a sample project"></p>
|
|
55
55
|
|
|
56
56
|
[Compare install paths](#quick-start) · [Open the console](#console-walkthrough) · [User guide](docs/USERGUIDE.md)
|
|
57
57
|
|
package/catalog-manifest.json
CHANGED
|
@@ -80,6 +80,8 @@ export declare function discoverTasks(sources: string[]): TaskInfo[];
|
|
|
80
80
|
export declare function isTerminal(status: string): boolean;
|
|
81
81
|
export declare function getProgress(tasks: TaskInfo[]): TaskProgress;
|
|
82
82
|
export declare function calculateReward(iterations: number, durationMs: number): number;
|
|
83
|
+
/** Why the last tryLoadLearning() returned null (null when it succeeded or was never called). */
|
|
84
|
+
export declare function getLearningLoadError(): string | null;
|
|
83
85
|
export declare function tryLoadLearning(): Promise<{
|
|
84
86
|
initialize: () => Promise<boolean>;
|
|
85
87
|
[key: string]: unknown;
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { randomUUID } from 'node:crypto';
|
|
11
11
|
import { existsSync, readFileSync, mkdirSync, readdirSync } from 'node:fs';
|
|
12
|
-
import { resolve, join } from 'node:path';
|
|
12
|
+
import { resolve, join, dirname } from 'node:path';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
14
|
+
import { pathToFileURL } from 'node:url';
|
|
13
15
|
import { homedir } from 'node:os';
|
|
14
16
|
import { writeFileAtomic } from './fs-secure.js';
|
|
15
17
|
// ── Constants ─────────────────────────────────────────────────
|
|
@@ -288,17 +290,43 @@ export function calculateReward(iterations, durationMs) {
|
|
|
288
290
|
return Math.round((iterFactor + timeFactor) * 100) / 100;
|
|
289
291
|
}
|
|
290
292
|
// ── Learning Integration ──────────────────────────────────────
|
|
293
|
+
let lastLearningLoadError = null;
|
|
294
|
+
/** Why the last tryLoadLearning() returned null (null when it succeeded or was never called). */
|
|
295
|
+
export function getLearningLoadError() {
|
|
296
|
+
return lastLearningLoadError;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* agentic-flow's `exports` map does not expose dist/coordination/*, so a bare
|
|
300
|
+
* specifier import throws ERR_PACKAGE_PATH_NOT_EXPORTED (#4146). Resolve the
|
|
301
|
+
* package root through its exported entry point and import the file by URL.
|
|
302
|
+
*/
|
|
303
|
+
function resolveAutopilotLearningUrl() {
|
|
304
|
+
const req = createRequire(import.meta.url);
|
|
305
|
+
let dir = dirname(req.resolve('agentic-flow'));
|
|
306
|
+
for (let i = 0; i < 6; i++) {
|
|
307
|
+
if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'dist', 'coordination', 'autopilot-learning.js'))) {
|
|
308
|
+
return pathToFileURL(join(dir, 'dist', 'coordination', 'autopilot-learning.js')).href;
|
|
309
|
+
}
|
|
310
|
+
dir = dirname(dir);
|
|
311
|
+
}
|
|
312
|
+
throw new Error('agentic-flow does not ship dist/coordination/autopilot-learning.js');
|
|
313
|
+
}
|
|
291
314
|
export async function tryLoadLearning() {
|
|
315
|
+
lastLearningLoadError = null;
|
|
292
316
|
try {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (await instance.initialize())
|
|
298
|
-
return instance;
|
|
317
|
+
const mod = await import(/* webpackIgnore: true */ resolveAutopilotLearningUrl());
|
|
318
|
+
if (!mod?.AutopilotLearning) {
|
|
319
|
+
lastLearningLoadError = 'agentic-flow autopilot-learning module has no AutopilotLearning export';
|
|
320
|
+
return null;
|
|
299
321
|
}
|
|
322
|
+
const instance = new mod.AutopilotLearning();
|
|
323
|
+
if (await instance.initialize())
|
|
324
|
+
return instance;
|
|
325
|
+
lastLearningLoadError = 'AutopilotLearning.initialize() returned false (AgentDB not initialized)';
|
|
326
|
+
}
|
|
327
|
+
catch (e) {
|
|
328
|
+
lastLearningLoadError = `Could not load agentic-flow autopilot learning: ${e instanceof Error ? e.message : String(e)}`;
|
|
300
329
|
}
|
|
301
|
-
catch { /* not available */ }
|
|
302
330
|
return null;
|
|
303
331
|
}
|
|
304
332
|
//# sourceMappingURL=autopilot-state.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve which (sub)command an invocation targets.
|
|
3
|
+
*
|
|
4
|
+
* The parser reports the matched command path (for example
|
|
5
|
+
* ['hooks', 'transfer', 'store', 'search']) and the remaining positional arguments.
|
|
6
|
+
* Resolution walks the whole path through each command's `subcommands`, so a command
|
|
7
|
+
* can sit at any depth. #4157: it used to stop two levels below the top-level command,
|
|
8
|
+
* so `hooks transfer store search` ran the `store` help instead of `search`.
|
|
9
|
+
*/
|
|
10
|
+
import type { Command } from './types.js';
|
|
11
|
+
export declare function resolveTargetCommand(command: Command, commandPath: string[], positional: string[]): {
|
|
12
|
+
target: Command;
|
|
13
|
+
args: string[];
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=command-resolution.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const matches = (sc, name) => sc.name === name || !!sc.aliases?.includes(name);
|
|
2
|
+
export function resolveTargetCommand(command, commandPath, positional) {
|
|
3
|
+
let target = command;
|
|
4
|
+
if (commandPath.length > 1 && command.subcommands) {
|
|
5
|
+
// The parser already removed the path from `positional`.
|
|
6
|
+
for (const name of commandPath.slice(1)) {
|
|
7
|
+
const next = target.subcommands?.find((sc) => matches(sc, name));
|
|
8
|
+
if (!next)
|
|
9
|
+
break;
|
|
10
|
+
target = next;
|
|
11
|
+
}
|
|
12
|
+
return { target, args: positional };
|
|
13
|
+
}
|
|
14
|
+
// Positional form: the path only named the top-level command.
|
|
15
|
+
let args = positional;
|
|
16
|
+
while (args.length > 0 && target.subcommands) {
|
|
17
|
+
const next = target.subcommands.find((sc) => matches(sc, args[0]));
|
|
18
|
+
if (!next)
|
|
19
|
+
break;
|
|
20
|
+
target = next;
|
|
21
|
+
args = args.slice(1);
|
|
22
|
+
}
|
|
23
|
+
return { target, args };
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=command-resolution.js.map
|
|
@@ -736,15 +736,16 @@ const healthCommand = {
|
|
|
736
736
|
output.writeln(output.bold('Agent Health'));
|
|
737
737
|
output.writeln();
|
|
738
738
|
// Overall summary with null checks
|
|
739
|
-
const overall = result.overall ?? { healthy: 0, degraded: 0, unhealthy: 0,
|
|
740
|
-
const avgCpu = overall.avgCpu ??
|
|
741
|
-
const avgMemory = overall.avgMemory ??
|
|
739
|
+
const overall = result.overall ?? { healthy: 0, degraded: 0, unhealthy: 0, unknown: 0 };
|
|
740
|
+
const avgCpu = overall.avgCpu ?? overall.cpu;
|
|
741
|
+
const avgMemory = overall.avgMemory ?? overall.memory;
|
|
742
742
|
output.printBox([
|
|
743
743
|
`Healthy: ${output.success(String(overall.healthy ?? 0))}`,
|
|
744
744
|
`Degraded: ${output.warning(String(overall.degraded ?? 0))}`,
|
|
745
745
|
`Unhealthy: ${output.error(String(overall.unhealthy ?? 0))}`,
|
|
746
|
-
`
|
|
747
|
-
`Avg
|
|
746
|
+
`Unknown: ${String(overall.unknown ?? 0)}`,
|
|
747
|
+
`Avg CPU: ${typeof avgCpu === 'number' ? `${avgCpu.toFixed(1)}%` : 'n/a'}`,
|
|
748
|
+
`Avg Memory: ${typeof avgMemory === 'number' ? `${(avgMemory * 100).toFixed(1)}%` : 'n/a'}`
|
|
748
749
|
].join(' | '), 'Overall Status');
|
|
749
750
|
const healthAgents = result.agents ?? [];
|
|
750
751
|
output.writeln();
|
|
@@ -753,14 +754,14 @@ const healthCommand = {
|
|
|
753
754
|
{ key: 'id', header: 'Agent ID', width: 18 },
|
|
754
755
|
{ key: 'type', header: 'Type', width: 12 },
|
|
755
756
|
{ key: 'health', header: 'Health', width: 10, format: formatHealthStatus },
|
|
756
|
-
{ key: 'cpu', header: 'CPU %', width: 8, align: 'right', format: (v) => `${
|
|
757
|
+
{ key: 'cpu', header: 'CPU %', width: 8, align: 'right', format: (v) => typeof v === 'number' ? `${v.toFixed(1)}%` : 'n/a' },
|
|
757
758
|
{ key: 'memory', header: 'Memory', width: 10, align: 'right', format: (v) => {
|
|
758
759
|
const mem = v;
|
|
759
|
-
if (!mem)
|
|
760
|
-
return '
|
|
760
|
+
if (!mem || !mem.limit)
|
|
761
|
+
return 'n/a';
|
|
761
762
|
return `${(mem.used / mem.limit * 100).toFixed(0)}%`;
|
|
762
763
|
} },
|
|
763
|
-
{ key: 'tasks', header: '
|
|
764
|
+
{ key: 'tasks', header: 'Calls', width: 12, align: 'right', format: (v) => {
|
|
764
765
|
const t = v;
|
|
765
766
|
if (!t)
|
|
766
767
|
return '0/0';
|
|
@@ -775,15 +776,14 @@ const healthCommand = {
|
|
|
775
776
|
for (const agent of healthAgents) {
|
|
776
777
|
output.writeln();
|
|
777
778
|
output.writeln(output.highlight(agent.id));
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
const
|
|
781
|
-
const errors = agent.errors ?? { count: 0 };
|
|
779
|
+
const latency = agent.latency;
|
|
780
|
+
const tasks = agent.tasks;
|
|
781
|
+
const errors = agent.errors;
|
|
782
782
|
output.printList([
|
|
783
|
-
`
|
|
784
|
-
`Latency:
|
|
785
|
-
`
|
|
786
|
-
`Errors: ${errors
|
|
783
|
+
`Registry age: ${typeof agent.registryAgeMs === 'number' ? `${(agent.registryAgeMs / 1000 / 60).toFixed(1)} min` : 'n/a'}`,
|
|
784
|
+
`Latency: p50 ${typeof latency?.p50 === 'number' ? `${latency.p50.toFixed(1)}ms` : 'n/a'}, p95 ${typeof latency?.p95 === 'number' ? `${latency.p95.toFixed(1)}ms` : 'n/a'}`,
|
|
785
|
+
`Observed provider calls (last 20): ${tasks?.completed ?? 0} API-success, ${tasks?.failed ?? 0} error`,
|
|
786
|
+
`Errors: ${errors?.count ?? 0}${errors?.lastError ? ` (${errors.lastError})` : ''}`
|
|
787
787
|
]);
|
|
788
788
|
}
|
|
789
789
|
}
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Created with ❤️ by ruv.io
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
8
|
-
import { resolve, dirname } from 'node:path';
|
|
8
|
+
import { resolve, dirname, join } from 'node:path';
|
|
9
|
+
import { homedir } from 'node:os';
|
|
9
10
|
import { output } from '../output.js';
|
|
10
11
|
const CLAIMS_CONFIG_PATHS = [
|
|
11
12
|
'.claude-flow/claims.json',
|
|
@@ -15,7 +16,9 @@ function getClaimsConfigPaths() {
|
|
|
15
16
|
return [
|
|
16
17
|
resolve(CLAIMS_CONFIG_PATHS[0]),
|
|
17
18
|
resolve(CLAIMS_CONFIG_PATHS[1]),
|
|
18
|
-
|
|
19
|
+
// os.homedir(), not $HOME: HOME is normally unset on Windows, and the old
|
|
20
|
+
// `process.env.HOME || '~'` fallback resolved to a literal <cwd>/~ folder.
|
|
21
|
+
join(homedir(), '.config', 'claude-flow', 'claims.json'),
|
|
19
22
|
];
|
|
20
23
|
}
|
|
21
24
|
function loadClaimsConfig() {
|
|
@@ -152,18 +155,13 @@ const checkCommand = {
|
|
|
152
155
|
const spinner = output.createSpinner({ text: 'Evaluating claim...', spinner: 'dots' });
|
|
153
156
|
spinner.start();
|
|
154
157
|
const fs = await import('fs');
|
|
155
|
-
const path = await import('path');
|
|
156
158
|
// Real claims evaluation from config file
|
|
157
159
|
let isGranted = false;
|
|
158
160
|
let reason = 'Claim not found in policy';
|
|
159
161
|
let policySource = 'default';
|
|
160
162
|
try {
|
|
161
163
|
// Check for claims config file
|
|
162
|
-
const claimsConfigPaths =
|
|
163
|
-
path.resolve('.claude-flow/claims.json'),
|
|
164
|
-
path.resolve('claude-flow.claims.json'),
|
|
165
|
-
path.resolve(process.env.HOME || '~', '.config/claude-flow/claims.json'),
|
|
166
|
-
];
|
|
164
|
+
const claimsConfigPaths = getClaimsConfigPaths();
|
|
167
165
|
let claimsConfig = {
|
|
168
166
|
// Default policy - allows basic operations
|
|
169
167
|
roles: {
|
|
@@ -164,37 +164,44 @@ const startCommand = {
|
|
|
164
164
|
fs.mkdirSync(stateDir, { recursive: true });
|
|
165
165
|
}
|
|
166
166
|
catch { /* exists */ }
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
return { success: true };
|
|
167
|
+
lockFd = tryAcquireDaemonLock(lockFile);
|
|
168
|
+
if (lockFd === null) {
|
|
169
|
+
// Another `daemon start` is mid-spawn. Wait for it while its holder
|
|
170
|
+
// is alive; only a lock whose holder is DEAD is stale and may be
|
|
171
|
+
// retaken — atomically, via O_EXCL again, never by proceeding
|
|
172
|
+
// lockless. The old fixed 5s timeout + unlink let every waiter
|
|
173
|
+
// through on Windows, where the holder's killStaleDaemons
|
|
174
|
+
// (`tasklist /v`) routinely takes longer than that, and each waiter
|
|
175
|
+
// then forked its own daemon (3 per workspace observed).
|
|
176
|
+
const waitMs = Number(process.env.RUFLO_DAEMON_LOCK_WAIT_MS) > 0
|
|
177
|
+
? Number(process.env.RUFLO_DAEMON_LOCK_WAIT_MS)
|
|
178
|
+
: 60_000;
|
|
179
|
+
const deadline = Date.now() + waitMs;
|
|
180
|
+
while (lockFd === null) {
|
|
181
|
+
const winnerPid = getBackgroundDaemonPid(projectRoot);
|
|
182
|
+
if (winnerPid && isProcessRunning(winnerPid)) {
|
|
183
|
+
if (!quiet) {
|
|
184
|
+
output.printWarning(`Daemon already running in background (PID: ${winnerPid}). Stop it first with: daemon stop`);
|
|
186
185
|
}
|
|
186
|
+
return { success: true };
|
|
187
187
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
188
|
+
if (daemonLockIsStale(lockFile)) {
|
|
189
|
+
try {
|
|
190
|
+
fs.unlinkSync(lockFile);
|
|
191
|
+
}
|
|
192
|
+
catch { /* raced with another taker */ }
|
|
193
|
+
lockFd = tryAcquireDaemonLock(lockFile);
|
|
194
|
+
continue;
|
|
193
195
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
196
|
+
if (Date.now() >= deadline) {
|
|
197
|
+
// A live process is still starting the daemon. Spawning another
|
|
198
|
+
// would be the duplicate this lock exists to prevent.
|
|
199
|
+
if (!quiet) {
|
|
200
|
+
output.printWarning('Another `daemon start` is still in progress for this workspace; not starting a second daemon.');
|
|
201
|
+
}
|
|
202
|
+
return { success: true };
|
|
203
|
+
}
|
|
204
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
198
205
|
}
|
|
199
206
|
}
|
|
200
207
|
// Dedup check while holding the lock.
|
|
@@ -298,15 +305,22 @@ const startCommand = {
|
|
|
298
305
|
// NOTE: Do NOT write PID file here — startDaemon() writes it internally.
|
|
299
306
|
// Writing it before startDaemon() causes checkExistingDaemon() to detect
|
|
300
307
|
// our own PID and return early, leaving no workers scheduled (#1478 Bug 1).
|
|
301
|
-
// Clean up PID file on exit
|
|
308
|
+
// Clean up PID file on exit — only if it is OURS. A process that lost
|
|
309
|
+
// the singleton race must never delete the winner's PID file, or the
|
|
310
|
+
// next auto-start sees "no daemon" and forks yet another one.
|
|
302
311
|
const cleanup = () => {
|
|
303
312
|
try {
|
|
304
|
-
if (fs.existsSync(pidFile)) {
|
|
313
|
+
if (fs.existsSync(pidFile) && fs.readFileSync(pidFile, 'utf-8').trim() === String(process.pid)) {
|
|
305
314
|
fs.unlinkSync(pidFile);
|
|
306
315
|
}
|
|
307
316
|
}
|
|
308
317
|
catch { /* ignore */ }
|
|
309
318
|
};
|
|
319
|
+
// WorkerDaemon.start() declines (returns without running) when another
|
|
320
|
+
// live daemon already owns daemon.pid. Such a process must exit rather
|
|
321
|
+
// than park on the keep-alive below: it has no workers and no TTL, so
|
|
322
|
+
// it would linger as an idle duplicate forever.
|
|
323
|
+
const declined = (d) => !d.getStatus().running;
|
|
310
324
|
process.on('exit', cleanup);
|
|
311
325
|
process.on('SIGINT', () => { cleanup(); process.exit(0); });
|
|
312
326
|
process.on('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
@@ -319,6 +333,11 @@ const startCommand = {
|
|
|
319
333
|
spinner.start();
|
|
320
334
|
const daemon = await startDaemon(projectRoot, config);
|
|
321
335
|
const status = daemon.getStatus();
|
|
336
|
+
if (declined(daemon)) {
|
|
337
|
+
spinner.stop();
|
|
338
|
+
output.printWarning(`Daemon already running (PID: ${getBackgroundDaemonPid(projectRoot) ?? 'unknown'}); not starting a second one.`);
|
|
339
|
+
return { success: true };
|
|
340
|
+
}
|
|
322
341
|
spinner.succeed('Worker daemon started (foreground mode)');
|
|
323
342
|
output.writeln();
|
|
324
343
|
output.printBox([
|
|
@@ -371,7 +390,9 @@ const startCommand = {
|
|
|
371
390
|
await new Promise(() => { }); // Never resolves - daemon runs until killed
|
|
372
391
|
}
|
|
373
392
|
else {
|
|
374
|
-
await startDaemon(projectRoot, config);
|
|
393
|
+
const daemon = await startDaemon(projectRoot, config);
|
|
394
|
+
if (declined(daemon))
|
|
395
|
+
return { success: true };
|
|
375
396
|
setInterval(() => { }, 60_000); // Keep alive with ref'd handle (#1478)
|
|
376
397
|
await new Promise(() => { }); // Keep alive
|
|
377
398
|
}
|
|
@@ -910,6 +931,40 @@ function getBackgroundDaemonPid(projectRoot) {
|
|
|
910
931
|
return null;
|
|
911
932
|
}
|
|
912
933
|
}
|
|
934
|
+
/**
|
|
935
|
+
* Atomically create the launcher lock and stamp it with our PID.
|
|
936
|
+
* Returns the fd, or null when another process holds the lock.
|
|
937
|
+
*/
|
|
938
|
+
function tryAcquireDaemonLock(lockFile) {
|
|
939
|
+
try {
|
|
940
|
+
const fd = fs.openSync(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
|
|
941
|
+
fs.writeSync(fd, String(process.pid));
|
|
942
|
+
return fd;
|
|
943
|
+
}
|
|
944
|
+
catch (e) {
|
|
945
|
+
if (e.code === 'EEXIST')
|
|
946
|
+
return null;
|
|
947
|
+
throw e;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
/**
|
|
951
|
+
* A lock is stale only when its holder is gone. An empty lock is the brief
|
|
952
|
+
* window between O_EXCL create and the PID write, so it counts as held
|
|
953
|
+
* unless it has been empty for a while (holder crashed in that window).
|
|
954
|
+
*/
|
|
955
|
+
function daemonLockIsStale(lockFile) {
|
|
956
|
+
try {
|
|
957
|
+
const raw = fs.readFileSync(lockFile, 'utf-8').trim();
|
|
958
|
+
const pid = parseInt(raw, 10);
|
|
959
|
+
if (!raw || Number.isNaN(pid)) {
|
|
960
|
+
return Date.now() - fs.statSync(lockFile).mtimeMs > 10_000;
|
|
961
|
+
}
|
|
962
|
+
return pid !== process.pid && !isProcessRunning(pid);
|
|
963
|
+
}
|
|
964
|
+
catch {
|
|
965
|
+
return false; // vanished: the next O_EXCL attempt decides
|
|
966
|
+
}
|
|
967
|
+
}
|
|
913
968
|
/**
|
|
914
969
|
* Check if a process is running
|
|
915
970
|
*/
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Memory operations for AgentDB integration
|
|
4
4
|
*/
|
|
5
5
|
import { validateAppendConditions } from '../memory/append-conditions.js';
|
|
6
|
+
import { DEFAULT_SEARCH_THRESHOLD, nearMisses } from '../memory/search-defaults.js';
|
|
6
7
|
import { output } from '../output.js';
|
|
7
8
|
import { select, confirm, input } from '../prompt.js';
|
|
8
9
|
import { callMCPTool, MCPClientError } from '../mcp-client.js';
|
|
@@ -430,7 +431,7 @@ const searchCommand = {
|
|
|
430
431
|
// default to 0.7, which silently re-broke #2558: `memory search` matched
|
|
431
432
|
// content word-for-word and still returned nothing. Regression guard:
|
|
432
433
|
// __tests__/memory-search-recall-2558.test.ts.
|
|
433
|
-
default: 0.
|
|
434
|
+
default: 0.2
|
|
434
435
|
},
|
|
435
436
|
{
|
|
436
437
|
name: 'type',
|
|
@@ -499,7 +500,9 @@ const searchCommand = {
|
|
|
499
500
|
// coalescing preserves an explicit zero. Fallback aligned with the
|
|
500
501
|
// option's declared `default: 0.7` (was `0.3` — the two disagreed
|
|
501
502
|
// and --help advertised a default the code did not honor).
|
|
502
|
-
const threshold = ctx.flags.threshold ??
|
|
503
|
+
const threshold = ctx.flags.threshold ?? DEFAULT_SEARCH_THRESHOLD;
|
|
504
|
+
// The parser fills the option's declared default, so an untouched flag arrives as the default value.
|
|
505
|
+
const thresholdWasDefault = ctx.flags.threshold === undefined || ctx.flags.threshold === DEFAULT_SEARCH_THRESHOLD;
|
|
503
506
|
const searchType = ctx.flags.type || 'semantic';
|
|
504
507
|
const buildHnsw = (ctx.flags['build-hnsw'] || ctx.flags.buildHnsw);
|
|
505
508
|
const requestedIntent = ctx.flags.intent || 'mixed';
|
|
@@ -738,6 +741,15 @@ const searchCommand = {
|
|
|
738
741
|
output.writeln();
|
|
739
742
|
if (results.length === 0) {
|
|
740
743
|
output.printWarning('No results found');
|
|
744
|
+
if (thresholdWasDefault) {
|
|
745
|
+
const near = await nearMisses(searchEntries, { query, namespace, provenanceFilter, dbPath: dbPathSearch });
|
|
746
|
+
if (near) {
|
|
747
|
+
output.writeln(output.dim(` ${near.hint.replace(' in nearMisses', ' below').replace('Pass threshold: 0', 'Pass --threshold 0')}`));
|
|
748
|
+
for (const n of near.nearMisses) {
|
|
749
|
+
output.writeln(output.dim(` ${n.similarity.toFixed(2)} ${n.namespace}/${n.key}`));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
741
753
|
await warnIfSiblingHasRows(ctx.flags.path);
|
|
742
754
|
output.writeln(output.dim('Try: claude-flow memory store -k "key" --value "data"'));
|
|
743
755
|
return { success: true, data: [] };
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* - every rule is tested after comment text is blanked, so a comment naming `child_process.execFile` or `eval(` is
|
|
7
7
|
* not a finding;
|
|
8
8
|
* - eval needs a word start: "retrieval (" in prose is not a call; `eval(` and `window.eval(` still are;
|
|
9
|
-
* - command injection keeps the old rule's reach (any `exec` after `child_process` on the line), and
|
|
10
|
-
* execSync
|
|
9
|
+
* - command injection keeps the old rule's reach (any `exec` after `child_process` on the line), and also catches
|
|
10
|
+
* dynamic exec/execSync command strings when the import was on another line;
|
|
11
11
|
* - a pg placeholder such as `$${paramCount + 1}` is a parameter number, not a value in the SQL text. It is exempt only
|
|
12
12
|
* when its expression is a number, a loop letter, `params.length`-style, or a name containing `param` or `placeholder`, it
|
|
13
13
|
* stands where SQL takes a value, and it is not inside a quoted SQL string.
|