@claude-flow/cli 3.59.17 → 3.60.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.claude/helpers/.helpers-version +1 -1
  2. package/.claude/helpers/helpers.manifest.json +2 -2
  3. package/README.md +2 -1
  4. package/catalog-manifest.json +4 -4
  5. package/dist/src/commands/agntcy/publish.js +1 -1
  6. package/dist/src/commands/agntcy/runtime.d.ts +4 -1
  7. package/dist/src/commands/agntcy/runtime.js +15 -2
  8. package/dist/src/commands/agntcy/swarm-join.js +1 -1
  9. package/dist/src/commands/agntcy/transport.js +1 -1
  10. package/dist/src/commands/update.js +5 -3
  11. package/dist/src/mcp-client.js +4 -0
  12. package/dist/src/mcp-tools/agentbbs-tools.js +7 -0
  13. package/dist/src/mcp-tools/capability-brain.js +17 -0
  14. package/dist/src/mcp-tools/daa-tools.js +20 -12
  15. package/dist/src/mcp-tools/github-tools.js +78 -75
  16. package/dist/src/mcp-tools/neural-tools.js +11 -17
  17. package/dist/src/mcp-tools/rure-tools.d.ts +11 -0
  18. package/dist/src/mcp-tools/rure-tools.js +359 -0
  19. package/dist/src/memory/memory-bridge.d.ts +17 -0
  20. package/dist/src/memory/memory-bridge.js +18 -9
  21. package/dist/src/mods/claude-installs.d.ts +12 -0
  22. package/dist/src/mods/claude-installs.js +21 -1
  23. package/dist/src/rure/aidefence-gate.d.ts +25 -0
  24. package/dist/src/rure/aidefence-gate.js +63 -0
  25. package/dist/src/rure/analyze-js-app.d.ts +17 -0
  26. package/dist/src/rure/analyze-js-app.js +26 -0
  27. package/dist/src/rure/binary-parse.d.ts +38 -0
  28. package/dist/src/rure/binary-parse.js +347 -0
  29. package/dist/src/rure/dotnet-parse.d.ts +30 -0
  30. package/dist/src/rure/dotnet-parse.js +299 -0
  31. package/dist/src/rure/evm-parse.d.ts +52 -0
  32. package/dist/src/rure/evm-parse.js +233 -0
  33. package/dist/src/rure/extract-asar.d.ts +33 -0
  34. package/dist/src/rure/extract-asar.js +70 -0
  35. package/dist/src/rure/find-feature.d.ts +26 -0
  36. package/dist/src/rure/find-feature.js +69 -0
  37. package/dist/src/rure/ts-walk.d.ts +47 -0
  38. package/dist/src/rure/ts-walk.js +158 -0
  39. package/dist/src/rure/walk-files.d.ts +21 -0
  40. package/dist/src/rure/walk-files.js +71 -0
  41. package/dist/src/services/worker-daemon.js +17 -3
  42. package/dist/src/update/checker.d.ts +1 -0
  43. package/dist/src/update/checker.js +4 -2
  44. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  45. package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts.map +1 -1
  46. package/node_modules/@claude-flow/mcp/dist/session-manager.js +4 -1
  47. package/node_modules/@claude-flow/mcp/dist/session-manager.js.map +1 -1
  48. package/node_modules/@claude-flow/mcp/package.json +1 -1
  49. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  50. package/package.json +4 -3
  51. package/plugins/ruflo-console/dist/tui.mjs +164 -162
@@ -1 +1 @@
1
- 3.57.1
1
+ 3.59.17
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.59.17",
3
+ "version": "3.60.0",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "631fac9d518835e2852a406888316bbbddddb49e7ed8e406d2e971f46833df9a",
6
6
  "hook-handler.cjs": "24502a3bcb78770971147940515c992e35b6736ae14ee2dc54e07d4cdef6a1d8",
@@ -9,6 +9,6 @@
9
9
  "router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
10
10
  }
11
11
  },
12
- "signature": "WEe3Qca2Em0MEclN+uTae/yTPDwdrLx3HpKW6F3c2v1kA01PUCqSWtMBErDztcgdXum4tg8EGQGxFnU1VYh7Aw==",
12
+ "signature": "b9szLUO3BqVyn5KCnvZxUamxS/mMeVx0nel+X3AH77zUfO/t060rj9mOtUfAWHhNeXH3mGuqvR4nrKXa58t/Dg==",
13
13
  "algorithm": "ed25519"
14
14
  }
package/README.md CHANGED
@@ -8,6 +8,7 @@
8
8
  [![npm version (ruflo)](https://img.shields.io/npm/v/ruflo?label=npx%20ruflo&style=for-the-badge&logo=npm&color=cb3837)](https://www.npmjs.com/package/ruflo)
9
9
  [![MIT License](https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge)](https://opensource.org/licenses/MIT)
10
10
  [![Star on GitHub](https://img.shields.io/github/stars/ruvnet/claude-flow?style=for-the-badge&logo=github&color=gold)](https://github.com/ruvnet/claude-flow)
11
+ [![Follow @rUv on X](https://img.shields.io/badge/Follow-@rUv-000000?style=for-the-badge&logo=x&logoColor=white)](https://x.com/rUv)
11
12
 
12
13
  <p align="center">
13
14
  <a href="docs/harness-origins.md"><img src="https://raw.githubusercontent.com/ruvnet/ruflo/e69e4b95aa08d65ea0f7715489bbf8e62a83a0d2/docs/assets/readme/badges/first-commit.svg" width="300" height="76" alt="Claude Flow lineage: first commit June 2, 2025 by rUv; inspect the evidence"></a>
@@ -242,7 +243,7 @@ This adds slash commands and agent definitions. `ruflo-core` (installed above) d
242
243
  | [**ruflo-browser**](plugins/ruflo-browser/README.md) | Automate browser testing with Playwright |
243
244
  | [**ruflo-jujutsu**](plugins/ruflo-jujutsu/README.md) | Analyze git diffs, score risk, suggest reviewers |
244
245
  | [**ruflo-docs**](plugins/ruflo-docs/README.md) | Generate and maintain documentation automatically |
245
- | [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.11/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
246
+ | [**agentic-qe-fleet**](https://github.com/proffesor-for-testing/agentic-qe/tree/v3.15.12/plugins/agentic-qe-fleet) | External, maintained by [Agentic QE](https://github.com/proffesor-for-testing/agentic-qe): 11 QE agents, test generation, coverage, chaos and TDD skills, the `agentic-qe` MCP server, and a guard for AQE learning data (`/plugin install agentic-qe-fleet@ruflo`). Install it from one marketplace only, `agentic-qe` or `ruflo`: enabled from both, its mod and commands load twice |
246
247
 
247
248
  #### Security & Compliance
248
249
 
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 7,
4
- "generatedAt": "2026-10-22T13:20:46.587Z",
5
- "gitSha": "8b200962",
4
+ "generatedAt": "2026-10-23T17:21:46.374Z",
5
+ "gitSha": "7402c321",
6
6
  "catalog": {
7
- "agents": 176,
8
- "tools": 432,
7
+ "agents": 177,
8
+ "tools": 438,
9
9
  "skills": 34
10
10
  },
11
11
  "benchmark": null
@@ -87,7 +87,7 @@ const publishCommand = {
87
87
  }
88
88
  // Step 2 (stub, requires the not-yet-published runtime): the actual
89
89
  // Directory publish call.
90
- const status = await detectAgntcyRuntime();
90
+ const status = await detectAgntcyRuntime(process.env, AGNTCY_PACKAGE_NAME, ['publishAgentRecord']);
91
91
  if (!status.configured) {
92
92
  output.printInfo(AGNTCY_NOT_CONFIGURED_MESSAGE);
93
93
  output.printInfo(`Validated OASF record at "${manifestPath}" locally (name=${parsed.name}, ` +
@@ -107,6 +107,9 @@ export interface AgntcyRuntimeStatus {
107
107
  * @param packageName Injectable for tests (to simulate "package not
108
108
  * installed" without needing to actually uninstall the real optional
109
109
  * dependency); defaults to {@link AGNTCY_PACKAGE_NAME}.
110
+ * @param requiredExports Function names the caller will use. A resolved module
111
+ * missing any of them is reported as not configured, with the missing names
112
+ * in `reason`, instead of failing later at the call site.
110
113
  */
111
- export declare function detectAgntcyRuntime(env?: NodeJS.ProcessEnv, packageName?: string): Promise<AgntcyRuntimeStatus>;
114
+ export declare function detectAgntcyRuntime(env?: NodeJS.ProcessEnv, packageName?: string, requiredExports?: readonly string[]): Promise<AgntcyRuntimeStatus>;
112
115
  //# sourceMappingURL=runtime.d.ts.map
@@ -100,8 +100,11 @@ export const AGNTCY_NOT_CONFIGURED_MESSAGE = `AGNTCY/SLIM transport is not confi
100
100
  * @param packageName Injectable for tests (to simulate "package not
101
101
  * installed" without needing to actually uninstall the real optional
102
102
  * dependency); defaults to {@link AGNTCY_PACKAGE_NAME}.
103
+ * @param requiredExports Function names the caller will use. A resolved module
104
+ * missing any of them is reported as not configured, with the missing names
105
+ * in `reason`, instead of failing later at the call site.
103
106
  */
104
- export async function detectAgntcyRuntime(env = process.env, packageName = AGNTCY_PACKAGE_NAME) {
107
+ export async function detectAgntcyRuntime(env = process.env, packageName = AGNTCY_PACKAGE_NAME, requiredExports = []) {
105
108
  const endpoint = env[AGNTCY_ENDPOINT_ENV];
106
109
  if (!endpoint) {
107
110
  return { configured: false, reason: `${AGNTCY_ENDPOINT_ENV} is not set` };
@@ -112,7 +115,17 @@ export async function detectAgntcyRuntime(env = process.env, packageName = AGNTC
112
115
  // `await import('pg')`). Never a static `import` — a static import
113
116
  // would make @claude-flow/cli fail to build/run when the package is
114
117
  // absent, which is the one thing ADR-150/ADR-380 forbid.
115
- await import(packageName);
118
+ const mod = await import(packageName);
119
+ if (requiredExports.length > 0) {
120
+ const missing = requiredExports.filter((name) => typeof mod[name] !== 'function');
121
+ if (missing.length > 0) {
122
+ return {
123
+ configured: false,
124
+ reason: `${packageName} resolved but does not export ${missing.join(', ')}; the installed SDK API is not the one this command implements`,
125
+ endpoint,
126
+ };
127
+ }
128
+ }
116
129
  return { configured: true, reason: `${packageName} resolved`, endpoint };
117
130
  }
118
131
  catch (error) {
@@ -54,7 +54,7 @@ const joinCommand = {
54
54
  output.printError(`Invalid namespace "${namespace}": ${nsCheck.error}`);
55
55
  return { success: false, exitCode: 1 };
56
56
  }
57
- const status = await detectAgntcyRuntime();
57
+ const status = await detectAgntcyRuntime(process.env, AGNTCY_PACKAGE_NAME, ['joinGroup']);
58
58
  if (!status.configured) {
59
59
  output.printInfo(AGNTCY_NOT_CONFIGURED_MESSAGE);
60
60
  output.printInfo(`Namespace "${namespace}" was not joined via SLIM. Local swarm/hive-mind coordination ` +
@@ -37,7 +37,7 @@ const useCommand = {
37
37
  output.printInfo('Local transport remains the default and needs no explicit "use".');
38
38
  return { success: false, exitCode: 1 };
39
39
  }
40
- const status = await detectAgntcyRuntime();
40
+ const status = await detectAgntcyRuntime(process.env, AGNTCY_PACKAGE_NAME, ['createSlimTransport']);
41
41
  if (!status.configured) {
42
42
  output.printInfo(AGNTCY_NOT_CONFIGURED_MESSAGE);
43
43
  output.printInfo('Active transport remains: local (in-process hooks routing).');
@@ -110,6 +110,8 @@ const allCommand = {
110
110
  ],
111
111
  async action(ctx) {
112
112
  const { flags } = ctx;
113
+ // The parser stores `--dry-run` as `dryRun`.
114
+ const dryRun = flags.dryRun === true || flags['dry-run'] === true;
113
115
  process.env.CLAUDE_FLOW_FORCE_UPDATE = 'true';
114
116
  try {
115
117
  output.printInfo('Checking for updates...');
@@ -135,14 +137,14 @@ const allCommand = {
135
137
  }
136
138
  }
137
139
  output.printInfo(`Updating ${results.length} package(s)...`);
138
- const updateResults = await executeMultipleUpdates(results, installedPackages, flags['dry-run']);
140
+ const updateResults = await executeMultipleUpdates(results, installedPackages, dryRun);
139
141
  const successful = updateResults.filter((r) => r.success);
140
142
  const failed = updateResults.filter((r) => !r.success);
141
143
  output.writeln();
142
- output.writeln(output.highlight(flags['dry-run'] ? '═══ Dry Run - Would Update ═══' : '═══ Update Results ═══'));
144
+ output.writeln(output.highlight(dryRun ? '═══ Dry Run - Would Update ═══' : '═══ Update Results ═══'));
143
145
  output.writeln();
144
146
  if (successful.length > 0) {
145
- output.printSuccess(`${successful.length} package(s) ${flags['dry-run'] ? 'would be ' : ''}updated:`);
147
+ output.printSuccess(`${successful.length} package(s) ${dryRun ? 'would be ' : ''}updated:`);
146
148
  for (const r of successful) {
147
149
  output.writeln(` ${output.success('✓')} ${r.package}@${r.version}`);
148
150
  }
@@ -33,6 +33,9 @@ import { performanceTools } from './mcp-tools/performance-tools.js';
33
33
  import { githubTools } from './mcp-tools/github-tools.js';
34
34
  import { daaTools } from './mcp-tools/daa-tools.js';
35
35
  import { coordinationTools } from './mcp-tools/coordination-tools.js';
36
+ // ruRE Phase 1 (plugins/ruflo-rure ADR-0001/0002) — JS/Electron static
37
+ // analysis. Always registered: pure-TS, no native engine dependency.
38
+ import { rureTools } from './mcp-tools/rure-tools.js';
36
39
  import { browserTools } from './mcp-tools/browser-tools.js';
37
40
  import { browserSessionTools } from './mcp-tools/browser-session-tools.js';
38
41
  // ADR-175 — page-agent natural-language intent layer (browser_act). Always
@@ -150,6 +153,7 @@ registerTools([
150
153
  ...githubTools,
151
154
  ...daaTools,
152
155
  ...coordinationTools,
156
+ ...rureTools,
153
157
  ...getBrowserTools(),
154
158
  ...getBrowserSessionTools(),
155
159
  ...getBrowserIntentTools(),
@@ -98,6 +98,13 @@ function validateRoomId(roomId) {
98
98
  if (!/^[A-Za-z0-9_.\-:/@#]+$/.test(roomId)) {
99
99
  throw new Error('roomId may only contain [A-Za-z0-9_.\\-:/@#]');
100
100
  }
101
+ // `.` and `/` are allowed chars, so reject `..` explicitly. The roomId is
102
+ // joined into a file path in roomLogPath; without this check a value like
103
+ // ../../../../tmp/x escapes the .agentbbs directory. Matches the validator
104
+ // in agentbbs-federation.ts.
105
+ if (roomId.includes('..')) {
106
+ throw new Error('roomId must not contain ..');
107
+ }
101
108
  return roomId;
102
109
  }
103
110
  function ensureDir(dir) {
@@ -411,6 +411,22 @@ export const CAPABILITY_DOMAINS = [
411
411
  useWhen: 'Interacting with web surfaces or testing endpoints under explicit network policy.',
412
412
  verifyBeforeUse: ['Check network target policy, authentication, side effects, and runtime provider health.'],
413
413
  },
414
+ {
415
+ id: 'reverse-engineering',
416
+ name: 'Reverse Engineering (ruRE)',
417
+ prefixes: ['rure_'],
418
+ description: 'Static JS/Electron analysis: evidence-sourced module/import/export graphs and Electron process-boundary detection. Parses only, never executes analyzed code; Phase 1 of ADR-0001 — native binary, .NET, EVM and dynamic-instrumentation analysis are future phases.',
419
+ taskSignals: ['reverse engineer', 'how does this feature work', 'electron app', 'asar', 'decompile', 'static analysis'],
420
+ commands: [],
421
+ skills: ['analyze-app'],
422
+ agents: ['reverse-engineer'],
423
+ maturity: 'experimental',
424
+ authority: 'capability-plane',
425
+ risk: 'reversible',
426
+ riskFlags: ['read', 'local-write', 'credential-pii'],
427
+ useWhen: 'Understanding or reimplementing a feature from a local app you are authorized to study; refuses DRM/license-circumvention requests.',
428
+ verifyBeforeUse: ['Confirm authorization context (own app, licensed target, or research/CTF). Never trust redacted-content markers as absent data — report them as withheld.'],
429
+ },
414
430
  {
415
431
  id: 'github-delivery',
416
432
  name: 'GitHub & Delivery',
@@ -554,6 +570,7 @@ const TOOL_OWNERSHIP = [
554
570
  { prefixes: ['guidance_'], packageOwner: '@claude-flow/guidance', pluginOwner: 'ruflo-core' },
555
571
  { prefixes: ['seraphina_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-core' },
556
572
  { prefixes: ['x_federation_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-x-gateway' },
573
+ { prefixes: ['rure_'], packageOwner: '@claude-flow/cli', pluginOwner: 'ruflo-rure' },
557
574
  { prefixes: ['team_'], packageOwner: '@claude-flow/cli' },
558
575
  ];
559
576
  function ownershipForTool(toolName, definition) {
@@ -151,26 +151,34 @@ export const daaTools = [
151
151
  if (!agent) {
152
152
  return { success: false, error: 'Agent not found' };
153
153
  }
154
- const performanceScore = input.performanceScore || 0.8;
154
+ // #4426: `|| 0.8` turned a failing 0 (and a missing score) into a 0.8
155
+ // success. A given score must be in [0, 1]; a missing one records none.
156
+ const performanceScore = input.performanceScore === undefined ? null : input.performanceScore;
157
+ if (performanceScore !== null && (typeof performanceScore !== 'number' || !Number.isFinite(performanceScore) || performanceScore < 0 || performanceScore > 1)) {
158
+ return { success: false, error: 'performanceScore must be a number between 0 and 1' };
159
+ }
155
160
  // Update agent metrics
156
161
  agent.metrics.adaptations++;
157
- agent.metrics.successRate = (agent.metrics.successRate + performanceScore) / 2;
162
+ if (performanceScore !== null)
163
+ agent.metrics.successRate = (agent.metrics.successRate + performanceScore) / 2;
158
164
  agent.lastActivity = new Date().toISOString();
159
165
  agent.status = 'active';
160
166
  saveDAAStore(store);
161
167
  // Store adaptation feedback in AgentDB for pattern learning (backward compat: JSON store above)
162
168
  let _storedIn = 'json-store';
163
- try {
164
- const bridge = await import('../memory/memory-bridge.js');
165
- await bridge.bridgeRecordFeedback({
166
- taskId: `adapt-${agentId}-${agent.metrics.adaptations}`,
167
- success: performanceScore >= 0.5,
168
- quality: performanceScore,
169
- agent: agentId,
170
- });
171
- _storedIn = 'agentdb';
169
+ if (performanceScore !== null) {
170
+ try {
171
+ const bridge = await import('../memory/memory-bridge.js');
172
+ await bridge.bridgeRecordFeedback({
173
+ taskId: `adapt-${agentId}-${agent.metrics.adaptations}`,
174
+ success: performanceScore >= 0.5,
175
+ quality: performanceScore,
176
+ agent: agentId,
177
+ });
178
+ _storedIn = 'agentdb';
179
+ }
180
+ catch { /* AgentDB not available */ }
172
181
  }
173
- catch { /* AgentDB not available */ }
174
182
  return {
175
183
  success: true,
176
184
  agentId,
@@ -70,17 +70,25 @@ function run(cmd, cwd) {
70
70
  * strings stay literal.
71
71
  */
72
72
  function runArgv(file, args, cwd) {
73
+ const r = runArgvResult(file, args, cwd);
74
+ return 'out' in r ? r.out : null;
75
+ }
76
+ /** runArgv that keeps the failure text (stderr, else the error message) for reporting. */
77
+ function runArgvResult(file, args, cwd) {
73
78
  try {
74
- return execFileSync(file, args, {
75
- encoding: 'utf-8',
76
- timeout: 15000,
77
- cwd: cwd || getProjectCwd(),
78
- stdio: ['pipe', 'pipe', 'pipe'],
79
- shell: false,
80
- }).trim();
79
+ return {
80
+ out: execFileSync(file, args, {
81
+ encoding: 'utf-8',
82
+ timeout: 15000,
83
+ cwd: cwd || getProjectCwd(),
84
+ stdio: ['pipe', 'pipe', 'pipe'],
85
+ shell: false,
86
+ }).trim(),
87
+ };
81
88
  }
82
- catch {
83
- return null;
89
+ catch (err) {
90
+ const e = err;
91
+ return { error: String(e.stderr ?? '').trim() || e.message || 'unknown error' };
84
92
  }
85
93
  }
86
94
  /**
@@ -255,9 +263,12 @@ export const githubTools = [
255
263
  const store = loadGitHubStore();
256
264
  const action = input.action || 'list';
257
265
  const gh = hasGhCli();
266
+ // #4424: like #2963 for issues — owner/repo were validated but never
267
+ // passed to `gh`, so PR actions (including merge/close) hit the cwd's repo.
268
+ const repoArgs = input.owner && input.repo ? ['--repo', `${input.owner}/${input.repo}`] : [];
258
269
  if (action === 'list') {
259
270
  if (gh) {
260
- const raw = run('gh pr list --state all --limit 20 --json number,title,state,headRefName,createdAt');
271
+ const raw = runArgv('gh', ['pr', 'list', '--state', 'all', '--limit', '20', '--json', 'number,title,state,headRefName,createdAt', ...repoArgs]);
261
272
  if (raw) {
262
273
  try {
263
274
  const prs = JSON.parse(raw);
@@ -285,6 +296,7 @@ export const githubTools = [
285
296
  '--base', baseBranch,
286
297
  '--head', headBranch,
287
298
  '--body', body,
299
+ ...repoArgs,
288
300
  ]);
289
301
  if (result) {
290
302
  return { success: true, _real: true, action: 'created', url: result };
@@ -306,6 +318,7 @@ export const githubTools = [
306
318
  const raw = runArgv('gh', [
307
319
  'pr', 'view', String(prNumber),
308
320
  '--json', 'number,title,state,body,additions,deletions,changedFiles,reviews,mergeable,statusCheckRollup',
321
+ ...repoArgs,
309
322
  ]);
310
323
  if (raw) {
311
324
  try {
@@ -316,36 +329,28 @@ export const githubTools = [
316
329
  }
317
330
  return { success: false, error: prNumber ? 'gh CLI not available or PR not found. Install gh: https://cli.github.com' : 'prNumber is required (positive integer) for review.' };
318
331
  }
319
- if (action === 'merge') {
320
- const prNumber = toPositiveInt(input.prNumber);
321
- if (gh && prNumber) {
322
- const result = runArgv('gh', ['pr', 'merge', String(prNumber), '--merge']);
323
- if (result !== null) {
324
- return { success: true, _real: true, action: 'merged', prNumber, mergedAt: new Date().toISOString() };
325
- }
326
- }
327
- // Fallback: local store
328
- const prKey = prNumber ? Object.keys(store.prs).find(k => k.includes(String(prNumber))) : undefined;
329
- if (prKey && store.prs[prKey]) {
330
- store.prs[prKey].status = 'merged';
331
- saveGitHubStore(store);
332
- }
333
- return { success: true, source: 'local-store', action: 'merged', prNumber, mergedAt: new Date().toISOString() };
334
- }
335
- if (action === 'close') {
332
+ if (action === 'merge' || action === 'close') {
333
+ // #4424: a failed `gh` call, or a local fallback that matched nothing,
334
+ // used to be reported as success. Fail instead; offline, only update a
335
+ // local record whose id is exactly `pr-<prNumber>`.
336
336
  const prNumber = toPositiveInt(input.prNumber);
337
- if (gh && prNumber) {
338
- const result = runArgv('gh', ['pr', 'close', String(prNumber)]);
339
- if (result !== null) {
340
- return { success: true, _real: true, action: 'closed', prNumber, closedAt: new Date().toISOString() };
337
+ if (!prNumber)
338
+ return { success: false, error: `prNumber is required (positive integer) for ${action}.` };
339
+ const status = action === 'merge' ? 'merged' : 'closed';
340
+ if (gh) {
341
+ const argv = action === 'merge' ? ['pr', 'merge', String(prNumber), '--merge'] : ['pr', 'close', String(prNumber)];
342
+ const r = runArgvResult('gh', [...argv, ...repoArgs]);
343
+ if ('error' in r) {
344
+ return { success: false, source: 'gh-cli', error: `gh pr ${action} ${prNumber} failed: ${r.error}` };
341
345
  }
346
+ return { success: true, _real: true, action: status, prNumber, [`${status}At`]: new Date().toISOString() };
342
347
  }
343
- const prKey = prNumber ? Object.keys(store.prs).find(k => k.includes(String(prNumber))) : undefined;
344
- if (prKey && store.prs[prKey]) {
345
- store.prs[prKey].status = 'closed';
346
- saveGitHubStore(store);
347
- }
348
- return { success: true, source: 'local-store', action: 'closed', prNumber, closedAt: new Date().toISOString() };
348
+ const pr = store.prs[`pr-${prNumber}`];
349
+ if (!pr)
350
+ return { success: false, source: 'local-store', error: `gh CLI not available and no local PR pr-${prNumber}` };
351
+ pr.status = status;
352
+ saveGitHubStore(store);
353
+ return { success: true, source: 'local-store', action: status, prNumber, [`${status}At`]: new Date().toISOString() };
349
354
  }
350
355
  return { success: false, error: 'Unknown action' };
351
356
  },
@@ -435,51 +440,49 @@ export const githubTools = [
435
440
  saveGitHubStore(store);
436
441
  return { success: true, source: 'local-store', action: 'created', issue };
437
442
  }
438
- if (action === 'update') {
443
+ if (action === 'update' || action === 'close') {
444
+ // #4424: same contract as github_pr_manage merge/close — a failed `gh`
445
+ // call or an unmatched local fallback is a failure, not a success.
439
446
  const issueNumber = toPositiveInt(input.issueNumber);
440
- if (gh && issueNumber) {
441
- const argv = ['issue', 'edit', String(issueNumber)];
442
- if (input.title)
443
- argv.push('--title', input.title);
444
- if (input.labels) {
445
- const labels = sanitizeLabels(input.labels);
446
- if (labels === null)
447
- return { success: false, error: 'labels contains disallowed characters' };
448
- if (labels.length > 0)
447
+ if (!issueNumber)
448
+ return { success: false, error: `issueNumber is required (positive integer) for ${action}.` };
449
+ const labels = input.labels === undefined ? undefined : sanitizeLabels(input.labels);
450
+ if (labels === null)
451
+ return { success: false, error: 'labels contains disallowed characters' };
452
+ if (gh) {
453
+ const argv = ['issue', action === 'update' ? 'edit' : 'close', String(issueNumber)];
454
+ if (action === 'update') {
455
+ if (input.title)
456
+ argv.push('--title', input.title);
457
+ if (labels && labels.length > 0)
449
458
  argv.push('--add-label', labels.join(','));
459
+ if (argv.length === 3)
460
+ return { success: false, error: 'update requires title or labels' };
450
461
  }
451
- if (argv.length > 3) {
452
- const result = runArgv('gh', [...argv, ...repoArgs]);
453
- if (result !== null)
454
- return { success: true, _real: true, action: 'updated', issueNumber };
455
- }
456
- }
457
- const issueKey = issueNumber ? Object.keys(store.issues).find(k => k.includes(String(issueNumber))) : undefined;
458
- if (issueKey && store.issues[issueKey]) {
459
- if (input.title)
460
- store.issues[issueKey].title = input.title;
461
- if (input.labels) {
462
- const labels = sanitizeLabels(input.labels);
463
- if (labels !== null)
464
- store.issues[issueKey].labels = labels;
462
+ const r = runArgvResult('gh', [...argv, ...repoArgs]);
463
+ if ('error' in r) {
464
+ return { success: false, source: 'gh-cli', error: `gh issue ${argv[1]} ${issueNumber} failed: ${r.error}` };
465
465
  }
466
- saveGitHubStore(store);
466
+ return action === 'update'
467
+ ? { success: true, _real: true, action: 'updated', issueNumber }
468
+ : { success: true, _real: true, action: 'closed', issueNumber, closedAt: new Date().toISOString() };
467
469
  }
468
- return { success: true, source: 'local-store', action: 'updated', issueNumber };
469
- }
470
- if (action === 'close') {
471
- const issueNumber = toPositiveInt(input.issueNumber);
472
- if (gh && issueNumber) {
473
- const result = runArgv('gh', ['issue', 'close', String(issueNumber), ...repoArgs]);
474
- if (result !== null)
475
- return { success: true, _real: true, action: 'closed', issueNumber, closedAt: new Date().toISOString() };
470
+ const issue = store.issues[`issue-${issueNumber}`];
471
+ if (!issue)
472
+ return { success: false, source: 'local-store', error: `gh CLI not available and no local issue issue-${issueNumber}` };
473
+ if (action === 'close') {
474
+ issue.status = 'closed';
476
475
  }
477
- const issueKey = issueNumber ? Object.keys(store.issues).find(k => k.includes(String(issueNumber))) : undefined;
478
- if (issueKey && store.issues[issueKey]) {
479
- store.issues[issueKey].status = 'closed';
480
- saveGitHubStore(store);
476
+ else {
477
+ if (input.title)
478
+ issue.title = input.title;
479
+ if (labels)
480
+ issue.labels = labels;
481
481
  }
482
- return { success: true, source: 'local-store', action: 'closed', issueNumber, closedAt: new Date().toISOString() };
482
+ saveGitHubStore(store);
483
+ return action === 'update'
484
+ ? { success: true, source: 'local-store', action: 'updated', issueNumber }
485
+ : { success: true, source: 'local-store', action: 'closed', issueNumber, closedAt: new Date().toISOString() };
483
486
  }
484
487
  return { success: false, error: 'Unknown action' };
485
488
  },
@@ -899,7 +899,7 @@ export const neuralTools = [
899
899
  properties: {
900
900
  modelId: { type: 'string', description: 'Model ID to compress' },
901
901
  method: { type: 'string', enum: ['quantize', 'prune', 'distill'], description: 'Compression method' },
902
- targetSize: { type: 'number', description: 'Target size reduction (0-1)' },
902
+ targetSize: { type: 'number', description: 'Not used: quantize (#4195) and prune (#4423) are unavailable, and distill merges by similarity' },
903
903
  },
904
904
  },
905
905
  handler: async (input) => {
@@ -910,7 +910,6 @@ export const neuralTools = [
910
910
  }
911
911
  const store = loadNeuralStore();
912
912
  const method = input.method || 'quantize';
913
- const targetReduction = input.targetSize || 0.5;
914
913
  const patterns = Object.values(store.patterns);
915
914
  if (patterns.length === 0) {
916
915
  return { success: false, error: 'No patterns to compress. Train patterns first with neural_train.' };
@@ -936,23 +935,18 @@ export const neuralTools = [
936
935
  // Resolve provider only for operations that actually use embeddings.
937
936
  await ensureEmbeddings();
938
937
  if (method === 'prune') {
939
- // Prune patterns with low usage count below threshold (targetReduction as min usage)
940
- const threshold = targetReduction;
941
- const toRemove = [];
942
- for (const [id, pattern] of Object.entries(store.patterns)) {
943
- if ((pattern.usageCount || 0) < threshold)
944
- toRemove.push(id);
945
- }
946
- for (const id of toRemove)
947
- delete store.patterns[id];
948
- saveNeuralStore(store);
938
+ // #4423: prune deleted patterns with usageCount below targetSize, but
939
+ // nothing in the neural store records usage (patterns are written with
940
+ // 0 and only max-merged), so any targetSize > 0, including the old 0.5
941
+ // default, deleted and persisted every pattern. Refuse until usage is
942
+ // tracked, the same way quantize refuses (#4195). The store is untouched.
949
943
  return {
950
- success: true, _real: true, method,
951
- embeddingProvider: embeddingServiceName,
952
- threshold,
953
- patternsRemoved: toRemove.length,
944
+ success: false,
945
+ error: 'Pruning is unavailable: neural patterns do not record usage, so there is no signal to choose which to remove.',
946
+ method,
947
+ patternsRemoved: 0,
954
948
  patternsBefore: beforeCount,
955
- patternsAfter: Object.keys(store.patterns).length,
949
+ patternsAfter: beforeCount,
956
950
  };
957
951
  }
958
952
  if (method === 'distill') {
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ruRE Phase 1 MCP Tools — JS/Electron static analysis.
3
+ *
4
+ * See plugins/ruflo-rure/docs/adrs/0001-scope-and-dual-use-policy.md and
5
+ * 0002-phase1-architecture.md. These tools parse local files; they never
6
+ * execute analyzed code, and all extracted text is routed through the
7
+ * AIDefence gate before being returned to the caller.
8
+ */
9
+ import type { MCPTool } from './types.js';
10
+ export declare const rureTools: MCPTool[];
11
+ //# sourceMappingURL=rure-tools.d.ts.map