@claude-flow/cli 3.10.9 → 3.10.11

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 (48) hide show
  1. package/dist/src/commands/hooks.d.ts.map +1 -1
  2. package/dist/src/commands/hooks.js +10 -8
  3. package/dist/src/commands/hooks.js.map +1 -1
  4. package/dist/src/commands/mcp.d.ts.map +1 -1
  5. package/dist/src/commands/mcp.js +14 -0
  6. package/dist/src/commands/mcp.js.map +1 -1
  7. package/dist/src/init/settings-generator.js +1 -1
  8. package/dist/src/init/statusline-generator.js +2 -2
  9. package/dist/src/mcp-tools/agent-execute-core.d.ts +2 -2
  10. package/dist/src/mcp-tools/agent-execute-core.d.ts.map +1 -1
  11. package/dist/src/mcp-tools/agent-execute-core.js +15 -6
  12. package/dist/src/mcp-tools/agent-execute-core.js.map +1 -1
  13. package/dist/src/mcp-tools/agent-tools.js +14 -14
  14. package/dist/src/mcp-tools/agent-tools.js.map +1 -1
  15. package/dist/src/mcp-tools/hooks-tools.d.ts +11 -0
  16. package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
  17. package/dist/src/mcp-tools/hooks-tools.js +216 -13
  18. package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
  19. package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
  20. package/dist/src/mcp-tools/system-tools.js +5 -2
  21. package/dist/src/mcp-tools/system-tools.js.map +1 -1
  22. package/dist/src/mcp-tools/tool-loop-guardrail.d.ts +31 -0
  23. package/dist/src/mcp-tools/tool-loop-guardrail.d.ts.map +1 -0
  24. package/dist/src/mcp-tools/tool-loop-guardrail.js +71 -0
  25. package/dist/src/mcp-tools/tool-loop-guardrail.js.map +1 -0
  26. package/dist/src/runtime/parent-death-watchdog.d.ts +42 -0
  27. package/dist/src/runtime/parent-death-watchdog.d.ts.map +1 -0
  28. package/dist/src/runtime/parent-death-watchdog.js +70 -0
  29. package/dist/src/runtime/parent-death-watchdog.js.map +1 -0
  30. package/dist/src/ruvector/codemods/engine.d.ts +45 -0
  31. package/dist/src/ruvector/codemods/engine.d.ts.map +1 -0
  32. package/dist/src/ruvector/codemods/engine.js +291 -0
  33. package/dist/src/ruvector/codemods/engine.js.map +1 -0
  34. package/dist/src/ruvector/codemods/scope-analysis.d.ts +29 -0
  35. package/dist/src/ruvector/codemods/scope-analysis.d.ts.map +1 -0
  36. package/dist/src/ruvector/codemods/scope-analysis.js +162 -0
  37. package/dist/src/ruvector/codemods/scope-analysis.js.map +1 -0
  38. package/dist/src/ruvector/enhanced-model-router.d.ts +29 -8
  39. package/dist/src/ruvector/enhanced-model-router.d.ts.map +1 -1
  40. package/dist/src/ruvector/enhanced-model-router.js +104 -81
  41. package/dist/src/ruvector/enhanced-model-router.js.map +1 -1
  42. package/dist/src/ruvector/q-learning-router.d.ts +14 -1
  43. package/dist/src/ruvector/q-learning-router.d.ts.map +1 -1
  44. package/dist/src/ruvector/q-learning-router.js +36 -8
  45. package/dist/src/ruvector/q-learning-router.js.map +1 -1
  46. package/dist/tsconfig.tsbuildinfo +1 -1
  47. package/package.json +1 -1
  48. package/scripts/benchmark-codemods.mjs +127 -0
@@ -3,11 +3,33 @@
3
3
  * Provides intelligent hooks functionality via MCP protocol
4
4
  */
5
5
  import { mkdirSync, writeFileSync, existsSync, readFileSync, statSync, unlinkSync, readdirSync } from 'fs';
6
+ import * as nodeFs from 'fs';
6
7
  import { dirname, join, resolve } from 'path';
7
8
  import { getProjectCwd } from './types.js';
8
9
  import { validateIdentifier, validateText, validatePath } from './validate-input.js';
10
+ import { checkCommandLoop, recordCommandOutcome } from './tool-loop-guardrail.js';
9
11
  // Real vector search functions - lazy loaded to avoid circular imports
10
12
  let searchEntriesFn = null;
13
+ /**
14
+ * Strip extended-thinking blocks from text before it enters a learning
15
+ * trajectory (hermes-agent think_scrubber pattern). Claude models with extended
16
+ * thinking emit <thinking>/<think>/<reasoning> blocks; if those land in a
17
+ * trajectory's action/result text, the DISTILL step embeds reasoning-token
18
+ * content that does not generalize, contaminating pattern confidence. Boundary-
19
+ * gated: only strips well-formed paired tags, leaving prose that merely mentions
20
+ * the tag names untouched.
21
+ */
22
+ export function scrubReasoningBlocks(text) {
23
+ if (typeof text !== 'string' || text.indexOf('<') === -1)
24
+ return text;
25
+ return text
26
+ .replace(/<think>[\s\S]*?<\/think>/gi, '')
27
+ .replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
28
+ .replace(/<reasoning>[\s\S]*?<\/reasoning>/gi, '')
29
+ .replace(/<thought>[\s\S]*?<\/thought>/gi, '')
30
+ .replace(/<REASONING_SCRATCHPAD>[\s\S]*?<\/REASONING_SCRATCHPAD>/gi, '')
31
+ .trim();
32
+ }
11
33
  async function getRealSearchFunction() {
12
34
  if (!searchEntriesFn) {
13
35
  try {
@@ -687,6 +709,14 @@ export const hooksPreCommand = {
687
709
  : assessment.level >= 0.6 ? 'high'
688
710
  : assessment.level >= 0.3 ? 'medium'
689
711
  : 'low';
712
+ // #6: tool-loop circuit breaker — warn/block when this exact command has
713
+ // failed repeatedly in a row (an agent stuck looping on a failing call).
714
+ const loop = checkCommandLoop(command);
715
+ const recommendations = assessment.warnings.length > 0
716
+ ? ['Review warnings before proceeding', 'Consider using safer alternative']
717
+ : ['Command appears safe to execute'];
718
+ if (loop.hint)
719
+ recommendations.unshift(loop.hint);
690
720
  return {
691
721
  command,
692
722
  riskLevel,
@@ -695,11 +725,11 @@ export const hooksPreCommand = {
695
725
  severity: assessment.level >= 0.6 ? 'high' : 'medium',
696
726
  description: warning,
697
727
  })),
698
- recommendations: assessment.warnings.length > 0
699
- ? ['Review warnings before proceeding', 'Consider using safer alternative']
700
- : ['Command appears safe to execute'],
728
+ recommendations,
729
+ loopGuard: { verdict: loop.verdict, consecutiveFailures: loop.consecutiveFailures },
701
730
  safeAlternatives: [],
702
- shouldProceed: assessment.level < 0.7,
731
+ // Don't proceed on a high-risk command OR a hard loop-block.
732
+ shouldProceed: assessment.level < 0.7 && loop.verdict !== 'block',
703
733
  };
704
734
  },
705
735
  };
@@ -723,6 +753,9 @@ export const hooksPostCommand = {
723
753
  if (!v.valid)
724
754
  return { success: false, error: v.error };
725
755
  }
756
+ // #6: feed the tool-loop circuit breaker so pre-command can warn/block on
757
+ // repeated consecutive failures of the same command.
758
+ recordCommandOutcome(command, success);
726
759
  // Persist command outcome via AgentDB
727
760
  let _storedIn = 'none';
728
761
  try {
@@ -761,7 +794,7 @@ export const hooksPostCommand = {
761
794
  };
762
795
  export const hooksRoute = {
763
796
  name: 'hooks_route',
764
- description: 'Get a 3-tier routing recommendation for a task: Tier 1 (Agent Booster, 0ms / $0 — for var-to-const, add-types, etc.), Tier 2 (Haiku — simple), Tier 3 (Sonnet/Opus — complex). Use this BEFORE spawning an agent to avoid sending simple transforms to Sonnet. Native tools have no equivalent — Claude Code does not introspect its own model-selection cost. Returns the recommended model + a `[AGENT_BOOSTER_AVAILABLE]` literal when the WASM bypass applies. Use when native Bash hooks (via Claude Code\'s settings.json) are wrong because you need Ruflo-side state — pattern persistence, neural training signals, model-routing learning, cost tracking, audit chain. For one-off shell commands, plain Bash hooks are fine.',
797
+ description: 'Get a 3-tier routing recommendation for a task: Tier 1 (deterministic codemod, ~0ms / $0 — for var-to-const, remove-console, add-logging), Tier 2 (Haiku — simple), Tier 3 (Sonnet/Opus — complex). Use this BEFORE spawning an agent to avoid sending simple transforms to Sonnet. Native tools have no equivalent — Claude Code does not introspect its own model-selection cost. Returns the recommended model + a `[CODEMOD_AVAILABLE]` literal when a deterministic codemod can fully apply the edit (then call hooks_codemod). Use when native Bash hooks (via Claude Code\'s settings.json) are wrong because you need Ruflo-side state — pattern persistence, neural training signals, model-routing learning, cost tracking, audit chain. For one-off shell commands, plain Bash hooks are fine.',
765
798
  inputSchema: {
766
799
  type: 'object',
767
800
  properties: {
@@ -1086,24 +1119,26 @@ export const hooksPreTask = {
1086
1119
  : descLower.includes('simple') || descLower.includes('fix') || description.length < 50
1087
1120
  ? 'low'
1088
1121
  : 'medium';
1089
- // Enhanced model routing with Agent Booster AST (ADR-026)
1122
+ // Enhanced model routing with deterministic Tier-1 codemods (ADR-026, ADR-143)
1090
1123
  let modelRouting;
1091
1124
  try {
1092
1125
  const { getEnhancedModelRouter } = await import('../ruvector/enhanced-model-router.js');
1093
1126
  const router = getEnhancedModelRouter();
1094
1127
  const routeResult = await router.route(description, { filePath });
1095
1128
  if (routeResult.tier === 1) {
1096
- // Agent Booster can handle this task
1129
+ // Deterministic codemod can apply this edit with $0 / no LLM (ADR-143)
1130
+ const intentType = routeResult.codemodIntent?.type ?? routeResult.agentBoosterIntent?.type;
1097
1131
  modelRouting = {
1098
1132
  tier: 1,
1099
- handler: 'agent-booster',
1133
+ handler: 'codemod',
1100
1134
  canSkipLLM: true,
1101
- agentBoosterIntent: routeResult.agentBoosterIntent?.type,
1102
- intentDescription: routeResult.agentBoosterIntent?.description,
1135
+ deterministic: true,
1136
+ codemodIntent: intentType,
1137
+ intentDescription: routeResult.codemodIntent?.description ?? routeResult.agentBoosterIntent?.description,
1103
1138
  confidence: routeResult.confidence,
1104
1139
  estimatedLatencyMs: routeResult.estimatedLatencyMs,
1105
1140
  estimatedCost: routeResult.estimatedCost,
1106
- recommendation: `[AGENT_BOOSTER_AVAILABLE] Skip LLM - use Agent Booster for "${routeResult.agentBoosterIntent?.type}"`,
1141
+ recommendation: `[CODEMOD_AVAILABLE] Skip LLM call hooks_codemod with intent="${intentType}" (deterministic, $0)`,
1107
1142
  };
1108
1143
  }
1109
1144
  else {
@@ -2322,8 +2357,10 @@ export const hooksTrajectoryStep = {
2322
2357
  },
2323
2358
  handler: async (params) => {
2324
2359
  const trajectoryId = params.trajectoryId;
2325
- const action = params.action;
2326
- const result = params.result || 'success';
2360
+ // #14: scrub extended-thinking blocks so reasoning tokens don't contaminate
2361
+ // the learning signal (DISTILL embeds this text).
2362
+ const action = scrubReasoningBlocks(params.action);
2363
+ const result = scrubReasoningBlocks(params.result || 'success');
2327
2364
  const quality = params.quality || 0.85;
2328
2365
  const timestamp = new Date().toISOString();
2329
2366
  const stepId = `step-${Date.now()}`;
@@ -3862,6 +3899,170 @@ export const hooksModelStats = {
3862
3899
  };
3863
3900
  },
3864
3901
  };
3902
+ // Supported source extensions for codemods.
3903
+ const CODEMOD_EXTENSIONS = new Set(['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.mts', '.cts']);
3904
+ const CODEMOD_MAX_FILES = 2000;
3905
+ function codemodLangForExt(abs) {
3906
+ const ext = abs.slice(abs.lastIndexOf('.')).toLowerCase();
3907
+ if (ext === '.tsx')
3908
+ return 'tsx';
3909
+ if (ext === '.jsx')
3910
+ return 'jsx';
3911
+ if (ext === '.js' || ext === '.mjs' || ext === '.cjs')
3912
+ return 'javascript';
3913
+ return 'typescript';
3914
+ }
3915
+ // Deterministic codemod execution — the real Tier-1 path (ADR-143)
3916
+ export const hooksCodemod = {
3917
+ name: 'hooks_codemod',
3918
+ description: 'Apply a deterministic, $0 (no-LLM) code transform — the real Tier-1 execution path (ADR-143). Supported intents: var-to-const, remove-console, add-logging. Uses the TypeScript compiler with formatting-preserving edits (comments/whitespace survive). Targets: raw `code` (returns transformed text, writes nothing) | a single `file` | a `files` array | a `glob` pattern (batch — applies the intent across every match in one $0 call). Files are rewritten in place unless `dryRun`. Intents that need reasoning — add-types, add-error-handling, async-await — are NOT supported here; route those to a model via hooks_model-route. Use when hooks_pre-task / hooks_route returned [CODEMOD_AVAILABLE].',
3919
+ inputSchema: {
3920
+ type: 'object',
3921
+ properties: {
3922
+ intent: { type: 'string', enum: ['var-to-const', 'remove-console', 'add-logging'], description: 'Deterministic codemod to apply' },
3923
+ file: { type: 'string', description: 'Path to a single existing source file to transform in place' },
3924
+ files: { type: 'array', items: { type: 'string' }, description: 'Multiple file paths to transform in one batch call' },
3925
+ glob: { type: 'string', description: 'Glob pattern (relative to project root, e.g. "src/**/*.ts") — applies the intent to every matching source file' },
3926
+ code: { type: 'string', description: 'Raw source to transform instead of files (returns transformed code, writes nothing)' },
3927
+ language: { type: 'string', enum: ['javascript', 'typescript', 'jsx', 'tsx'], description: 'Language hint for raw code (default typescript; inferred from extension for files)' },
3928
+ dryRun: { type: 'boolean', description: 'Report what would change without writing files' },
3929
+ },
3930
+ required: ['intent'],
3931
+ },
3932
+ handler: async (params) => {
3933
+ const intent = params.intent;
3934
+ const file = params.file;
3935
+ const files = Array.isArray(params.files) ? params.files : undefined;
3936
+ const glob = params.glob;
3937
+ const rawCode = params.code;
3938
+ const dryRun = params.dryRun === true;
3939
+ const langParam = params.language;
3940
+ const { applyCodemod, isDeterministicCodemod } = await import('../ruvector/codemods/engine.js');
3941
+ if (!isDeterministicCodemod(intent)) {
3942
+ return {
3943
+ success: false,
3944
+ error: `"${intent}" is not a deterministic codemod. Route it to a model via hooks_model-route (Tier 2/3).`,
3945
+ };
3946
+ }
3947
+ // Mode A: transform raw code (never touches disk)
3948
+ if (typeof rawCode === 'string') {
3949
+ const language = langParam ?? 'typescript';
3950
+ const r = applyCodemod(intent, rawCode, { language });
3951
+ return {
3952
+ success: r.success, intent, mode: 'code', changed: r.changed, edits: r.edits,
3953
+ output: r.output, language: r.language, reason: r.reason, cost: 0, tier: 1,
3954
+ };
3955
+ }
3956
+ const cwd = getProjectCwd();
3957
+ // Resolve the target file set (single / array / glob), with path containment.
3958
+ const resolveTargets = () => {
3959
+ const out = new Set();
3960
+ const addRaw = (p) => {
3961
+ const v = validatePath(p, 'path');
3962
+ if (!v.valid)
3963
+ return v.error;
3964
+ const abs = resolve(cwd, v.sanitized);
3965
+ if (!abs.startsWith(cwd))
3966
+ return `path escapes project root: ${p}`;
3967
+ out.add(abs);
3968
+ return undefined;
3969
+ };
3970
+ if (file) {
3971
+ const e = addRaw(file);
3972
+ if (e)
3973
+ return { abs: [], truncated: false, error: e };
3974
+ }
3975
+ if (files)
3976
+ for (const p of files) {
3977
+ const e = addRaw(p);
3978
+ if (e)
3979
+ return { abs: [], truncated: false, error: e };
3980
+ }
3981
+ if (glob) {
3982
+ if (glob.includes('..'))
3983
+ return { abs: [], truncated: false, error: 'glob must not contain ".."' };
3984
+ // fs.globSync is Node 22+; @types/node here predates it, so type it locally.
3985
+ const globSync = nodeFs.globSync;
3986
+ if (typeof globSync !== 'function') {
3987
+ return { abs: [], truncated: false, error: 'glob requires Node 22+ (fs.globSync unavailable); pass `files[]` instead' };
3988
+ }
3989
+ let matches = [];
3990
+ try {
3991
+ matches = globSync(glob, { cwd });
3992
+ }
3993
+ catch (err) {
3994
+ return { abs: [], truncated: false, error: `glob failed: ${err.message}` };
3995
+ }
3996
+ for (const m of matches) {
3997
+ const abs = resolve(cwd, m);
3998
+ if (abs.startsWith(cwd) && CODEMOD_EXTENSIONS.has(abs.slice(abs.lastIndexOf('.')).toLowerCase())) {
3999
+ out.add(abs);
4000
+ }
4001
+ }
4002
+ }
4003
+ const all = [...out];
4004
+ const truncated = all.length > CODEMOD_MAX_FILES;
4005
+ return { abs: truncated ? all.slice(0, CODEMOD_MAX_FILES) : all, truncated };
4006
+ };
4007
+ const targets = resolveTargets();
4008
+ if (targets.error)
4009
+ return { success: false, error: targets.error };
4010
+ if (targets.abs.length === 0) {
4011
+ return { success: false, error: 'No target files. Provide `code`, `file`, `files[]`, or a matching `glob`.' };
4012
+ }
4013
+ // Apply to each file.
4014
+ const results = [];
4015
+ let filesChanged = 0, totalEdits = 0, failures = 0, skipped = 0;
4016
+ for (const abs of targets.abs) {
4017
+ const rel = abs.startsWith(cwd) ? abs.slice(cwd.length).replace(/^[/\\]/, '') : abs;
4018
+ if (!existsSync(abs)) {
4019
+ results.push({ file: rel, success: false, reason: 'not found' });
4020
+ failures++;
4021
+ continue;
4022
+ }
4023
+ if (!CODEMOD_EXTENSIONS.has(abs.slice(abs.lastIndexOf('.')).toLowerCase())) {
4024
+ results.push({ file: rel, success: false, reason: 'unsupported extension' });
4025
+ skipped++;
4026
+ continue;
4027
+ }
4028
+ const before = readFileSync(abs, 'utf-8');
4029
+ const r = applyCodemod(intent, before, { language: codemodLangForExt(abs) });
4030
+ if (!r.success) {
4031
+ results.push({ file: rel, success: false, changed: false, reason: r.reason });
4032
+ failures++;
4033
+ continue;
4034
+ }
4035
+ const written = r.changed && !dryRun;
4036
+ if (written)
4037
+ writeFileSync(abs, r.output, 'utf-8');
4038
+ if (r.changed) {
4039
+ filesChanged++;
4040
+ totalEdits += r.edits;
4041
+ }
4042
+ results.push({ file: rel, success: true, changed: r.changed, edits: r.edits, written });
4043
+ }
4044
+ const single = targets.abs.length === 1 && !files && !glob;
4045
+ return {
4046
+ success: failures === 0,
4047
+ intent,
4048
+ mode: single ? (dryRun ? 'dry-run' : 'file') : (dryRun ? 'batch-dry-run' : 'batch'),
4049
+ summary: {
4050
+ filesScanned: targets.abs.length,
4051
+ filesChanged,
4052
+ filesUnchanged: targets.abs.length - filesChanged - failures - skipped,
4053
+ totalEdits,
4054
+ failures,
4055
+ skipped,
4056
+ truncatedAt: targets.truncated ? CODEMOD_MAX_FILES : undefined,
4057
+ },
4058
+ results: results.slice(0, 500),
4059
+ resultsTruncated: results.length > 500,
4060
+ cost: 0,
4061
+ tier: 1,
4062
+ timestamp: new Date().toISOString(),
4063
+ };
4064
+ },
4065
+ };
3865
4066
  // Simple fallback complexity analyzer
3866
4067
  function analyzeComplexityFallback(task) {
3867
4068
  const taskLower = task.toLowerCase();
@@ -4019,6 +4220,8 @@ export const hooksTools = [
4019
4220
  hooksModelRoute,
4020
4221
  hooksModelOutcome,
4021
4222
  hooksModelStats,
4223
+ // Deterministic Tier-1 codemod execution (ADR-143)
4224
+ hooksCodemod,
4022
4225
  ];
4023
4226
  export default hooksTools;
4024
4227
  //# sourceMappingURL=hooks-tools.js.map