@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.
- package/dist/src/commands/hooks.d.ts.map +1 -1
- package/dist/src/commands/hooks.js +10 -8
- package/dist/src/commands/hooks.js.map +1 -1
- package/dist/src/commands/mcp.d.ts.map +1 -1
- package/dist/src/commands/mcp.js +14 -0
- package/dist/src/commands/mcp.js.map +1 -1
- package/dist/src/init/settings-generator.js +1 -1
- package/dist/src/init/statusline-generator.js +2 -2
- package/dist/src/mcp-tools/agent-execute-core.d.ts +2 -2
- package/dist/src/mcp-tools/agent-execute-core.d.ts.map +1 -1
- package/dist/src/mcp-tools/agent-execute-core.js +15 -6
- package/dist/src/mcp-tools/agent-execute-core.js.map +1 -1
- package/dist/src/mcp-tools/agent-tools.js +14 -14
- package/dist/src/mcp-tools/agent-tools.js.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.d.ts +11 -0
- package/dist/src/mcp-tools/hooks-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/hooks-tools.js +216 -13
- package/dist/src/mcp-tools/hooks-tools.js.map +1 -1
- package/dist/src/mcp-tools/system-tools.d.ts.map +1 -1
- package/dist/src/mcp-tools/system-tools.js +5 -2
- package/dist/src/mcp-tools/system-tools.js.map +1 -1
- package/dist/src/mcp-tools/tool-loop-guardrail.d.ts +31 -0
- package/dist/src/mcp-tools/tool-loop-guardrail.d.ts.map +1 -0
- package/dist/src/mcp-tools/tool-loop-guardrail.js +71 -0
- package/dist/src/mcp-tools/tool-loop-guardrail.js.map +1 -0
- package/dist/src/runtime/parent-death-watchdog.d.ts +42 -0
- package/dist/src/runtime/parent-death-watchdog.d.ts.map +1 -0
- package/dist/src/runtime/parent-death-watchdog.js +70 -0
- package/dist/src/runtime/parent-death-watchdog.js.map +1 -0
- package/dist/src/ruvector/codemods/engine.d.ts +45 -0
- package/dist/src/ruvector/codemods/engine.d.ts.map +1 -0
- package/dist/src/ruvector/codemods/engine.js +291 -0
- package/dist/src/ruvector/codemods/engine.js.map +1 -0
- package/dist/src/ruvector/codemods/scope-analysis.d.ts +29 -0
- package/dist/src/ruvector/codemods/scope-analysis.d.ts.map +1 -0
- package/dist/src/ruvector/codemods/scope-analysis.js +162 -0
- package/dist/src/ruvector/codemods/scope-analysis.js.map +1 -0
- package/dist/src/ruvector/enhanced-model-router.d.ts +29 -8
- package/dist/src/ruvector/enhanced-model-router.d.ts.map +1 -1
- package/dist/src/ruvector/enhanced-model-router.js +104 -81
- package/dist/src/ruvector/enhanced-model-router.js.map +1 -1
- package/dist/src/ruvector/q-learning-router.d.ts +14 -1
- package/dist/src/ruvector/q-learning-router.d.ts.map +1 -1
- package/dist/src/ruvector/q-learning-router.js +36 -8
- package/dist/src/ruvector/q-learning-router.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- 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
|
|
699
|
-
|
|
700
|
-
: ['Command appears safe to execute'],
|
|
728
|
+
recommendations,
|
|
729
|
+
loopGuard: { verdict: loop.verdict, consecutiveFailures: loop.consecutiveFailures },
|
|
701
730
|
safeAlternatives: [],
|
|
702
|
-
|
|
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 (
|
|
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
|
|
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
|
-
//
|
|
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: '
|
|
1133
|
+
handler: 'codemod',
|
|
1100
1134
|
canSkipLLM: true,
|
|
1101
|
-
|
|
1102
|
-
|
|
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: `[
|
|
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
|
-
|
|
2326
|
-
|
|
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
|