claude-flow 3.59.17 → 3.60.1

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 (52) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +2 -1
  3. package/node_modules/@claude-flow/mcp/dist/session-manager.d.ts.map +1 -1
  4. package/node_modules/@claude-flow/mcp/dist/session-manager.js +4 -1
  5. package/node_modules/@claude-flow/mcp/dist/session-manager.js.map +1 -1
  6. package/node_modules/@claude-flow/mcp/package.json +1 -1
  7. package/package.json +3 -2
  8. package/v3/@claude-flow/cli/README.md +2 -1
  9. package/v3/@claude-flow/cli/catalog-manifest.json +4 -4
  10. package/v3/@claude-flow/cli/dist/src/commands/agntcy/publish.js +1 -1
  11. package/v3/@claude-flow/cli/dist/src/commands/agntcy/runtime.d.ts +4 -1
  12. package/v3/@claude-flow/cli/dist/src/commands/agntcy/runtime.js +15 -2
  13. package/v3/@claude-flow/cli/dist/src/commands/agntcy/swarm-join.js +1 -1
  14. package/v3/@claude-flow/cli/dist/src/commands/agntcy/transport.js +1 -1
  15. package/v3/@claude-flow/cli/dist/src/commands/cleanup.js +1 -1
  16. package/v3/@claude-flow/cli/dist/src/commands/update.js +5 -3
  17. package/v3/@claude-flow/cli/dist/src/mcp-client.js +4 -0
  18. package/v3/@claude-flow/cli/dist/src/mcp-tools/agentbbs-tools.js +7 -0
  19. package/v3/@claude-flow/cli/dist/src/mcp-tools/capability-brain.js +17 -0
  20. package/v3/@claude-flow/cli/dist/src/mcp-tools/daa-tools.js +20 -12
  21. package/v3/@claude-flow/cli/dist/src/mcp-tools/github-tools.js +78 -75
  22. package/v3/@claude-flow/cli/dist/src/mcp-tools/neural-tools.js +11 -17
  23. package/v3/@claude-flow/cli/dist/src/mcp-tools/rure-tools.d.ts +11 -0
  24. package/v3/@claude-flow/cli/dist/src/mcp-tools/rure-tools.js +359 -0
  25. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +17 -0
  26. package/v3/@claude-flow/cli/dist/src/memory/memory-bridge.js +18 -9
  27. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.d.ts +12 -0
  28. package/v3/@claude-flow/cli/dist/src/mods/claude-installs.js +21 -1
  29. package/v3/@claude-flow/cli/dist/src/rure/aidefence-gate.d.ts +25 -0
  30. package/v3/@claude-flow/cli/dist/src/rure/aidefence-gate.js +63 -0
  31. package/v3/@claude-flow/cli/dist/src/rure/analyze-js-app.d.ts +17 -0
  32. package/v3/@claude-flow/cli/dist/src/rure/analyze-js-app.js +26 -0
  33. package/v3/@claude-flow/cli/dist/src/rure/binary-parse.d.ts +38 -0
  34. package/v3/@claude-flow/cli/dist/src/rure/binary-parse.js +347 -0
  35. package/v3/@claude-flow/cli/dist/src/rure/dotnet-parse.d.ts +30 -0
  36. package/v3/@claude-flow/cli/dist/src/rure/dotnet-parse.js +299 -0
  37. package/v3/@claude-flow/cli/dist/src/rure/evm-parse.d.ts +52 -0
  38. package/v3/@claude-flow/cli/dist/src/rure/evm-parse.js +233 -0
  39. package/v3/@claude-flow/cli/dist/src/rure/extract-asar.d.ts +33 -0
  40. package/v3/@claude-flow/cli/dist/src/rure/extract-asar.js +70 -0
  41. package/v3/@claude-flow/cli/dist/src/rure/find-feature.d.ts +26 -0
  42. package/v3/@claude-flow/cli/dist/src/rure/find-feature.js +69 -0
  43. package/v3/@claude-flow/cli/dist/src/rure/ts-walk.d.ts +47 -0
  44. package/v3/@claude-flow/cli/dist/src/rure/ts-walk.js +158 -0
  45. package/v3/@claude-flow/cli/dist/src/rure/walk-files.d.ts +21 -0
  46. package/v3/@claude-flow/cli/dist/src/rure/walk-files.js +71 -0
  47. package/v3/@claude-flow/cli/dist/src/services/worker-daemon.js +17 -3
  48. package/v3/@claude-flow/cli/dist/src/update/checker.d.ts +1 -0
  49. package/v3/@claude-flow/cli/dist/src/update/checker.js +4 -2
  50. package/v3/@claude-flow/cli/package.json +4 -3
  51. package/.claude/.proven-config-version +0 -1
  52. package/.claude/proven-config.json +0 -42
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ruRE Phase 1 — bounded lexical/structural feature search (ADR-0002).
3
+ *
4
+ * Deterministic, auditable: scores files by term overlap between the query
5
+ * and each file's identifiers/strings/comments, plus raw substring hits.
6
+ * Not semantic/LLM-based matching, by design — Phase 1 stays explainable.
7
+ */
8
+ import { type Evidence } from './ts-walk.js';
9
+ export interface FeatureMatch {
10
+ file: string;
11
+ lineRange: {
12
+ start: number;
13
+ end: number;
14
+ };
15
+ snippet: string;
16
+ score: number;
17
+ }
18
+ export interface FindFeatureResult {
19
+ matches: FeatureMatch[];
20
+ filesScanned: number;
21
+ truncated: boolean;
22
+ skippedFiles: number;
23
+ }
24
+ export declare function findFeature(root: string, query: string, maxMatches?: number): Promise<FindFeatureResult>;
25
+ export type { Evidence };
26
+ //# sourceMappingURL=find-feature.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * ruRE Phase 1 — bounded lexical/structural feature search (ADR-0002).
3
+ *
4
+ * Deterministic, auditable: scores files by term overlap between the query
5
+ * and each file's identifiers/strings/comments, plus raw substring hits.
6
+ * Not semantic/LLM-based matching, by design — Phase 1 stays explainable.
7
+ */
8
+ import { walkAnalyzableFiles } from './walk-files.js';
9
+ import { analyzeModuleSource } from './ts-walk.js';
10
+ function tokenize(text) {
11
+ return text
12
+ .toLowerCase()
13
+ .split(/[^a-z0-9]+/)
14
+ .filter((t) => t.length > 1);
15
+ }
16
+ function scoreLine(lineLower, queryTerms) {
17
+ let score = 0;
18
+ for (const term of queryTerms) {
19
+ if (lineLower.includes(term))
20
+ score += 1;
21
+ }
22
+ return score;
23
+ }
24
+ export async function findFeature(root, query, maxMatches = 10) {
25
+ const queryTerms = Array.from(new Set(tokenize(query)));
26
+ const { files, truncated } = walkAnalyzableFiles(root);
27
+ const candidates = [];
28
+ let skippedFiles = 0;
29
+ for (const f of files) {
30
+ const lines = f.text.split('\n');
31
+ // Use the parser for structural signal (imports/exports/electron
32
+ // boundaries contribute to relevance) without executing anything.
33
+ let structuralBoost = 0;
34
+ try {
35
+ const analysis = await analyzeModuleSource(f.path, f.text);
36
+ const names = [
37
+ ...analysis.imports.flatMap((i) => [i.source, ...i.names]),
38
+ ...analysis.exports.map((e) => e.name),
39
+ ].join(' ').toLowerCase();
40
+ structuralBoost = scoreLine(names, queryTerms) * 0.5;
41
+ }
42
+ catch {
43
+ skippedFiles += 1; // unparseable: lexical score alone still applies
44
+ }
45
+ let bestLine = -1;
46
+ let bestScore = 0;
47
+ for (let i = 0; i < lines.length; i++) {
48
+ const s = scoreLine(lines[i].toLowerCase(), queryTerms);
49
+ if (s > bestScore) {
50
+ bestScore = s;
51
+ bestLine = i;
52
+ }
53
+ }
54
+ const total = bestScore + structuralBoost;
55
+ if (total > 0 && bestLine >= 0) {
56
+ const start = Math.max(0, bestLine - 2);
57
+ const end = Math.min(lines.length - 1, bestLine + 2);
58
+ candidates.push({
59
+ file: f.path,
60
+ lineRange: { start: start + 1, end: end + 1 },
61
+ snippet: lines.slice(start, end + 1).join('\n'),
62
+ score: total,
63
+ });
64
+ }
65
+ }
66
+ candidates.sort((a, b) => b.score - a.score);
67
+ return { matches: candidates.slice(0, maxMatches), filesScanned: files.length, truncated, skippedFiles };
68
+ }
69
+ //# sourceMappingURL=find-feature.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * ruRE Phase 1 — static analysis via the TypeScript compiler API (ADR-0002).
3
+ *
4
+ * Parses JS/MJS/CJS/TS/TSX source into an AST and inspects it. This module
5
+ * only reads and inspects; it never calls eval, vm, child_process, or a
6
+ * dynamic require on analyzed content (ADR-0001 "parses, never executes").
7
+ *
8
+ * The `typescript` package is loaded lazily (not as a top-level import):
9
+ * mcp-tools/rure-tools.ts is registered into every CLI invocation's tool
10
+ * set via mcp-client.ts, so a static `import ts from 'typescript'` here
11
+ * would load the full compiler into memory on every `ruflo` command, not
12
+ * just the ones that use ruRE. Same reasoning as aidefence-gate.ts's lazy
13
+ * load, and the same failure shape as the #2561 cold-start regression.
14
+ */
15
+ export interface Evidence {
16
+ file: string;
17
+ /** 1-indexed, inclusive. */
18
+ lineStart: number;
19
+ lineEnd: number;
20
+ }
21
+ export interface ImportRecord {
22
+ source: string;
23
+ names: string[];
24
+ evidence: Evidence;
25
+ }
26
+ export interface ExportRecord {
27
+ name: string;
28
+ evidence: Evidence;
29
+ }
30
+ export interface ElectronBoundaryHit {
31
+ kind: 'require-electron' | 'ipcMain' | 'ipcRenderer' | 'contextBridge' | 'nodeIntegration-flag';
32
+ detail: string;
33
+ evidence: Evidence;
34
+ }
35
+ export interface ModuleAnalysis {
36
+ file: string;
37
+ imports: ImportRecord[];
38
+ exports: ExportRecord[];
39
+ electronBoundaries: ElectronBoundaryHit[];
40
+ /** Raw text snippets this module's analysis quoted, for the AIDefence gate to scan. */
41
+ rawSnippets: string[];
42
+ }
43
+ /** No `typescript` dependency — pure extension check, safe to call eagerly. */
44
+ export declare function isAnalyzableFile(path: string): boolean;
45
+ /** Parses one file's source text into a structural analysis. Never executes it. */
46
+ export declare function analyzeModuleSource(file: string, sourceText: string): Promise<ModuleAnalysis>;
47
+ //# sourceMappingURL=ts-walk.d.ts.map
@@ -0,0 +1,158 @@
1
+ /**
2
+ * ruRE Phase 1 — static analysis via the TypeScript compiler API (ADR-0002).
3
+ *
4
+ * Parses JS/MJS/CJS/TS/TSX source into an AST and inspects it. This module
5
+ * only reads and inspects; it never calls eval, vm, child_process, or a
6
+ * dynamic require on analyzed content (ADR-0001 "parses, never executes").
7
+ *
8
+ * The `typescript` package is loaded lazily (not as a top-level import):
9
+ * mcp-tools/rure-tools.ts is registered into every CLI invocation's tool
10
+ * set via mcp-client.ts, so a static `import ts from 'typescript'` here
11
+ * would load the full compiler into memory on every `ruflo` command, not
12
+ * just the ones that use ruRE. Same reasoning as aidefence-gate.ts's lazy
13
+ * load, and the same failure shape as the #2561 cold-start regression.
14
+ */
15
+ let tsModulePromise = null;
16
+ function loadTs() {
17
+ tsModulePromise ??= import('typescript');
18
+ return tsModulePromise;
19
+ }
20
+ const ANALYZABLE_EXT = new Set(['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx']);
21
+ /** No `typescript` dependency — pure extension check, safe to call eagerly. */
22
+ export function isAnalyzableFile(path) {
23
+ const dot = path.lastIndexOf('.');
24
+ if (dot < 0)
25
+ return false;
26
+ return ANALYZABLE_EXT.has(path.slice(dot));
27
+ }
28
+ function scriptKindFor(ts, path) {
29
+ if (path.endsWith('.tsx'))
30
+ return ts.ScriptKind.TSX;
31
+ if (path.endsWith('.jsx'))
32
+ return ts.ScriptKind.JSX;
33
+ if (path.endsWith('.ts'))
34
+ return ts.ScriptKind.TS;
35
+ return ts.ScriptKind.JS;
36
+ }
37
+ function evidenceFor(sf, node, file) {
38
+ const start = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
39
+ const end = sf.getLineAndCharacterOfPosition(node.getEnd()).line + 1;
40
+ return { file, lineStart: start, lineEnd: end };
41
+ }
42
+ function textOf(node) {
43
+ return node.getText();
44
+ }
45
+ /** Parses one file's source text into a structural analysis. Never executes it. */
46
+ export async function analyzeModuleSource(file, sourceText) {
47
+ const ts = await loadTs();
48
+ const sf = ts.createSourceFile(file, sourceText, ts.ScriptTarget.Latest, true, scriptKindFor(ts, file));
49
+ const imports = [];
50
+ const exports = [];
51
+ const electronBoundaries = [];
52
+ const rawSnippets = [];
53
+ const pushSnippet = (n) => {
54
+ const t = textOf(n);
55
+ if (t)
56
+ rawSnippets.push(t);
57
+ };
58
+ function visit(node) {
59
+ // ESM: import ... from "x"
60
+ if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
61
+ const source = node.moduleSpecifier.text;
62
+ const names = [];
63
+ const bindings = node.importClause?.namedBindings;
64
+ if (node.importClause?.name)
65
+ names.push(node.importClause.name.text);
66
+ if (bindings && ts.isNamedImports(bindings)) {
67
+ for (const el of bindings.elements)
68
+ names.push(el.name.text);
69
+ }
70
+ else if (bindings && ts.isNamespaceImport(bindings)) {
71
+ names.push(`* as ${bindings.name.text}`);
72
+ }
73
+ imports.push({ source, names, evidence: evidenceFor(sf, node, file) });
74
+ pushSnippet(node);
75
+ if (source === 'electron') {
76
+ electronBoundaries.push({
77
+ kind: 'require-electron',
78
+ detail: `import from "electron": ${names.join(', ') || '(side-effect only)'}`,
79
+ evidence: evidenceFor(sf, node, file),
80
+ });
81
+ }
82
+ }
83
+ // ESM: export ... (named, default, declarations)
84
+ if (ts.isExportDeclaration(node)) {
85
+ const clause = node.exportClause;
86
+ if (clause && ts.isNamedExports(clause)) {
87
+ for (const el of clause.elements) {
88
+ exports.push({ name: el.name.text, evidence: evidenceFor(sf, node, file) });
89
+ }
90
+ }
91
+ pushSnippet(node);
92
+ }
93
+ if (ts.isExportAssignment(node)) {
94
+ exports.push({ name: 'default', evidence: evidenceFor(sf, node, file) });
95
+ pushSnippet(node);
96
+ }
97
+ if ((ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node) || ts.isVariableStatement(node)) &&
98
+ hasExportModifier(ts, node)) {
99
+ const name = exportedNameOf(ts, node);
100
+ if (name)
101
+ exports.push({ name, evidence: evidenceFor(sf, node, file) });
102
+ }
103
+ // CJS: require("x") / require("electron")
104
+ if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'require') {
105
+ const arg = node.arguments[0];
106
+ if (arg && ts.isStringLiteral(arg)) {
107
+ const source = arg.text;
108
+ imports.push({ source, names: [], evidence: evidenceFor(sf, node, file) });
109
+ pushSnippet(node);
110
+ if (source === 'electron') {
111
+ electronBoundaries.push({
112
+ kind: 'require-electron',
113
+ detail: `require("electron")`,
114
+ evidence: evidenceFor(sf, node, file),
115
+ });
116
+ }
117
+ }
118
+ }
119
+ // Electron process-boundary signals: ipcMain / ipcRenderer / contextBridge usage,
120
+ // and the nodeIntegration webPreferences flag (renderer-trust indicator).
121
+ if (ts.isPropertyAccessExpression(node) || ts.isIdentifier(node)) {
122
+ const text = ts.isIdentifier(node) ? node.text : node.name.text;
123
+ if (text === 'ipcMain' || text === 'ipcRenderer' || text === 'contextBridge') {
124
+ electronBoundaries.push({
125
+ kind: text,
126
+ detail: textOf(node).slice(0, 160),
127
+ evidence: evidenceFor(sf, node, file),
128
+ });
129
+ pushSnippet(node);
130
+ }
131
+ }
132
+ if (ts.isPropertyAssignment(node) &&
133
+ ts.isIdentifier(node.name) &&
134
+ node.name.text === 'nodeIntegration') {
135
+ electronBoundaries.push({
136
+ kind: 'nodeIntegration-flag',
137
+ detail: textOf(node).slice(0, 160),
138
+ evidence: evidenceFor(sf, node, file),
139
+ });
140
+ pushSnippet(node);
141
+ }
142
+ ts.forEachChild(node, visit);
143
+ }
144
+ visit(sf);
145
+ return { file, imports, exports, electronBoundaries, rawSnippets };
146
+ }
147
+ function hasExportModifier(ts, node) {
148
+ const mods = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
149
+ return !!mods?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);
150
+ }
151
+ function exportedNameOf(ts, node) {
152
+ if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
153
+ return node.name?.text ?? null;
154
+ }
155
+ const first = node.declarationList.declarations[0];
156
+ return first && ts.isIdentifier(first.name) ? first.name.text : null;
157
+ }
158
+ //# sourceMappingURL=ts-walk.js.map
@@ -0,0 +1,21 @@
1
+ /**
2
+ * ruRE Phase 1 — bounded directory walk shared by the analyze and
3
+ * find-feature tools. Caps file count and per-file read size so a
4
+ * maliciously or accidentally huge target can't exhaust memory.
5
+ */
6
+ export interface WalkLimits {
7
+ maxFiles: number;
8
+ maxFileBytes: number;
9
+ /** Total bytes of file text held across the whole walk. */
10
+ maxTotalBytes: number;
11
+ }
12
+ export declare const DEFAULT_WALK_LIMITS: WalkLimits;
13
+ export interface WalkedFile {
14
+ path: string;
15
+ text: string;
16
+ }
17
+ export declare function walkAnalyzableFiles(root: string, limits?: WalkLimits): {
18
+ files: WalkedFile[];
19
+ truncated: boolean;
20
+ };
21
+ //# sourceMappingURL=walk-files.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * ruRE Phase 1 — bounded directory walk shared by the analyze and
3
+ * find-feature tools. Caps file count and per-file read size so a
4
+ * maliciously or accidentally huge target can't exhaust memory.
5
+ */
6
+ import { readdirSync, lstatSync, readFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
+ import { isAnalyzableFile } from './ts-walk.js';
9
+ export const DEFAULT_WALK_LIMITS = {
10
+ maxFiles: 20_000,
11
+ maxFileBytes: 8 * 1024 * 1024, // 8 MiB per file
12
+ maxTotalBytes: 128 * 1024 * 1024, // 128 MiB across the walk
13
+ };
14
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.turbo', '.cache']);
15
+ export function walkAnalyzableFiles(root, limits = DEFAULT_WALK_LIMITS) {
16
+ const files = [];
17
+ let truncated = false;
18
+ let totalBytes = 0;
19
+ function visit(dir) {
20
+ if (truncated || files.length >= limits.maxFiles) {
21
+ truncated = truncated || files.length >= limits.maxFiles;
22
+ return;
23
+ }
24
+ let entries;
25
+ try {
26
+ entries = readdirSync(dir);
27
+ }
28
+ catch {
29
+ return;
30
+ }
31
+ for (const entry of entries) {
32
+ if (truncated)
33
+ return;
34
+ if (files.length >= limits.maxFiles) {
35
+ truncated = true;
36
+ return;
37
+ }
38
+ if (SKIP_DIRS.has(entry))
39
+ continue;
40
+ const full = join(dir, entry);
41
+ let st;
42
+ try {
43
+ st = lstatSync(full); // lstat: symlinks are never followed (loops, fan-out, escapes)
44
+ }
45
+ catch {
46
+ continue;
47
+ }
48
+ if (st.isDirectory()) {
49
+ visit(full);
50
+ }
51
+ else if (st.isFile() && isAnalyzableFile(full)) {
52
+ if (st.size > limits.maxFileBytes)
53
+ continue; // skip oversized files, don't abort the walk
54
+ if (totalBytes + st.size > limits.maxTotalBytes) {
55
+ truncated = true;
56
+ return;
57
+ }
58
+ totalBytes += st.size;
59
+ try {
60
+ files.push({ path: full, text: readFileSync(full, 'utf8') });
61
+ }
62
+ catch {
63
+ /* unreadable file, skip */
64
+ }
65
+ }
66
+ }
67
+ }
68
+ visit(root);
69
+ return { files, truncated };
70
+ }
71
+ //# sourceMappingURL=walk-files.js.map
@@ -1159,6 +1159,14 @@ export class WorkerDaemon extends EventEmitter {
1159
1159
  const state = this.workers.get(workerConfig.type);
1160
1160
  const internalConfig = workerConfig;
1161
1161
  const staggerOffset = internalConfig.offsetMs || 0;
1162
+ // #4410: only one timer may own a worker type. A redundant enable used to
1163
+ // schedule a second timeout while this.timers kept only the newer handle,
1164
+ // so the original leaked and could fire again after stop().
1165
+ const previousTimer = this.timers.get(workerConfig.type);
1166
+ if (previousTimer) {
1167
+ clearTimeout(previousTimer);
1168
+ this.timers.delete(workerConfig.type);
1169
+ }
1162
1170
  // Calculate initial delay with stagger offset
1163
1171
  let initialDelay = staggerOffset;
1164
1172
  if (state.lastRun) {
@@ -1171,8 +1179,9 @@ export class WorkerDaemon extends EventEmitter {
1171
1179
  return;
1172
1180
  // Use concurrency-controlled execution (P0 fix)
1173
1181
  await this.executeWorkerWithConcurrencyControl(workerConfig);
1174
- // Reschedule
1175
- if (this.running) {
1182
+ // Reschedule only while the daemon is running and this worker is still
1183
+ // enabled. Disabling during an in-flight run must not resurrect it.
1184
+ if (this.running && workerConfig.enabled) {
1176
1185
  const timer = setTimeout(runAndReschedule, workerConfig.intervalMs);
1177
1186
  this.timers.set(workerConfig.type, timer);
1178
1187
  state.nextRun = new Date(Date.now() + workerConfig.intervalMs);
@@ -1914,7 +1923,11 @@ export class WorkerDaemon extends EventEmitter {
1914
1923
  if (workerConfig) {
1915
1924
  workerConfig.enabled = enabled;
1916
1925
  if (enabled && this.running) {
1917
- this.scheduleWorker(workerConfig);
1926
+ // A live timer or an in-flight run will schedule the next iteration
1927
+ // itself; scheduling here would create a second chain (#4410).
1928
+ if (!this.timers.has(type) && !this.runningWorkers.has(type)) {
1929
+ this.scheduleWorker(workerConfig);
1930
+ }
1918
1931
  }
1919
1932
  else if (!enabled) {
1920
1933
  const timer = this.timers.get(type);
@@ -1922,6 +1935,7 @@ export class WorkerDaemon extends EventEmitter {
1922
1935
  clearTimeout(timer);
1923
1936
  this.timers.delete(type);
1924
1937
  }
1938
+ this.pendingWorkers = this.pendingWorkers.filter(workerType => workerType !== type);
1925
1939
  }
1926
1940
  this.saveState();
1927
1941
  }
@@ -23,6 +23,7 @@ export interface UpdateConfig {
23
23
  exclude: string[];
24
24
  }
25
25
  declare const DEFAULT_CONFIG: UpdateConfig;
26
+ export declare function getUpdateType(current: string, latest: string): 'major' | 'minor' | 'patch' | 'none';
26
27
  export declare function getInstalledVersion(packageName: string): string | null;
27
28
  export declare function checkForUpdates(config?: UpdateConfig): Promise<{
28
29
  results: UpdateCheckResult[];
@@ -45,11 +45,13 @@ async function fetchPackageInfo(packageName) {
45
45
  return null;
46
46
  }
47
47
  }
48
- function getUpdateType(current, latest) {
48
+ export function getUpdateType(current, latest) {
49
49
  if (!semver.valid(current) || !semver.valid(latest)) {
50
50
  return 'none';
51
51
  }
52
- if (semver.eq(current, latest)) {
52
+ // An update is a strictly newer version. Comparing the parts one by one called 3.60.0 (running) against 3.59.17 (latest) a
53
+ // 'patch' update, since 17 > 0, and the startup check then auto-installed the older version: a downgrade.
54
+ if (!semver.gt(latest, current)) {
53
55
  return 'none';
54
56
  }
55
57
  if (semver.major(latest) > semver.major(current)) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.59.17",
3
+ "version": "3.60.1",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",
@@ -96,13 +96,13 @@
96
96
  },
97
97
  "devDependencies": {
98
98
  "tsx": "^4.19.0",
99
- "typescript": "^5.3.0",
100
99
  "vitest": "^4.1.0"
101
100
  },
102
101
  "dependencies": {
102
+ "@electron/asar": "^4.3.1",
103
103
  "@claude-flow/cli-core": "3.7.5",
104
104
  "@claude-flow/codex": "3.0.5",
105
- "@claude-flow/mcp": "3.1.1",
105
+ "@claude-flow/mcp": "3.1.2",
106
106
  "@claude-flow/neural": "3.0.0",
107
107
  "@claude-flow/plugin-agent-federation": "1.0.1",
108
108
  "@claude-flow/security": "3.0.5",
@@ -123,6 +123,7 @@
123
123
  "inquirer": "^9.2.0",
124
124
  "semver": "7.7.3",
125
125
  "sql.js": "^1.13.0",
126
+ "typescript": "^5.3.0",
126
127
  "ws": "^8.21.0",
127
128
  "yaml": "^2.8.0",
128
129
  "zod": "^3.22.0",
@@ -1 +0,0 @@
1
- sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319
@@ -1,42 +0,0 @@
1
- {
2
- "adoptedAt": 1792673042956,
3
- "championId": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
4
- "manifest": {
5
- "schema": "ruflo.proven-config/v1",
6
- "policy": {
7
- "ref": "sha256:6141a8ea990c5063b77e090ae8f37f9c539d8aa8f58dcceb30f3a82f97e57319",
8
- "value": {
9
- "alpha": 0.3,
10
- "subjectWeight": 1,
11
- "mmrLambda": 0.5,
12
- "bodyWeight": 1.5,
13
- "typePenaltyFactor": 0.5
14
- }
15
- },
16
- "layer": "framework/node-cli",
17
- "compatibility": {
18
- "ruflo": ">=3.24.0"
19
- },
20
- "benchmark": {
21
- "corpus": "ADR-081-labelled-v1",
22
- "corpusHash": "sha256:2f700b5c363e20a3bd88ce2bc9b87bbbbaa61732c6177894c6ec37890f888982"
23
- },
24
- "receipt": {
25
- "heldOutDelta": 0.07381404928570845,
26
- "redblue": "PASS",
27
- "drift": 0,
28
- "canary": {
29
- "rollbackRate": 0,
30
- "latencyP95": 244.612458000076,
31
- "costPerTask": 0
32
- },
33
- "receiptCoverage": 1
34
- },
35
- "platform": [
36
- "linux",
37
- "macOS",
38
- "windows"
39
- ]
40
- },
41
- "previous": ""
42
- }