auto-mode 0.11.0 → 0.12.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.
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as getEditFields } from "./get-edit-fields-BtAvHIsq.js";
|
|
2
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, matchesGlob, normalize, parse, relative, resolve } from "node:path";
|
|
2
4
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, isAbsolute, join, matchesGlob, normalize, parse, relative, resolve } from "node:path";
|
|
4
5
|
import { spawn } from "node:child_process";
|
|
5
|
-
import { createHash } from "node:crypto";
|
|
6
6
|
import { existsSync } from "node:fs";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
//#region src/bypass/resolve-edit-target.ts
|
|
9
|
+
async function resolveEditTarget(path) {
|
|
10
|
+
const missing = [];
|
|
11
|
+
for (let current = path;; current = dirname(current)) try {
|
|
12
|
+
const real = await realpath(current);
|
|
13
|
+
return join(real, ...missing.toReversed());
|
|
14
|
+
} catch {
|
|
15
|
+
if (await lstat(current).catch(() => null) !== null || dirname(current) === current) return null;
|
|
16
|
+
missing.push(basename(current));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//#endregion
|
|
7
20
|
//#region node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/util.js
|
|
8
21
|
function getEnumValues(entries) {
|
|
9
22
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
@@ -5868,66 +5881,100 @@ async function loadClaudeRules(path) {
|
|
|
5868
5881
|
};
|
|
5869
5882
|
}
|
|
5870
5883
|
//#endregion
|
|
5871
|
-
//#region src/
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
"
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5884
|
+
//#region src/rules/split-command-words.ts
|
|
5885
|
+
function splitCommandWords(text) {
|
|
5886
|
+
const words = splitWords$1(text);
|
|
5887
|
+
let index = 0;
|
|
5888
|
+
while (index < words.length) {
|
|
5889
|
+
const word = words[index] ?? "";
|
|
5890
|
+
if (/^\w+=/u.test(word) || word === "sudo" || word === "command") index += 1;
|
|
5891
|
+
else if (word === "env") {
|
|
5892
|
+
index += 1;
|
|
5893
|
+
while (words[index] === "-u" || /^\w+=/u.test(words[index] ?? "")) index += words[index] === "-u" ? 2 : 1;
|
|
5894
|
+
} else if (word === "timeout") index += 2;
|
|
5895
|
+
else break;
|
|
5896
|
+
}
|
|
5897
|
+
return words.slice(index).filter((word, position, all) => !/^\d*[<>]/u.test(word) && !/^\d*>{1,2}\|?$/u.test(all[position - 1] ?? ""));
|
|
5898
|
+
}
|
|
5899
|
+
function splitWords$1(text) {
|
|
5900
|
+
const words = [];
|
|
5901
|
+
let current = "";
|
|
5902
|
+
let quote = null;
|
|
5903
|
+
let started = false;
|
|
5904
|
+
for (const ch of text) if (quote !== null) {
|
|
5905
|
+
if (ch === quote) quote = null;
|
|
5906
|
+
else current += ch;
|
|
5907
|
+
} else if (ch === "'" || ch === "\"") {
|
|
5908
|
+
quote = ch;
|
|
5909
|
+
started = true;
|
|
5910
|
+
} else if (/\s/u.test(ch)) {
|
|
5911
|
+
if (started) words.push(current);
|
|
5912
|
+
current = "";
|
|
5913
|
+
started = false;
|
|
5914
|
+
} else {
|
|
5915
|
+
current += ch;
|
|
5916
|
+
started = true;
|
|
5917
|
+
}
|
|
5918
|
+
if (started) words.push(current);
|
|
5919
|
+
return words;
|
|
5920
|
+
}
|
|
5921
|
+
//#endregion
|
|
5922
|
+
//#region src/rules/split-shell-command.ts
|
|
5923
|
+
function splitShellCommand(command) {
|
|
5924
|
+
const segments = [];
|
|
5925
|
+
let current = "";
|
|
5926
|
+
let quote = null;
|
|
5927
|
+
let unparsed = false;
|
|
5928
|
+
let i = 0;
|
|
5929
|
+
const collectSegment = () => {
|
|
5930
|
+
const text = current.trim();
|
|
5931
|
+
if (text !== "") segments.push({ text });
|
|
5932
|
+
current = "";
|
|
5933
|
+
};
|
|
5934
|
+
while (i < command.length) {
|
|
5935
|
+
const ch = command.charAt(i);
|
|
5936
|
+
if (quote !== null) {
|
|
5937
|
+
if (quote === "\"" && (command.startsWith("$(", i) || ch === "`")) unparsed = true;
|
|
5938
|
+
if (ch === quote) quote = null;
|
|
5939
|
+
current += ch;
|
|
5940
|
+
i += 1;
|
|
5888
5941
|
continue;
|
|
5889
5942
|
}
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
} catch {
|
|
5896
|
-
const link = await readFile(gitPath, "utf8");
|
|
5897
|
-
const path = /^gitdir: (?<path>.+)\r?\n?$/u.exec(link)?.groups?.["path"];
|
|
5898
|
-
if (path === void 0) return null;
|
|
5899
|
-
gitDir = resolve(directory, path.trim());
|
|
5900
|
-
head = await readFile(join(gitDir, "HEAD"), "utf8");
|
|
5901
|
-
}
|
|
5902
|
-
} catch {
|
|
5903
|
-
return null;
|
|
5943
|
+
if (ch === "'" || ch === "\"") {
|
|
5944
|
+
quote = ch;
|
|
5945
|
+
current += ch;
|
|
5946
|
+
i += 1;
|
|
5947
|
+
continue;
|
|
5904
5948
|
}
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
} catch {
|
|
5910
|
-
commonDir = gitDir;
|
|
5949
|
+
if (ch === "\\") {
|
|
5950
|
+
current += command.slice(i, i + 2);
|
|
5951
|
+
i += 2;
|
|
5952
|
+
continue;
|
|
5911
5953
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5954
|
+
if (command.startsWith("$(", i) || ch === "`" || command.startsWith("<(", i) || ch === ">") {
|
|
5955
|
+
unparsed = true;
|
|
5956
|
+
current += ch;
|
|
5957
|
+
i += 1;
|
|
5958
|
+
continue;
|
|
5917
5959
|
}
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
}
|
|
5960
|
+
if (command.startsWith("&&", i) || command.startsWith("||", i)) {
|
|
5961
|
+
collectSegment();
|
|
5962
|
+
i += 2;
|
|
5963
|
+
continue;
|
|
5964
|
+
}
|
|
5965
|
+
if (ch === ";" || ch === "|" || ch === "\n" || ch === "&") {
|
|
5966
|
+
collectSegment();
|
|
5967
|
+
i += 1;
|
|
5968
|
+
continue;
|
|
5969
|
+
}
|
|
5970
|
+
current += ch;
|
|
5971
|
+
i += 1;
|
|
5923
5972
|
}
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
const branch = value.slice(marker.length);
|
|
5930
|
-
return branch !== "" && branch.length <= 256 && !/\s/u.test(branch) ? branch : null;
|
|
5973
|
+
collectSegment();
|
|
5974
|
+
return {
|
|
5975
|
+
segments,
|
|
5976
|
+
hasUnparsedConstruct: unparsed || quote !== null
|
|
5977
|
+
};
|
|
5931
5978
|
}
|
|
5932
5979
|
//#endregion
|
|
5933
5980
|
//#region src/containment/to-repository-slug.ts
|
|
@@ -5938,346 +5985,57 @@ function toRepositorySlug(reference) {
|
|
|
5938
5985
|
return `${parts["host"] ?? ""}/${parts["owner"] ?? ""}/${parts["name"] ?? ""}`.toLowerCase();
|
|
5939
5986
|
}
|
|
5940
5987
|
//#endregion
|
|
5941
|
-
//#region src/
|
|
5942
|
-
function
|
|
5943
|
-
const
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
}
|
|
5952
|
-
//#endregion
|
|
5953
|
-
//#region src/scope/build-task-scope.ts
|
|
5954
|
-
function buildTaskScope(input) {
|
|
5955
|
-
const merged = mergeScopeFacts(input.facts);
|
|
5956
|
-
const branches = merged.branches.filter((branch) => branch !== input.defaultBranch && !(input.defaultBranch === null && DEFAULT_NAMES.has(branch)));
|
|
5957
|
-
const repositories = new Set(input.remotes.map((remote) => toRepositorySlug(remote.url)));
|
|
5958
|
-
const pullRequests = merged.pullRequests.filter((pull) => branches.includes(pull.head) && repositories.has(pull.repository)).map((pull) => ({
|
|
5959
|
-
number: pull.number,
|
|
5960
|
-
repository: pull.repository
|
|
5961
|
-
}));
|
|
5962
|
-
return {
|
|
5963
|
-
home: input.home,
|
|
5964
|
-
worktrees: merged.worktrees,
|
|
5965
|
-
branches,
|
|
5966
|
-
currentBranch: input.currentBranch,
|
|
5967
|
-
remotes: input.remotes,
|
|
5968
|
-
pullRequests,
|
|
5969
|
-
pathGlobs: merged.pathGlobs
|
|
5970
|
-
};
|
|
5988
|
+
//#region src/containment/collect-scope-findings.ts
|
|
5989
|
+
function collectScopeFindings(action, scope) {
|
|
5990
|
+
const filePath = action.input["file_path"] ?? action.input["notebook_path"];
|
|
5991
|
+
const command = action.input["command"];
|
|
5992
|
+
const findings = [
|
|
5993
|
+
...FILE_WRITE_TOOLS.has(action.tool) && typeof filePath === "string" ? collectTargetFindings(action.cwd, filePath, scope) : [],
|
|
5994
|
+
...action.tool.startsWith("mcp__") ? collectToolFindings(action, scope) : [],
|
|
5995
|
+
...action.tool === "Bash" && typeof command === "string" ? collectCommandFindings(command, action.cwd, scope) : []
|
|
5996
|
+
];
|
|
5997
|
+
return findings.filter((finding, index) => findings.findIndex((other) => other.kind === finding.kind && other.target === finding.target) === index);
|
|
5971
5998
|
}
|
|
5972
|
-
const
|
|
5973
|
-
"
|
|
5974
|
-
"
|
|
5975
|
-
"
|
|
5976
|
-
"
|
|
5999
|
+
const FILE_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
6000
|
+
"Edit",
|
|
6001
|
+
"MultiEdit",
|
|
6002
|
+
"Write",
|
|
6003
|
+
"NotebookEdit"
|
|
5977
6004
|
]);
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
"
|
|
6005
|
+
function collectTargetFindings(directory, target, scope) {
|
|
6006
|
+
const path = resolvePath(directory, target, scope.home);
|
|
6007
|
+
return path === null ? [] : collectPathFindings(path, scope);
|
|
6008
|
+
}
|
|
6009
|
+
const CREDENTIAL_PATHS = [
|
|
6010
|
+
".ssh",
|
|
6011
|
+
".aws",
|
|
6012
|
+
".gnupg",
|
|
6013
|
+
".gitconfig",
|
|
6014
|
+
".netrc",
|
|
6015
|
+
".npmrc",
|
|
6016
|
+
".config/gh"
|
|
5984
6017
|
];
|
|
5985
|
-
|
|
5986
|
-
if (
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
commonDir: await findCommonDir(gitDir)
|
|
5995
|
-
};
|
|
5996
|
-
}
|
|
5997
|
-
if (dirname(directory) === directory) return null;
|
|
5998
|
-
}
|
|
6018
|
+
function collectPathFindings(path, scope) {
|
|
6019
|
+
if (CREDENTIAL_PATHS.some((entry) => isUnder(path, join(scope.home, entry)))) return [{
|
|
6020
|
+
kind: "credential",
|
|
6021
|
+
target: path
|
|
6022
|
+
}];
|
|
6023
|
+
return isInScope(path, scope) ? [] : [{
|
|
6024
|
+
kind: "path",
|
|
6025
|
+
target: path
|
|
6026
|
+
}];
|
|
5999
6027
|
}
|
|
6000
|
-
|
|
6001
|
-
const
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
const
|
|
6007
|
-
return
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
async function loadCheckoutRemotes(commonDir) {
|
|
6012
|
-
const config = await readFile(join(commonDir, "config"), "utf8").catch(() => "");
|
|
6013
|
-
const remotes = [];
|
|
6014
|
-
let section = null;
|
|
6015
|
-
for (const line of config.split("\n")) {
|
|
6016
|
-
const header = /^\s*\[remote "(?<name>[^"]+)"\]/u.exec(line)?.groups?.["name"];
|
|
6017
|
-
if (header !== void 0) section = header;
|
|
6018
|
-
else if (/^\s*\[/u.test(line)) section = null;
|
|
6019
|
-
else if (section !== null) {
|
|
6020
|
-
const url = /^\s*(?:push)?url\s*=\s*(?<url>\S+)/u.exec(line)?.groups?.["url"];
|
|
6021
|
-
if (url !== void 0) remotes.push({
|
|
6022
|
-
name: section,
|
|
6023
|
-
url
|
|
6024
|
-
});
|
|
6025
|
-
}
|
|
6026
|
-
}
|
|
6027
|
-
return remotes;
|
|
6028
|
-
}
|
|
6029
|
-
//#endregion
|
|
6030
|
-
//#region src/scope/types.ts
|
|
6031
|
-
const EMPTY_SCOPE_FACTS = {
|
|
6032
|
-
worktrees: [],
|
|
6033
|
-
branches: [],
|
|
6034
|
-
pullRequests: [],
|
|
6035
|
-
pathGlobs: []
|
|
6036
|
-
};
|
|
6037
|
-
const EMPTY_SESSION_SCOPE = {
|
|
6038
|
-
worktrees: [],
|
|
6039
|
-
branches: [],
|
|
6040
|
-
pullRequests: []
|
|
6041
|
-
};
|
|
6042
|
-
//#endregion
|
|
6043
|
-
//#region src/scope/load-session-scope.ts
|
|
6044
|
-
const name = string().min(1);
|
|
6045
|
-
const pullRequestSchema = strictObject({
|
|
6046
|
-
number: number().int().positive(),
|
|
6047
|
-
head: name,
|
|
6048
|
-
repository: name
|
|
6049
|
-
});
|
|
6050
|
-
const branchSchema = strictObject({
|
|
6051
|
-
name,
|
|
6052
|
-
commonDir: name
|
|
6053
|
-
});
|
|
6054
|
-
const sessionScopeSchema = strictObject({
|
|
6055
|
-
worktrees: array(name),
|
|
6056
|
-
branches: array(branchSchema),
|
|
6057
|
-
pullRequests: array(pullRequestSchema)
|
|
6058
|
-
});
|
|
6059
|
-
async function loadSessionScope(path) {
|
|
6060
|
-
let raw;
|
|
6061
|
-
try {
|
|
6062
|
-
raw = await readFile(path, "utf8");
|
|
6063
|
-
} catch {
|
|
6064
|
-
return EMPTY_SESSION_SCOPE;
|
|
6065
|
-
}
|
|
6066
|
-
try {
|
|
6067
|
-
const parsed = sessionScopeSchema.safeParse(JSON.parse(raw));
|
|
6068
|
-
return parsed.success ? parsed.data : EMPTY_SESSION_SCOPE;
|
|
6069
|
-
} catch {
|
|
6070
|
-
return EMPTY_SESSION_SCOPE;
|
|
6071
|
-
}
|
|
6072
|
-
}
|
|
6073
|
-
//#endregion
|
|
6074
|
-
//#region src/scope/resolve-session-scope-path.ts
|
|
6075
|
-
function resolveSessionScopePath(stateDir, sessionID) {
|
|
6076
|
-
const key = createHash("sha256").update(sessionID).digest("hex").slice(0, 32);
|
|
6077
|
-
return join(stateDir, "session-scope", `${key}.json`);
|
|
6078
|
-
}
|
|
6079
|
-
//#endregion
|
|
6080
|
-
//#region src/scope/pick-scope-source-reader.ts
|
|
6081
|
-
function pickScopeSourceReader(source) {
|
|
6082
|
-
if (source.kind === "cwd") return (context) => Promise.resolve(buildFacts({
|
|
6083
|
-
worktrees: [context.worktree],
|
|
6084
|
-
branches: context.branch === null ? [] : [context.branch]
|
|
6085
|
-
}));
|
|
6086
|
-
if (source.kind === "session") return async (context) => {
|
|
6087
|
-
const scope = await loadSessionScope(resolveSessionScopePath(context.stateDir, context.sessionID));
|
|
6088
|
-
return buildFacts({
|
|
6089
|
-
worktrees: scope.worktrees,
|
|
6090
|
-
branches: scope.branches.filter((branch) => branch.commonDir === context.commonDir).map((branch) => branch.name),
|
|
6091
|
-
pullRequests: scope.pullRequests
|
|
6092
|
-
});
|
|
6093
|
-
};
|
|
6094
|
-
if (source.kind === "globs") return () => Promise.resolve(buildFacts({ pathGlobs: source.paths ?? [] }));
|
|
6095
|
-
return () => Promise.resolve(EMPTY_SCOPE_FACTS);
|
|
6096
|
-
}
|
|
6097
|
-
function buildFacts(facts) {
|
|
6098
|
-
return {
|
|
6099
|
-
...EMPTY_SCOPE_FACTS,
|
|
6100
|
-
...facts
|
|
6101
|
-
};
|
|
6102
|
-
}
|
|
6103
|
-
//#endregion
|
|
6104
|
-
//#region src/scope/load-task-scope.ts
|
|
6105
|
-
async function loadTaskScope(request, sources) {
|
|
6106
|
-
const cwd = resolve(request.cwd);
|
|
6107
|
-
const [checkout, context] = await Promise.all([findCheckout(cwd), loadRepositoryContext(cwd)]);
|
|
6108
|
-
const branch = context?.branch ?? null;
|
|
6109
|
-
const sourceContext = {
|
|
6110
|
-
sessionID: request.sessionID,
|
|
6111
|
-
cwd,
|
|
6112
|
-
worktree: checkout?.worktree ?? cwd,
|
|
6113
|
-
commonDir: checkout?.commonDir ?? null,
|
|
6114
|
-
branch,
|
|
6115
|
-
stateDir: request.stateDir
|
|
6116
|
-
};
|
|
6117
|
-
const [remotes, ...facts] = await Promise.all([checkout === null ? [] : loadCheckoutRemotes(checkout.commonDir), ...Object.values(sources).map((source) => pickScopeSourceReader(source)(sourceContext))]);
|
|
6118
|
-
return buildTaskScope({
|
|
6119
|
-
home: homedir(),
|
|
6120
|
-
currentBranch: branch,
|
|
6121
|
-
defaultBranch: context?.defaultBranch ?? null,
|
|
6122
|
-
remotes,
|
|
6123
|
-
facts
|
|
6124
|
-
});
|
|
6125
|
-
}
|
|
6126
|
-
//#endregion
|
|
6127
|
-
//#region src/state/resolve-state-dir.ts
|
|
6128
|
-
function resolveStateDir() {
|
|
6129
|
-
const stateHome = process.env["XDG_STATE_HOME"];
|
|
6130
|
-
const base = stateHome === void 0 || stateHome === "" ? join(homedir(), ".local", "state") : stateHome;
|
|
6131
|
-
return join(base, "auto-mode");
|
|
6132
|
-
}
|
|
6133
|
-
//#endregion
|
|
6134
|
-
//#region src/rules/split-command-words.ts
|
|
6135
|
-
function splitCommandWords(text) {
|
|
6136
|
-
const words = splitWords$1(text);
|
|
6137
|
-
let index = 0;
|
|
6138
|
-
while (index < words.length) {
|
|
6139
|
-
const word = words[index] ?? "";
|
|
6140
|
-
if (/^\w+=/u.test(word) || word === "sudo" || word === "command") index += 1;
|
|
6141
|
-
else if (word === "env") {
|
|
6142
|
-
index += 1;
|
|
6143
|
-
while (words[index] === "-u" || /^\w+=/u.test(words[index] ?? "")) index += words[index] === "-u" ? 2 : 1;
|
|
6144
|
-
} else if (word === "timeout") index += 2;
|
|
6145
|
-
else break;
|
|
6146
|
-
}
|
|
6147
|
-
return words.slice(index).filter((word, position, all) => !/^\d*[<>]/u.test(word) && !/^\d*>{1,2}\|?$/u.test(all[position - 1] ?? ""));
|
|
6148
|
-
}
|
|
6149
|
-
function splitWords$1(text) {
|
|
6150
|
-
const words = [];
|
|
6151
|
-
let current = "";
|
|
6152
|
-
let quote = null;
|
|
6153
|
-
let started = false;
|
|
6154
|
-
for (const ch of text) if (quote !== null) {
|
|
6155
|
-
if (ch === quote) quote = null;
|
|
6156
|
-
else current += ch;
|
|
6157
|
-
} else if (ch === "'" || ch === "\"") {
|
|
6158
|
-
quote = ch;
|
|
6159
|
-
started = true;
|
|
6160
|
-
} else if (/\s/u.test(ch)) {
|
|
6161
|
-
if (started) words.push(current);
|
|
6162
|
-
current = "";
|
|
6163
|
-
started = false;
|
|
6164
|
-
} else {
|
|
6165
|
-
current += ch;
|
|
6166
|
-
started = true;
|
|
6167
|
-
}
|
|
6168
|
-
if (started) words.push(current);
|
|
6169
|
-
return words;
|
|
6170
|
-
}
|
|
6171
|
-
//#endregion
|
|
6172
|
-
//#region src/rules/split-shell-command.ts
|
|
6173
|
-
function splitShellCommand(command) {
|
|
6174
|
-
const segments = [];
|
|
6175
|
-
let current = "";
|
|
6176
|
-
let quote = null;
|
|
6177
|
-
let unparsed = false;
|
|
6178
|
-
let i = 0;
|
|
6179
|
-
const collectSegment = () => {
|
|
6180
|
-
const text = current.trim();
|
|
6181
|
-
if (text !== "") segments.push({ text });
|
|
6182
|
-
current = "";
|
|
6183
|
-
};
|
|
6184
|
-
while (i < command.length) {
|
|
6185
|
-
const ch = command.charAt(i);
|
|
6186
|
-
if (quote !== null) {
|
|
6187
|
-
if (quote === "\"" && (command.startsWith("$(", i) || ch === "`")) unparsed = true;
|
|
6188
|
-
if (ch === quote) quote = null;
|
|
6189
|
-
current += ch;
|
|
6190
|
-
i += 1;
|
|
6191
|
-
continue;
|
|
6192
|
-
}
|
|
6193
|
-
if (ch === "'" || ch === "\"") {
|
|
6194
|
-
quote = ch;
|
|
6195
|
-
current += ch;
|
|
6196
|
-
i += 1;
|
|
6197
|
-
continue;
|
|
6198
|
-
}
|
|
6199
|
-
if (ch === "\\") {
|
|
6200
|
-
current += command.slice(i, i + 2);
|
|
6201
|
-
i += 2;
|
|
6202
|
-
continue;
|
|
6203
|
-
}
|
|
6204
|
-
if (command.startsWith("$(", i) || ch === "`" || command.startsWith("<(", i) || ch === ">") {
|
|
6205
|
-
unparsed = true;
|
|
6206
|
-
current += ch;
|
|
6207
|
-
i += 1;
|
|
6208
|
-
continue;
|
|
6209
|
-
}
|
|
6210
|
-
if (command.startsWith("&&", i) || command.startsWith("||", i)) {
|
|
6211
|
-
collectSegment();
|
|
6212
|
-
i += 2;
|
|
6213
|
-
continue;
|
|
6214
|
-
}
|
|
6215
|
-
if (ch === ";" || ch === "|" || ch === "\n" || ch === "&") {
|
|
6216
|
-
collectSegment();
|
|
6217
|
-
i += 1;
|
|
6218
|
-
continue;
|
|
6219
|
-
}
|
|
6220
|
-
current += ch;
|
|
6221
|
-
i += 1;
|
|
6222
|
-
}
|
|
6223
|
-
collectSegment();
|
|
6224
|
-
return {
|
|
6225
|
-
segments,
|
|
6226
|
-
hasUnparsedConstruct: unparsed || quote !== null
|
|
6227
|
-
};
|
|
6228
|
-
}
|
|
6229
|
-
//#endregion
|
|
6230
|
-
//#region src/containment/collect-scope-findings.ts
|
|
6231
|
-
function collectScopeFindings(action, scope) {
|
|
6232
|
-
const filePath = action.input["file_path"] ?? action.input["notebook_path"];
|
|
6233
|
-
const command = action.input["command"];
|
|
6234
|
-
const findings = [
|
|
6235
|
-
...FILE_WRITE_TOOLS.has(action.tool) && typeof filePath === "string" ? collectTargetFindings(action.cwd, filePath, scope) : [],
|
|
6236
|
-
...action.tool.startsWith("mcp__") ? collectToolFindings(action, scope) : [],
|
|
6237
|
-
...action.tool === "Bash" && typeof command === "string" ? collectCommandFindings(command, action.cwd, scope) : []
|
|
6238
|
-
];
|
|
6239
|
-
return findings.filter((finding, index) => findings.findIndex((other) => other.kind === finding.kind && other.target === finding.target) === index);
|
|
6240
|
-
}
|
|
6241
|
-
const FILE_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
6242
|
-
"Edit",
|
|
6243
|
-
"MultiEdit",
|
|
6244
|
-
"Write",
|
|
6245
|
-
"NotebookEdit"
|
|
6246
|
-
]);
|
|
6247
|
-
function collectTargetFindings(directory, target, scope) {
|
|
6248
|
-
const path = resolvePath(directory, target, scope.home);
|
|
6249
|
-
return path === null ? [] : collectPathFindings(path, scope);
|
|
6250
|
-
}
|
|
6251
|
-
const CREDENTIAL_PATHS = [
|
|
6252
|
-
".ssh",
|
|
6253
|
-
".aws",
|
|
6254
|
-
".gnupg",
|
|
6255
|
-
".gitconfig",
|
|
6256
|
-
".netrc",
|
|
6257
|
-
".npmrc",
|
|
6258
|
-
".config/gh"
|
|
6259
|
-
];
|
|
6260
|
-
function collectPathFindings(path, scope) {
|
|
6261
|
-
if (CREDENTIAL_PATHS.some((entry) => isUnder(path, join(scope.home, entry)))) return [{
|
|
6262
|
-
kind: "credential",
|
|
6263
|
-
target: path
|
|
6264
|
-
}];
|
|
6265
|
-
return isInScope(path, scope) ? [] : [{
|
|
6266
|
-
kind: "path",
|
|
6267
|
-
target: path
|
|
6268
|
-
}];
|
|
6269
|
-
}
|
|
6270
|
-
function collectToolFindings(action, scope) {
|
|
6271
|
-
const verb = action.tool.split("__").at(-1) ?? "";
|
|
6272
|
-
if (!/merge|create|comment|update|delete|push|close|review/u.test(verb)) return [];
|
|
6273
|
-
const number = action.input["pullNumber"] ?? action.input["pull_number"] ?? action.input["issue_number"];
|
|
6274
|
-
const owner = action.input["owner"];
|
|
6275
|
-
const name = action.input["repo"];
|
|
6276
|
-
const repo = typeof owner === "string" && typeof name === "string" ? `${owner}/${name}` : void 0;
|
|
6277
|
-
return !verb.includes("merge") && typeof number === "number" && isOwnedPullRequest(number, repo, scope) ? [] : [{
|
|
6278
|
-
kind: "remote-write",
|
|
6279
|
-
target: action.tool
|
|
6280
|
-
}];
|
|
6028
|
+
function collectToolFindings(action, scope) {
|
|
6029
|
+
const verb = action.tool.split("__").at(-1) ?? "";
|
|
6030
|
+
if (!/merge|create|comment|update|delete|push|close|review/u.test(verb)) return [];
|
|
6031
|
+
const number = action.input["pullNumber"] ?? action.input["pull_number"] ?? action.input["issue_number"];
|
|
6032
|
+
const owner = action.input["owner"];
|
|
6033
|
+
const name = action.input["repo"];
|
|
6034
|
+
const repo = typeof owner === "string" && typeof name === "string" ? `${owner}/${name}` : void 0;
|
|
6035
|
+
return !verb.includes("merge") && typeof number === "number" && isOwnedPullRequest(number, repo, scope) ? [] : [{
|
|
6036
|
+
kind: "remote-write",
|
|
6037
|
+
target: action.tool
|
|
6038
|
+
}];
|
|
6281
6039
|
}
|
|
6282
6040
|
const PATH_WRITERS = /* @__PURE__ */ new Set([
|
|
6283
6041
|
"rm",
|
|
@@ -6719,17 +6477,7 @@ function isUnder(path, root) {
|
|
|
6719
6477
|
//#endregion
|
|
6720
6478
|
//#region src/containment/check-containment.ts
|
|
6721
6479
|
const CONTAINMENT_RULE = "Outside Task Scope";
|
|
6722
|
-
|
|
6723
|
-
let scope;
|
|
6724
|
-
try {
|
|
6725
|
-
scope = await loadTaskScope({
|
|
6726
|
-
sessionID: request.sessionID,
|
|
6727
|
-
cwd: request.cwd,
|
|
6728
|
-
stateDir: resolveStateDir()
|
|
6729
|
-
}, sources);
|
|
6730
|
-
} catch {
|
|
6731
|
-
return null;
|
|
6732
|
-
}
|
|
6480
|
+
function checkContainment(request, scope) {
|
|
6733
6481
|
const findings = collectScopeFindings({
|
|
6734
6482
|
tool: request.toolName,
|
|
6735
6483
|
cwd: request.cwd,
|
|
@@ -7020,11 +6768,73 @@ var DecisionRequestError = class extends Error {
|
|
|
7020
6768
|
this.reason = reason;
|
|
7021
6769
|
this.requestBytes = requestBytes;
|
|
7022
6770
|
}
|
|
7023
|
-
};
|
|
7024
|
-
//#endregion
|
|
7025
|
-
//#region src/model/format-classifier-note.ts
|
|
7026
|
-
function formatClassifierNote(note, apiKey) {
|
|
7027
|
-
return apiKey === null || apiKey === "" ? note : note.split(apiKey).join("[redacted]");
|
|
6771
|
+
};
|
|
6772
|
+
//#endregion
|
|
6773
|
+
//#region src/model/format-classifier-note.ts
|
|
6774
|
+
function formatClassifierNote(note, apiKey) {
|
|
6775
|
+
return apiKey === null || apiKey === "" ? note : note.split(apiKey).join("[redacted]");
|
|
6776
|
+
}
|
|
6777
|
+
//#endregion
|
|
6778
|
+
//#region src/model/load-repository-context.ts
|
|
6779
|
+
async function loadRepositoryContext(cwd) {
|
|
6780
|
+
if ([
|
|
6781
|
+
"GIT_DIR",
|
|
6782
|
+
"GIT_WORK_TREE",
|
|
6783
|
+
"GIT_COMMON_DIR"
|
|
6784
|
+
].some((name) => process.env[name] !== void 0)) return null;
|
|
6785
|
+
let directory = resolve(cwd);
|
|
6786
|
+
for (;;) {
|
|
6787
|
+
const gitPath = join(directory, ".git");
|
|
6788
|
+
try {
|
|
6789
|
+
await stat(gitPath);
|
|
6790
|
+
} catch (error) {
|
|
6791
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) return null;
|
|
6792
|
+
const parent = dirname(directory);
|
|
6793
|
+
if (parent === directory) return null;
|
|
6794
|
+
directory = parent;
|
|
6795
|
+
continue;
|
|
6796
|
+
}
|
|
6797
|
+
let gitDir = gitPath;
|
|
6798
|
+
let head;
|
|
6799
|
+
try {
|
|
6800
|
+
try {
|
|
6801
|
+
head = await readFile(join(gitDir, "HEAD"), "utf8");
|
|
6802
|
+
} catch {
|
|
6803
|
+
const link = await readFile(gitPath, "utf8");
|
|
6804
|
+
const path = /^gitdir: (?<path>.+)\r?\n?$/u.exec(link)?.groups?.["path"];
|
|
6805
|
+
if (path === void 0) return null;
|
|
6806
|
+
gitDir = resolve(directory, path.trim());
|
|
6807
|
+
head = await readFile(join(gitDir, "HEAD"), "utf8");
|
|
6808
|
+
}
|
|
6809
|
+
} catch {
|
|
6810
|
+
return null;
|
|
6811
|
+
}
|
|
6812
|
+
let commonDir = gitDir;
|
|
6813
|
+
try {
|
|
6814
|
+
const common = await readFile(join(gitDir, "commondir"), "utf8");
|
|
6815
|
+
commonDir = resolve(gitDir, common.trim());
|
|
6816
|
+
} catch {
|
|
6817
|
+
commonDir = gitDir;
|
|
6818
|
+
}
|
|
6819
|
+
let defaultBranch = null;
|
|
6820
|
+
try {
|
|
6821
|
+
defaultBranch = parseReference(await readFile(join(commonDir, "refs", "remotes", "origin", "HEAD"), "utf8"), "refs/remotes/origin/");
|
|
6822
|
+
} catch {
|
|
6823
|
+
defaultBranch = null;
|
|
6824
|
+
}
|
|
6825
|
+
return {
|
|
6826
|
+
cwd: resolve(cwd),
|
|
6827
|
+
branch: parseReference(head, "refs/heads/"),
|
|
6828
|
+
defaultBranch
|
|
6829
|
+
};
|
|
6830
|
+
}
|
|
6831
|
+
}
|
|
6832
|
+
function parseReference(content, prefix) {
|
|
6833
|
+
const value = content.trim();
|
|
6834
|
+
const marker = `ref: ${prefix}`;
|
|
6835
|
+
if (!value.startsWith(marker)) return null;
|
|
6836
|
+
const branch = value.slice(marker.length);
|
|
6837
|
+
return branch !== "" && branch.length <= 256 && !/\s/u.test(branch) ? branch : null;
|
|
7028
6838
|
}
|
|
7029
6839
|
//#endregion
|
|
7030
6840
|
//#region node_modules/.bun/ts-pattern@5.9.0/node_modules/ts-pattern/dist/index.js
|
|
@@ -7871,6 +7681,199 @@ function toUnquoted(word) {
|
|
|
7871
7681
|
return (first === "'" || first === "\"") && word.at(-1) === first ? word.slice(1, -1) : word;
|
|
7872
7682
|
}
|
|
7873
7683
|
//#endregion
|
|
7684
|
+
//#region src/scope/find-checkout.ts
|
|
7685
|
+
const GIT_OVERRIDES$1 = [
|
|
7686
|
+
"GIT_DIR",
|
|
7687
|
+
"GIT_WORK_TREE",
|
|
7688
|
+
"GIT_COMMON_DIR"
|
|
7689
|
+
];
|
|
7690
|
+
async function findCheckout(start) {
|
|
7691
|
+
if (GIT_OVERRIDES$1.some((name) => process.env[name] !== void 0)) return null;
|
|
7692
|
+
for (let directory = start;; directory = dirname(directory)) {
|
|
7693
|
+
const gitPath = join(directory, ".git");
|
|
7694
|
+
const info = await stat(gitPath).catch(() => null);
|
|
7695
|
+
if (info !== null) {
|
|
7696
|
+
const gitDir = info.isDirectory() ? gitPath : await findLinkedGitDir(directory, gitPath);
|
|
7697
|
+
return gitDir === null ? null : {
|
|
7698
|
+
worktree: directory,
|
|
7699
|
+
commonDir: await findCommonDir(gitDir)
|
|
7700
|
+
};
|
|
7701
|
+
}
|
|
7702
|
+
if (dirname(directory) === directory) return null;
|
|
7703
|
+
}
|
|
7704
|
+
}
|
|
7705
|
+
async function findLinkedGitDir(directory, gitPath) {
|
|
7706
|
+
const link = await readFile(gitPath, "utf8").catch(() => "");
|
|
7707
|
+
const path = /^gitdir: (?<path>.+)$/mu.exec(link)?.groups?.["path"];
|
|
7708
|
+
return path === void 0 ? null : resolve(directory, path.trim());
|
|
7709
|
+
}
|
|
7710
|
+
async function findCommonDir(gitDir) {
|
|
7711
|
+
const common = await readFile(join(gitDir, "commondir"), "utf8").catch(() => null);
|
|
7712
|
+
return common === null ? gitDir : resolve(gitDir, common.trim());
|
|
7713
|
+
}
|
|
7714
|
+
//#endregion
|
|
7715
|
+
//#region src/scope/merge-scope-facts.ts
|
|
7716
|
+
function mergeScopeFacts(all) {
|
|
7717
|
+
const pullRequests = /* @__PURE__ */ new Map();
|
|
7718
|
+
for (const pull of all.flatMap((facts) => facts.pullRequests)) pullRequests.set(`${pull.repository}#${String(pull.number)}`, pull);
|
|
7719
|
+
return {
|
|
7720
|
+
worktrees: [...new Set(all.flatMap((facts) => facts.worktrees))],
|
|
7721
|
+
branches: [...new Set(all.flatMap((facts) => facts.branches))],
|
|
7722
|
+
pullRequests: [...pullRequests.values()],
|
|
7723
|
+
pathGlobs: [...new Set(all.flatMap((facts) => facts.pathGlobs))]
|
|
7724
|
+
};
|
|
7725
|
+
}
|
|
7726
|
+
//#endregion
|
|
7727
|
+
//#region src/scope/build-task-scope.ts
|
|
7728
|
+
function buildTaskScope(input) {
|
|
7729
|
+
const merged = mergeScopeFacts(input.facts);
|
|
7730
|
+
const branches = merged.branches.filter((branch) => branch !== input.defaultBranch && !(input.defaultBranch === null && DEFAULT_NAMES.has(branch)));
|
|
7731
|
+
const repositories = new Set(input.remotes.map((remote) => toRepositorySlug(remote.url)));
|
|
7732
|
+
const pullRequests = merged.pullRequests.filter((pull) => branches.includes(pull.head) && repositories.has(pull.repository)).map((pull) => ({
|
|
7733
|
+
number: pull.number,
|
|
7734
|
+
repository: pull.repository
|
|
7735
|
+
}));
|
|
7736
|
+
return {
|
|
7737
|
+
home: input.home,
|
|
7738
|
+
worktrees: merged.worktrees,
|
|
7739
|
+
branches,
|
|
7740
|
+
currentBranch: input.currentBranch,
|
|
7741
|
+
remotes: input.remotes,
|
|
7742
|
+
pullRequests,
|
|
7743
|
+
pathGlobs: merged.pathGlobs
|
|
7744
|
+
};
|
|
7745
|
+
}
|
|
7746
|
+
const DEFAULT_NAMES = /* @__PURE__ */ new Set([
|
|
7747
|
+
"main",
|
|
7748
|
+
"master",
|
|
7749
|
+
"trunk",
|
|
7750
|
+
"develop"
|
|
7751
|
+
]);
|
|
7752
|
+
//#endregion
|
|
7753
|
+
//#region src/scope/load-checkout-remotes.ts
|
|
7754
|
+
async function loadCheckoutRemotes(commonDir) {
|
|
7755
|
+
const config = await readFile(join(commonDir, "config"), "utf8").catch(() => "");
|
|
7756
|
+
const remotes = [];
|
|
7757
|
+
let section = null;
|
|
7758
|
+
for (const line of config.split("\n")) {
|
|
7759
|
+
const header = /^\s*\[remote "(?<name>[^"]+)"\]/u.exec(line)?.groups?.["name"];
|
|
7760
|
+
if (header !== void 0) section = header;
|
|
7761
|
+
else if (/^\s*\[/u.test(line)) section = null;
|
|
7762
|
+
else if (section !== null) {
|
|
7763
|
+
const url = /^\s*(?:push)?url\s*=\s*(?<url>\S+)/u.exec(line)?.groups?.["url"];
|
|
7764
|
+
if (url !== void 0) remotes.push({
|
|
7765
|
+
name: section,
|
|
7766
|
+
url
|
|
7767
|
+
});
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
return remotes;
|
|
7771
|
+
}
|
|
7772
|
+
//#endregion
|
|
7773
|
+
//#region src/scope/types.ts
|
|
7774
|
+
const EMPTY_SCOPE_FACTS = {
|
|
7775
|
+
worktrees: [],
|
|
7776
|
+
branches: [],
|
|
7777
|
+
pullRequests: [],
|
|
7778
|
+
pathGlobs: []
|
|
7779
|
+
};
|
|
7780
|
+
const EMPTY_SESSION_SCOPE = {
|
|
7781
|
+
worktrees: [],
|
|
7782
|
+
branches: [],
|
|
7783
|
+
pullRequests: []
|
|
7784
|
+
};
|
|
7785
|
+
//#endregion
|
|
7786
|
+
//#region src/scope/load-session-scope.ts
|
|
7787
|
+
const name = string().min(1);
|
|
7788
|
+
const pullRequestSchema = strictObject({
|
|
7789
|
+
number: number().int().positive(),
|
|
7790
|
+
head: name,
|
|
7791
|
+
repository: name
|
|
7792
|
+
});
|
|
7793
|
+
const branchSchema = strictObject({
|
|
7794
|
+
name,
|
|
7795
|
+
commonDir: name
|
|
7796
|
+
});
|
|
7797
|
+
const sessionScopeSchema = strictObject({
|
|
7798
|
+
worktrees: array(name),
|
|
7799
|
+
branches: array(branchSchema),
|
|
7800
|
+
pullRequests: array(pullRequestSchema)
|
|
7801
|
+
});
|
|
7802
|
+
async function loadSessionScope(path) {
|
|
7803
|
+
let raw;
|
|
7804
|
+
try {
|
|
7805
|
+
raw = await readFile(path, "utf8");
|
|
7806
|
+
} catch {
|
|
7807
|
+
return EMPTY_SESSION_SCOPE;
|
|
7808
|
+
}
|
|
7809
|
+
try {
|
|
7810
|
+
const parsed = sessionScopeSchema.safeParse(JSON.parse(raw));
|
|
7811
|
+
return parsed.success ? parsed.data : EMPTY_SESSION_SCOPE;
|
|
7812
|
+
} catch {
|
|
7813
|
+
return EMPTY_SESSION_SCOPE;
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7816
|
+
//#endregion
|
|
7817
|
+
//#region src/scope/resolve-session-scope-path.ts
|
|
7818
|
+
function resolveSessionScopePath(stateDir, sessionID) {
|
|
7819
|
+
const key = createHash("sha256").update(sessionID).digest("hex").slice(0, 32);
|
|
7820
|
+
return join(stateDir, "session-scope", `${key}.json`);
|
|
7821
|
+
}
|
|
7822
|
+
//#endregion
|
|
7823
|
+
//#region src/scope/pick-scope-source-reader.ts
|
|
7824
|
+
function pickScopeSourceReader(source) {
|
|
7825
|
+
if (source.kind === "cwd") return (context) => Promise.resolve(buildFacts({
|
|
7826
|
+
worktrees: [context.worktree],
|
|
7827
|
+
branches: context.branch === null ? [] : [context.branch]
|
|
7828
|
+
}));
|
|
7829
|
+
if (source.kind === "session") return async (context) => {
|
|
7830
|
+
const scope = await loadSessionScope(resolveSessionScopePath(context.stateDir, context.sessionID));
|
|
7831
|
+
return buildFacts({
|
|
7832
|
+
worktrees: scope.worktrees,
|
|
7833
|
+
branches: scope.branches.filter((branch) => branch.commonDir === context.commonDir).map((branch) => branch.name),
|
|
7834
|
+
pullRequests: scope.pullRequests
|
|
7835
|
+
});
|
|
7836
|
+
};
|
|
7837
|
+
if (source.kind === "globs") return () => Promise.resolve(buildFacts({ pathGlobs: source.paths ?? [] }));
|
|
7838
|
+
return () => Promise.resolve(EMPTY_SCOPE_FACTS);
|
|
7839
|
+
}
|
|
7840
|
+
function buildFacts(facts) {
|
|
7841
|
+
return {
|
|
7842
|
+
...EMPTY_SCOPE_FACTS,
|
|
7843
|
+
...facts
|
|
7844
|
+
};
|
|
7845
|
+
}
|
|
7846
|
+
//#endregion
|
|
7847
|
+
//#region src/scope/load-task-scope.ts
|
|
7848
|
+
async function loadTaskScope(request, sources) {
|
|
7849
|
+
const cwd = resolve(request.cwd);
|
|
7850
|
+
const [checkout, context] = await Promise.all([findCheckout(cwd), loadRepositoryContext(cwd)]);
|
|
7851
|
+
const branch = context?.branch ?? null;
|
|
7852
|
+
const sourceContext = {
|
|
7853
|
+
sessionID: request.sessionID,
|
|
7854
|
+
cwd,
|
|
7855
|
+
worktree: checkout?.worktree ?? cwd,
|
|
7856
|
+
commonDir: checkout?.commonDir ?? null,
|
|
7857
|
+
branch,
|
|
7858
|
+
stateDir: request.stateDir
|
|
7859
|
+
};
|
|
7860
|
+
const [remotes, ...facts] = await Promise.all([checkout === null ? [] : loadCheckoutRemotes(checkout.commonDir), ...Object.values(sources).map((source) => pickScopeSourceReader(source)(sourceContext))]);
|
|
7861
|
+
return buildTaskScope({
|
|
7862
|
+
home: homedir(),
|
|
7863
|
+
currentBranch: branch,
|
|
7864
|
+
defaultBranch: context?.defaultBranch ?? null,
|
|
7865
|
+
remotes,
|
|
7866
|
+
facts
|
|
7867
|
+
});
|
|
7868
|
+
}
|
|
7869
|
+
//#endregion
|
|
7870
|
+
//#region src/state/resolve-state-dir.ts
|
|
7871
|
+
function resolveStateDir() {
|
|
7872
|
+
const stateHome = process.env["XDG_STATE_HOME"];
|
|
7873
|
+
const base = stateHome === void 0 || stateHome === "" ? join(homedir(), ".local", "state") : stateHome;
|
|
7874
|
+
return join(base, "auto-mode");
|
|
7875
|
+
}
|
|
7876
|
+
//#endregion
|
|
7874
7877
|
//#region src/classify-action.ts
|
|
7875
7878
|
async function classifyAction(request, config, options = {}) {
|
|
7876
7879
|
let configured = null;
|
|
@@ -7886,9 +7889,11 @@ async function classifyAction(request, config, options = {}) {
|
|
|
7886
7889
|
unavailable: true
|
|
7887
7890
|
};
|
|
7888
7891
|
}
|
|
7889
|
-
const
|
|
7892
|
+
const hasConfiguredDenies = configured !== null && (configured.hard_deny.length > 0 || configured.soft_deny.length > 0);
|
|
7893
|
+
const local = hasConfiguredDenies ? { kind: "escalate" } : classifyLocally(request);
|
|
7890
7894
|
if (local.kind === "allow" && local.exception === READ_ONLY_EXCEPTION) return buildLocalAllow(local.exception);
|
|
7891
|
-
const
|
|
7895
|
+
const scope = await tryLoadTaskScope(request, config);
|
|
7896
|
+
const containment = scope === null ? null : checkContainment(request, scope);
|
|
7892
7897
|
if (containment !== null) {
|
|
7893
7898
|
const guidance = await tryReadDenialGuidance();
|
|
7894
7899
|
return {
|
|
@@ -7899,6 +7904,13 @@ async function classifyAction(request, config, options = {}) {
|
|
|
7899
7904
|
};
|
|
7900
7905
|
}
|
|
7901
7906
|
if (local.kind === "allow") return buildLocalAllow(local.exception);
|
|
7907
|
+
const edit = hasConfiguredDenies || scope === null ? null : await tryClassifyEdit(request, scope);
|
|
7908
|
+
if (edit?.kind === "bypass") return {
|
|
7909
|
+
verdict: { kind: "allow" },
|
|
7910
|
+
decidingStage: "bypass",
|
|
7911
|
+
note: `allowed by the edit bypass: ${edit.target}`,
|
|
7912
|
+
status: "allow"
|
|
7913
|
+
};
|
|
7902
7914
|
if (options.localOnly === true) return {
|
|
7903
7915
|
verdict: null,
|
|
7904
7916
|
decidingStage: "local",
|
|
@@ -7914,6 +7926,51 @@ async function classifyAction(request, config, options = {}) {
|
|
|
7914
7926
|
};
|
|
7915
7927
|
}
|
|
7916
7928
|
const READ_ONLY_EXCEPTION = "Read-only actions";
|
|
7929
|
+
async function tryLoadTaskScope(request, config) {
|
|
7930
|
+
try {
|
|
7931
|
+
return await loadTaskScope({
|
|
7932
|
+
sessionID: request.sessionID,
|
|
7933
|
+
cwd: request.cwd,
|
|
7934
|
+
stateDir: resolveStateDir()
|
|
7935
|
+
}, config.scopeSources ?? DEFAULT_SCOPE_SOURCES);
|
|
7936
|
+
} catch {
|
|
7937
|
+
return null;
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7940
|
+
const GIT_OVERRIDES = [
|
|
7941
|
+
"GIT_DIR",
|
|
7942
|
+
"GIT_WORK_TREE",
|
|
7943
|
+
"GIT_COMMON_DIR"
|
|
7944
|
+
];
|
|
7945
|
+
async function tryClassifyEdit(request, scope) {
|
|
7946
|
+
const fields = getEditFields(request.toolName);
|
|
7947
|
+
const path = fields === null ? void 0 : request.toolInput[fields.path];
|
|
7948
|
+
if (typeof path !== "string" || path === "") return null;
|
|
7949
|
+
try {
|
|
7950
|
+
const ownDirs = [dirname(resolveConfigPath()), resolveStateDir()];
|
|
7951
|
+
const requested = resolve(request.cwd, path);
|
|
7952
|
+
const [target, worktrees, protectedDirs] = await Promise.all([
|
|
7953
|
+
resolveEditTarget(requested),
|
|
7954
|
+
Promise.all(scope.worktrees.map((worktree) => resolveEditTarget(worktree))),
|
|
7955
|
+
Promise.all(ownDirs.map((dir) => resolveEditTarget(dir)))
|
|
7956
|
+
]);
|
|
7957
|
+
if (target === null || GIT_OVERRIDES.some((name) => process.env[name] !== void 0)) return null;
|
|
7958
|
+
const [checkout, current] = await Promise.all([findCheckout(dirname(target)), request.toolName === "Edit" ? readFile(target, "utf8").catch(() => null) : null]);
|
|
7959
|
+
return (await import("./classify-edit-D8GC6b91.js")).classifyEdit({
|
|
7960
|
+
toolName: request.toolName,
|
|
7961
|
+
toolInput: request.toolInput,
|
|
7962
|
+
requested,
|
|
7963
|
+
target,
|
|
7964
|
+
checkout: checkout?.worktree ?? null,
|
|
7965
|
+
current
|
|
7966
|
+
}, {
|
|
7967
|
+
worktrees: worktrees.filter((worktree) => worktree !== null),
|
|
7968
|
+
protectedDirs: [...ownDirs, ...protectedDirs.filter((dir) => dir !== null)]
|
|
7969
|
+
});
|
|
7970
|
+
} catch {
|
|
7971
|
+
return null;
|
|
7972
|
+
}
|
|
7973
|
+
}
|
|
7917
7974
|
function buildLocalAllow(exception) {
|
|
7918
7975
|
return {
|
|
7919
7976
|
verdict: { kind: "allow" },
|
|
@@ -7995,4 +8052,4 @@ function renderVerdict(verdict) {
|
|
|
7995
8052
|
})).exhaustive();
|
|
7996
8053
|
}
|
|
7997
8054
|
//#endregion
|
|
7998
|
-
export { resolveConfigPath as C, string as D, strictObject as E, resolveApiKey as S, number as T,
|
|
8055
|
+
export { resolveConfigPath as C, string as D, strictObject as E, resolveApiKey as S, number as T, splitCommandWords as _, resolveSessionScopePath as a, DEFAULT_SCOPE_SOURCES as b, loadCheckoutRemotes as c, classifyWithModel as d, parseModelVerdict as f, splitShellCommand as g, toRepositorySlug as h, resolveStateDir as i, findCheckout as l, loadPolicy as m, parseActionRequest as n, loadSessionScope as o, loadRepositoryContext as p, classifyAction as r, EMPTY_SESSION_SCOPE as s, renderVerdict as t, classifyLocally as u, DEFAULT_CONFIG as v, PRESETS as w, loadConfig as x, DEFAULT_DENIAL_BUDGET as y };
|