@claude-flow/cli 3.56.8 → 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 +4 -4
- package/.claude/helpers/hook-handler.cjs +109 -23
- 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/analyze.js +40 -29
- 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/helpers-generator.js +109 -23
- 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/parser.js +32 -22
- package/dist/src/production/monitoring.d.ts +1 -0
- package/dist/src/production/monitoring.js +19 -6
- package/dist/src/production/rate-limiter.js +5 -4
- package/dist/src/production/retry.js +3 -0
- 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/config-file-manager.js +3 -3
- 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/codex/dist/dual-mode/orchestrator.d.ts +1 -0
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +3 -1
- package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
- package/node_modules/@claude-flow/codex/dist/index.d.ts +3 -3
- package/node_modules/@claude-flow/codex/dist/index.js +1 -1
- package/node_modules/@claude-flow/codex/package.json +1 -1
- 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/node_modules/@claude-flow/security/dist/policy/envelope.js +6 -6
- package/node_modules/@claude-flow/security/dist/policy/envelope.js.map +1 -1
- package/node_modules/@claude-flow/security/package.json +1 -1
- package/package.json +7 -7
- package/plugins/ruflo-console/dist/tui.mjs +162 -160
- 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/.claude-plugin/plugin.json +1 -1
- 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/CHANGELOG.md +3 -0
- package/plugins/ruflo-metaharness/agentdb.rvf +0 -0
- package/plugins/ruflo-metaharness/agentdb.rvf.lock +0 -0
- package/plugins/ruflo-metaharness/hooks/screen.ts +10 -3
- package/plugins/ruflo-metaharness/ruvector.db +0 -0
- package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Diff Classifier for Change Analysis
|
|
3
3
|
*/
|
|
4
4
|
import { execFileSync, execFile } from 'node:child_process';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
5
6
|
import { promisify } from 'node:util';
|
|
6
7
|
const DEFAULT_CONFIG = {
|
|
7
8
|
maxDiffSize: 10000,
|
|
@@ -9,7 +10,7 @@ const DEFAULT_CONFIG = {
|
|
|
9
10
|
detectRefactoring: true,
|
|
10
11
|
minConfidence: 0.5,
|
|
11
12
|
};
|
|
12
|
-
const
|
|
13
|
+
const COMMIT_CLASSIFICATION_PATTERNS = {
|
|
13
14
|
feature: [/^feat/, /add.*feature/, /implement/, /new.*functionality/i],
|
|
14
15
|
bugfix: [/^fix/, /bug/, /patch/, /resolve.*issue/i, /hotfix/i],
|
|
15
16
|
refactor: [/^refactor/, /restructure/, /reorganize/, /cleanup/i, /rename/i],
|
|
@@ -18,10 +19,45 @@ const CLASSIFICATION_PATTERNS = {
|
|
|
18
19
|
config: [/^config/, /\.config\./, /package\.json/, /tsconfig/, /\.env/],
|
|
19
20
|
style: [/^style/, /format/, /lint/, /prettier/, /eslint/],
|
|
20
21
|
};
|
|
22
|
+
// File paths and commit subjects are different signals. Prefixes such as
|
|
23
|
+
// "fix" and words such as "bug" are useful in subjects, but match unrelated
|
|
24
|
+
// paths like src/debug/logger.ts and src/dispatcher.ts.
|
|
25
|
+
const PATH_CLASSIFICATION_PATTERNS = {
|
|
26
|
+
docs: [/(^|\/)docs?\//i, /(^|\/)readme(?:\.[^/]*)?$/i, /\.mdx?$/i],
|
|
27
|
+
test: [/(^|\/)(?:__tests__|tests?|specs?)\//i, /\.(?:test|spec)\.[^/]+$/i],
|
|
28
|
+
config: [/(^|\/)(?:configs?\/|config(?:\.[^/]*)?$)/i, /\.config\.[^/]+$/i,
|
|
29
|
+
/(^|\/)(?:package|tsconfig)(?:\.[^/]*)?\.json$/i, /(^|\/)\.env(?:\.[^/]*)?$/i],
|
|
30
|
+
style: [/\.(?:css|scss|sass|less)$/i],
|
|
31
|
+
};
|
|
21
32
|
const IMPACT_KEYWORDS = {
|
|
22
|
-
|
|
33
|
+
payment: 3, database: 2, api: 2, core: 2,
|
|
23
34
|
util: 1, helper: 1, test: 0, mock: 0, fixture: 0,
|
|
24
35
|
};
|
|
36
|
+
function pathHasTerm(path, term) {
|
|
37
|
+
const tokens = path.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().split(/[^a-z0-9]+/);
|
|
38
|
+
const aliases = {
|
|
39
|
+
auth: ['auth', 'authentication', 'authorization', 'authenticator', 'oauth'],
|
|
40
|
+
util: ['util', 'utils', 'utility', 'utilities'],
|
|
41
|
+
test: ['test', 'tests'],
|
|
42
|
+
mock: ['mock', 'mocks'],
|
|
43
|
+
fixture: ['fixture', 'fixtures'],
|
|
44
|
+
config: ['config', 'configuration'],
|
|
45
|
+
};
|
|
46
|
+
return (aliases[term] ?? [term]).some(value => tokens.includes(value));
|
|
47
|
+
}
|
|
48
|
+
// Match path terms rather than substrings: `encryption` must trigger review,
|
|
49
|
+
// while unrelated names such as `author` and `tokenizer` must not.
|
|
50
|
+
const SECURITY_PATH_TERMS = new Set([
|
|
51
|
+
'security', 'secure', 'auth', 'oauth', 'authenticate', 'authentication',
|
|
52
|
+
'authorization', 'authorized', 'authenticator', 'crypto', 'cryptography',
|
|
53
|
+
'encrypt', 'encryption', 'encrypted', 'decrypt', 'decryption', 'decrypted',
|
|
54
|
+
'cipher', 'secret', 'secrets', 'password', 'passwords', 'token', 'tokens',
|
|
55
|
+
'vault', 'kms',
|
|
56
|
+
]);
|
|
57
|
+
function isSecuritySensitivePath(path) {
|
|
58
|
+
const terms = path.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase().split(/[^a-z0-9]+/);
|
|
59
|
+
return terms.some(term => SECURITY_PATH_TERMS.has(term));
|
|
60
|
+
}
|
|
25
61
|
export class DiffClassifier {
|
|
26
62
|
config;
|
|
27
63
|
ruvectorEngine = null;
|
|
@@ -47,10 +83,12 @@ export class DiffClassifier {
|
|
|
47
83
|
const files = [];
|
|
48
84
|
const fileBlocks = diffContent.split(/^diff --git/m).filter(Boolean);
|
|
49
85
|
for (const block of fileBlocks) {
|
|
50
|
-
const
|
|
51
|
-
|
|
86
|
+
const headerEnd = block.indexOf('\n');
|
|
87
|
+
const header = block.slice(0, headerEnd < 0 ? undefined : headerEnd);
|
|
88
|
+
const destination = header.lastIndexOf(' b/');
|
|
89
|
+
if (!header.startsWith(' a/') || destination < 0)
|
|
52
90
|
continue;
|
|
53
|
-
const path =
|
|
91
|
+
const path = header.slice(destination + 3);
|
|
54
92
|
const hunks = this.parseHunks(block);
|
|
55
93
|
const additions = hunks.reduce((sum, h) => sum + h.changes.filter(c => c.type === 'add').length, 0);
|
|
56
94
|
const deletions = hunks.reduce((sum, h) => sum + h.changes.filter(c => c.type === 'remove').length, 0);
|
|
@@ -71,7 +109,15 @@ export class DiffClassifier {
|
|
|
71
109
|
}
|
|
72
110
|
classifyCommitMessage(message) {
|
|
73
111
|
const lowerMessage = message.toLowerCase();
|
|
74
|
-
|
|
112
|
+
const conventional = lowerMessage.match(/^([a-z]+)(?:\([^)]*\))?!?:/);
|
|
113
|
+
const conventionalTypes = {
|
|
114
|
+
feat: 'feature', feature: 'feature', fix: 'bugfix', refactor: 'refactor',
|
|
115
|
+
docs: 'docs', doc: 'docs', test: 'test', config: 'config', style: 'style',
|
|
116
|
+
};
|
|
117
|
+
if (conventional && conventionalTypes[conventional[1]]) {
|
|
118
|
+
return conventionalTypes[conventional[1]];
|
|
119
|
+
}
|
|
120
|
+
for (const [type, patterns] of Object.entries(COMMIT_CLASSIFICATION_PATTERNS)) {
|
|
75
121
|
for (const pattern of patterns) {
|
|
76
122
|
if (pattern.test(lowerMessage))
|
|
77
123
|
return type;
|
|
@@ -85,16 +131,43 @@ export class DiffClassifier {
|
|
|
85
131
|
clearCache() { this.classificationCache.clear(); }
|
|
86
132
|
parseHunks(block) {
|
|
87
133
|
const hunks = [];
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
const
|
|
96
|
-
|
|
134
|
+
const readRange = (value, prefix) => {
|
|
135
|
+
if (!value.startsWith(prefix))
|
|
136
|
+
return null;
|
|
137
|
+
const parts = value.slice(1).split(',');
|
|
138
|
+
if (parts.length > 2 || parts.some(part => !/^\d+$/.test(part)))
|
|
139
|
+
return null;
|
|
140
|
+
const start = Number(parts[0]);
|
|
141
|
+
const count = parts.length === 2 ? Number(parts[1]) : 1;
|
|
142
|
+
return Number.isSafeInteger(start) && Number.isSafeInteger(count) ? [start, count] : null;
|
|
143
|
+
};
|
|
144
|
+
let current = null;
|
|
145
|
+
const finish = () => {
|
|
146
|
+
if (!current)
|
|
147
|
+
return;
|
|
148
|
+
const content = current.lines.join('\n');
|
|
149
|
+
hunks.push({
|
|
150
|
+
oldStart: current.oldStart, oldLines: current.oldLines,
|
|
151
|
+
newStart: current.newStart, newLines: current.newLines,
|
|
152
|
+
content, changes: this.parseChanges(content, current.newStart),
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
for (const line of block.split('\n')) {
|
|
156
|
+
if (line.startsWith('@@ -')) {
|
|
157
|
+
const end = line.indexOf(' @@', 3);
|
|
158
|
+
const coordinates = end < 0 ? [] : line.slice(3, end).split(' ');
|
|
159
|
+
const oldRange = coordinates.length === 2 ? readRange(coordinates[0], '-') : null;
|
|
160
|
+
const newRange = coordinates.length === 2 ? readRange(coordinates[1], '+') : null;
|
|
161
|
+
if (oldRange && newRange) {
|
|
162
|
+
finish();
|
|
163
|
+
current = { oldStart: oldRange[0], oldLines: oldRange[1], newStart: newRange[0], newLines: newRange[1], lines: [] };
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (current)
|
|
168
|
+
current.lines.push(line);
|
|
97
169
|
}
|
|
170
|
+
finish();
|
|
98
171
|
return hunks;
|
|
99
172
|
}
|
|
100
173
|
parseChanges(content, startLine) {
|
|
@@ -133,20 +206,26 @@ export class DiffClassifier {
|
|
|
133
206
|
return classification;
|
|
134
207
|
}
|
|
135
208
|
getCacheKey(path, hunks) {
|
|
136
|
-
const
|
|
137
|
-
|
|
209
|
+
const digest = createHash('sha256');
|
|
210
|
+
digest.update(path);
|
|
211
|
+
for (const hunk of hunks) {
|
|
212
|
+
digest.update(`${hunk.oldStart}:${hunk.newStart}:`);
|
|
213
|
+
for (const change of hunk.changes)
|
|
214
|
+
digest.update(`${change.type}:${change.content}\n`);
|
|
215
|
+
}
|
|
216
|
+
return digest.digest('hex');
|
|
138
217
|
}
|
|
139
218
|
determinePrimaryClassification(path, hunks) {
|
|
140
|
-
for (const [type, patterns] of Object.entries(
|
|
219
|
+
for (const [type, patterns] of Object.entries(PATH_CLASSIFICATION_PATTERNS)) {
|
|
141
220
|
for (const pattern of patterns) {
|
|
142
221
|
if (pattern.test(path))
|
|
143
222
|
return type;
|
|
144
223
|
}
|
|
145
224
|
}
|
|
146
|
-
const allContent = hunks.flatMap(h => h.changes.map(c => c.content)).join('\n').toLowerCase();
|
|
225
|
+
const allContent = hunks.flatMap(h => h.changes.filter(c => c.type !== 'context').map(c => c.content)).join('\n').toLowerCase();
|
|
147
226
|
if (/function|class|interface|type\s+\w+/.test(allContent) && hunks.some(h => h.changes.filter(c => c.type === 'add').length > 10))
|
|
148
227
|
return 'feature';
|
|
149
|
-
if (
|
|
228
|
+
if (/\b(?:fix(?:ed|es)?|bug|issue|exception)\b/.test(allContent))
|
|
150
229
|
return 'bugfix';
|
|
151
230
|
if (this.config.detectRefactoring && this.isRefactoring(hunks))
|
|
152
231
|
return 'refactor';
|
|
@@ -167,7 +246,7 @@ export class DiffClassifier {
|
|
|
167
246
|
}
|
|
168
247
|
determineSecondaryClassifications(path, hunks, primary) {
|
|
169
248
|
const secondary = [];
|
|
170
|
-
for (const [type, patterns] of Object.entries(
|
|
249
|
+
for (const [type, patterns] of Object.entries(PATH_CLASSIFICATION_PATTERNS)) {
|
|
171
250
|
if (type === primary)
|
|
172
251
|
continue;
|
|
173
252
|
for (const pattern of patterns) {
|
|
@@ -181,7 +260,7 @@ export class DiffClassifier {
|
|
|
181
260
|
}
|
|
182
261
|
calculateConfidence(path, hunks, primary) {
|
|
183
262
|
let confidence = 0.5;
|
|
184
|
-
for (const patterns of Object.values(
|
|
263
|
+
for (const patterns of Object.values(PATH_CLASSIFICATION_PATTERNS)) {
|
|
185
264
|
for (const pattern of patterns) {
|
|
186
265
|
if (pattern.test(path)) {
|
|
187
266
|
confidence += 0.2;
|
|
@@ -200,11 +279,12 @@ export class DiffClassifier {
|
|
|
200
279
|
}
|
|
201
280
|
determineImpactLevel(path, hunks) {
|
|
202
281
|
let score = 0;
|
|
203
|
-
const lowerPath = path.toLowerCase();
|
|
204
282
|
for (const [keyword, weight] of Object.entries(IMPACT_KEYWORDS)) {
|
|
205
|
-
if (
|
|
283
|
+
if (pathHasTerm(path, keyword))
|
|
206
284
|
score = Math.max(score, weight);
|
|
207
285
|
}
|
|
286
|
+
if (isSecuritySensitivePath(path))
|
|
287
|
+
score = Math.max(score, 3);
|
|
208
288
|
const totalChanges = hunks.reduce((sum, h) => sum + h.changes.filter(c => c.type !== 'context').length, 0);
|
|
209
289
|
if (totalChanges > 100)
|
|
210
290
|
score = Math.max(score, 2);
|
|
@@ -224,9 +304,9 @@ export class DiffClassifier {
|
|
|
224
304
|
reviewers.push(...(typeReviewers[primary] || typeReviewers.unknown));
|
|
225
305
|
if (impact === 'critical' || impact === 'high')
|
|
226
306
|
reviewers.push('security-reviewer');
|
|
227
|
-
if (
|
|
307
|
+
if (isSecuritySensitivePath(path))
|
|
228
308
|
reviewers.push('security-team');
|
|
229
|
-
if (
|
|
309
|
+
if (pathHasTerm(path, 'database') || pathHasTerm(path, 'migration'))
|
|
230
310
|
reviewers.push('dba');
|
|
231
311
|
return [...new Set(reviewers)].slice(0, 4);
|
|
232
312
|
}
|
|
@@ -240,9 +320,9 @@ export class DiffClassifier {
|
|
|
240
320
|
strategies.push('regression-tests');
|
|
241
321
|
strategies.push('e2e-tests');
|
|
242
322
|
}
|
|
243
|
-
if (
|
|
323
|
+
if (['api', 'endpoint', 'route', 'handler'].some(term => pathHasTerm(path, term)))
|
|
244
324
|
strategies.push('api-contract-tests');
|
|
245
|
-
if (
|
|
325
|
+
if (isSecuritySensitivePath(path))
|
|
246
326
|
strategies.push('security-audit');
|
|
247
327
|
return strategies.slice(0, 5);
|
|
248
328
|
}
|
|
@@ -255,11 +335,11 @@ export class DiffClassifier {
|
|
|
255
335
|
risks.push('Critical system component - requires careful review');
|
|
256
336
|
if (impact === 'high')
|
|
257
337
|
risks.push('High-impact area - monitor after deployment');
|
|
258
|
-
if (
|
|
338
|
+
if (isSecuritySensitivePath(path))
|
|
259
339
|
risks.push('Security-sensitive code');
|
|
260
|
-
if (
|
|
340
|
+
if (pathHasTerm(path, 'database') || pathHasTerm(path, 'migration'))
|
|
261
341
|
risks.push('Database changes - ensure backup strategy');
|
|
262
|
-
if (
|
|
342
|
+
if (pathHasTerm(path, 'config') || pathHasTerm(path, 'env'))
|
|
263
343
|
risks.push('Configuration changes - verify all environments');
|
|
264
344
|
const allContent = hunks.flatMap(h => h.changes.map(c => c.content)).join('\n');
|
|
265
345
|
if (/TODO|FIXME|HACK/.test(allContent))
|
|
@@ -318,20 +398,24 @@ const CACHE_TTL_MS = 5000; // 5 seconds - short TTL since diffs change frequentl
|
|
|
318
398
|
* Only allows safe characters: alphanumeric, -, _, /, ., ~, ^
|
|
319
399
|
*/
|
|
320
400
|
function validateGitRef(ref) {
|
|
321
|
-
|
|
322
|
-
|
|
401
|
+
if (ref.length > 256) {
|
|
402
|
+
throw new Error(`Invalid git ref: too long`);
|
|
403
|
+
}
|
|
404
|
+
// A ref is passed as one argv element, but still must not be an option or
|
|
405
|
+
// contain shell metacharacters. A range may omit its right endpoint (HEAD).
|
|
406
|
+
if (!/^[a-zA-Z0-9_][a-zA-Z0-9_\-./~^@]*$/.test(ref)) {
|
|
323
407
|
throw new Error(`Invalid git ref: contains unsafe characters`);
|
|
324
408
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
409
|
+
if (ref.includes('..')) {
|
|
410
|
+
const range = ref.match(/^(.+?)(\.\.\.?)(.*)$/);
|
|
411
|
+
// Only one range separator is allowed. In particular, reject traversal
|
|
412
|
+
// inside either endpoint, while accepting branch paths and ancestry syntax.
|
|
413
|
+
if (!range || range[1].includes('..') || range[3].includes('..') ||
|
|
414
|
+
!/^[a-zA-Z0-9_][a-zA-Z0-9_\-./~^@]*$/.test(range[1]) ||
|
|
415
|
+
(range[3] !== '' && !/^[a-zA-Z0-9_][a-zA-Z0-9_\-./~^@]*$/.test(range[3]))) {
|
|
328
416
|
throw new Error(`Invalid git ref: suspicious pattern`);
|
|
329
417
|
}
|
|
330
418
|
}
|
|
331
|
-
// Max length check
|
|
332
|
-
if (ref.length > 256) {
|
|
333
|
-
throw new Error(`Invalid git ref: too long`);
|
|
334
|
-
}
|
|
335
419
|
}
|
|
336
420
|
/**
|
|
337
421
|
* Get git diff statistics using SINGLE combined command (optimized)
|
|
@@ -500,24 +584,23 @@ export function assessFileRisk(file) {
|
|
|
500
584
|
reasons.push('Medium change size (>100 lines)');
|
|
501
585
|
}
|
|
502
586
|
// Path-based risk
|
|
503
|
-
|
|
504
|
-
if (/security|auth|crypto|password/.test(lowerPath)) {
|
|
587
|
+
if (isSecuritySensitivePath(file.path)) {
|
|
505
588
|
score += 40;
|
|
506
589
|
reasons.push('Security-sensitive file');
|
|
507
590
|
}
|
|
508
|
-
if (
|
|
591
|
+
if (['payment', 'billing', 'transaction'].some(term => pathHasTerm(file.path, term))) {
|
|
509
592
|
score += 35;
|
|
510
593
|
reasons.push('Payment-related file');
|
|
511
594
|
}
|
|
512
|
-
if (
|
|
595
|
+
if (['database', 'migration', 'schema'].some(term => pathHasTerm(file.path, term))) {
|
|
513
596
|
score += 25;
|
|
514
597
|
reasons.push('Database-related file');
|
|
515
598
|
}
|
|
516
|
-
if (
|
|
599
|
+
if (['core', 'main', 'index'].some(term => pathHasTerm(file.path, term))) {
|
|
517
600
|
score += 15;
|
|
518
601
|
reasons.push('Core module');
|
|
519
602
|
}
|
|
520
|
-
if (
|
|
603
|
+
if (['config', 'env', 'settings'].some(term => pathHasTerm(file.path, term))) {
|
|
521
604
|
score += 20;
|
|
522
605
|
reasons.push('Configuration file');
|
|
523
606
|
}
|
|
@@ -570,19 +653,68 @@ function getClassifier() {
|
|
|
570
653
|
}
|
|
571
654
|
return classifierInstance;
|
|
572
655
|
}
|
|
573
|
-
/**
|
|
574
|
-
|
|
575
|
-
*/
|
|
576
|
-
export function classifyDiff(files) {
|
|
656
|
+
/** Classify file metadata with the actual patch and, for one-commit ranges, its subject. */
|
|
657
|
+
export function classifyDiff(files, patch = '', commitSubject = '') {
|
|
577
658
|
const classifier = getClassifier();
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
659
|
+
const parsed = new Map(classifier.parseDiff(patch).map(file => [file.path, file]));
|
|
660
|
+
const fileDiffs = files.map(f => {
|
|
661
|
+
const patchFile = parsed.get(f.path);
|
|
662
|
+
const hunks = patchFile?.hunks ?? [];
|
|
663
|
+
return {
|
|
664
|
+
path: f.path,
|
|
665
|
+
hunks,
|
|
666
|
+
additions: f.additions,
|
|
667
|
+
deletions: f.deletions,
|
|
668
|
+
classification: patchFile?.classification ?? classifier['classifyFile'](f.path, hunks),
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
const overall = classifier['computeOverallClassification'](fileDiffs);
|
|
672
|
+
if (fileDiffs.length > 0 && commitSubject) {
|
|
673
|
+
const subjectType = classifier.classifyCommitMessage(commitSubject);
|
|
674
|
+
if (subjectType !== 'unknown') {
|
|
675
|
+
if (overall.primary !== subjectType && overall.primary !== 'unknown') {
|
|
676
|
+
overall.secondary = [overall.primary, ...overall.secondary].filter((type, index, all) => type !== subjectType && all.indexOf(type) === index).slice(0, 3);
|
|
677
|
+
}
|
|
678
|
+
overall.primary = subjectType;
|
|
679
|
+
overall.confidence = Math.max(overall.confidence, 0.85);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return overall;
|
|
683
|
+
}
|
|
684
|
+
function getSingleCommitSubjectSync(ref) {
|
|
685
|
+
if (!ref.includes('..'))
|
|
686
|
+
return '';
|
|
687
|
+
try {
|
|
688
|
+
const output = execFileSync('git', ['log', '-2', '--format=%s', ref], { encoding: 'utf-8', maxBuffer: 64 * 1024 });
|
|
689
|
+
const subjects = output.trim().split('\n').filter(Boolean);
|
|
690
|
+
return subjects.length === 1 ? subjects[0] : '';
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
return '';
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
function getDiffContextSync(ref) {
|
|
697
|
+
validateGitRef(ref);
|
|
698
|
+
let patch = '';
|
|
699
|
+
try {
|
|
700
|
+
patch = execFileSync('git', ['diff', '--no-ext-diff', '--no-textconv', '--unified=0', ref], { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
|
|
701
|
+
}
|
|
702
|
+
catch { /* A large or unavailable patch falls back to path-only classification. */ }
|
|
703
|
+
return { patch, subject: getSingleCommitSubjectSync(ref) };
|
|
704
|
+
}
|
|
705
|
+
async function getDiffContextAsync(ref) {
|
|
706
|
+
validateGitRef(ref);
|
|
707
|
+
const execFileAsync = promisify(execFile);
|
|
708
|
+
const patchPromise = execFileAsync('git', ['diff', '--no-ext-diff', '--no-textconv', '--unified=0', ref], { maxBuffer: 10 * 1024 * 1024 }).then(({ stdout }) => stdout).catch(() => '');
|
|
709
|
+
const subjectPromise = ref.includes('..')
|
|
710
|
+
? execFileAsync('git', ['log', '-2', '--format=%s', ref], { maxBuffer: 64 * 1024 })
|
|
711
|
+
.then(({ stdout }) => {
|
|
712
|
+
const subjects = stdout.trim().split('\n').filter(Boolean);
|
|
713
|
+
return subjects.length === 1 ? subjects[0] : '';
|
|
714
|
+
}).catch(() => '')
|
|
715
|
+
: Promise.resolve('');
|
|
716
|
+
const [patch, subject] = await Promise.all([patchPromise, subjectPromise]);
|
|
717
|
+
return { patch, subject };
|
|
586
718
|
}
|
|
587
719
|
/**
|
|
588
720
|
* Suggest reviewers based on files and risks
|
|
@@ -590,20 +722,20 @@ export function classifyDiff(files) {
|
|
|
590
722
|
export function suggestReviewers(files, fileRisks) {
|
|
591
723
|
const reviewers = new Set();
|
|
592
724
|
for (const file of files) {
|
|
593
|
-
const
|
|
594
|
-
if (
|
|
725
|
+
const hasAny = (...terms) => terms.some(term => pathHasTerm(file.path, term));
|
|
726
|
+
if (isSecuritySensitivePath(file.path))
|
|
595
727
|
reviewers.add('security-team');
|
|
596
|
-
if (
|
|
728
|
+
if (hasAny('database', 'migration'))
|
|
597
729
|
reviewers.add('dba');
|
|
598
|
-
if (
|
|
730
|
+
if (hasAny('api', 'endpoint', 'route'))
|
|
599
731
|
reviewers.add('api-owner');
|
|
600
|
-
if (
|
|
732
|
+
if (hasAny('test', 'spec'))
|
|
601
733
|
reviewers.add('qa-engineer');
|
|
602
|
-
if (
|
|
734
|
+
if (hasAny('config', 'deploy', 'ci'))
|
|
603
735
|
reviewers.add('devops');
|
|
604
|
-
if (
|
|
736
|
+
if (hasAny('ui', 'component', 'style'))
|
|
605
737
|
reviewers.add('frontend-lead');
|
|
606
|
-
if (
|
|
738
|
+
if (hasAny('model', 'service', 'repository'))
|
|
607
739
|
reviewers.add('backend-lead');
|
|
608
740
|
}
|
|
609
741
|
// Add based on risk
|
|
@@ -634,13 +766,13 @@ export async function analyzeDiff(options) {
|
|
|
634
766
|
}
|
|
635
767
|
}
|
|
636
768
|
// Use async git diff for non-blocking operation
|
|
637
|
-
const files = await getGitDiffNumstatAsync(ref);
|
|
769
|
+
const [files, context] = await Promise.all([getGitDiffNumstatAsync(ref), getDiffContextAsync(ref)]);
|
|
638
770
|
// Parallel file risk assessment for large diffs
|
|
639
771
|
const fileRisks = files.length > 20
|
|
640
772
|
? await Promise.all(files.map(f => Promise.resolve(assessFileRisk(f))))
|
|
641
773
|
: files.map(assessFileRisk);
|
|
642
774
|
const risk = assessOverallRisk(files, fileRisks);
|
|
643
|
-
const classification = classifyDiff(files);
|
|
775
|
+
const classification = classifyDiff(files, context.patch, context.subject);
|
|
644
776
|
const recommendedReviewers = suggestReviewers(files, fileRisks);
|
|
645
777
|
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
646
778
|
const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
|
|
@@ -669,9 +801,10 @@ export function analyzeDiffSync(options) {
|
|
|
669
801
|
return cached.result;
|
|
670
802
|
}
|
|
671
803
|
const files = getGitDiffNumstat(ref);
|
|
804
|
+
const context = getDiffContextSync(ref);
|
|
672
805
|
const fileRisks = files.map(assessFileRisk);
|
|
673
806
|
const risk = assessOverallRisk(files, fileRisks);
|
|
674
|
-
const classification = classifyDiff(files);
|
|
807
|
+
const classification = classifyDiff(files, context.patch, context.subject);
|
|
675
808
|
const recommendedReviewers = suggestReviewers(files, fileRisks);
|
|
676
809
|
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
677
810
|
const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
|
|
@@ -65,7 +65,11 @@ export interface RuvllmStatus {
|
|
|
65
65
|
available: boolean;
|
|
66
66
|
initialized: boolean;
|
|
67
67
|
version: string | null;
|
|
68
|
+
/** Present when unavailable: how to install the missing package. */
|
|
69
|
+
hint?: string;
|
|
68
70
|
}
|
|
71
|
+
/** #4149: the wasm package is an optional add-on, not bundled with the CLI. */
|
|
72
|
+
export declare const RUVLLM_WASM_INSTALL_HINT: string;
|
|
69
73
|
/**
|
|
70
74
|
* Check if @ruvector/ruvllm-wasm is installed and loadable.
|
|
71
75
|
*/
|
|
@@ -24,6 +24,14 @@ import { readFileSync } from 'node:fs';
|
|
|
24
24
|
import { createRequire } from 'node:module';
|
|
25
25
|
// WASM binary requires at least 768-dim input for MicroLoRA adapt()
|
|
26
26
|
const MICROLORA_WASM_MIN_DIM = 768;
|
|
27
|
+
/** #4149: the wasm package is an optional add-on, not bundled with the CLI. */
|
|
28
|
+
export const RUVLLM_WASM_INSTALL_HINT = '@ruvector/ruvllm-wasm is not installed. Install it next to the CLI: ' +
|
|
29
|
+
'"npm i -g @ruvector/ruvllm-wasm" for a global install, or ' +
|
|
30
|
+
'"npm i @ruvector/ruvllm-wasm" in your project.';
|
|
31
|
+
function isModuleNotFound(err) {
|
|
32
|
+
const code = err?.code;
|
|
33
|
+
return code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND';
|
|
34
|
+
}
|
|
27
35
|
// ── WASM Module Detection & Init ─────────────────────────────
|
|
28
36
|
let _wasmReady = false;
|
|
29
37
|
/**
|
|
@@ -55,6 +63,9 @@ export async function initRuvllmWasm() {
|
|
|
55
63
|
_wasmReady = true;
|
|
56
64
|
}
|
|
57
65
|
catch (err) {
|
|
66
|
+
if (isModuleNotFound(err)) {
|
|
67
|
+
throw new Error(`Failed to initialize @ruvector/ruvllm-wasm: ${RUVLLM_WASM_INSTALL_HINT}`);
|
|
68
|
+
}
|
|
58
69
|
throw new Error(`Failed to initialize @ruvector/ruvllm-wasm: ${err}`);
|
|
59
70
|
}
|
|
60
71
|
}
|
|
@@ -64,7 +75,7 @@ export async function initRuvllmWasm() {
|
|
|
64
75
|
export async function getRuvllmStatus() {
|
|
65
76
|
const available = await isRuvllmWasmAvailable();
|
|
66
77
|
if (!available) {
|
|
67
|
-
return { available: false, initialized: false, version: null };
|
|
78
|
+
return { available: false, initialized: false, version: null, hint: RUVLLM_WASM_INSTALL_HINT };
|
|
68
79
|
}
|
|
69
80
|
try {
|
|
70
81
|
const mod = await import('@ruvector/ruvllm-wasm');
|
|
@@ -123,16 +123,16 @@ export class ConfigFileManager {
|
|
|
123
123
|
this.selectProject(cwd);
|
|
124
124
|
// A targeted update must not persist unrelated defaults (notably the
|
|
125
125
|
// default memory path, which would relocate an existing memory store).
|
|
126
|
-
const config = this.config ?? this.load(cwd) ?? {};
|
|
126
|
+
const config = structuredClone(this.config ?? this.load(cwd) ?? {});
|
|
127
127
|
if (Object.hasOwn(config, key)) {
|
|
128
128
|
config[key] = value;
|
|
129
129
|
}
|
|
130
130
|
else {
|
|
131
131
|
setNestedValue(config, key, value);
|
|
132
132
|
}
|
|
133
|
-
this.config = config;
|
|
134
133
|
const targetPath = this.configPath ?? path.resolve(cwd, CONFIG_FILENAMES[0]);
|
|
135
134
|
this.writeAtomic(targetPath, config);
|
|
135
|
+
this.config = config;
|
|
136
136
|
this.configPath = targetPath;
|
|
137
137
|
}
|
|
138
138
|
/** Create a new config file with defaults */
|
|
@@ -271,7 +271,7 @@ export function parseConfigValue(value) {
|
|
|
271
271
|
return parseFloat(value);
|
|
272
272
|
try {
|
|
273
273
|
const parsed = JSON.parse(value);
|
|
274
|
-
if (typeof parsed === 'object')
|
|
274
|
+
if (typeof parsed === 'object' || (typeof parsed === 'number' && Number.isFinite(parsed)))
|
|
275
275
|
return parsed;
|
|
276
276
|
}
|
|
277
277
|
catch { /* not JSON, use as string */ }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AgenticPolicyEngine, type BudgetLimit, type CapabilityEnvelope, type PolicyApproval, type PolicyDecision, type PolicyRequest, type PolicyRule, type PolicyState } from '@claude-flow/security';
|
|
1
|
+
import { AgenticPolicyEngine, type BudgetLimit, type CapabilityEnvelope, type PolicyApproval, type PolicyDecision, type PolicyRequest, type PolicyRule, type PolicyState } from '@claude-flow/security/policy/index';
|
|
2
2
|
/**
|
|
3
3
|
* Exported for ADR-406 mission storage, which takes the same per-file lock
|
|
4
4
|
* (pid, pid namespace and boot id identify a dead owner) rather than a copy.
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
// Narrow subpath imports (not the '@claude-flow/security' barrel): this module
|
|
2
|
+
// is loaded eagerly by the MCP server, and the barrel also evaluates the OAuth
|
|
3
|
+
// callback server (an ESM `node:http` import that materialises http's lazy
|
|
4
|
+
// undici getters), bcryptjs, keychain and credential helpers that the policy
|
|
5
|
+
// runtime never uses.
|
|
6
|
+
import { AgenticPolicyEngine, createLegacyCompatibleState, } from '@claude-flow/security/policy/index';
|
|
7
|
+
import { isMcpCallerAuthEnabled, decodeTokenEnvelope, publicKeyFromHex, verifyInvocationToken, } from '@claude-flow/security/mcp-caller-identity';
|
|
2
8
|
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
3
9
|
import { execFileSync } from 'node:child_process';
|
|
4
10
|
import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readlinkSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
@@ -316,6 +316,17 @@ export declare class WorkerDaemon extends EventEmitter {
|
|
|
316
316
|
* Execute a worker with timeout protection
|
|
317
317
|
*/
|
|
318
318
|
private executeWorker;
|
|
319
|
+
/**
|
|
320
|
+
* Feed a finished worker job into the self-learning pipeline as a one-step
|
|
321
|
+
* trajectory (SONA + ReasoningBank + @ruvector/ruvllm forwarding, persisted
|
|
322
|
+
* to `.claude-flow/neural/stats.json`). Before this, the daemon could run
|
|
323
|
+
* hundreds of jobs at "100% success" while `trajectoriesRecorded` stayed 0:
|
|
324
|
+
* no daemon code path ever called the intelligence module.
|
|
325
|
+
*
|
|
326
|
+
* Best-effort and bounded: never throws, never changes the WorkerResult.
|
|
327
|
+
* Opt-out: RUFLO_DAEMON_NO_LEARN=1.
|
|
328
|
+
*/
|
|
329
|
+
private recordWorkerTrajectory;
|
|
319
330
|
/**
|
|
320
331
|
* Run a function with timeout (P1 fix)
|
|
321
332
|
* @param fn - The async function to execute
|
|
@@ -75,6 +75,9 @@ const CONSOLIDATE_DEDUP_DISTANCE = 0.2;
|
|
|
75
75
|
// `--no-distill` flag on `daemon start` (see commands/daemon.ts), which sets
|
|
76
76
|
// this env var on the forked/foreground daemon process.
|
|
77
77
|
const NO_DISTILL_ENV = 'RUFLO_DAEMON_NO_DISTILL';
|
|
78
|
+
// Opt-out for recording each finished worker job as a learning trajectory
|
|
79
|
+
// (see WorkerDaemon.recordWorkerTrajectory).
|
|
80
|
+
const NO_LEARN_ENV = 'RUFLO_DAEMON_NO_LEARN';
|
|
78
81
|
// #2356 — Self-terminating lifecycle defaults. A background daemon with no
|
|
79
82
|
// upper bound on its lifetime runs until the box reboots; in the field this
|
|
80
83
|
// leaked tens of thousands of headless `claude --print` sweeps over many days
|
|
@@ -1234,6 +1237,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1234
1237
|
this.emit('worker:complete', result);
|
|
1235
1238
|
this.log('info', `Worker ${workerConfig.type} completed in ${durationMs}ms`);
|
|
1236
1239
|
this.saveState();
|
|
1240
|
+
await this.recordWorkerTrajectory(result);
|
|
1237
1241
|
return result;
|
|
1238
1242
|
}
|
|
1239
1243
|
catch (error) {
|
|
@@ -1253,6 +1257,7 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1253
1257
|
this.emit('worker:error', result);
|
|
1254
1258
|
this.log('error', `Worker ${workerConfig.type} failed: ${result.error}`);
|
|
1255
1259
|
this.saveState();
|
|
1260
|
+
await this.recordWorkerTrajectory(result);
|
|
1256
1261
|
return result;
|
|
1257
1262
|
}
|
|
1258
1263
|
finally {
|
|
@@ -1261,6 +1266,38 @@ export class WorkerDaemon extends EventEmitter {
|
|
|
1261
1266
|
this.processPendingWorkers();
|
|
1262
1267
|
}
|
|
1263
1268
|
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Feed a finished worker job into the self-learning pipeline as a one-step
|
|
1271
|
+
* trajectory (SONA + ReasoningBank + @ruvector/ruvllm forwarding, persisted
|
|
1272
|
+
* to `.claude-flow/neural/stats.json`). Before this, the daemon could run
|
|
1273
|
+
* hundreds of jobs at "100% success" while `trajectoriesRecorded` stayed 0:
|
|
1274
|
+
* no daemon code path ever called the intelligence module.
|
|
1275
|
+
*
|
|
1276
|
+
* Best-effort and bounded: never throws, never changes the WorkerResult.
|
|
1277
|
+
* Opt-out: RUFLO_DAEMON_NO_LEARN=1.
|
|
1278
|
+
*/
|
|
1279
|
+
async recordWorkerTrajectory(result) {
|
|
1280
|
+
if (process.env[NO_LEARN_ENV] === '1')
|
|
1281
|
+
return;
|
|
1282
|
+
try {
|
|
1283
|
+
const intel = await import('../memory/intelligence.js');
|
|
1284
|
+
await intel.recordTrajectory([{
|
|
1285
|
+
type: 'result',
|
|
1286
|
+
// A worker error is free text (it can quote command output): keep only its first line, cut, so it cannot fill the learning store.
|
|
1287
|
+
content: `daemon worker ${result.type} ${result.success ? 'succeeded' : `failed: ${String(result.error ?? 'unknown error').split('\n')[0].slice(0, 200)}`}`,
|
|
1288
|
+
metadata: {
|
|
1289
|
+
source: 'worker-daemon',
|
|
1290
|
+
worker: result.type,
|
|
1291
|
+
success: result.success,
|
|
1292
|
+
durationMs: result.durationMs,
|
|
1293
|
+
},
|
|
1294
|
+
timestamp: Date.now(),
|
|
1295
|
+
}], result.success ? 'success' : 'failure');
|
|
1296
|
+
}
|
|
1297
|
+
catch (error) {
|
|
1298
|
+
this.log('warn', `Worker ${result.type}: trajectory recording skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1264
1301
|
/**
|
|
1265
1302
|
* Run a function with timeout (P1 fix)
|
|
1266
1303
|
* @param fn - The async function to execute
|