@claude-flow/cli 3.56.3 → 3.56.5
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/auto-memory-hook.mjs +13 -8
- package/.claude/helpers/helpers.manifest.json +5 -5
- package/.claude/helpers/hook-handler.cjs +19 -14
- package/.claude/helpers/learning-service.mjs +5 -2
- package/.claude/helpers/session.cjs +48 -12
- package/.claude/helpers/statusline.cjs +14 -4
- package/catalog-manifest.json +2 -2
- package/dist/src/commands/doctor.js +7 -13
- package/dist/src/commands/hooks.js +3 -6
- package/dist/src/commands/init.js +3 -10
- package/dist/src/commands/metaharness.js +9 -14
- package/dist/src/commands/plugins.js +81 -8
- package/dist/src/commands/security-code-patterns.d.ts +40 -0
- package/dist/src/commands/security-code-patterns.js +192 -0
- package/dist/src/commands/security.js +8 -11
- package/dist/src/commands/status.js +8 -10
- package/dist/src/init/executor.d.ts +1 -1
- package/dist/src/init/executor.js +25 -16
- package/dist/src/init/helper-commands.d.ts +18 -0
- package/dist/src/init/helper-commands.js +142 -0
- package/dist/src/init/helpers-generator.js +128 -29
- package/dist/src/init/settings-generator.js +36 -103
- package/dist/src/mcp-tools/metaharness-tools.js +7 -10
- package/dist/src/mcp-tools/task-tools.js +24 -3
- package/dist/src/mcp-tools/testgen-tools.js +8 -12
- package/dist/src/mcp-tools/transfer-tools.js +9 -8
- package/dist/src/mcp-tools/workflow-tools.js +10 -4
- package/dist/src/memory/graph-edge-writer.js +33 -11
- package/dist/src/memory/memory-initializer.js +2 -19
- package/dist/src/memory/shared-sqlite.d.ts +6 -0
- package/dist/src/memory/shared-sqlite.js +35 -7
- package/dist/src/missions/schemas.d.ts +125 -125
- package/dist/src/mods/command-registry/schema.d.ts +199 -199
- package/dist/src/plugins/manager.d.ts +3 -1
- package/dist/src/plugins/manager.js +31 -10
- package/dist/src/plugins/own-install.d.ts +15 -0
- package/dist/src/plugins/own-install.js +51 -0
- package/dist/src/plugins/store/discovery.d.ts +26 -2
- package/dist/src/plugins/store/discovery.js +108 -81
- package/dist/src/plugins/store/index.d.ts +1 -1
- package/dist/src/plugins/store/index.js +1 -1
- package/dist/src/plugins/store/types.d.ts +5 -0
- package/dist/src/plugins/trust-policy.d.ts +13 -0
- package/dist/src/plugins/trust-policy.js +29 -0
- package/dist/src/ruvector/graph-analyzer.js +18 -6
- package/dist/src/transfer/ipfs/client.js +3 -1
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts +1 -0
- package/node_modules/@claude-flow/mcp/dist/resource-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js +10 -4
- package/node_modules/@claude-flow/mcp/dist/resource-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/sampling.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/sampling.js +13 -6
- package/node_modules/@claude-flow/mcp/dist/sampling.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.js +10 -1
- package/node_modules/@claude-flow/mcp/dist/task-manager.js.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts +1 -0
- package/node_modules/@claude-flow/mcp/dist/tool-registry.d.ts.map +1 -1
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js +17 -11
- package/node_modules/@claude-flow/mcp/dist/tool-registry.js.map +1 -1
- package/node_modules/@claude-flow/mcp/package.json +1 -1
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/index.d.ts +1 -1
- package/node_modules/@claude-flow/security/dist/index.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/index.js +1 -1
- package/node_modules/@claude-flow/security/dist/index.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts +9 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js +15 -2
- package/node_modules/@claude-flow/security/dist/plugins/integrity-verifier.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +5 -5
|
@@ -17,11 +17,16 @@ import { fileURLToPath } from 'url';
|
|
|
17
17
|
|
|
18
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
19
19
|
const __dirname = dirname(__filename);
|
|
20
|
+
// Executable modules resolve only from the helper's own install root. A
|
|
21
|
+
// home-level helper can serve an untrusted project, so CLAUDE_PROJECT_DIR must
|
|
22
|
+
// never decide which JavaScript gets imported.
|
|
23
|
+
const MODULE_ROOT = join(__dirname, '../..');
|
|
20
24
|
// Home-level helpers can serve a different project. Keep the memory writer on
|
|
21
25
|
// the same project root as intelligence.cjs, which reads CLAUDE_PROJECT_DIR.
|
|
26
|
+
// PROJECT_ROOT is for data only.
|
|
22
27
|
const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
|
|
23
28
|
? resolve(process.env.CLAUDE_PROJECT_DIR)
|
|
24
|
-
:
|
|
29
|
+
: MODULE_ROOT;
|
|
25
30
|
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow', 'data');
|
|
26
31
|
const STORE_PATH = join(DATA_DIR, 'auto-memory-store.json');
|
|
27
32
|
|
|
@@ -41,7 +46,7 @@ const dim = (msg) => console.log(` ${DIM}${msg}${RESET}`);
|
|
|
41
46
|
// told exactly how to fix it (on both stdout, so it shows in the Claude Code hook
|
|
42
47
|
// transcript, and stderr, per the issue's requested channel).
|
|
43
48
|
function warnMemoryUnavailable() {
|
|
44
|
-
const line1 = `[AutoMemory] @claude-flow/memory not resolvable from ${
|
|
49
|
+
const line1 = `[AutoMemory] @claude-flow/memory not resolvable from ${MODULE_ROOT} — self-learning imports are DISABLED.`;
|
|
45
50
|
const line2 = ' Fix: npm i -D @claude-flow/memory (or re-run: npx ruflo@latest init, then npx ruflo@latest doctor --fix)';
|
|
46
51
|
console.log(`${YELLOW}${line1}${RESET}`);
|
|
47
52
|
console.log(`${YELLOW}${line2}${RESET}`);
|
|
@@ -175,7 +180,7 @@ async function loadMemoryPackage() {
|
|
|
175
180
|
// project — so init resolves it from the CLI's own context and records the
|
|
176
181
|
// absolute path here. This is the only strategy that works on that install.
|
|
177
182
|
try {
|
|
178
|
-
const sidecar = join(
|
|
183
|
+
const sidecar = join(MODULE_ROOT, '.claude-flow', 'memory-package.json');
|
|
179
184
|
if (existsSync(sidecar)) {
|
|
180
185
|
const rec = JSON.parse(readFileSync(sidecar, 'utf-8'));
|
|
181
186
|
if (rec?.distPath && existsSync(rec.distPath)) {
|
|
@@ -185,7 +190,7 @@ async function loadMemoryPackage() {
|
|
|
185
190
|
} catch { /* fall through */ }
|
|
186
191
|
|
|
187
192
|
// Strategy 1: Local dev (built dist)
|
|
188
|
-
const localDist = join(
|
|
193
|
+
const localDist = join(MODULE_ROOT, 'v3/@claude-flow/memory/dist/index.js');
|
|
189
194
|
if (existsSync(localDist)) {
|
|
190
195
|
try {
|
|
191
196
|
return await import(`file://${localDist}`);
|
|
@@ -196,7 +201,7 @@ async function loadMemoryPackage() {
|
|
|
196
201
|
// when installed as a transitive dependency via npx ruflo / npx @claude-flow/cli@latest)
|
|
197
202
|
try {
|
|
198
203
|
const { createRequire } = await import('module');
|
|
199
|
-
const require = createRequire(join(
|
|
204
|
+
const require = createRequire(join(MODULE_ROOT, 'package.json'));
|
|
200
205
|
return require('@claude-flow/memory');
|
|
201
206
|
} catch { /* fall through */ }
|
|
202
207
|
|
|
@@ -205,8 +210,8 @@ async function loadMemoryPackage() {
|
|
|
205
210
|
return await import('@claude-flow/memory');
|
|
206
211
|
} catch { /* fall through */ }
|
|
207
212
|
|
|
208
|
-
// Strategy 4: Walk up from
|
|
209
|
-
let searchDir =
|
|
213
|
+
// Strategy 4: Walk up from MODULE_ROOT looking for @claude-flow/memory in any node_modules
|
|
214
|
+
let searchDir = MODULE_ROOT;
|
|
210
215
|
const { parse } = await import('path');
|
|
211
216
|
while (searchDir !== parse(searchDir).root) {
|
|
212
217
|
const candidate = join(searchDir, 'node_modules', '@claude-flow', 'memory', 'dist', 'index.js');
|
|
@@ -378,7 +383,7 @@ async function doStatus() {
|
|
|
378
383
|
const memPkg = await loadMemoryPackage();
|
|
379
384
|
const config = readConfig();
|
|
380
385
|
|
|
381
|
-
const sidecar = join(
|
|
386
|
+
const sidecar = join(MODULE_ROOT, '.claude-flow', 'memory-package.json');
|
|
382
387
|
const hasSidecar = existsSync(sidecar);
|
|
383
388
|
|
|
384
389
|
console.log('\n=== Auto Memory Bridge Status ===\n');
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest": {
|
|
3
|
-
"version": "3.56.
|
|
3
|
+
"version": "3.56.5",
|
|
4
4
|
"files": {
|
|
5
|
-
"auto-memory-hook.mjs": "
|
|
6
|
-
"hook-handler.cjs": "
|
|
5
|
+
"auto-memory-hook.mjs": "381412515fb490def7d821ddfe1ebf8d9bee76e49a86e182806c0274f568bf15",
|
|
6
|
+
"hook-handler.cjs": "38fc235079ddcf74b62452a91b9e56703026a1a7682dcce3c635be5ad1c8ea19",
|
|
7
7
|
"intelligence.cjs": "ac5033a77bcea9796856629d5d319bde9ab6b7edf0dcd0d4af187ce30af90836",
|
|
8
|
-
"statusline.cjs": "
|
|
8
|
+
"statusline.cjs": "d72417ecb38634011a19f095a30e07b9929e6f97663520172ece9be7491b2eaa",
|
|
9
9
|
"router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"signature": "
|
|
12
|
+
"signature": "/MW5zFGHYeVuoNFAcDlUhkjIdDhPmBiUQTVJTz8Ku20SpFOoxpIXbgv01PxS5IloBqfmuDLieu++2cG55SIzAg==",
|
|
13
13
|
"algorithm": "ed25519"
|
|
14
14
|
}
|
|
@@ -18,6 +18,10 @@ const fs = require('fs');
|
|
|
18
18
|
const os = require('os');
|
|
19
19
|
|
|
20
20
|
const helpersDir = __dirname;
|
|
21
|
+
// The helper's own install root: the project for a project-local helper,
|
|
22
|
+
// $HOME for a home-level one. CLI resolution starts here, never at the opened
|
|
23
|
+
// project, so a home-level helper does not run code the project supplies.
|
|
24
|
+
const helperRoot = path.resolve(helpersDir, '..', '..');
|
|
21
25
|
|
|
22
26
|
// Resolve an installed @claude-flow/cli (or ruflo) bin — mirrors
|
|
23
27
|
// statusline-generator.ts's resolveCliBin() candidate list. Used only to
|
|
@@ -38,12 +42,11 @@ const helpersDir = __dirname;
|
|
|
38
42
|
function resolveCliBinForHook() {
|
|
39
43
|
try {
|
|
40
44
|
const home = os.homedir();
|
|
41
|
-
const cwd = process.cwd();
|
|
42
45
|
const candidates = [
|
|
43
46
|
path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
|
|
44
|
-
path.join(
|
|
45
|
-
path.join(
|
|
46
|
-
path.join(
|
|
47
|
+
path.join(helperRoot, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
48
|
+
path.join(helperRoot, 'node_modules', 'ruflo', 'bin', 'cli.js'),
|
|
49
|
+
path.join(helperRoot, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
47
50
|
// helpersDir is .claude/helpers/ inside the package itself when this
|
|
48
51
|
// file is running from a real @claude-flow/cli install (not a project
|
|
49
52
|
// that merely copied the helper) — its bin/ is two levels up.
|
|
@@ -77,14 +80,19 @@ function resolveCliBinForHook() {
|
|
|
77
80
|
// render path, where local-first exists purely for per-render latency.
|
|
78
81
|
// `--prefer-offline` avoids a registry round trip for the tarball when
|
|
79
82
|
// already cached while still resolving the current `@latest` version.
|
|
83
|
+
//
|
|
84
|
+
// `--prefix helperRoot` makes npx resolve @claude-flow/cli from the helper's
|
|
85
|
+
// install root instead of the cwd (the opened project); the spawned CLI still
|
|
86
|
+
// runs with the project as its cwd.
|
|
87
|
+
function cliSpawnArgs(cliBin, args, platform = process.platform) {
|
|
88
|
+
if (cliBin) return [process.execPath, [cliBin, ...args]];
|
|
89
|
+
return [platform === 'win32' ? 'npx.cmd' : 'npx', ['--prefer-offline', '--prefix', helperRoot, '@claude-flow/cli', ...args]];
|
|
90
|
+
}
|
|
91
|
+
|
|
80
92
|
function spawnDetachedHookRefresh(subcommand) {
|
|
81
93
|
try {
|
|
82
94
|
const { spawn } = require('child_process');
|
|
83
|
-
const
|
|
84
|
-
const cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
85
|
-
const spawnArgs = cliBin
|
|
86
|
-
? [process.execPath, [cliBin, 'hooks', subcommand, '--quiet']]
|
|
87
|
-
: [cmd, ['--prefer-offline', '@claude-flow/cli', 'hooks', subcommand, '--quiet']];
|
|
95
|
+
const spawnArgs = cliSpawnArgs(resolveCliBinForHook(), ['hooks', subcommand, '--quiet']);
|
|
88
96
|
const child = spawn(spawnArgs[0], spawnArgs[1], {
|
|
89
97
|
detached: true,
|
|
90
98
|
stdio: 'ignore',
|
|
@@ -151,10 +159,7 @@ function firstRunAutoEnableIfEligible() {
|
|
|
151
159
|
const cliBin = resolveCliBinForHook();
|
|
152
160
|
const runDetached = (args) => {
|
|
153
161
|
try {
|
|
154
|
-
const spawnArgs = cliBin
|
|
155
|
-
? [process.execPath, [cliBin, ...args]]
|
|
156
|
-
: [process.platform === 'win32' ? 'npx.cmd' : 'npx',
|
|
157
|
-
['--prefer-offline', '@claude-flow/cli', ...args]];
|
|
162
|
+
const spawnArgs = cliSpawnArgs(cliBin, args);
|
|
158
163
|
const child = spawn(spawnArgs[0], spawnArgs[1], {
|
|
159
164
|
detached: true, stdio: 'ignore', env: process.env, windowsHide: true,
|
|
160
165
|
});
|
|
@@ -772,4 +777,4 @@ if (require.main === module) {
|
|
|
772
777
|
// Which sibling helpers loaded (all CommonJS, shipped as .cjs — #3555).
|
|
773
778
|
const loadedHelpers = { router: !!router, session: !!session, memory: !!memory, intelligence: !!intelligence };
|
|
774
779
|
|
|
775
|
-
module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS };
|
|
780
|
+
module.exports = { runWithTimeout, INTELLIGENCE_TIMEOUT_MS, loadedHelpers, ownedByMod, MOD_OWNABLE_EVENTS, resolveCliBinForHook, cliSpawnArgs, helperRoot };
|
|
@@ -26,9 +26,12 @@ import Database from 'better-sqlite3';
|
|
|
26
26
|
|
|
27
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
28
28
|
const __dirname = dirname(__filename);
|
|
29
|
+
// Executable modules resolve only from the helper's own install root; a
|
|
30
|
+
// home-level helper can serve an untrusted project. PROJECT_ROOT is data only.
|
|
31
|
+
const MODULE_ROOT = join(__dirname, '../..');
|
|
29
32
|
const PROJECT_ROOT = process.env.CLAUDE_PROJECT_DIR
|
|
30
33
|
? resolve(process.env.CLAUDE_PROJECT_DIR)
|
|
31
|
-
:
|
|
34
|
+
: MODULE_ROOT;
|
|
32
35
|
const DATA_DIR = join(PROJECT_ROOT, '.claude-flow/learning');
|
|
33
36
|
const DB_PATH = join(DATA_DIR, 'patterns.db');
|
|
34
37
|
const METRICS_PATH = join(DATA_DIR, 'learning-metrics.json');
|
|
@@ -463,7 +466,7 @@ class EmbeddingService {
|
|
|
463
466
|
|
|
464
467
|
try {
|
|
465
468
|
// Dynamically import agentic-flow OptimizedEmbedder
|
|
466
|
-
const agenticFlowPath = join(
|
|
469
|
+
const agenticFlowPath = join(MODULE_ROOT, 'node_modules/agentic-flow/dist/embeddings/optimized-embedder.js');
|
|
467
470
|
|
|
468
471
|
if (existsSync(agenticFlowPath)) {
|
|
469
472
|
const { getOptimizedEmbedder } = await import(agenticFlowPath);
|
|
@@ -6,9 +6,41 @@
|
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
8
|
const path = require('path');
|
|
9
|
+
const crypto = require('crypto');
|
|
9
10
|
|
|
10
11
|
const SESSION_DIR = path.join(process.cwd(), '.claude-flow', 'sessions');
|
|
11
12
|
const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
|
13
|
+
// Archive names are generated by start(); anything else in current.json is
|
|
14
|
+
// untrusted project content and must never pick a path.
|
|
15
|
+
const SESSION_ID = /^session-\d+$/;
|
|
16
|
+
|
|
17
|
+
// current.json lives in the opened project. A symlink there must never be read
|
|
18
|
+
// or written through, so only a regular file counts as an active session.
|
|
19
|
+
function hasSessionFile() {
|
|
20
|
+
try { return fs.lstatSync(SESSION_FILE).isFile(); } catch { return false; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function archiveId(id) {
|
|
24
|
+
return SESSION_ID.test(String(id)) ? String(id) : `session-${Date.now()}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The session directory must resolve inside the project (no symlinked escape).
|
|
28
|
+
function assertSessionDirContained() {
|
|
29
|
+
const root = fs.realpathSync(process.cwd());
|
|
30
|
+
// Resolve the deepest part of the path that exists (lstat: a symlink counts),
|
|
31
|
+
// so a symlinked .claude-flow is refused before anything is created under it.
|
|
32
|
+
let dir = SESSION_DIR;
|
|
33
|
+
for (;;) {
|
|
34
|
+
try { fs.lstatSync(dir); break; } catch { /* not created yet */ }
|
|
35
|
+
const parent = path.dirname(dir);
|
|
36
|
+
if (parent === dir) break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
const rel = path.relative(root, fs.realpathSync(dir));
|
|
40
|
+
if (rel === '..' || rel.startsWith(`..${path.sep}`) || path.isAbsolute(rel)) {
|
|
41
|
+
throw new Error('Session directory resolves outside the project');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
12
44
|
|
|
13
45
|
// #2307: Atomic write — serialize to a per-process temp file, then rename()
|
|
14
46
|
// into place. rename() is atomic on the same filesystem, so concurrent writers
|
|
@@ -18,9 +50,12 @@ const SESSION_FILE = path.join(SESSION_DIR, 'current.json');
|
|
|
18
50
|
// tail dangling past the end (valid JSON + trailing garbage = parse error).
|
|
19
51
|
// Temp name includes process.pid so concurrent writers don't collide on it.
|
|
20
52
|
// Same class as #1707 (metrics) / #1637 (daemon-state) — session.js was missed.
|
|
53
|
+
// The temp file is created exclusively (never through a planted symlink) and
|
|
54
|
+
// rename() replaces a symlinked target instead of writing through it.
|
|
21
55
|
function atomicWrite(file, data) {
|
|
22
|
-
|
|
23
|
-
|
|
56
|
+
assertSessionDirContained();
|
|
57
|
+
const tmp = `${file}.tmp.${process.pid}.${crypto.randomBytes(6).toString('hex')}`;
|
|
58
|
+
fs.writeFileSync(tmp, data, { flag: 'wx' });
|
|
24
59
|
fs.renameSync(tmp, file);
|
|
25
60
|
}
|
|
26
61
|
|
|
@@ -40,6 +75,7 @@ const commands = {
|
|
|
40
75
|
},
|
|
41
76
|
};
|
|
42
77
|
|
|
78
|
+
assertSessionDirContained();
|
|
43
79
|
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
|
44
80
|
atomicWrite(SESSION_FILE, JSON.stringify(session, null, 2));
|
|
45
81
|
|
|
@@ -48,7 +84,7 @@ const commands = {
|
|
|
48
84
|
},
|
|
49
85
|
|
|
50
86
|
restore: () => {
|
|
51
|
-
if (!
|
|
87
|
+
if (!hasSessionFile()) {
|
|
52
88
|
console.log('No session to restore');
|
|
53
89
|
return null;
|
|
54
90
|
}
|
|
@@ -70,7 +106,7 @@ const commands = {
|
|
|
70
106
|
},
|
|
71
107
|
|
|
72
108
|
end: () => {
|
|
73
|
-
if (!
|
|
109
|
+
if (!hasSessionFile()) {
|
|
74
110
|
console.log('No active session');
|
|
75
111
|
return null;
|
|
76
112
|
}
|
|
@@ -79,12 +115,12 @@ const commands = {
|
|
|
79
115
|
session.endedAt = new Date().toISOString();
|
|
80
116
|
session.duration = Date.now() - new Date(session.startedAt).getTime();
|
|
81
117
|
|
|
82
|
-
// Archive session
|
|
83
|
-
const
|
|
84
|
-
atomicWrite(
|
|
118
|
+
// Archive session under a validated name; a crafted id cannot pick the path.
|
|
119
|
+
const id = archiveId(session.id);
|
|
120
|
+
atomicWrite(path.join(SESSION_DIR, `${id}.json`), JSON.stringify(session, null, 2));
|
|
85
121
|
fs.unlinkSync(SESSION_FILE);
|
|
86
122
|
|
|
87
|
-
console.log(`Session ended: ${
|
|
123
|
+
console.log(`Session ended: ${id}`);
|
|
88
124
|
console.log(`Duration: ${Math.round(session.duration / 1000 / 60)} minutes`);
|
|
89
125
|
console.log(`Metrics: ${JSON.stringify(session.metrics)}`);
|
|
90
126
|
|
|
@@ -92,7 +128,7 @@ const commands = {
|
|
|
92
128
|
},
|
|
93
129
|
|
|
94
130
|
status: () => {
|
|
95
|
-
if (!
|
|
131
|
+
if (!hasSessionFile()) {
|
|
96
132
|
console.log('No active session');
|
|
97
133
|
return null;
|
|
98
134
|
}
|
|
@@ -109,7 +145,7 @@ const commands = {
|
|
|
109
145
|
},
|
|
110
146
|
|
|
111
147
|
update: (key, value) => {
|
|
112
|
-
if (!
|
|
148
|
+
if (!hasSessionFile()) {
|
|
113
149
|
console.log('No active session');
|
|
114
150
|
return null;
|
|
115
151
|
}
|
|
@@ -123,7 +159,7 @@ const commands = {
|
|
|
123
159
|
},
|
|
124
160
|
|
|
125
161
|
get: (key) => {
|
|
126
|
-
if (!
|
|
162
|
+
if (!hasSessionFile()) return null;
|
|
127
163
|
try {
|
|
128
164
|
const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf-8'));
|
|
129
165
|
return key ? (session.context || {})[key] : session.context;
|
|
@@ -131,7 +167,7 @@ const commands = {
|
|
|
131
167
|
},
|
|
132
168
|
|
|
133
169
|
metric: (name) => {
|
|
134
|
-
if (!
|
|
170
|
+
if (!hasSessionFile()) {
|
|
135
171
|
return null;
|
|
136
172
|
}
|
|
137
173
|
|
|
@@ -43,6 +43,10 @@ const CONFIG = {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
const CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
46
|
+
// The helper's own install root: the project for a project-local helper,
|
|
47
|
+
// $HOME for a home-level one. The CLI is resolved from here, never from CWD,
|
|
48
|
+
// so a home-level statusline does not run code the opened project supplies.
|
|
49
|
+
const HELPER_ROOT = path.resolve(__dirname, '..', '..');
|
|
46
50
|
// Replaced by statusline-generator with the package root of the CLI that
|
|
47
51
|
// installed this helper. This survives custom npm prefixes and bundled Node
|
|
48
52
|
// runtimes whose process.execPath belongs to a different tree (#2811).
|
|
@@ -111,9 +115,9 @@ function resolveCliBinCandidates() {
|
|
|
111
115
|
const home = os.homedir();
|
|
112
116
|
candidates.push(
|
|
113
117
|
path.join(home, '.claude', 'plugins', 'marketplaces', 'ruflo', 'bin', 'cli.js'),
|
|
114
|
-
path.join(
|
|
115
|
-
path.join(
|
|
116
|
-
path.join(
|
|
118
|
+
path.join(HELPER_ROOT, 'node_modules', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
119
|
+
path.join(HELPER_ROOT, 'node_modules', 'ruflo', 'bin', 'cli.js'),
|
|
120
|
+
path.join(HELPER_ROOT, 'v3', '@claude-flow', 'cli', 'bin', 'cli.js'),
|
|
117
121
|
);
|
|
118
122
|
try {
|
|
119
123
|
const binDir = path.dirname(process.execPath);
|
|
@@ -237,9 +241,15 @@ function getStatuslineData() {
|
|
|
237
241
|
// intelligence and an empty promo row (the memo cache that keeps the row
|
|
238
242
|
// populated across CLI hiccups is only ever written from a SUCCESSFUL
|
|
239
243
|
// delegation, so it could never get seeded on Windows either).
|
|
244
|
+
// These command lines go through a shell, so a path the shell would interpret
|
|
245
|
+
// (a quote, $, backtick, %, !, newline, or a backslash on POSIX) is never
|
|
246
|
+
// interpolated: the directory name is chosen by whoever made the checkout.
|
|
247
|
+
const shellSafe = (p) => !/["`$%!\r\n]/.test(p) && (process.platform === 'win32' || !p.includes('\\'));
|
|
240
248
|
const cmds = resolveCliBinCandidates()
|
|
249
|
+
.filter((bin) => shellSafe(bin) && shellSafe(process.execPath))
|
|
241
250
|
.map((bin) => '"' + process.execPath + '" "' + bin + '" hooks statusline --json')
|
|
242
|
-
|
|
251
|
+
// --prefix: npx resolves the package from HELPER_ROOT, not the project.
|
|
252
|
+
.concat(shellSafe(HELPER_ROOT) ? ['npx --prefer-offline --prefix "' + HELPER_ROOT + '" @claude-flow/cli hooks statusline --json'] : []);
|
|
243
253
|
for (const cmd of cmds) {
|
|
244
254
|
try {
|
|
245
255
|
const raw = execSync(
|
package/catalog-manifest.json
CHANGED
|
@@ -9,6 +9,7 @@ import { existsSync, readFileSync, statSync, openSync, readSync, closeSync } fro
|
|
|
9
9
|
import { join, dirname } from 'path';
|
|
10
10
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
11
11
|
import { createHash } from 'crypto';
|
|
12
|
+
import { ownInstallAncestors } from '../plugins/own-install.js';
|
|
12
13
|
import { execSync, exec, execFile } from 'child_process';
|
|
13
14
|
import { promisify } from 'util';
|
|
14
15
|
import { decodeKey, isEncryptionEnabled } from '../encryption/vault.js';
|
|
@@ -2045,26 +2046,19 @@ async function checkMetaharnessIntegration() {
|
|
|
2045
2046
|
// `dist/src/commands/doctor.js` (built) or `src/commands/doctor.ts`
|
|
2046
2047
|
// (dev) until we find a directory containing `plugins/ruflo-metaharness/`.
|
|
2047
2048
|
const candidates = [];
|
|
2048
|
-
//
|
|
2049
|
+
// Walk up from this module's own URL — covers npx, global and project-local
|
|
2050
|
+
// installs and monorepo dev. The plugin found here is imported below, so it
|
|
2051
|
+
// must come from this CLI's own install, never from the cwd (the project).
|
|
2049
2052
|
try {
|
|
2053
|
+
// Bounded at node_modules: a project-local install must not reach the project.
|
|
2050
2054
|
const selfDir = dirname(fileURLToPath(import.meta.url));
|
|
2051
|
-
|
|
2052
|
-
for (let i = 0; i < 8; i++) {
|
|
2055
|
+
for (const q of ownInstallAncestors(selfDir)) {
|
|
2053
2056
|
candidates.push(join(q, 'plugins', 'ruflo-metaharness'));
|
|
2054
|
-
q = dirname(q);
|
|
2055
2057
|
}
|
|
2056
2058
|
}
|
|
2057
2059
|
catch {
|
|
2058
|
-
// import.meta.url unavailable under some bundlers —
|
|
2059
|
-
}
|
|
2060
|
-
// Strategy 2: walk up from cwd — covers monorepo dev (running from a sub-package).
|
|
2061
|
-
let p = process.cwd();
|
|
2062
|
-
for (let i = 0; i < 8; i++) {
|
|
2063
|
-
candidates.push(join(p, 'plugins', 'ruflo-metaharness'));
|
|
2064
|
-
p = dirname(p);
|
|
2060
|
+
// import.meta.url unavailable under some bundlers — the plugin is reported absent.
|
|
2065
2061
|
}
|
|
2066
|
-
// Strategy 3: explicit node_modules path relative to cwd — covers project-local install.
|
|
2067
|
-
candidates.push(join(process.cwd(), 'node_modules', '@claude-flow', 'cli', 'plugins', 'ruflo-metaharness'));
|
|
2068
2062
|
let pluginDir = null;
|
|
2069
2063
|
for (const c of candidates) {
|
|
2070
2064
|
if (existsSync(join(c, 'scripts', '_harness.mjs'))) {
|
|
@@ -4171,18 +4171,15 @@ const tokenOptimizeCommand = {
|
|
|
4171
4171
|
try {
|
|
4172
4172
|
// Check if agentic-flow v3 is available
|
|
4173
4173
|
const rb = await import('agentic-flow/reasoningbank').catch(() => null);
|
|
4174
|
+
// No root-entry fallback: importing 'agentic-flow' runs its CLI main()
|
|
4175
|
+
// in 3.0.0-alpha.x, and the root re-exports this same reasoningbank
|
|
4176
|
+
// module, so it cannot load when this subpath did not.
|
|
4174
4177
|
if (rb) {
|
|
4175
4178
|
agenticFlowAvailable = true;
|
|
4176
4179
|
if (typeof rb.retrieveMemories === 'function') {
|
|
4177
4180
|
reasoningBank = rb;
|
|
4178
4181
|
}
|
|
4179
4182
|
}
|
|
4180
|
-
else {
|
|
4181
|
-
// Legacy check for older agentic-flow
|
|
4182
|
-
const af = await import('agentic-flow').catch(() => null);
|
|
4183
|
-
if (af)
|
|
4184
|
-
agenticFlowAvailable = true;
|
|
4185
|
-
}
|
|
4186
4183
|
const versionLabel = agenticFlowAvailable ? `agentic-flow v3 detected (ReasoningBank: ${reasoningBank ? 'active' : 'unavailable'})` : 'agentic-flow not available (using fallbacks)';
|
|
4187
4184
|
spinner.succeed(versionLabel);
|
|
4188
4185
|
output.writeln();
|
|
@@ -50,16 +50,9 @@ async function resolveCodexInitializer(cwd) {
|
|
|
50
50
|
const resolutionStrategies = [
|
|
51
51
|
// Strategy 1: Direct import (works if installed as CLI dependency)
|
|
52
52
|
async () => (await import(codexModuleId)).CodexInitializer,
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
if (fs.existsSync(projectPath)) {
|
|
57
|
-
const mod = await import(`file://${projectPath}`);
|
|
58
|
-
return mod.CodexInitializer;
|
|
59
|
-
}
|
|
60
|
-
throw new Error('Not found in project');
|
|
61
|
-
},
|
|
62
|
-
// Strategy 3: Global node_modules
|
|
53
|
+
// (No project node_modules strategy: init must not import code the
|
|
54
|
+
// target project supplies.)
|
|
55
|
+
// Strategy 2: Global node_modules
|
|
63
56
|
async () => {
|
|
64
57
|
const { execSync } = await import('child_process');
|
|
65
58
|
const globalPath = execSync('npm root -g', { encoding: 'utf-8' }).trim();
|
|
@@ -38,6 +38,8 @@ import { spawnSync } from 'child_process';
|
|
|
38
38
|
import { existsSync, readFileSync } from 'fs';
|
|
39
39
|
import { join, dirname, resolve } from 'path';
|
|
40
40
|
import { fileURLToPath } from 'url';
|
|
41
|
+
import { homedir } from 'os';
|
|
42
|
+
import { ownPluginDirs } from '../plugins/own-install.js';
|
|
41
43
|
import { runFlywheelWorker } from '../services/harness-flywheel-runtime.js';
|
|
42
44
|
import { listFlywheelReceipts, promoteFlywheelCandidate, readFlywheelTransactionState, resetSequentialEvidence, verifyFlywheelLedger, } from '../services/flywheel-transaction.js';
|
|
43
45
|
import { evaluatePolicyRequest } from '../services/policy-runtime.js';
|
|
@@ -213,20 +215,13 @@ async function dispatchFlywheel(operation, positional, flags) {
|
|
|
213
215
|
* `prepublishOnly`).
|
|
214
216
|
*/
|
|
215
217
|
function locatePluginScripts(requiredScript) {
|
|
216
|
-
|
|
217
|
-
//
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
// Also try from cwd (covers the "npx ruflo" case where the user is
|
|
225
|
-
// sitting in their own repo and `npx ruflo metaharness score` should
|
|
226
|
-
// score THAT repo using the LOCAL plugin if present).
|
|
227
|
-
const cwd = process.cwd();
|
|
228
|
-
candidates.push(join(cwd, 'plugins', 'ruflo-metaharness', 'scripts'));
|
|
229
|
-
candidates.push(join(cwd, 'node_modules', '@claude-flow', 'cli', 'plugins', 'ruflo-metaharness', 'scripts'));
|
|
218
|
+
// Up from the cli dist dir, bounded at node_modules: a project-local install
|
|
219
|
+
// must not reach the project.
|
|
220
|
+
const candidates = ownPluginDirs(__dirname, 'ruflo-metaharness', 'scripts');
|
|
221
|
+
// The scripts are executed, so they come from this CLI's own install (or the
|
|
222
|
+
// user's marketplace checkout), never from the cwd: scoring a repo must not
|
|
223
|
+
// run plugin code that repo supplies. The repo is still the scoring target.
|
|
224
|
+
candidates.push(join(homedir(), '.claude', 'plugins', 'marketplaces', 'ruflo', 'plugins', 'ruflo-metaharness', 'scripts'));
|
|
230
225
|
for (const c of candidates) {
|
|
231
226
|
if (!existsSync(join(c, '_harness.mjs')))
|
|
232
227
|
continue;
|