token-goat 2.6.33 → 2.6.34
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/README.md +4 -0
- package/dist/{token-goat-chunk-WFRR2DBG.mjs → token-goat-chunk-7FWBSPCT.mjs} +446 -339
- package/dist/{token-goat-chunk-HPWPUBAK.mjs → token-goat-chunk-CKRATWCJ.mjs} +5 -5
- package/dist/{token-goat-chunk-TDYTBCT4.mjs → token-goat-chunk-CN7GUPX6.mjs} +328 -86
- package/dist/{token-goat-chunk-L6FVTDPD.mjs → token-goat-chunk-FRMEOIRU.mjs} +361 -23
- package/dist/{token-goat-chunk-RPOXYJWK.mjs → token-goat-chunk-GXBPEQR2.mjs} +70 -25
- package/dist/{token-goat-chunk-77CX3MCN.mjs → token-goat-chunk-I6EQH27G.mjs} +2 -2
- package/dist/{token-goat-chunk-FN6WLFIX.mjs → token-goat-chunk-IYYQOPY4.mjs} +801 -51
- package/dist/{token-goat-chunk-72R7M5H7.mjs → token-goat-chunk-L7QLEVO7.mjs} +3 -3
- package/dist/{token-goat-chunk-OHQ7RBLL.mjs → token-goat-chunk-OA3GFKP2.mjs} +2 -2
- package/dist/{token-goat-hook-chunk-HXHFLKEL.mjs → token-goat-chunk-WA2ER6TZ.mjs} +7 -2
- package/dist/{token-goat-chunk-RQQ4SY2P.mjs → token-goat-hook-chunk-2ISZ4AZR.mjs} +7 -2
- package/dist/{token-goat-hook-chunk-ZT47M3IA.mjs → token-goat-hook-chunk-6C5RN3CM.mjs} +2 -2
- package/dist/{token-goat-hook-chunk-UWV4MTYI.mjs → token-goat-hook-chunk-6QTWMSRO.mjs} +328 -86
- package/dist/{token-goat-hook-chunk-XBOWDNJB.mjs → token-goat-hook-chunk-ATEGFQAU.mjs} +5 -5
- package/dist/{token-goat-hook-chunk-QD2DX2U7.mjs → token-goat-hook-chunk-BU5OFDGQ.mjs} +361 -23
- package/dist/{token-goat-hook-chunk-E6AIPKNF.mjs → token-goat-hook-chunk-CNLMPHXT.mjs} +442 -335
- package/dist/{token-goat-hook-chunk-WBBHV6TZ.mjs → token-goat-hook-chunk-I5VM5PO5.mjs} +70 -25
- package/dist/{token-goat-hook-chunk-VDXQXYJK.mjs → token-goat-hook-chunk-KGLMNOGD.mjs} +801 -51
- package/dist/{token-goat-hook-chunk-EKGDLI4D.mjs → token-goat-hook-chunk-OQML5EMA.mjs} +3 -3
- package/dist/{token-goat-hook-chunk-26DLRODR.mjs → token-goat-hook-chunk-QTZ6YUMQ.mjs} +2 -2
- package/dist/token-goat-hook.mjs +5 -5
- package/dist/token-goat.core.mjs +5 -5
- package/package.json +8 -8
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
buildEvent,
|
|
5
5
|
relay,
|
|
6
6
|
relayInProcess
|
|
7
|
-
} from "./token-goat-hook-chunk-
|
|
8
|
-
import "./token-goat-hook-chunk-
|
|
7
|
+
} from "./token-goat-hook-chunk-BU5OFDGQ.mjs";
|
|
8
|
+
import "./token-goat-hook-chunk-2ISZ4AZR.mjs";
|
|
9
9
|
import {
|
|
10
10
|
MAX_STDIN_BYTES,
|
|
11
11
|
readStdinJson
|
|
12
|
-
} from "./token-goat-hook-chunk-
|
|
13
|
-
import "./token-goat-hook-chunk-
|
|
14
|
-
import "./token-goat-hook-chunk-
|
|
12
|
+
} from "./token-goat-hook-chunk-6C5RN3CM.mjs";
|
|
13
|
+
import "./token-goat-hook-chunk-6QTWMSRO.mjs";
|
|
14
|
+
import "./token-goat-hook-chunk-CNLMPHXT.mjs";
|
|
15
15
|
import "./token-goat-hook-chunk-BUOCULAM.mjs";
|
|
16
16
|
import "./token-goat-hook-chunk-RFRLWOQH.mjs";
|
|
17
17
|
export {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
isRewriteWorthwhile,
|
|
9
9
|
resolveMinNetSavingsBytes,
|
|
10
10
|
shlexSplit
|
|
11
|
-
} from "./token-goat-hook-chunk-
|
|
11
|
+
} from "./token-goat-hook-chunk-2ISZ4AZR.mjs";
|
|
12
12
|
import {
|
|
13
13
|
BASH_OUTPUT_SUBDIR,
|
|
14
14
|
HOOK_EVENTS,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
storeBashOutput,
|
|
27
27
|
storeWebOutput,
|
|
28
28
|
summarizeOutputDelta
|
|
29
|
-
} from "./token-goat-hook-chunk-
|
|
29
|
+
} from "./token-goat-hook-chunk-6C5RN3CM.mjs";
|
|
30
30
|
import {
|
|
31
31
|
BODY_FIRST_TOOL_RESPONSE_KEYS,
|
|
32
32
|
OUTPUT_FIRST_TOOL_RESPONSE_KEYS,
|
|
@@ -116,11 +116,12 @@ import {
|
|
|
116
116
|
wasCliReadThisSession,
|
|
117
117
|
wasFileReadThisSession,
|
|
118
118
|
wasHintShown
|
|
119
|
-
} from "./token-goat-hook-chunk-
|
|
119
|
+
} from "./token-goat-hook-chunk-6QTWMSRO.mjs";
|
|
120
120
|
import {
|
|
121
121
|
VERSION,
|
|
122
122
|
detectHarness,
|
|
123
123
|
detectLanguage,
|
|
124
|
+
displaySafePath,
|
|
124
125
|
extractErrorMessage,
|
|
125
126
|
foldPath,
|
|
126
127
|
getDb,
|
|
@@ -134,7 +135,7 @@ import {
|
|
|
134
135
|
runGit,
|
|
135
136
|
shortFingerprint,
|
|
136
137
|
toKB
|
|
137
|
-
} from "./token-goat-hook-chunk-
|
|
138
|
+
} from "./token-goat-hook-chunk-CNLMPHXT.mjs";
|
|
138
139
|
|
|
139
140
|
// src/hooks_grep.ts
|
|
140
141
|
function grepIntInput(toolInput, key) {
|
|
@@ -311,9 +312,9 @@ function postEditHandlerInner(event) {
|
|
|
311
312
|
} catch {
|
|
312
313
|
}
|
|
313
314
|
if (meetsSavingsFloor(editedSize)) {
|
|
314
|
-
const escapedPath = normalized.replace(/`/g, "\\`").replace(/"/g, '\\"');
|
|
315
|
+
const escapedPath = displaySafePath(normalized).replace(/`/g, "\\`").replace(/"/g, '\\"');
|
|
315
316
|
return contextOutput(
|
|
316
|
-
editedBasename + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
|
|
317
|
+
displaySafePath(editedBasename) + ' was edited. Use `token-goat section "' + escapedPath + '::HeadingName"` to re-read a specific section rather than the full file.'
|
|
317
318
|
);
|
|
318
319
|
}
|
|
319
320
|
}
|
|
@@ -572,7 +573,7 @@ import crypto from "node:crypto";
|
|
|
572
573
|
var TRACKED_SKILL = "token-goat";
|
|
573
574
|
var MAX_COMMANDS_SHOWN = 8;
|
|
574
575
|
async function currentCommandNames() {
|
|
575
|
-
const { buildProgram } = await import("./token-goat-hook-chunk-
|
|
576
|
+
const { buildProgram } = await import("./token-goat-hook-chunk-I5VM5PO5.mjs");
|
|
576
577
|
return flattenCommandNames(buildCommandManifest(buildProgram()));
|
|
577
578
|
}
|
|
578
579
|
async function recordSkillVersionSnapshot(sessionId, skillName) {
|
|
@@ -7051,6 +7052,10 @@ function stripCdPrefix(cmd) {
|
|
|
7051
7052
|
const stripped = cmd.replace(/^(?:cd\s+(?:"[^"]*"|'[^']*'|\S+)\s*&&\s*)+/, "");
|
|
7052
7053
|
return stripped.trim() || cmd;
|
|
7053
7054
|
}
|
|
7055
|
+
function stripTrailingStderrRedirect(cmd) {
|
|
7056
|
+
const stripped = cmd.replace(/\s*2>&1\s*$/, "");
|
|
7057
|
+
return stripped.trim() || cmd;
|
|
7058
|
+
}
|
|
7054
7059
|
function hasTestRunScopeOrBudget(cmd) {
|
|
7055
7060
|
const hasTimeout = /(?:^|\s)(?:timeout(?:\.exe)?\s+\S+|--(?:test)?timeout(?:=|\s)\S+)/i.test(cmd);
|
|
7056
7061
|
const hasSelector = /(?:\s::\S+|\s(?:-k|-t|-run|--(?:testNamePattern|last-failed|test|project))(?:=|\s)|\b(?:tests?|spec)\S*\.(?:[cm]?[jt]sx?|py|go|rs)\b|(?:^|\s)\.\/\S+)/i.test(cmd);
|
|
@@ -7142,6 +7147,182 @@ function isHeadMovingGitCommand(cmd) {
|
|
|
7142
7147
|
}
|
|
7143
7148
|
var ORIG_HEAD_ELIGIBLE_GIT_RE = /^\s*git\s+(?:pull|merge|rebase)\b/i;
|
|
7144
7149
|
var ORIG_HEAD_REFLOG_MSG_RE = /^(merge\s|rebase\s\(|pull\s)/i;
|
|
7150
|
+
var GIT_RESTORE_RE = /^\s*git\s+restore\b/i;
|
|
7151
|
+
var GIT_RESTORE_STAGED_ONLY_RE = /(?:^|\s)(?:--staged|-S)(?:\s|$)/;
|
|
7152
|
+
var GIT_STASH_APPLY_RE = /^\s*git\s+stash\s+(?:pop|apply)\b/i;
|
|
7153
|
+
var PATCH_APPLY_SEGMENT_RE = /^\s*(?:\S*[/\\])?(?:git\s+apply|patch)\b/i;
|
|
7154
|
+
var PATCH_APPLY_INSPECT_ONLY_RE = /(?:^|\s)--(?:check|stat|numstat|summary|dry-run)(?:\s|=|$)/i;
|
|
7155
|
+
function isFormatterWriteSegment(segment) {
|
|
7156
|
+
return /(?:^|\s|[/\\])(?:prettier|eslint)\b/i.test(segment) && /(?:^|\s)--(?:write|fix)(?:\s|$)/i.test(segment);
|
|
7157
|
+
}
|
|
7158
|
+
function splitShellSegments(cmd) {
|
|
7159
|
+
const segments = [];
|
|
7160
|
+
let cur = "";
|
|
7161
|
+
let quote = null;
|
|
7162
|
+
for (let i = 0; i < cmd.length; i++) {
|
|
7163
|
+
const ch = cmd[i];
|
|
7164
|
+
if (quote !== null) {
|
|
7165
|
+
cur += ch;
|
|
7166
|
+
if (ch === quote) quote = null;
|
|
7167
|
+
continue;
|
|
7168
|
+
}
|
|
7169
|
+
if (ch === '"' || ch === "'") {
|
|
7170
|
+
quote = ch;
|
|
7171
|
+
cur += ch;
|
|
7172
|
+
continue;
|
|
7173
|
+
}
|
|
7174
|
+
if (ch === "\\" && i + 1 < cmd.length) {
|
|
7175
|
+
cur += ch + cmd[i + 1];
|
|
7176
|
+
i++;
|
|
7177
|
+
continue;
|
|
7178
|
+
}
|
|
7179
|
+
if (ch === "|" || ch === ";" || ch === "\n" || ch === "&") {
|
|
7180
|
+
segments.push(cur);
|
|
7181
|
+
cur = "";
|
|
7182
|
+
if ((ch === "|" || ch === "&") && cmd[i + 1] === ch) i++;
|
|
7183
|
+
continue;
|
|
7184
|
+
}
|
|
7185
|
+
cur += ch;
|
|
7186
|
+
}
|
|
7187
|
+
segments.push(cur);
|
|
7188
|
+
return segments.map((s) => s.trim()).filter((s) => s.length > 0);
|
|
7189
|
+
}
|
|
7190
|
+
function safeShlexSplit(segment) {
|
|
7191
|
+
try {
|
|
7192
|
+
return shlexSplit(segment);
|
|
7193
|
+
} catch {
|
|
7194
|
+
return null;
|
|
7195
|
+
}
|
|
7196
|
+
}
|
|
7197
|
+
function segmentCommandIs(segment, name2) {
|
|
7198
|
+
return new RegExp("^\\s*(?:\\S*[/\\\\])?" + name2 + "(?:\\.exe)?\\b", "i").test(segment);
|
|
7199
|
+
}
|
|
7200
|
+
function extractSedInPlaceFiles(segment) {
|
|
7201
|
+
if (!segmentCommandIs(segment, "sed")) return [];
|
|
7202
|
+
const tokens = safeShlexSplit(segment);
|
|
7203
|
+
if (tokens === null) return [];
|
|
7204
|
+
const rest = tokens.slice(1);
|
|
7205
|
+
const inPlaceIdx = rest.findIndex((t) => t === "--in-place" || t.startsWith("--in-place=") || /^-[a-zA-Z]*i/.test(t));
|
|
7206
|
+
if (inPlaceIdx === -1) return [];
|
|
7207
|
+
const files = [];
|
|
7208
|
+
let scriptTaken = rest.some((t) => t === "-e" || t === "-f" || t.startsWith("--expression") || t.startsWith("--file"));
|
|
7209
|
+
for (let i = 0; i < rest.length; i++) {
|
|
7210
|
+
const t = rest[i];
|
|
7211
|
+
if (i === inPlaceIdx) {
|
|
7212
|
+
if (t === "-i" && rest[i + 1] === "") i++;
|
|
7213
|
+
continue;
|
|
7214
|
+
}
|
|
7215
|
+
if (t === "-e" || t === "-f" || t === "--expression" || t === "--file") {
|
|
7216
|
+
i++;
|
|
7217
|
+
continue;
|
|
7218
|
+
}
|
|
7219
|
+
if (t.length > 1 && t.startsWith("-")) continue;
|
|
7220
|
+
if (!scriptTaken) {
|
|
7221
|
+
scriptTaken = true;
|
|
7222
|
+
continue;
|
|
7223
|
+
}
|
|
7224
|
+
files.push(t);
|
|
7225
|
+
}
|
|
7226
|
+
return files;
|
|
7227
|
+
}
|
|
7228
|
+
function extractTeeFiles(segment) {
|
|
7229
|
+
if (!segmentCommandIs(segment, "tee")) return [];
|
|
7230
|
+
const tokens = safeShlexSplit(segment);
|
|
7231
|
+
if (tokens === null) return [];
|
|
7232
|
+
return tokens.slice(1).filter((t) => t.length > 0 && !t.startsWith("-"));
|
|
7233
|
+
}
|
|
7234
|
+
function extractRedirectTargets(segment) {
|
|
7235
|
+
const targets = [];
|
|
7236
|
+
const re = /(?:^|\s)[0-9]?>>?\s*("[^"]*"|'[^']*'|[^\s|&<>]+)/g;
|
|
7237
|
+
let m;
|
|
7238
|
+
while ((m = re.exec(segment)) !== null) {
|
|
7239
|
+
const raw = m[1];
|
|
7240
|
+
targets.push(raw.length >= 2 && (raw[0] === '"' || raw[0] === "'") ? raw.slice(1, -1) : raw);
|
|
7241
|
+
}
|
|
7242
|
+
return targets;
|
|
7243
|
+
}
|
|
7244
|
+
function extractGitRestorePathspecs(cmd) {
|
|
7245
|
+
const m = /^\s*git\s+restore\b(.*)$/i.exec(cmd);
|
|
7246
|
+
if (m === null) return [];
|
|
7247
|
+
const tokens = safeShlexSplit(m[1] ?? "");
|
|
7248
|
+
if (tokens === null) return [];
|
|
7249
|
+
const dashDash = tokens.indexOf("--");
|
|
7250
|
+
if (dashDash !== -1) return tokens.slice(dashDash + 1);
|
|
7251
|
+
const specs = [];
|
|
7252
|
+
let skipNext = false;
|
|
7253
|
+
for (const t of tokens) {
|
|
7254
|
+
if (skipNext) {
|
|
7255
|
+
skipNext = false;
|
|
7256
|
+
continue;
|
|
7257
|
+
}
|
|
7258
|
+
if (t === "-s" || t === "--source") {
|
|
7259
|
+
skipNext = true;
|
|
7260
|
+
continue;
|
|
7261
|
+
}
|
|
7262
|
+
if (t.startsWith("-")) continue;
|
|
7263
|
+
specs.push(t);
|
|
7264
|
+
}
|
|
7265
|
+
return specs;
|
|
7266
|
+
}
|
|
7267
|
+
function gitRepoRoot(gitDir) {
|
|
7268
|
+
const toplevel = runGit(["rev-parse", "--show-toplevel"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
7269
|
+
return toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
|
|
7270
|
+
}
|
|
7271
|
+
function expandGitPathspecs(specs, gitDir) {
|
|
7272
|
+
if (specs.length === 0) return [];
|
|
7273
|
+
const listed = runGit(["ls-files", "--full-name", "--", ...specs], { cwd: gitDir, timeoutMs: 5e3 });
|
|
7274
|
+
if (listed.exitCode !== 0) return [];
|
|
7275
|
+
const repoRoot = gitRepoRoot(gitDir);
|
|
7276
|
+
return listed.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0).map((rel) => resolveIndexPath(rel, repoRoot));
|
|
7277
|
+
}
|
|
7278
|
+
function workingTreeStatusPaths(gitDir) {
|
|
7279
|
+
const status = runGit(["status", "--porcelain"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
7280
|
+
if (status.exitCode !== 0) return [];
|
|
7281
|
+
const repoRoot = gitRepoRoot(gitDir);
|
|
7282
|
+
const out = [];
|
|
7283
|
+
for (const line of status.stdout.split("\n")) {
|
|
7284
|
+
if (line.length < 4) continue;
|
|
7285
|
+
let rel = line.slice(3).trim();
|
|
7286
|
+
if (rel.length === 0) continue;
|
|
7287
|
+
const arrow = rel.lastIndexOf(" -> ");
|
|
7288
|
+
if (arrow !== -1) rel = rel.slice(arrow + 4);
|
|
7289
|
+
if (rel.startsWith('"')) {
|
|
7290
|
+
try {
|
|
7291
|
+
rel = JSON.parse(rel);
|
|
7292
|
+
} catch {
|
|
7293
|
+
continue;
|
|
7294
|
+
}
|
|
7295
|
+
}
|
|
7296
|
+
out.push(resolveIndexPath(rel, repoRoot));
|
|
7297
|
+
}
|
|
7298
|
+
return out;
|
|
7299
|
+
}
|
|
7300
|
+
function enqueueRewrittenPath(absPath) {
|
|
7301
|
+
if (/(?:^|[/\\])(?:NUL|nul)$/.test(absPath) || absPath.replace(/\\/g, "/").endsWith("/dev/null")) return;
|
|
7302
|
+
try {
|
|
7303
|
+
if (!statSync3(absPath).isFile()) return;
|
|
7304
|
+
} catch {
|
|
7305
|
+
return;
|
|
7306
|
+
}
|
|
7307
|
+
enqueueDirtyPathSafe(absPath, { alreadyResolved: true });
|
|
7308
|
+
}
|
|
7309
|
+
function enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd) {
|
|
7310
|
+
const atCwd = (f) => resolveIndexPath(resolveCdHintPath(rawCmd, f, cwd), cwd);
|
|
7311
|
+
const paths = [];
|
|
7312
|
+
let needsStatusSweep = GIT_STASH_APPLY_RE.test(cmd);
|
|
7313
|
+
if (GIT_RESTORE_RE.test(cmd) && !GIT_RESTORE_STAGED_ONLY_RE.test(cmd)) {
|
|
7314
|
+
paths.push(...expandGitPathspecs(extractGitRestorePathspecs(cmd), cwd));
|
|
7315
|
+
}
|
|
7316
|
+
for (const segment of splitShellSegments(cmd)) {
|
|
7317
|
+
if (PATCH_APPLY_SEGMENT_RE.test(segment) && !PATCH_APPLY_INSPECT_ONLY_RE.test(segment)) needsStatusSweep = true;
|
|
7318
|
+
if (isFormatterWriteSegment(segment)) needsStatusSweep = true;
|
|
7319
|
+
for (const f of extractSedInPlaceFiles(segment)) paths.push(atCwd(f));
|
|
7320
|
+
for (const f of extractTeeFiles(segment)) paths.push(atCwd(f));
|
|
7321
|
+
for (const f of extractRedirectTargets(segment)) paths.push(atCwd(f));
|
|
7322
|
+
}
|
|
7323
|
+
if (needsStatusSweep) paths.push(...workingTreeStatusPaths(cwd));
|
|
7324
|
+
for (const p of paths) enqueueRewrittenPath(p);
|
|
7325
|
+
}
|
|
7145
7326
|
function isTempPath(fp) {
|
|
7146
7327
|
const norm = fp.replace(/\\/g, "/");
|
|
7147
7328
|
return /^\/tmp\//i.test(norm) || /\/var\/folders\//i.test(norm) || /AppData\/Local\/Temp\//i.test(norm) || norm.startsWith("/c/Users/") && norm.includes("/AppData/Local/Temp/") || isUnderSystemTemp(fp);
|
|
@@ -7256,9 +7437,126 @@ function extractWslCatFile(cmd) {
|
|
|
7256
7437
|
if (flags === null) return null;
|
|
7257
7438
|
return { filePath, ...flags };
|
|
7258
7439
|
}
|
|
7440
|
+
function pythonScanText(text) {
|
|
7441
|
+
const dashC = /^python3?\s+-c\s*(['"])([\s\S]*)\1\s*$/.exec(text);
|
|
7442
|
+
if (dashC) {
|
|
7443
|
+
const source = dashC[2] ?? "";
|
|
7444
|
+
return { source, masked: maskPythonStrings(source) };
|
|
7445
|
+
}
|
|
7446
|
+
if (/^python3?\b/.test(text)) return { source: text, masked: text };
|
|
7447
|
+
return { source: text, masked: maskPythonStrings(text) };
|
|
7448
|
+
}
|
|
7449
|
+
function maskPythonStrings(text) {
|
|
7450
|
+
const out = text.split("");
|
|
7451
|
+
let i = 0;
|
|
7452
|
+
while (i < text.length) {
|
|
7453
|
+
const ch = text[i];
|
|
7454
|
+
if (ch !== "'" && ch !== '"') {
|
|
7455
|
+
i += 1;
|
|
7456
|
+
continue;
|
|
7457
|
+
}
|
|
7458
|
+
const triple = text.slice(i, i + 3);
|
|
7459
|
+
const delim = triple === ch + ch + ch ? triple : ch;
|
|
7460
|
+
let j = i + delim.length;
|
|
7461
|
+
while (j < text.length) {
|
|
7462
|
+
if (text[j] === "\\") {
|
|
7463
|
+
j += 2;
|
|
7464
|
+
continue;
|
|
7465
|
+
}
|
|
7466
|
+
if (text.slice(j, j + delim.length) === delim) break;
|
|
7467
|
+
out[j] = " ";
|
|
7468
|
+
j += 1;
|
|
7469
|
+
}
|
|
7470
|
+
i = j + delim.length;
|
|
7471
|
+
}
|
|
7472
|
+
return out.join("");
|
|
7473
|
+
}
|
|
7474
|
+
function pythonOpenCalls(text) {
|
|
7475
|
+
const { source, masked } = pythonScanText(text);
|
|
7476
|
+
const calls = [];
|
|
7477
|
+
for (const opener of masked.matchAll(/\bopen\s*\(/g)) {
|
|
7478
|
+
const start = (opener.index ?? 0) + opener[0].length;
|
|
7479
|
+
const args = [];
|
|
7480
|
+
let current = "";
|
|
7481
|
+
let depth = 0;
|
|
7482
|
+
let quote = "";
|
|
7483
|
+
let closed = false;
|
|
7484
|
+
for (let i = start; i < source.length; i++) {
|
|
7485
|
+
const ch = source[i] ?? "";
|
|
7486
|
+
if (quote !== "") {
|
|
7487
|
+
if (ch === "\\") {
|
|
7488
|
+
current += ch + (source[i + 1] ?? "");
|
|
7489
|
+
i += 1;
|
|
7490
|
+
continue;
|
|
7491
|
+
}
|
|
7492
|
+
if (ch === quote) quote = "";
|
|
7493
|
+
current += ch;
|
|
7494
|
+
continue;
|
|
7495
|
+
}
|
|
7496
|
+
if (ch === "'" || ch === '"') {
|
|
7497
|
+
quote = ch;
|
|
7498
|
+
current += ch;
|
|
7499
|
+
continue;
|
|
7500
|
+
}
|
|
7501
|
+
if (ch === "(" || ch === "[" || ch === "{") {
|
|
7502
|
+
depth += 1;
|
|
7503
|
+
current += ch;
|
|
7504
|
+
continue;
|
|
7505
|
+
}
|
|
7506
|
+
if (ch === ")" && depth === 0) {
|
|
7507
|
+
args.push(current);
|
|
7508
|
+
closed = true;
|
|
7509
|
+
break;
|
|
7510
|
+
}
|
|
7511
|
+
if (ch === ")" || ch === "]" || ch === "}") {
|
|
7512
|
+
depth -= 1;
|
|
7513
|
+
current += ch;
|
|
7514
|
+
continue;
|
|
7515
|
+
}
|
|
7516
|
+
if (ch === "," && depth === 0) {
|
|
7517
|
+
args.push(current);
|
|
7518
|
+
current = "";
|
|
7519
|
+
continue;
|
|
7520
|
+
}
|
|
7521
|
+
current += ch;
|
|
7522
|
+
}
|
|
7523
|
+
if (!closed) continue;
|
|
7524
|
+
const literalOf = (arg) => {
|
|
7525
|
+
const m = /^\s*r?(['"])([^'"]*)\1\s*$/.exec(arg);
|
|
7526
|
+
return m?.[2] ?? null;
|
|
7527
|
+
};
|
|
7528
|
+
const keyword = args.find((a) => /^\s*mode\s*=/.test(a));
|
|
7529
|
+
const positional = args[1] !== void 0 && /^\s*[A-Za-z_]\w*\s*=[^=]/.test(args[1]) ? void 0 : args[1];
|
|
7530
|
+
const modeArg = keyword !== void 0 ? keyword.replace(/^\s*mode\s*=/, "") : positional;
|
|
7531
|
+
calls.push({
|
|
7532
|
+
pathLiteral: args.length > 0 ? literalOf(args[0] ?? "") : null,
|
|
7533
|
+
hasMode: modeArg !== void 0,
|
|
7534
|
+
modeLiteral: modeArg === void 0 ? null : literalOf(modeArg)
|
|
7535
|
+
});
|
|
7536
|
+
}
|
|
7537
|
+
return calls;
|
|
7538
|
+
}
|
|
7539
|
+
function pythonOpenWritesAFile(text) {
|
|
7540
|
+
return pythonOpenCalls(text).some(
|
|
7541
|
+
(call) => call.hasMode && (call.modeLiteral === null || /[wax+]/.test(call.modeLiteral))
|
|
7542
|
+
);
|
|
7543
|
+
}
|
|
7544
|
+
function pythonWritesThroughFileObject(text) {
|
|
7545
|
+
const { masked } = pythonScanText(text);
|
|
7546
|
+
for (const call of masked.matchAll(/\.write(?:lines)?\s*\(/g)) {
|
|
7547
|
+
const receiver = masked.slice(0, call.index ?? 0);
|
|
7548
|
+
if (/(?:^|[^\w.])(?:sys\s*\.\s*)?(?:stdout|stderr|stdin)(?:\s*\.\s*buffer)?$/.test(receiver)) continue;
|
|
7549
|
+
return true;
|
|
7550
|
+
}
|
|
7551
|
+
return false;
|
|
7552
|
+
}
|
|
7553
|
+
function pythonOpenPathsAreAllLiteral(text) {
|
|
7554
|
+
const calls = pythonOpenCalls(text);
|
|
7555
|
+
return calls.length > 0 && calls.every((call) => call.pathLiteral !== null);
|
|
7556
|
+
}
|
|
7259
7557
|
function extractPythonFileRead(cmd) {
|
|
7260
7558
|
if (!/^python3?\b/.test(cmd)) return null;
|
|
7261
|
-
if (
|
|
7559
|
+
if (pythonOpenWritesAFile(cmd) || pythonWritesThroughFileObject(cmd)) return null;
|
|
7262
7560
|
const outputOpen = /open\s*\(\s*r?['"]([^'"]+\.output)['"]/i.exec(cmd);
|
|
7263
7561
|
if (outputOpen?.[1]) {
|
|
7264
7562
|
const filePath = outputOpen[1];
|
|
@@ -7269,7 +7567,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7269
7567
|
const heredocMatch = /^python3?\s+-\s+<<\s*'?(\w+)'?\s*\n([\s\S]*?)\n\1\s*$/.exec(cmd);
|
|
7270
7568
|
if (heredocMatch) {
|
|
7271
7569
|
const body = heredocMatch[2] ?? "";
|
|
7272
|
-
if (
|
|
7570
|
+
if (pythonOpenWritesAFile(body) || pythonWritesThroughFileObject(body)) return null;
|
|
7273
7571
|
const heredocOpen = /open\s*\(\s*r?['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
|
|
7274
7572
|
if (heredocOpen?.[1]) {
|
|
7275
7573
|
const filePath = heredocOpen[1];
|
|
@@ -7277,7 +7575,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7277
7575
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7278
7576
|
return { filePath, isDoc, isTranscript: false };
|
|
7279
7577
|
}
|
|
7280
|
-
if (/open\s*\(/.test(body)) {
|
|
7578
|
+
if (/open\s*\(/.test(body) && !pythonOpenPathsAreAllLiteral(body)) {
|
|
7281
7579
|
const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(body);
|
|
7282
7580
|
if (literal2?.[1]) {
|
|
7283
7581
|
const filePath = literal2[1];
|
|
@@ -7298,7 +7596,7 @@ function extractPythonFileRead(cmd) {
|
|
|
7298
7596
|
const isDoc = /\.(?:md|mdx|rst|txt)$/i.test(filePath);
|
|
7299
7597
|
return { filePath, isDoc, isTranscript: false };
|
|
7300
7598
|
}
|
|
7301
|
-
if (/open\s*\(/.test(cmd)) {
|
|
7599
|
+
if (/open\s*\(/.test(cmd) && !pythonOpenPathsAreAllLiteral(cmd)) {
|
|
7302
7600
|
const literal2 = /['"]([^'"]+\.(?:java|py|ts|tsx|js|jsx|go|rb|rs|cpp|cc|cxx|c|h|hpp|kt|swift|cs|php|scala|clj|md|mdx|rst|txt|json|yaml|yml|toml|xml|conf|cfg|ini|properties))['"]/i.exec(cmd);
|
|
7303
7601
|
if (literal2) {
|
|
7304
7602
|
const filePath = literal2[1] ?? "";
|
|
@@ -7345,6 +7643,30 @@ function extractSedRange(cmd) {
|
|
|
7345
7643
|
if (isTempPath(filePath)) return null;
|
|
7346
7644
|
return { filePath, ranges };
|
|
7347
7645
|
}
|
|
7646
|
+
function extractAwkRange(cmd) {
|
|
7647
|
+
const m = /^awk\s+(?:'([^']+)'|"([^"]+)")\s+(?:"([^"]+)"|'([^']+)'|(\S+))(?:\s+2>\/dev\/null)?\s*$/.exec(cmd);
|
|
7648
|
+
if (!m) return null;
|
|
7649
|
+
const program = m[1] ?? m[2];
|
|
7650
|
+
if (program === void 0) return null;
|
|
7651
|
+
const cmp = /^\s*NR\s*>=\s*(\d+)\s*&&\s*NR\s*<=\s*(\d+)\s*$/.exec(program);
|
|
7652
|
+
const rng = /^\s*NR\s*==\s*(\d+)\s*,\s*NR\s*==\s*(\d+)\s*$/.exec(program);
|
|
7653
|
+
const hit = cmp ?? rng;
|
|
7654
|
+
if (!hit) return null;
|
|
7655
|
+
const start = parseInt(hit[1], 10);
|
|
7656
|
+
const end = parseInt(hit[2], 10);
|
|
7657
|
+
if (start < 1 || end < start) return null;
|
|
7658
|
+
const filePath = m[3] ?? m[4] ?? m[5];
|
|
7659
|
+
if (filePath === void 0) return null;
|
|
7660
|
+
if (isTempPath(filePath)) return null;
|
|
7661
|
+
return { filePath, ranges: [[start, end]] };
|
|
7662
|
+
}
|
|
7663
|
+
function extractLineRangeRead(cmd) {
|
|
7664
|
+
const sed = extractSedRange(cmd);
|
|
7665
|
+
if (sed !== null) return { ...sed, tool: "sed" };
|
|
7666
|
+
const awk = extractAwkRange(cmd);
|
|
7667
|
+
if (awk !== null) return { ...awk, tool: "awk" };
|
|
7668
|
+
return null;
|
|
7669
|
+
}
|
|
7348
7670
|
var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
|
|
7349
7671
|
"python",
|
|
7350
7672
|
"typescript",
|
|
@@ -7375,11 +7697,11 @@ var SYMBOL_BEARING_LANGUAGES = /* @__PURE__ */ new Set([
|
|
|
7375
7697
|
"salesforce_metadata",
|
|
7376
7698
|
"salesforce_markup"
|
|
7377
7699
|
]);
|
|
7378
|
-
function sedRangeHint(filePath, ranges) {
|
|
7700
|
+
function sedRangeHint(filePath, ranges, tool) {
|
|
7379
7701
|
const lang = detectLanguage(filePath);
|
|
7380
7702
|
const rangeReads = ranges.map(([s, e]) => '`token-goat read "' + filePath + "@" + s + "-" + e + '"`');
|
|
7381
7703
|
const allReads = rangeReads.length === 2 ? rangeReads.join(" and ") : rangeReads.length >= 3 ? rangeReads.slice(0, -1).join(", ") + ", and " + rangeReads[rangeReads.length - 1] : rangeReads[0];
|
|
7382
|
-
const prefix = "`sed -n` line-range reads bypass read hooks. ";
|
|
7704
|
+
const prefix = "`" + (tool === "awk" ? "awk" : "sed -n") + "` line-range reads bypass read hooks. ";
|
|
7383
7705
|
if (lang === "markdown") {
|
|
7384
7706
|
return prefix + 'For Markdown, `token-goat section "' + filePath + '::<heading>"` extracts a whole section by name (robust to line shifts); or ' + allReads + " for exactly those lines.";
|
|
7385
7707
|
}
|
|
@@ -7404,7 +7726,7 @@ function findRangeOverlap(prior, start, end) {
|
|
|
7404
7726
|
return best;
|
|
7405
7727
|
}
|
|
7406
7728
|
function sedOverlapHint(filePath, prior, start, end) {
|
|
7407
|
-
const base = "You already read lines " + prior[0] + "-" + prior[1] + " of " + filePath + " via an earlier
|
|
7729
|
+
const base = "You already read lines " + prior[0] + "-" + prior[1] + " of " + filePath + " via an earlier line-range read this session; this read (" + start + "-" + end + ") overlaps. ";
|
|
7408
7730
|
const segments = [];
|
|
7409
7731
|
if (start < prior[0]) segments.push([start, Math.min(end, prior[0] - 1)]);
|
|
7410
7732
|
if (end > prior[1]) segments.push([Math.max(start, prior[1] + 1), end]);
|
|
@@ -7705,11 +8027,12 @@ function maybeCompressRewrite(event, rawCmd, cmd) {
|
|
|
7705
8027
|
}
|
|
7706
8028
|
if (!cfg.enabled) return null;
|
|
7707
8029
|
if (!canRunWrappedShell()) return null;
|
|
7708
|
-
const
|
|
8030
|
+
const gateCmd = stripTrailingStderrRedirect(cmd);
|
|
8031
|
+
const detected = detectFromCommand(gateCmd, getCwd(event));
|
|
7709
8032
|
let filterName;
|
|
7710
8033
|
if (detected !== null) {
|
|
7711
8034
|
filterName = detected.filter.name;
|
|
7712
|
-
} else if (isCompressibleSingleCommand(
|
|
8035
|
+
} else if (isCompressibleSingleCommand(gateCmd)) {
|
|
7713
8036
|
filterName = "generic";
|
|
7714
8037
|
} else {
|
|
7715
8038
|
return null;
|
|
@@ -7926,9 +8249,9 @@ function preBashHandlerInner(event) {
|
|
|
7926
8249
|
"Use `token-goat outline <file>` to see symbol names and line counts without loading files."
|
|
7927
8250
|
);
|
|
7928
8251
|
}
|
|
7929
|
-
const sedRange =
|
|
8252
|
+
const sedRange = extractLineRangeRead(cmd);
|
|
7930
8253
|
if (sedRange !== null) {
|
|
7931
|
-
const { filePath, ranges } = sedRange;
|
|
8254
|
+
const { filePath, ranges, tool } = sedRange;
|
|
7932
8255
|
const hintPath = cdStripped ? resolveCdHintPath(rawCmd, filePath, hintCwd) : filePath;
|
|
7933
8256
|
recordStat("session_hint", 0, 0);
|
|
7934
8257
|
const sedDedupKey = resolveIndexPath(hintPath, preHookCwd ?? process.cwd());
|
|
@@ -7944,7 +8267,7 @@ function preBashHandlerInner(event) {
|
|
|
7944
8267
|
}
|
|
7945
8268
|
}
|
|
7946
8269
|
const hints = [...overlapHints];
|
|
7947
|
-
if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges));
|
|
8270
|
+
if (freshRanges.length > 0) hints.push(sedRangeHint(hintPath, freshRanges, tool));
|
|
7948
8271
|
return contextOutput(hints.join(" "));
|
|
7949
8272
|
}
|
|
7950
8273
|
const catJsonPipe = extractCatJsonPipe(cmd);
|
|
@@ -8197,7 +8520,7 @@ function preBashHandlerInner(event) {
|
|
|
8197
8520
|
);
|
|
8198
8521
|
}
|
|
8199
8522
|
}
|
|
8200
|
-
if (!isBuildCommand(cmd) && detectFromCommand(cmd, preHookCwd ?? void 0) === null) return passOutput();
|
|
8523
|
+
if (!isBuildCommand(cmd) && detectFromCommand(stripTrailingStderrRedirect(cmd), preHookCwd ?? void 0) === null) return passOutput();
|
|
8201
8524
|
const cmdHash = shortFingerprint(stripOutputPipeline(cmd));
|
|
8202
8525
|
const outputId = getBashOutputId(cmdHash);
|
|
8203
8526
|
const entryRaw = outputId !== null ? getBashOutput(outputId) : null;
|
|
@@ -8312,8 +8635,7 @@ async function postBashHandler(event) {
|
|
|
8312
8635
|
}
|
|
8313
8636
|
const mutationDiff = runGit(["diff", "--name-only", diffBase, "HEAD"], { cwd: gitDir, timeoutMs: 5e3 });
|
|
8314
8637
|
if (mutationDiff.exitCode === 0) {
|
|
8315
|
-
const
|
|
8316
|
-
const repoRoot = toplevel.exitCode === 0 && toplevel.stdout.trim() !== "" ? toplevel.stdout.trim() : gitDir;
|
|
8638
|
+
const repoRoot = gitRepoRoot(gitDir);
|
|
8317
8639
|
for (const rel of mutationDiff.stdout.split("\n")) {
|
|
8318
8640
|
const trimmed = rel.trim();
|
|
8319
8641
|
if (trimmed.length === 0) continue;
|
|
@@ -8321,6 +8643,9 @@ async function postBashHandler(event) {
|
|
|
8321
8643
|
}
|
|
8322
8644
|
}
|
|
8323
8645
|
}
|
|
8646
|
+
if (exitCode === null || exitCode === 0) {
|
|
8647
|
+
enqueueNonHeadMovingRewrites(cmd, rawCmd, cwd ?? process.cwd());
|
|
8648
|
+
}
|
|
8324
8649
|
const curlDl = extractCurlDownload(cmd);
|
|
8325
8650
|
if (curlDl !== null && (exitCode === null || exitCode === 0)) {
|
|
8326
8651
|
const resolvedOutputPath = resolveIndexPath(curlDl.outputPath, cwd ?? process.cwd());
|
|
@@ -8968,8 +9293,21 @@ function compressGWorkspaceMcpResult(toolName, resultText) {
|
|
|
8968
9293
|
const summarized = summarizeAttachments(trimmed);
|
|
8969
9294
|
return finishWithGenericPass(resultText, summarized);
|
|
8970
9295
|
}
|
|
9296
|
+
var DATA_URL_BASE64_RE = /data:([a-zA-Z0-9.+-]+\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)/g;
|
|
9297
|
+
var MIN_BASE64_PAYLOAD_LEN = 100;
|
|
9298
|
+
function stripBase64DataUrls(resultText) {
|
|
9299
|
+
let changed = false;
|
|
9300
|
+
const out = resultText.replace(DATA_URL_BASE64_RE, (match, mime, payload) => {
|
|
9301
|
+
if (payload.length < MIN_BASE64_PAYLOAD_LEN) return match;
|
|
9302
|
+
changed = true;
|
|
9303
|
+
return `data:${mime};base64,<stripped ${payload.length} bytes>`;
|
|
9304
|
+
});
|
|
9305
|
+
return changed ? out : null;
|
|
9306
|
+
}
|
|
8971
9307
|
function compressMcpResultWithPacks(toolName, resultText) {
|
|
8972
|
-
|
|
9308
|
+
const base64Stripped = stripBase64DataUrls(resultText);
|
|
9309
|
+
const text = base64Stripped ?? resultText;
|
|
9310
|
+
return compressGithubMcpResult(toolName, text) ?? compressBrowserMcpResult(toolName, text) ?? compressGWorkspaceMcpResult(toolName, text) ?? base64Stripped;
|
|
8973
9311
|
}
|
|
8974
9312
|
|
|
8975
9313
|
// src/hooks_mcp.ts
|