@claude-flow/cli 3.56.9 → 3.57.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.
- package/.claude/helpers/.helpers-version +1 -1
- package/.claude/helpers/helpers.manifest.json +3 -3
- package/.claude/helpers/intelligence.cjs +51 -9
- package/.claude/helpers/router.cjs +0 -0
- package/.claude/skills/verification-quality/SKILL.md +14 -11
- package/README.md +1 -1
- package/catalog-manifest.json +2 -2
- package/dist/src/autopilot-state.d.ts +2 -0
- package/dist/src/autopilot-state.js +36 -8
- package/dist/src/command-resolution.d.ts +15 -0
- package/dist/src/command-resolution.js +25 -0
- package/dist/src/commands/agent.js +17 -17
- package/dist/src/commands/claims.js +6 -8
- package/dist/src/commands/daemon.js +94 -33
- package/dist/src/commands/doctor.js +95 -18
- package/dist/src/commands/memory.js +14 -2
- package/dist/src/commands/policy.js +6 -1
- 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 +11 -47
- package/dist/src/init/settings-generator.js +4 -58
- 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.d.ts +1 -1
- package/dist/src/mcp-tools/memory-tools.js +100 -25
- package/dist/src/mcp-tools/metaharness-tools.js +1 -1
- package/dist/src/mcp-tools/neural-tools.js +48 -54
- package/dist/src/mcp-tools/policy-tools.js +5 -3
- 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 +158 -35
- 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/memory/stats-file-lock.d.ts +7 -0
- package/dist/src/memory/stats-file-lock.js +53 -0
- package/dist/src/mods/command-registry/schema.d.ts +24 -24
- package/dist/src/ruvector/agent-wasm.d.ts +10 -0
- package/dist/src/ruvector/agent-wasm.js +27 -1
- package/dist/src/ruvector/diff-classifier.d.ts +2 -4
- package/dist/src/ruvector/diff-classifier.js +202 -69
- package/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
- package/dist/src/ruvector/ruvllm-wasm.js +12 -1
- package/dist/src/services/distill-oracle.d.ts +1 -1
- package/dist/src/services/distill-oracle.js +2 -2
- package/dist/src/services/policy-anchor-log.d.ts +13 -0
- package/dist/src/services/policy-anchor-log.js +242 -0
- package/dist/src/services/policy-archive-scan.d.ts +7 -0
- package/dist/src/services/policy-archive-scan.js +50 -0
- package/dist/src/services/policy-ledger-anchor.d.ts +14 -8
- package/dist/src/services/policy-ledger-anchor.js +356 -40
- package/dist/src/services/policy-ledger-audit.d.ts +22 -0
- package/dist/src/services/policy-ledger-audit.js +260 -0
- package/dist/src/services/policy-ledger-record.d.ts +3 -0
- package/dist/src/services/policy-ledger-record.js +22 -0
- package/dist/src/services/policy-runtime.d.ts +2 -1
- package/dist/src/services/policy-runtime.js +267 -40
- package/dist/src/services/worker-daemon.d.ts +11 -0
- package/dist/src/services/worker-daemon.js +37 -0
- package/dist/src/transfer/store/discovery.js +16 -17
- package/dist/src/transfer/store/types.d.ts +4 -0
- package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
- package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts +17 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/engine.js +99 -24
- package/node_modules/@claude-flow/security/dist/policy/engine.js.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts +14 -1
- package/node_modules/@claude-flow/security/dist/policy/types.d.ts.map +1 -1
- package/node_modules/@claude-flow/security/dist/policy/types.js +3 -1
- package/node_modules/@claude-flow/security/dist/policy/types.js.map +1 -1
- package/package.json +5 -5
- package/plugins/ruflo-console/dist/tui.mjs +162 -161
- package/plugins/ruflo-metaharness/hooks/screen.ts +7 -1
- package/plugins/ruflo-metaharness/scripts/_darwin.mjs +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');
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import { type VerifyTaskKind } from '../ruvector/output-verifier.js';
|
|
38
38
|
import { FableHarness, type ReflectItem, type ReflectResult } from './fable-harness.js';
|
|
39
|
-
export declare const MH_DARWIN_PIN = "0.
|
|
39
|
+
export declare const MH_DARWIN_PIN = "0.12.0";
|
|
40
40
|
export type ResolvedProvenance = 'oracle:test-exec' | 'judge:fable' | 'proxy:structural';
|
|
41
41
|
/** SWE-bench-shaped / bench-suite-mapped test spec that Tier 1 can execute. */
|
|
42
42
|
export interface TestSpec {
|
|
@@ -43,9 +43,9 @@ import { FableHarness, } from './fable-harness.js';
|
|
|
43
43
|
// behavior mid-run (the #142 pin-drift failure mode). Pin it, and let
|
|
44
44
|
// scripts/check-metaharness-pins.mjs watch this constant for drift. Kept in
|
|
45
45
|
// lock-step with the optionalDependencies pin in package.json and the plugin
|
|
46
|
-
// darwin cache (versioned by the plugin's own `~0.
|
|
46
|
+
// darwin cache (versioned by the plugin's own `~0.12.0` pin in _darwin.mjs;
|
|
47
47
|
// both are checked by scripts/check-metaharness-pins.mjs — #3366).
|
|
48
|
-
export const MH_DARWIN_PIN = '0.
|
|
48
|
+
export const MH_DARWIN_PIN = '0.12.0';
|
|
49
49
|
// ── Public API ───────────────────────────────────────────────────────────
|
|
50
50
|
/**
|
|
51
51
|
* Label each trajectory with `resolved` + honest provenance, trying the tiers
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const ATOMIC_JSON_WRITER: unique symbol;
|
|
2
|
+
export interface AtomicJsonSource {
|
|
3
|
+
[ATOMIC_JSON_WRITER](fd: number): void;
|
|
4
|
+
}
|
|
5
|
+
/** Supports property order/whitespace from ordinary JSON writers. Unknown
|
|
6
|
+
* root values remain bounded and syntax-checked; duplicate known keys fail. */
|
|
7
|
+
export declare function anchorEntries(file: string): Generator<unknown>;
|
|
8
|
+
/** Serialization callback runs inside the runtime's existing durable atomic
|
|
9
|
+
* writer. Failure removes the temporary file; no partial log is published. */
|
|
10
|
+
export declare function appendedAnchorLog(file: string | undefined, next: unknown, validate: (value: unknown, sequence: number, previous: string | null) => value is {
|
|
11
|
+
hash: string;
|
|
12
|
+
}): AtomicJsonSource;
|
|
13
|
+
//# sourceMappingURL=policy-anchor-log.d.ts.map
|