auto-mode 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +320 -10
- package/dist/index.js +1 -1
- package/dist/{render-verdict-1xtCI2is.js → render-verdict-BiGjYb1x.js} +1128 -983
- package/mods/auto-mode/hooks/register.claude-check.ts +97 -0
- package/mods/auto-mode/hooks/register.ts +36 -2
- package/mods/auto-mode/hooks/types.ts +13 -2
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFile, stat } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import { dirname, isAbsolute, join, normalize, parse, relative, resolve } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, join, matchesGlob, normalize, parse, relative, resolve } from "node:path";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
5
6
|
import { existsSync } from "node:fs";
|
|
6
7
|
//#region node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/util.js
|
|
7
8
|
function getEnumValues(entries) {
|
|
@@ -2922,7 +2923,7 @@ function bucketFor(state, inst) {
|
|
|
2922
2923
|
return bucket;
|
|
2923
2924
|
}
|
|
2924
2925
|
let handoff;
|
|
2925
|
-
const open = [];
|
|
2926
|
+
const open$1 = [];
|
|
2926
2927
|
const memo = {
|
|
2927
2928
|
alloc(_inst, payload, empty) {
|
|
2928
2929
|
const bucket = handoff;
|
|
@@ -2933,7 +2934,7 @@ const memo = {
|
|
|
2933
2934
|
issues: null
|
|
2934
2935
|
};
|
|
2935
2936
|
bucket.set(payload.value, entry);
|
|
2936
|
-
open.push(entry);
|
|
2937
|
+
open$1.push(entry);
|
|
2937
2938
|
return empty;
|
|
2938
2939
|
},
|
|
2939
2940
|
guard(inst) {
|
|
@@ -2999,10 +3000,10 @@ const memo = {
|
|
|
2999
3000
|
return payload;
|
|
3000
3001
|
}
|
|
3001
3002
|
handoff = bucket;
|
|
3002
|
-
const depth = open.length;
|
|
3003
|
+
const depth = open$1.length;
|
|
3003
3004
|
const result = base(payload, ctx);
|
|
3004
3005
|
handoff = void 0;
|
|
3005
|
-
const entry = open.length > depth ? open.pop() : void 0;
|
|
3006
|
+
const entry = open$1.length > depth ? open$1.pop() : void 0;
|
|
3006
3007
|
if (result instanceof Promise) return result.then((r) => {
|
|
3007
3008
|
if (entry) entry.issues = r.issues.length ? cloneIssues(r.issues) : NO_ISSUES;
|
|
3008
3009
|
return r;
|
|
@@ -5526,574 +5527,613 @@ function refine(fn, _params = {}) {
|
|
|
5526
5527
|
function superRefine(fn, params) {
|
|
5527
5528
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
5528
5529
|
}
|
|
5530
|
+
const PRESETS = {
|
|
5531
|
+
jev: {
|
|
5532
|
+
protocol: "system-one",
|
|
5533
|
+
baseURL: "https://api.typesafe.ai",
|
|
5534
|
+
model: "jev-1.13.0",
|
|
5535
|
+
apiKeyEnv: "TYPESAFE_API_KEY",
|
|
5536
|
+
reasoning: false,
|
|
5537
|
+
maxTokens: 3e3,
|
|
5538
|
+
timeoutMs: 5e3
|
|
5539
|
+
},
|
|
5540
|
+
spark: {
|
|
5541
|
+
baseURL: "https://api.meta.ai",
|
|
5542
|
+
model: "muse-spark-1.3-contributor",
|
|
5543
|
+
apiKeyEnv: "META_API_KEY",
|
|
5544
|
+
reasoning: true,
|
|
5545
|
+
maxTokens: 3e3,
|
|
5546
|
+
timeoutMs: 45e3
|
|
5547
|
+
},
|
|
5548
|
+
claude: {
|
|
5549
|
+
baseURL: "https://api.anthropic.com",
|
|
5550
|
+
model: "claude-haiku-4-5-20251001",
|
|
5551
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
5552
|
+
reasoning: true,
|
|
5553
|
+
maxTokens: 3e3,
|
|
5554
|
+
timeoutMs: 45e3
|
|
5555
|
+
},
|
|
5556
|
+
glm: {
|
|
5557
|
+
baseURL: "https://api.z.ai/api/anthropic",
|
|
5558
|
+
model: "glm-5.3-flash",
|
|
5559
|
+
apiKeyEnv: "ZAI_API_KEY",
|
|
5560
|
+
reasoning: true,
|
|
5561
|
+
maxTokens: 3e3,
|
|
5562
|
+
timeoutMs: 6e4
|
|
5563
|
+
}
|
|
5564
|
+
};
|
|
5565
|
+
const MESSAGES_DEFAULTS = {
|
|
5566
|
+
protocol: "messages",
|
|
5567
|
+
baseURL: "https://api.anthropic.com",
|
|
5568
|
+
reasoning: true,
|
|
5569
|
+
maxTokens: 3e3,
|
|
5570
|
+
timeoutMs: 45e3
|
|
5571
|
+
};
|
|
5572
|
+
function findPreset(id) {
|
|
5573
|
+
return Object.hasOwn(PRESETS, id) ? PRESETS[id] : void 0;
|
|
5574
|
+
}
|
|
5529
5575
|
//#endregion
|
|
5530
|
-
//#region src/config/
|
|
5531
|
-
|
|
5532
|
-
const
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5576
|
+
//#region src/config/read-api-key-from-command.ts
|
|
5577
|
+
function readApiKeyFromCommand(command, options = {}) {
|
|
5578
|
+
const remainingMs = options.deadlineAt === void 0 ? 5e3 : options.deadlineAt - Date.now();
|
|
5579
|
+
if (remainingMs <= 0 || options.signal?.aborted === true) return Promise.resolve(null);
|
|
5580
|
+
return new Promise((resolve) => {
|
|
5581
|
+
const grouped = process.platform !== "win32";
|
|
5582
|
+
const child = spawn("/bin/sh", ["-c", command], {
|
|
5583
|
+
detached: grouped,
|
|
5584
|
+
stdio: [
|
|
5585
|
+
"ignore",
|
|
5586
|
+
"pipe",
|
|
5587
|
+
"ignore"
|
|
5588
|
+
]
|
|
5589
|
+
});
|
|
5590
|
+
let output = "";
|
|
5591
|
+
let settled = false;
|
|
5592
|
+
let stopped = false;
|
|
5593
|
+
const setResult = (value) => {
|
|
5594
|
+
if (!settled) {
|
|
5595
|
+
settled = true;
|
|
5596
|
+
clearTimeout(timer);
|
|
5597
|
+
options.signal?.removeEventListener("abort", stopChild);
|
|
5598
|
+
resolve(value);
|
|
5599
|
+
}
|
|
5600
|
+
};
|
|
5601
|
+
const stopChild = () => {
|
|
5602
|
+
if (stopped) return;
|
|
5603
|
+
stopped = true;
|
|
5604
|
+
try {
|
|
5605
|
+
if (grouped && child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
5606
|
+
else child.kill("SIGKILL");
|
|
5607
|
+
} catch {
|
|
5608
|
+
child.kill("SIGKILL");
|
|
5609
|
+
}
|
|
5610
|
+
};
|
|
5611
|
+
const timer = setTimeout(stopChild, Math.min(5e3, remainingMs));
|
|
5612
|
+
options.signal?.addEventListener("abort", stopChild, { once: true });
|
|
5613
|
+
child.stdout.setEncoding("utf8");
|
|
5614
|
+
child.stdout.on("data", (chunk) => {
|
|
5615
|
+
if (stopped) return;
|
|
5616
|
+
output += chunk;
|
|
5617
|
+
if (output.length > 65536) stopChild();
|
|
5618
|
+
});
|
|
5619
|
+
child.on("error", () => {
|
|
5620
|
+
setResult(null);
|
|
5621
|
+
});
|
|
5622
|
+
child.on("close", (code) => {
|
|
5623
|
+
const key = !stopped && code === 0 && output.trim() !== "" ? output.trim() : null;
|
|
5624
|
+
setResult(key);
|
|
5625
|
+
});
|
|
5626
|
+
});
|
|
5627
|
+
}
|
|
5628
|
+
//#endregion
|
|
5629
|
+
//#region src/config/config.ts
|
|
5630
|
+
const SCOPE_SOURCE_KINDS = [
|
|
5631
|
+
"cwd",
|
|
5632
|
+
"session",
|
|
5633
|
+
"globs",
|
|
5634
|
+
"atc"
|
|
5635
|
+
];
|
|
5636
|
+
const DEFAULT_PROVIDER = findPreset("jev");
|
|
5637
|
+
if (DEFAULT_PROVIDER === void 0) throw new Error("the jev preset is missing");
|
|
5638
|
+
const DEFAULT_DENIAL_BUDGET = {
|
|
5639
|
+
consecutive: 3,
|
|
5640
|
+
perSession: 20
|
|
5641
|
+
};
|
|
5642
|
+
const DEFAULT_SCOPE_SOURCES = {
|
|
5643
|
+
cwd: { kind: "cwd" },
|
|
5644
|
+
session: { kind: "session" }
|
|
5645
|
+
};
|
|
5646
|
+
const DEFAULT_CONFIG = {
|
|
5647
|
+
provider: DEFAULT_PROVIDER,
|
|
5648
|
+
onFailure: "defer",
|
|
5649
|
+
minConfidence: .8,
|
|
5650
|
+
denialBudget: DEFAULT_DENIAL_BUDGET,
|
|
5651
|
+
scopeSources: DEFAULT_SCOPE_SOURCES
|
|
5652
|
+
};
|
|
5653
|
+
function resolveConfigPath() {
|
|
5654
|
+
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
5655
|
+
const base = xdg !== void 0 && xdg !== "" ? xdg : join(homedir(), ".config");
|
|
5656
|
+
return join(base, "auto-mode", "config.json");
|
|
5657
|
+
}
|
|
5658
|
+
async function loadConfig(path = resolveConfigPath()) {
|
|
5542
5659
|
let raw;
|
|
5543
5660
|
try {
|
|
5544
|
-
raw = await readFile(
|
|
5661
|
+
raw = await readFile(path, "utf8");
|
|
5545
5662
|
} catch (error) {
|
|
5546
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return
|
|
5547
|
-
throw new Error("
|
|
5663
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return DEFAULT_CONFIG;
|
|
5664
|
+
throw new Error("auto-mode configuration unreadable", { cause: error });
|
|
5548
5665
|
}
|
|
5549
|
-
|
|
5550
|
-
const schema = object({ autoMode: object({
|
|
5551
|
-
environment: entries,
|
|
5552
|
-
allow: entries,
|
|
5553
|
-
soft_deny: entries,
|
|
5554
|
-
hard_deny: entries
|
|
5555
|
-
}).optional() });
|
|
5556
|
-
let body;
|
|
5666
|
+
let json;
|
|
5557
5667
|
try {
|
|
5558
|
-
|
|
5668
|
+
json = JSON.parse(raw);
|
|
5559
5669
|
} catch {
|
|
5560
|
-
throw new Error(
|
|
5670
|
+
throw new Error(`${path} is not valid JSON`);
|
|
5561
5671
|
}
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5672
|
+
return buildConfig(json, path);
|
|
5673
|
+
}
|
|
5674
|
+
const text = string().min(1);
|
|
5675
|
+
const positive = number().positive();
|
|
5676
|
+
const denialBudgetSchema = strictObject({
|
|
5677
|
+
consecutive: number().int().positive().optional(),
|
|
5678
|
+
perSession: number().int().positive().optional()
|
|
5679
|
+
});
|
|
5680
|
+
const configFileSchema = strictObject({
|
|
5681
|
+
classifiers: record(text, unknown()).optional(),
|
|
5682
|
+
scopeSources: record(text, unknown()).optional(),
|
|
5683
|
+
decision: strictObject({
|
|
5684
|
+
classifier: text.optional(),
|
|
5685
|
+
judge: text.nullable().optional(),
|
|
5686
|
+
minConfidence: number().min(.5).max(1).optional(),
|
|
5687
|
+
onFailure: _enum(["defer", "deny"]).optional(),
|
|
5688
|
+
denialBudget: denialBudgetSchema.optional()
|
|
5689
|
+
}).optional(),
|
|
5690
|
+
policy: strictObject({
|
|
5691
|
+
rulesPath: text.nullable().optional(),
|
|
5692
|
+
frameworkPath: text.nullable().optional(),
|
|
5693
|
+
claudeSettingsPath: text.nullable().optional()
|
|
5694
|
+
}).optional()
|
|
5695
|
+
});
|
|
5696
|
+
function buildConfig(json, path) {
|
|
5697
|
+
const parsed = configFileSchema.safeParse(json);
|
|
5698
|
+
if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
|
|
5699
|
+
const file = parsed.data;
|
|
5700
|
+
const warnings = [];
|
|
5701
|
+
const classifiers = buildRegistry(file.classifiers ?? {}, (id, entry) => parseClassifierEntry(id, entry));
|
|
5702
|
+
const scopeSources = buildRegistry(file.scopeSources ?? {}, (id, entry) => parseScopeSourceEntry(id, entry));
|
|
5703
|
+
const configuredSources = file.scopeSources === void 0 ? null : scopeSources.entries;
|
|
5704
|
+
warnings.push(...classifiers.dropped.map((line) => `${path}: classifiers.${line}`), ...scopeSources.dropped.map((line) => `${path}: scopeSources.${line}`), ...collectScopeWarnings(configuredSources).map((line) => `${path}: scopeSources ${line}`));
|
|
5705
|
+
const decision = file.decision ?? {};
|
|
5706
|
+
const policy = file.policy ?? {};
|
|
5707
|
+
const judgeID = decision.judge ?? null;
|
|
5565
5708
|
return {
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5709
|
+
provider: resolveRole("classifier", decision.classifier ?? "jev", classifiers.entries, classifiers.droppedIDs, path),
|
|
5710
|
+
judge: judgeID === null ? null : resolveRole("judge", judgeID, classifiers.entries, classifiers.droppedIDs, path),
|
|
5711
|
+
scopeSources: configuredSources ?? DEFAULT_SCOPE_SOURCES,
|
|
5712
|
+
classifierPath: expandHomePath(policy.frameworkPath ?? void 0),
|
|
5713
|
+
rulesPath: expandHomePath(policy.rulesPath ?? void 0),
|
|
5714
|
+
onFailure: decision.onFailure ?? DEFAULT_CONFIG.onFailure,
|
|
5715
|
+
claudeSettingsPath: policy.claudeSettingsPath === null ? null : expandHomePath(policy.claudeSettingsPath),
|
|
5716
|
+
minConfidence: decision.minConfidence ?? DEFAULT_CONFIG.minConfidence,
|
|
5717
|
+
denialBudget: {
|
|
5718
|
+
consecutive: decision.denialBudget?.consecutive ?? DEFAULT_DENIAL_BUDGET.consecutive,
|
|
5719
|
+
perSession: decision.denialBudget?.perSession ?? DEFAULT_DENIAL_BUDGET.perSession
|
|
5720
|
+
},
|
|
5721
|
+
warnings
|
|
5570
5722
|
};
|
|
5571
5723
|
}
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
const
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5724
|
+
function collectScopeWarnings(entries) {
|
|
5725
|
+
if (entries === null) return [];
|
|
5726
|
+
const sources = Object.values(entries);
|
|
5727
|
+
return [...sources.some((source) => source.kind === "cwd") ? [] : ["has no cwd entry, so the task owns only what the other sources name"], ...sources.flatMap((source) => source.paths ?? []).filter((glob) => glob.includes(".worktrees")).map((glob) => `glob ${glob} covers worktrees that other tasks own`)];
|
|
5728
|
+
}
|
|
5729
|
+
function buildRegistry(raw, parseEntry) {
|
|
5730
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5731
|
+
const dropped = [];
|
|
5732
|
+
const droppedIDs = [];
|
|
5733
|
+
for (const [id, entry] of Object.entries(raw)) {
|
|
5734
|
+
const result = parseEntry(id, entry);
|
|
5735
|
+
if (typeof result === "string") {
|
|
5736
|
+
dropped.push(`${id} dropped: ${result}`);
|
|
5737
|
+
droppedIDs.push(id);
|
|
5738
|
+
} else entries.set(id, result);
|
|
5739
|
+
}
|
|
5740
|
+
return {
|
|
5741
|
+
entries: Object.fromEntries(entries),
|
|
5742
|
+
dropped,
|
|
5743
|
+
droppedIDs
|
|
5584
5744
|
};
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
current += ch;
|
|
5608
|
-
i += 1;
|
|
5609
|
-
continue;
|
|
5610
|
-
}
|
|
5611
|
-
if (command.startsWith("&&", i) || command.startsWith("||", i)) {
|
|
5612
|
-
collectSegment();
|
|
5613
|
-
i += 2;
|
|
5614
|
-
continue;
|
|
5615
|
-
}
|
|
5616
|
-
if (ch === ";" || ch === "|" || ch === "\n" || ch === "&") {
|
|
5617
|
-
collectSegment();
|
|
5618
|
-
i += 1;
|
|
5619
|
-
continue;
|
|
5620
|
-
}
|
|
5621
|
-
current += ch;
|
|
5622
|
-
i += 1;
|
|
5745
|
+
}
|
|
5746
|
+
const CLASSIFIER_KINDS = [...Object.keys(PRESETS), "messages"];
|
|
5747
|
+
const classifierEntrySchema = strictObject({
|
|
5748
|
+
kind: text.optional(),
|
|
5749
|
+
baseURL: text.optional(),
|
|
5750
|
+
model: text.optional(),
|
|
5751
|
+
apiKeyEnv: text.optional(),
|
|
5752
|
+
apiKeyCommand: text.optional(),
|
|
5753
|
+
reasoning: boolean().optional(),
|
|
5754
|
+
maxTokens: positive.optional(),
|
|
5755
|
+
timeoutMs: positive.optional()
|
|
5756
|
+
});
|
|
5757
|
+
const CREDENTIAL_KEYS = /* @__PURE__ */ new Set([
|
|
5758
|
+
"apiKey",
|
|
5759
|
+
"key",
|
|
5760
|
+
"token",
|
|
5761
|
+
"secret"
|
|
5762
|
+
]);
|
|
5763
|
+
function parseClassifierEntry(id, entry) {
|
|
5764
|
+
if (typeof entry === "object" && entry !== null) {
|
|
5765
|
+
const credential = Object.keys(entry).find((key) => CREDENTIAL_KEYS.has(key));
|
|
5766
|
+
if (credential !== void 0) return `holds a literal ${credential}; name the key with apiKeyEnv or apiKeyCommand`;
|
|
5623
5767
|
}
|
|
5624
|
-
|
|
5768
|
+
const parsed = classifierEntrySchema.safeParse(entry);
|
|
5769
|
+
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
5770
|
+
const kind = parsed.data.kind ?? id;
|
|
5771
|
+
if (!CLASSIFIER_KINDS.includes(kind)) return `unknown kind '${kind}'; known kinds are ${CLASSIFIER_KINDS.join(", ")}`;
|
|
5772
|
+
const base = kind === "messages" ? MESSAGES_DEFAULTS : findPreset(kind);
|
|
5773
|
+
const model = parsed.data.model ?? base?.model;
|
|
5774
|
+
if (base === void 0 || model === void 0) return `kind '${kind}' needs a model`;
|
|
5625
5775
|
return {
|
|
5626
|
-
|
|
5627
|
-
|
|
5776
|
+
protocol: base.protocol ?? "messages",
|
|
5777
|
+
baseURL: parsed.data.baseURL ?? base.baseURL,
|
|
5778
|
+
model,
|
|
5779
|
+
apiKeyEnv: parsed.data.apiKeyEnv ?? base.apiKeyEnv,
|
|
5780
|
+
apiKeyCommand: parsed.data.apiKeyCommand ?? base.apiKeyCommand,
|
|
5781
|
+
reasoning: parsed.data.reasoning ?? base.reasoning,
|
|
5782
|
+
maxTokens: parsed.data.maxTokens ?? base.maxTokens,
|
|
5783
|
+
timeoutMs: parsed.data.timeoutMs ?? base.timeoutMs
|
|
5628
5784
|
};
|
|
5629
5785
|
}
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
const
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5786
|
+
const scopeSourceEntrySchema = strictObject({
|
|
5787
|
+
kind: text.optional(),
|
|
5788
|
+
paths: array(text).min(1).optional()
|
|
5789
|
+
});
|
|
5790
|
+
function parseScopeSourceEntry(id, entry) {
|
|
5791
|
+
const parsed = scopeSourceEntrySchema.safeParse(entry);
|
|
5792
|
+
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
5793
|
+
const kind = SCOPE_SOURCE_KINDS.find((known) => known === (parsed.data.kind ?? id));
|
|
5794
|
+
if (kind === void 0) return `unknown kind '${parsed.data.kind ?? id}'; known kinds are ${SCOPE_SOURCE_KINDS.join(", ")}`;
|
|
5795
|
+
if (kind === "globs") return parsed.data.paths === void 0 ? "kind 'globs' needs paths" : {
|
|
5796
|
+
kind,
|
|
5797
|
+
paths: parsed.data.paths.map((glob) => expandHomePath(glob) ?? glob)
|
|
5798
|
+
};
|
|
5799
|
+
return parsed.data.paths === void 0 ? { kind } : `kind '${kind}' takes no paths`;
|
|
5641
5800
|
}
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
"
|
|
5645
|
-
"
|
|
5646
|
-
"NotebookEdit"
|
|
5647
|
-
]);
|
|
5648
|
-
function collectTargetFindings(directory, target, scope) {
|
|
5649
|
-
const path = resolvePath(directory, target, scope.home);
|
|
5650
|
-
return path === null ? [] : collectPathFindings(path, scope);
|
|
5801
|
+
function formatFirstIssue(issues) {
|
|
5802
|
+
const [issue] = issues;
|
|
5803
|
+
if (issue === void 0) return "invalid entry";
|
|
5804
|
+
return issue.path.length === 0 ? issue.message : `${issue.path.map(String).join(".")}: ${issue.message}`;
|
|
5651
5805
|
}
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
".npmrc",
|
|
5659
|
-
".config/gh"
|
|
5660
|
-
];
|
|
5661
|
-
function collectPathFindings(path, scope) {
|
|
5662
|
-
if (CREDENTIAL_PATHS.some((entry) => isUnder(path, join(scope.home, entry)))) return [{
|
|
5663
|
-
kind: "credential",
|
|
5664
|
-
target: path
|
|
5665
|
-
}];
|
|
5666
|
-
return isInScope(path, scope) ? [] : [{
|
|
5667
|
-
kind: "path",
|
|
5668
|
-
target: path
|
|
5669
|
-
}];
|
|
5806
|
+
function resolveRole(role, id, entries, droppedIDs, path) {
|
|
5807
|
+
const entry = Object.hasOwn(entries, id) ? entries[id] : void 0;
|
|
5808
|
+
if (entry !== void 0) return entry;
|
|
5809
|
+
if (droppedIDs.includes(id)) throw new Error(`${path}: decision.${role} names '${id}', whose entry was dropped`);
|
|
5810
|
+
if (findPreset(id) === void 0) throw new Error(`${path}: decision.${role} names '${id}', which is neither a classifiers entry nor a built-in kind (${Object.keys(PRESETS).join(", ")})`);
|
|
5811
|
+
return parseBuiltIn(id);
|
|
5670
5812
|
}
|
|
5671
|
-
function
|
|
5672
|
-
const
|
|
5673
|
-
if (
|
|
5674
|
-
|
|
5675
|
-
return !verb.includes("merge") && typeof number === "number" && scope.pullRequests.includes(number) ? [] : [{
|
|
5676
|
-
kind: "remote-write",
|
|
5677
|
-
target: action.tool
|
|
5678
|
-
}];
|
|
5813
|
+
function parseBuiltIn(id) {
|
|
5814
|
+
const resolved = parseClassifierEntry(id, {});
|
|
5815
|
+
if (typeof resolved === "string") throw new TypeError(`built-in kind ${id} does not resolve: ${resolved}`);
|
|
5816
|
+
return resolved;
|
|
5679
5817
|
}
|
|
5680
|
-
|
|
5681
|
-
"
|
|
5682
|
-
"rmdir",
|
|
5683
|
-
"shred",
|
|
5684
|
-
"truncate",
|
|
5685
|
-
"chmod",
|
|
5686
|
-
"chown"
|
|
5687
|
-
]);
|
|
5688
|
-
const DESTINATION_WRITERS = /* @__PURE__ */ new Set([
|
|
5689
|
-
"cp",
|
|
5690
|
-
"mv",
|
|
5691
|
-
"rsync",
|
|
5692
|
-
"ln"
|
|
5693
|
-
]);
|
|
5694
|
-
function collectCommandFindings(command, cwd, scope) {
|
|
5695
|
-
const findings = [];
|
|
5696
|
-
const hasPipe = /(?<!\|)\|(?!\|)/u.test(command);
|
|
5697
|
-
let directory = cwd;
|
|
5698
|
-
for (const segment of splitShellCommand(command).segments) {
|
|
5699
|
-
const [name, ...args] = getCommandWords(splitWords$1(segment.text));
|
|
5700
|
-
for (const target of collectRedirectTargets(segment.text)) findings.push(...collectTargetFindings(directory, target, scope));
|
|
5701
|
-
if (name === void 0) continue;
|
|
5702
|
-
if (name === "cd" && hasPipe) directory = null;
|
|
5703
|
-
else if (name === "cd") directory = args[0] === void 0 ? scope.home : resolvePath(directory, args[0], scope.home);
|
|
5704
|
-
else findings.push(...collectProgramFindings(name, args, directory, cwd, scope));
|
|
5705
|
-
}
|
|
5706
|
-
return findings;
|
|
5818
|
+
function expandHomePath(path) {
|
|
5819
|
+
return path?.startsWith("~/") === true ? join(homedir(), path.slice(2)) : path;
|
|
5707
5820
|
}
|
|
5708
|
-
function
|
|
5709
|
-
const
|
|
5710
|
-
|
|
5711
|
-
if (
|
|
5712
|
-
|
|
5713
|
-
if (PATH_WRITERS.has(name)) return operands.flatMap((path) => resolve(path));
|
|
5714
|
-
if (DESTINATION_WRITERS.has(name)) return collectCopyFindings(name, args, operands).flatMap((path) => resolve(path));
|
|
5715
|
-
if (name === "find" && args.some((word) => word === "-delete" || word === "-exec")) return operands.slice(0, 1).flatMap((path) => resolve(path));
|
|
5716
|
-
if ((name === "perl" || name === "sed") && args.some((word) => /^-\w*i/u.test(word))) return collectInPlaceFiles(args).flatMap((path) => resolve(path));
|
|
5717
|
-
if (name === "ssh-keygen") return collectKeygenFiles(args).flatMap((path) => resolve(path));
|
|
5718
|
-
if (name === "ssh") {
|
|
5719
|
-
const logIndex = args.indexOf("-E");
|
|
5720
|
-
const log = logIndex === -1 ? void 0 : args[logIndex + 1];
|
|
5721
|
-
return [...log === void 0 ? [] : resolve(log), ...collectSSHFindings(args)];
|
|
5722
|
-
}
|
|
5723
|
-
if (name === "scp") {
|
|
5724
|
-
const destination = collectSSHOperands(args).at(-1);
|
|
5725
|
-
if (destination === void 0 || /^[^/]+:/u.test(destination)) return destination === void 0 ? [] : [{
|
|
5726
|
-
kind: "remote-write",
|
|
5727
|
-
target: "scp"
|
|
5728
|
-
}];
|
|
5729
|
-
return resolve(destination);
|
|
5730
|
-
}
|
|
5731
|
-
if (name === "curl") return collectCurlFindings(args);
|
|
5732
|
-
const finding = findProgramFinding(name, args);
|
|
5733
|
-
return finding === null ? [] : [finding];
|
|
5821
|
+
function resolveApiKey(provider, options = {}) {
|
|
5822
|
+
const fromEnv = provider.apiKeyEnv === void 0 ? void 0 : process.env[provider.apiKeyEnv];
|
|
5823
|
+
if (fromEnv !== void 0 && fromEnv !== "") return Promise.resolve(fromEnv);
|
|
5824
|
+
if (provider.apiKeyCommand === void 0 || provider.apiKeyCommand === "") return Promise.resolve(null);
|
|
5825
|
+
return readApiKeyFromCommand(provider.apiKeyCommand, options);
|
|
5734
5826
|
}
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5827
|
+
//#endregion
|
|
5828
|
+
//#region src/config/load-claude-rules.ts
|
|
5829
|
+
async function loadClaudeRules(path) {
|
|
5830
|
+
const empty = {
|
|
5831
|
+
environment: [],
|
|
5832
|
+
allow: [],
|
|
5833
|
+
soft_deny: [],
|
|
5834
|
+
hard_deny: []
|
|
5835
|
+
};
|
|
5836
|
+
if (path === null) return empty;
|
|
5837
|
+
const configDir = process.env["CLAUDE_CONFIG_DIR"];
|
|
5838
|
+
const base = configDir === void 0 || configDir === "" ? join(homedir(), ".claude") : configDir;
|
|
5839
|
+
const settingsPath = path ?? join(base, "settings.json");
|
|
5840
|
+
let raw;
|
|
5841
|
+
try {
|
|
5842
|
+
raw = await readFile(settingsPath, "utf8");
|
|
5843
|
+
} catch (error) {
|
|
5844
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return empty;
|
|
5845
|
+
throw new Error("Claude settings unreadable", { cause: error });
|
|
5742
5846
|
}
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
const
|
|
5757
|
-
|
|
5847
|
+
const entries = array(string()).optional();
|
|
5848
|
+
const schema = object({ autoMode: object({
|
|
5849
|
+
environment: entries,
|
|
5850
|
+
allow: entries,
|
|
5851
|
+
soft_deny: entries,
|
|
5852
|
+
hard_deny: entries
|
|
5853
|
+
}).optional() });
|
|
5854
|
+
let body;
|
|
5855
|
+
try {
|
|
5856
|
+
body = JSON.parse(raw);
|
|
5857
|
+
} catch {
|
|
5858
|
+
throw new Error("Claude settings contain invalid JSON");
|
|
5859
|
+
}
|
|
5860
|
+
const parsed = schema.safeParse(body);
|
|
5861
|
+
if (!parsed.success) throw new Error("Claude autoMode settings are invalid");
|
|
5862
|
+
const rules = parsed.data;
|
|
5863
|
+
return {
|
|
5864
|
+
environment: (rules.autoMode?.environment ?? []).filter((entry) => entry !== "$defaults"),
|
|
5865
|
+
allow: (rules.autoMode?.allow ?? []).filter((entry) => entry !== "$defaults"),
|
|
5866
|
+
soft_deny: (rules.autoMode?.soft_deny ?? []).filter((entry) => entry !== "$defaults"),
|
|
5867
|
+
hard_deny: (rules.autoMode?.hard_deny ?? []).filter((entry) => entry !== "$defaults")
|
|
5868
|
+
};
|
|
5758
5869
|
}
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
kind: "remote-write",
|
|
5813
|
-
target: "ssh"
|
|
5814
|
-
}];
|
|
5815
|
-
}
|
|
5816
|
-
const LOCAL_HOSTS = /* @__PURE__ */ new Set([
|
|
5817
|
-
"localhost",
|
|
5818
|
-
"127.0.0.1",
|
|
5819
|
-
"[::1]",
|
|
5820
|
-
"0.0.0.0"
|
|
5821
|
-
]);
|
|
5822
|
-
function collectCurlFindings(args) {
|
|
5823
|
-
const joined = args.join(" ");
|
|
5824
|
-
const isUpload = args.some((word) => /^-[a-zA-Z]*[dFT]$|^--(?:data\S*|form|upload-file|json)$/u.test(word));
|
|
5825
|
-
const isWriteMethod = /(?:-X\s*|--request\s+)(?:POST|PUT|PATCH|DELETE)/u.test(joined);
|
|
5826
|
-
if (!isUpload && !isWriteMethod) return [];
|
|
5827
|
-
const hosts = args.filter((word) => /^(?:https?:\/\/|localhost\b|127\.)/u.test(word)).map((word) => /^(?:https?:\/\/)?(?<host>\[[^\]]+\]|[^/:?#\s]+)/u.exec(word)?.groups?.["host"]?.toLowerCase() ?? "");
|
|
5828
|
-
return hosts.length > 0 && hosts.every((host) => LOCAL_HOSTS.has(host)) ? [] : [{
|
|
5829
|
-
kind: "remote-write",
|
|
5830
|
-
target: "curl upload"
|
|
5831
|
-
}];
|
|
5832
|
-
}
|
|
5833
|
-
const GIT_DIRECTORY_WRITES = /* @__PURE__ */ new Set([
|
|
5834
|
-
"clean",
|
|
5835
|
-
"reset",
|
|
5836
|
-
"checkout",
|
|
5837
|
-
"switch",
|
|
5838
|
-
"merge",
|
|
5839
|
-
"commit",
|
|
5840
|
-
"rm",
|
|
5841
|
-
"stash",
|
|
5842
|
-
"push"
|
|
5843
|
-
]);
|
|
5844
|
-
function collectGitFindings(args, directory, cwd, scope) {
|
|
5845
|
-
let gitDirectory = directory;
|
|
5846
|
-
let index = 0;
|
|
5847
|
-
while (index < args.length && args[index]?.startsWith("-") === true) {
|
|
5848
|
-
const option = args[index];
|
|
5849
|
-
if (option === "-C") gitDirectory = resolvePath(gitDirectory, args[index + 1] ?? "", scope.home);
|
|
5850
|
-
index += option === "-C" || option === "-c" ? 2 : 1;
|
|
5851
|
-
}
|
|
5852
|
-
const subcommand = args[index] ?? "";
|
|
5853
|
-
const rest = args.slice(index + 1);
|
|
5854
|
-
const operands = rest.filter((word) => !word.startsWith("-"));
|
|
5855
|
-
const findings = [];
|
|
5856
|
-
if (gitDirectory !== null && GIT_DIRECTORY_WRITES.has(subcommand) && !isInScope(gitDirectory, scope)) findings.push({
|
|
5857
|
-
kind: "path",
|
|
5858
|
-
target: gitDirectory
|
|
5859
|
-
});
|
|
5860
|
-
if (subcommand === "worktree" && operands[0] === "remove" && operands[1] !== void 0) findings.push(...collectTargetFindings(gitDirectory, operands[1], scope));
|
|
5861
|
-
if (subcommand === "branch" && rest.some((word) => /^-(?:d|D|-delete)$/u.test(word))) for (const branch of operands.filter((word) => !scope.branches.includes(word))) findings.push({
|
|
5862
|
-
kind: "branch",
|
|
5863
|
-
target: branch
|
|
5864
|
-
});
|
|
5865
|
-
const isConfigRead = rest.some((word) => /^--(?:get|get-all|get-regexp|list)$|^-l$/u.test(word));
|
|
5866
|
-
if (subcommand === "config" && rest.includes("--global") && !isConfigRead) findings.push({
|
|
5867
|
-
kind: "credential",
|
|
5868
|
-
target: `git config ${operands[0] ?? ""}`
|
|
5869
|
-
});
|
|
5870
|
-
if (subcommand === "push") {
|
|
5871
|
-
const currentBranch = gitDirectory === cwd ? scope.currentBranch : null;
|
|
5872
|
-
findings.push(...collectPushFindings(rest, operands, currentBranch, scope));
|
|
5873
|
-
}
|
|
5874
|
-
return findings;
|
|
5875
|
-
}
|
|
5876
|
-
function collectPushFindings(rest, operands, currentBranch, scope) {
|
|
5877
|
-
const [remote, ...refspecs] = operands;
|
|
5878
|
-
const findings = [];
|
|
5879
|
-
if (rest.includes("--all") || rest.includes("--mirror")) findings.push({
|
|
5880
|
-
kind: "branch",
|
|
5881
|
-
target: "--all"
|
|
5882
|
-
});
|
|
5883
|
-
if (remote !== void 0 && !isUnresolved(remote) && !isOwnedRemote(remote, scope)) findings.push({
|
|
5884
|
-
kind: "remote-write",
|
|
5885
|
-
target: remote
|
|
5886
|
-
});
|
|
5887
|
-
const destinations = refspecs.length === 0 && !rest.includes("--tags") ? ["HEAD"] : refspecs;
|
|
5888
|
-
for (const refspec of destinations) {
|
|
5889
|
-
const named = (refspec.split(":").at(-1) ?? refspec).replace(/^\+/u, "").replace(/^refs\/heads\//u, "");
|
|
5890
|
-
const branch = named === "HEAD" ? currentBranch : named;
|
|
5891
|
-
if (branch !== null && !isUnresolved(branch) && !branch.startsWith("refs/tags/") && !scope.branches.includes(branch)) findings.push({
|
|
5892
|
-
kind: "branch",
|
|
5893
|
-
target: branch
|
|
5894
|
-
});
|
|
5870
|
+
//#endregion
|
|
5871
|
+
//#region src/model/load-repository-context.ts
|
|
5872
|
+
async function loadRepositoryContext(cwd) {
|
|
5873
|
+
if ([
|
|
5874
|
+
"GIT_DIR",
|
|
5875
|
+
"GIT_WORK_TREE",
|
|
5876
|
+
"GIT_COMMON_DIR"
|
|
5877
|
+
].some((name) => process.env[name] !== void 0)) return null;
|
|
5878
|
+
let directory = resolve(cwd);
|
|
5879
|
+
for (;;) {
|
|
5880
|
+
const gitPath = join(directory, ".git");
|
|
5881
|
+
try {
|
|
5882
|
+
await stat(gitPath);
|
|
5883
|
+
} catch (error) {
|
|
5884
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) return null;
|
|
5885
|
+
const parent = dirname(directory);
|
|
5886
|
+
if (parent === directory) return null;
|
|
5887
|
+
directory = parent;
|
|
5888
|
+
continue;
|
|
5889
|
+
}
|
|
5890
|
+
let gitDir = gitPath;
|
|
5891
|
+
let head;
|
|
5892
|
+
try {
|
|
5893
|
+
try {
|
|
5894
|
+
head = await readFile(join(gitDir, "HEAD"), "utf8");
|
|
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;
|
|
5904
|
+
}
|
|
5905
|
+
let commonDir = gitDir;
|
|
5906
|
+
try {
|
|
5907
|
+
const common = await readFile(join(gitDir, "commondir"), "utf8");
|
|
5908
|
+
commonDir = resolve(gitDir, common.trim());
|
|
5909
|
+
} catch {
|
|
5910
|
+
commonDir = gitDir;
|
|
5911
|
+
}
|
|
5912
|
+
let defaultBranch = null;
|
|
5913
|
+
try {
|
|
5914
|
+
defaultBranch = parseReference(await readFile(join(commonDir, "refs", "remotes", "origin", "HEAD"), "utf8"), "refs/remotes/origin/");
|
|
5915
|
+
} catch {
|
|
5916
|
+
defaultBranch = null;
|
|
5917
|
+
}
|
|
5918
|
+
return {
|
|
5919
|
+
cwd: resolve(cwd),
|
|
5920
|
+
branch: parseReference(head, "refs/heads/"),
|
|
5921
|
+
defaultBranch
|
|
5922
|
+
};
|
|
5895
5923
|
}
|
|
5896
|
-
return findings;
|
|
5897
|
-
}
|
|
5898
|
-
function isUnresolved(word) {
|
|
5899
|
-
return /[$`]/u.test(word);
|
|
5900
5924
|
}
|
|
5901
|
-
function
|
|
5902
|
-
|
|
5903
|
-
const
|
|
5904
|
-
|
|
5925
|
+
function parseReference(content, prefix) {
|
|
5926
|
+
const value = content.trim();
|
|
5927
|
+
const marker = `ref: ${prefix}`;
|
|
5928
|
+
if (!value.startsWith(marker)) return null;
|
|
5929
|
+
const branch = value.slice(marker.length);
|
|
5930
|
+
return branch !== "" && branch.length <= 256 && !/\s/u.test(branch) ? branch : null;
|
|
5905
5931
|
}
|
|
5932
|
+
//#endregion
|
|
5933
|
+
//#region src/containment/to-repository-slug.ts
|
|
5906
5934
|
function toRepositorySlug(reference) {
|
|
5907
5935
|
const match = /^(?:[a-z+]+:\/\/)?(?:[^@/]+@)?(?<host>[^/:]+)[:/](?<owner>[^/]+)\/(?<name>[^/]+?)(?:\.git)?\/?$/iu.exec(reference);
|
|
5908
5936
|
if (match?.groups === void 0) return null;
|
|
5909
5937
|
const parts = match.groups;
|
|
5910
5938
|
return `${parts["host"] ?? ""}/${parts["owner"] ?? ""}/${parts["name"] ?? ""}`.toLowerCase();
|
|
5911
5939
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
]
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
const
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5940
|
+
//#endregion
|
|
5941
|
+
//#region src/scope/merge-scope-facts.ts
|
|
5942
|
+
function mergeScopeFacts(all) {
|
|
5943
|
+
const pullRequests = /* @__PURE__ */ new Map();
|
|
5944
|
+
for (const pull of all.flatMap((facts) => facts.pullRequests)) pullRequests.set(`${pull.repository}#${String(pull.number)}`, pull);
|
|
5945
|
+
return {
|
|
5946
|
+
worktrees: [...new Set(all.flatMap((facts) => facts.worktrees))],
|
|
5947
|
+
branches: [...new Set(all.flatMap((facts) => facts.branches))],
|
|
5948
|
+
pullRequests: [...pullRequests.values()],
|
|
5949
|
+
pathGlobs: [...new Set(all.flatMap((facts) => facts.pathGlobs))]
|
|
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
|
+
};
|
|
5971
|
+
}
|
|
5972
|
+
const DEFAULT_NAMES = /* @__PURE__ */ new Set([
|
|
5973
|
+
"main",
|
|
5974
|
+
"master",
|
|
5975
|
+
"trunk",
|
|
5976
|
+
"develop"
|
|
5942
5977
|
]);
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
].includes(verb)) return [{
|
|
5965
|
-
kind: "credential",
|
|
5966
|
-
target: `gh auth ${verb}`
|
|
5967
|
-
}];
|
|
5968
|
-
if (GH_CREDENTIAL_WRITES.has(group) && !GH_READS.has(verb)) return [{
|
|
5969
|
-
kind: "credential",
|
|
5970
|
-
target: `gh ${group} ${verb}`
|
|
5971
|
-
}];
|
|
5972
|
-
if (GH_REMOTE_WRITES.has(group) && !GH_READS.has(verb)) return [{
|
|
5973
|
-
kind: "remote-write",
|
|
5974
|
-
target: `gh ${group} ${verb}`
|
|
5975
|
-
}];
|
|
5976
|
-
return [];
|
|
5977
|
-
}
|
|
5978
|
-
function isCheckoutRepository(repo, scope) {
|
|
5979
|
-
const wanted = repo.toLowerCase().replace(/\.git$/u, "");
|
|
5980
|
-
return scope.remotes.some((remote) => {
|
|
5981
|
-
const slug = toRepositorySlug(remote.url);
|
|
5982
|
-
return slug !== null && (slug === wanted || slug.endsWith(`/${wanted}`));
|
|
5983
|
-
});
|
|
5978
|
+
//#endregion
|
|
5979
|
+
//#region src/scope/find-checkout.ts
|
|
5980
|
+
const GIT_OVERRIDES = [
|
|
5981
|
+
"GIT_DIR",
|
|
5982
|
+
"GIT_WORK_TREE",
|
|
5983
|
+
"GIT_COMMON_DIR"
|
|
5984
|
+
];
|
|
5985
|
+
async function findCheckout(start) {
|
|
5986
|
+
if (GIT_OVERRIDES.some((name) => process.env[name] !== void 0)) return null;
|
|
5987
|
+
for (let directory = start;; directory = dirname(directory)) {
|
|
5988
|
+
const gitPath = join(directory, ".git");
|
|
5989
|
+
const info = await stat(gitPath).catch(() => null);
|
|
5990
|
+
if (info !== null) {
|
|
5991
|
+
const gitDir = info.isDirectory() ? gitPath : await findLinkedGitDir(directory, gitPath);
|
|
5992
|
+
return gitDir === null ? null : {
|
|
5993
|
+
worktree: directory,
|
|
5994
|
+
commonDir: await findCommonDir(gitDir)
|
|
5995
|
+
};
|
|
5996
|
+
}
|
|
5997
|
+
if (dirname(directory) === directory) return null;
|
|
5998
|
+
}
|
|
5984
5999
|
}
|
|
5985
|
-
function
|
|
5986
|
-
const
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
target: "gh api graphql mutation"
|
|
5990
|
-
}] : [];
|
|
5991
|
-
const methodIndex = args.findIndex((word) => word === "-X" || word === "--method");
|
|
5992
|
-
const method = methodIndex === -1 ? null : (args[methodIndex + 1] ?? "").toUpperCase();
|
|
5993
|
-
const hasFields = args.some((word) => /^(?:-f|-F|--field|--raw-field|--input)$/u.test(word));
|
|
5994
|
-
if (method === "GET" || method === null && !hasFields) return [];
|
|
5995
|
-
const pull = /^\/?repos\/(?<repo>[^/]+\/[^/]+)\/(?:pulls|issues)\/(?<number>\d+)(?:\/|$)/u.exec(endpoint)?.groups;
|
|
5996
|
-
if (pull !== void 0 && scope.pullRequests.includes(Number(pull["number"])) && isCheckoutRepository(pull["repo"] ?? "", scope) && !endpoint.includes("/merge")) return [];
|
|
5997
|
-
return [{
|
|
5998
|
-
kind: /secret|collaborator|protection|key|permission/u.test(endpoint) ? "credential" : "remote-write",
|
|
5999
|
-
target: `gh api ${endpoint}`
|
|
6000
|
-
}];
|
|
6000
|
+
async function findLinkedGitDir(directory, gitPath) {
|
|
6001
|
+
const link = await readFile(gitPath, "utf8").catch(() => "");
|
|
6002
|
+
const path = /^gitdir: (?<path>.+)$/mu.exec(link)?.groups?.["path"];
|
|
6003
|
+
return path === void 0 ? null : resolve(directory, path.trim());
|
|
6001
6004
|
}
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
if (name === "op" && /^(?:item|vault|document) (?:edit|create|delete)\b/u.test(joined)) return {
|
|
6006
|
-
kind: "credential",
|
|
6007
|
-
target: `op ${args[0] ?? ""} ${args[1] ?? ""}`
|
|
6008
|
-
};
|
|
6009
|
-
if (name === "aws" && args[0] === "iam" && IAM_WRITE.test(args[1] ?? "")) return {
|
|
6010
|
-
kind: "credential",
|
|
6011
|
-
target: `aws iam ${args[1] ?? ""}`
|
|
6012
|
-
};
|
|
6013
|
-
if (name === "docker" && /\b(?:volume|system|image|container) prune\b|\bvolume rm\b|\bdown\b.*(?:-v|--volumes)\b/u.test(joined)) return {
|
|
6014
|
-
kind: "prune",
|
|
6015
|
-
target: `docker ${joined}`
|
|
6016
|
-
};
|
|
6017
|
-
if (name === "npm" && args[0] === "publish") return {
|
|
6018
|
-
kind: "remote-write",
|
|
6019
|
-
target: "npm publish"
|
|
6020
|
-
};
|
|
6021
|
-
if (name === "kubectl" && /\b(?:apply|delete|replace|patch|scale)\b/u.test(joined)) return {
|
|
6022
|
-
kind: "remote-write",
|
|
6023
|
-
target: "kubectl write"
|
|
6024
|
-
};
|
|
6025
|
-
if (name === "terraform" && /\b(?:apply|destroy|import)\b/u.test(joined)) return {
|
|
6026
|
-
kind: "remote-write",
|
|
6027
|
-
target: "terraform write"
|
|
6028
|
-
};
|
|
6029
|
-
if (name === "psql" && /\b(?:UPDATE|DELETE|DROP|INSERT|TRUNCATE|ALTER)\b/iu.test(joined)) return {
|
|
6030
|
-
kind: "remote-write",
|
|
6031
|
-
target: "database write"
|
|
6032
|
-
};
|
|
6033
|
-
if (/^(?:bun|npm|pnpm|yarn)$/u.test(name) && /\bdeploy\b/u.test(joined) && /prod/u.test(joined)) return {
|
|
6034
|
-
kind: "remote-write",
|
|
6035
|
-
target: "production deploy"
|
|
6036
|
-
};
|
|
6037
|
-
return null;
|
|
6005
|
+
async function findCommonDir(gitDir) {
|
|
6006
|
+
const common = await readFile(join(gitDir, "commondir"), "utf8").catch(() => null);
|
|
6007
|
+
return common === null ? gitDir : resolve(gitDir, common.trim());
|
|
6038
6008
|
}
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6049
|
-
|
|
6050
|
-
|
|
6051
|
-
|
|
6052
|
-
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
continue;
|
|
6057
|
-
}
|
|
6058
|
-
const [target, end] = readWord(text, next);
|
|
6059
|
-
if (target !== "") targets.push(target);
|
|
6060
|
-
index = end;
|
|
6061
|
-
} else index += 1;
|
|
6009
|
+
//#endregion
|
|
6010
|
+
//#region src/scope/load-checkout-remotes.ts
|
|
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
|
+
}
|
|
6062
6026
|
}
|
|
6063
|
-
return
|
|
6027
|
+
return remotes;
|
|
6064
6028
|
}
|
|
6065
|
-
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6070
|
-
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
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;
|
|
6079
6065
|
}
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
let sawScript = false;
|
|
6086
|
-
for (const word of args) if (skipNext) {
|
|
6087
|
-
skipNext = false;
|
|
6088
|
-
sawScript = true;
|
|
6089
|
-
} else if (/^-\w*e$/u.test(word)) skipNext = true;
|
|
6090
|
-
else if (!word.startsWith("-")) {
|
|
6091
|
-
if (sawScript) files.push(word);
|
|
6092
|
-
else sawScript = true;
|
|
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;
|
|
6093
6071
|
}
|
|
6094
|
-
return files;
|
|
6095
6072
|
}
|
|
6096
|
-
|
|
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);
|
|
6097
6137
|
let index = 0;
|
|
6098
6138
|
while (index < words.length) {
|
|
6099
6139
|
const word = words[index] ?? "";
|
|
@@ -6128,490 +6168,595 @@ function splitWords$1(text) {
|
|
|
6128
6168
|
if (started) words.push(current);
|
|
6129
6169
|
return words;
|
|
6130
6170
|
}
|
|
6131
|
-
function resolvePath(cwd, path, home) {
|
|
6132
|
-
const expanded = path.replace(/^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/u, home);
|
|
6133
|
-
if (/[$`]|^~/u.test(expanded) || expanded === "") return null;
|
|
6134
|
-
if (cwd === null && !isAbsolute(expanded)) return null;
|
|
6135
|
-
const absolute = isAbsolute(expanded) || cwd === null ? expanded : join(cwd, expanded);
|
|
6136
|
-
return normalize(absolute).replace(/(?<=.)\/$/u, "");
|
|
6137
|
-
}
|
|
6138
|
-
const SCRATCH_PATHS = [
|
|
6139
|
-
"/tmp",
|
|
6140
|
-
"/dev/null",
|
|
6141
|
-
"/dev/stdout",
|
|
6142
|
-
"/dev/stderr"
|
|
6143
|
-
];
|
|
6144
|
-
function isInScope(path, scope) {
|
|
6145
|
-
if (SCRATCH_PATHS.some((entry) => isUnder(path, entry))) return true;
|
|
6146
|
-
return scope.worktrees.some((worktree) => {
|
|
6147
|
-
const isNested = isUnder(path, join(worktree, ".worktrees"));
|
|
6148
|
-
const isOtherOwned = scope.worktrees.some((other) => other !== worktree && isUnder(path, other));
|
|
6149
|
-
return isUnder(path, worktree) && (!isNested || isOtherOwned);
|
|
6150
|
-
});
|
|
6151
|
-
}
|
|
6152
|
-
function isUnder(path, root) {
|
|
6153
|
-
return path === root || path.startsWith(`${root}/`);
|
|
6154
|
-
}
|
|
6155
6171
|
//#endregion
|
|
6156
|
-
//#region src/
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
if (
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
let head;
|
|
6177
|
-
try {
|
|
6178
|
-
try {
|
|
6179
|
-
head = await readFile(join(gitDir, "HEAD"), "utf8");
|
|
6180
|
-
} catch {
|
|
6181
|
-
const link = await readFile(gitPath, "utf8");
|
|
6182
|
-
const path = /^gitdir: (?<path>.+)\r?\n?$/u.exec(link)?.groups?.["path"];
|
|
6183
|
-
if (path === void 0) return null;
|
|
6184
|
-
gitDir = resolve(directory, path.trim());
|
|
6185
|
-
head = await readFile(join(gitDir, "HEAD"), "utf8");
|
|
6186
|
-
}
|
|
6187
|
-
} catch {
|
|
6188
|
-
return null;
|
|
6189
|
-
}
|
|
6190
|
-
let commonDir = gitDir;
|
|
6191
|
-
try {
|
|
6192
|
-
const common = await readFile(join(gitDir, "commondir"), "utf8");
|
|
6193
|
-
commonDir = resolve(gitDir, common.trim());
|
|
6194
|
-
} catch {
|
|
6195
|
-
commonDir = gitDir;
|
|
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;
|
|
6196
6192
|
}
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6193
|
+
if (ch === "'" || ch === "\"") {
|
|
6194
|
+
quote = ch;
|
|
6195
|
+
current += ch;
|
|
6196
|
+
i += 1;
|
|
6197
|
+
continue;
|
|
6202
6198
|
}
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
}
|
|
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;
|
|
6208
6222
|
}
|
|
6209
|
-
|
|
6210
|
-
function parseReference(content, prefix) {
|
|
6211
|
-
const value = content.trim();
|
|
6212
|
-
const marker = `ref: ${prefix}`;
|
|
6213
|
-
if (!value.startsWith(marker)) return null;
|
|
6214
|
-
const branch = value.slice(marker.length);
|
|
6215
|
-
return branch !== "" && branch.length <= 256 && !/\s/u.test(branch) ? branch : null;
|
|
6216
|
-
}
|
|
6217
|
-
//#endregion
|
|
6218
|
-
//#region src/containment/build-cwd-scope.ts
|
|
6219
|
-
function buildCWDScope(input) {
|
|
6220
|
-
const isDefault = input.branch === input.defaultBranch || input.defaultBranch === null && DEFAULT_NAMES.has(input.branch ?? "");
|
|
6223
|
+
collectSegment();
|
|
6221
6224
|
return {
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
branches: input.branch !== null && !isDefault ? [input.branch] : [],
|
|
6225
|
-
currentBranch: input.branch,
|
|
6226
|
-
remotes: input.remotes,
|
|
6227
|
-
pullRequests: []
|
|
6225
|
+
segments,
|
|
6226
|
+
hasUnparsedConstruct: unparsed || quote !== null
|
|
6228
6227
|
};
|
|
6229
6228
|
}
|
|
6230
|
-
const DEFAULT_NAMES = /* @__PURE__ */ new Set([
|
|
6231
|
-
"main",
|
|
6232
|
-
"master",
|
|
6233
|
-
"trunk",
|
|
6234
|
-
"develop"
|
|
6235
|
-
]);
|
|
6236
6229
|
//#endregion
|
|
6237
|
-
//#region src/containment/
|
|
6238
|
-
|
|
6239
|
-
const [
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
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);
|
|
6247
6240
|
}
|
|
6248
|
-
const
|
|
6249
|
-
"
|
|
6250
|
-
"
|
|
6251
|
-
"
|
|
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"
|
|
6252
6259
|
];
|
|
6253
|
-
|
|
6254
|
-
if (
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
commonDir: await findCommonDir(gitDir)
|
|
6263
|
-
};
|
|
6264
|
-
}
|
|
6265
|
-
if (dirname(directory) === directory) return null;
|
|
6266
|
-
}
|
|
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
|
+
}];
|
|
6267
6269
|
}
|
|
6268
|
-
|
|
6269
|
-
const
|
|
6270
|
-
|
|
6271
|
-
|
|
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
|
+
}];
|
|
6272
6281
|
}
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6282
|
+
const PATH_WRITERS = /* @__PURE__ */ new Set([
|
|
6283
|
+
"rm",
|
|
6284
|
+
"rmdir",
|
|
6285
|
+
"shred",
|
|
6286
|
+
"truncate",
|
|
6287
|
+
"chmod",
|
|
6288
|
+
"chown"
|
|
6289
|
+
]);
|
|
6290
|
+
const DESTINATION_WRITERS = /* @__PURE__ */ new Set([
|
|
6291
|
+
"cp",
|
|
6292
|
+
"mv",
|
|
6293
|
+
"rsync",
|
|
6294
|
+
"ln"
|
|
6295
|
+
]);
|
|
6296
|
+
function collectCommandFindings(command, cwd, scope) {
|
|
6297
|
+
const findings = [];
|
|
6298
|
+
const hasPipe = /(?<!\|)\|(?!\|)/u.test(command);
|
|
6299
|
+
let directory = cwd;
|
|
6300
|
+
for (const segment of splitShellCommand(command).segments) {
|
|
6301
|
+
const [name, ...args] = splitCommandWords(segment.text);
|
|
6302
|
+
for (const target of collectRedirectTargets(segment.text)) findings.push(...collectTargetFindings(directory, target, scope));
|
|
6303
|
+
if (name === void 0) continue;
|
|
6304
|
+
if (name === "cd" && hasPipe) directory = null;
|
|
6305
|
+
else if (name === "cd") directory = args[0] === void 0 ? scope.home : resolvePath(directory, args[0], scope.home);
|
|
6306
|
+
else findings.push(...collectProgramFindings(name, args, directory, cwd, scope));
|
|
6307
|
+
}
|
|
6308
|
+
return findings;
|
|
6276
6309
|
}
|
|
6277
|
-
|
|
6278
|
-
const
|
|
6279
|
-
const
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
}
|
|
6310
|
+
function collectProgramFindings(name, args, directory, cwd, scope) {
|
|
6311
|
+
const resolve = (path) => collectTargetFindings(directory, path, scope);
|
|
6312
|
+
const operands = args.filter((word) => !word.startsWith("-"));
|
|
6313
|
+
if (name === "git") return collectGitFindings(args, directory, cwd, scope);
|
|
6314
|
+
if (name === "gh") return collectGhFindings(args, scope);
|
|
6315
|
+
if (PATH_WRITERS.has(name)) return operands.flatMap((path) => resolve(path));
|
|
6316
|
+
if (DESTINATION_WRITERS.has(name)) return collectCopyFindings(name, args, operands).flatMap((path) => resolve(path));
|
|
6317
|
+
if (name === "find" && args.some((word) => word === "-delete" || word === "-exec")) return operands.slice(0, 1).flatMap((path) => resolve(path));
|
|
6318
|
+
if ((name === "perl" || name === "sed") && args.some((word) => /^-\w*i/u.test(word))) return collectInPlaceFiles(args).flatMap((path) => resolve(path));
|
|
6319
|
+
if (name === "ssh-keygen") return collectKeygenFiles(args).flatMap((path) => resolve(path));
|
|
6320
|
+
if (name === "ssh") {
|
|
6321
|
+
const logIndex = args.indexOf("-E");
|
|
6322
|
+
const log = logIndex === -1 ? void 0 : args[logIndex + 1];
|
|
6323
|
+
return [...log === void 0 ? [] : resolve(log), ...collectSSHFindings(args)];
|
|
6292
6324
|
}
|
|
6293
|
-
|
|
6325
|
+
if (name === "scp") {
|
|
6326
|
+
const destination = collectSSHOperands(args).at(-1);
|
|
6327
|
+
if (destination === void 0 || /^[^/]+:/u.test(destination)) return destination === void 0 ? [] : [{
|
|
6328
|
+
kind: "remote-write",
|
|
6329
|
+
target: "scp"
|
|
6330
|
+
}];
|
|
6331
|
+
return resolve(destination);
|
|
6332
|
+
}
|
|
6333
|
+
if (name === "curl") return collectCurlFindings(args);
|
|
6334
|
+
const launcher = [name, ...args].findIndex((word) => word.includes("auto-mode"));
|
|
6335
|
+
if (launcher !== -1 && [name, ...args].slice(launcher + 1).includes("record")) return [{
|
|
6336
|
+
kind: "credential",
|
|
6337
|
+
target: "auto-mode record"
|
|
6338
|
+
}];
|
|
6339
|
+
const finding = findProgramFinding(name, args);
|
|
6340
|
+
return finding === null ? [] : [finding];
|
|
6294
6341
|
}
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
} catch {
|
|
6303
|
-
return null;
|
|
6342
|
+
function collectCopyFindings(name, args, operands) {
|
|
6343
|
+
const optionIndex = args.findIndex((word) => word === "-t" || word === "--target-directory");
|
|
6344
|
+
let targetDirectory = args.find((word) => word.startsWith("--target-directory="))?.slice(19);
|
|
6345
|
+
if (targetDirectory === void 0 && optionIndex !== -1) targetDirectory = args[optionIndex + 1];
|
|
6346
|
+
if (targetDirectory !== void 0) {
|
|
6347
|
+
const sources = operands.filter((word) => word !== targetDirectory);
|
|
6348
|
+
return [targetDirectory, ...name === "mv" ? sources : []];
|
|
6304
6349
|
}
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
cwd: request.cwd,
|
|
6308
|
-
input: request.toolInput
|
|
6309
|
-
}, scope);
|
|
6310
|
-
if (findings.length === 0) return null;
|
|
6311
|
-
return {
|
|
6312
|
-
rule: CONTAINMENT_RULE,
|
|
6313
|
-
reason: formatContainmentReason(findings, scope),
|
|
6314
|
-
findings
|
|
6315
|
-
};
|
|
6350
|
+
if (operands.length < 2) return [];
|
|
6351
|
+
return [operands.at(-1) ?? "", ...name === "mv" ? operands.slice(0, -1) : []];
|
|
6316
6352
|
}
|
|
6317
|
-
const
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
"
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
function
|
|
6325
|
-
|
|
6326
|
-
const
|
|
6327
|
-
const
|
|
6328
|
-
return
|
|
6353
|
+
const KEYGEN_READ_OPTIONS = /* @__PURE__ */ new Set([
|
|
6354
|
+
"-l",
|
|
6355
|
+
"-y",
|
|
6356
|
+
"-F",
|
|
6357
|
+
"-B",
|
|
6358
|
+
"-R"
|
|
6359
|
+
]);
|
|
6360
|
+
function collectKeygenFiles(args) {
|
|
6361
|
+
if (args.some((word) => KEYGEN_READ_OPTIONS.has(word))) return [];
|
|
6362
|
+
const index = args.indexOf("-f");
|
|
6363
|
+
const file = index === -1 ? void 0 : args[index + 1];
|
|
6364
|
+
return file === void 0 ? [] : [file];
|
|
6329
6365
|
}
|
|
6330
|
-
const
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
model: "glm-5.3-flash",
|
|
6359
|
-
apiKeyEnv: "ZAI_API_KEY",
|
|
6360
|
-
reasoning: true,
|
|
6361
|
-
maxTokens: 3e3,
|
|
6362
|
-
timeoutMs: 6e4
|
|
6366
|
+
const SSH_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
6367
|
+
"-b",
|
|
6368
|
+
"-c",
|
|
6369
|
+
"-D",
|
|
6370
|
+
"-E",
|
|
6371
|
+
"-F",
|
|
6372
|
+
"-i",
|
|
6373
|
+
"-J",
|
|
6374
|
+
"-l",
|
|
6375
|
+
"-L",
|
|
6376
|
+
"-m"
|
|
6377
|
+
]);
|
|
6378
|
+
const SSH_MORE_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
6379
|
+
"-o",
|
|
6380
|
+
"-p",
|
|
6381
|
+
"-P",
|
|
6382
|
+
"-Q",
|
|
6383
|
+
"-R",
|
|
6384
|
+
"-S",
|
|
6385
|
+
"-W",
|
|
6386
|
+
"-w"
|
|
6387
|
+
]);
|
|
6388
|
+
function collectSSHOperands(args) {
|
|
6389
|
+
const operands = [];
|
|
6390
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
6391
|
+
const word = args[index] ?? "";
|
|
6392
|
+
if (operands.length > 0 || !word.startsWith("-")) operands.push(word);
|
|
6393
|
+
else if (SSH_VALUE_OPTIONS.has(word) || SSH_MORE_VALUE_OPTIONS.has(word)) index += 1;
|
|
6363
6394
|
}
|
|
6364
|
-
|
|
6365
|
-
const MESSAGES_DEFAULTS = {
|
|
6366
|
-
protocol: "messages",
|
|
6367
|
-
baseURL: "https://api.anthropic.com",
|
|
6368
|
-
reasoning: true,
|
|
6369
|
-
maxTokens: 3e3,
|
|
6370
|
-
timeoutMs: 45e3
|
|
6371
|
-
};
|
|
6372
|
-
function findPreset(id) {
|
|
6373
|
-
return Object.hasOwn(PRESETS, id) ? PRESETS[id] : void 0;
|
|
6395
|
+
return operands;
|
|
6374
6396
|
}
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
6385
|
-
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
|
|
6419
|
-
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6397
|
+
const SSH_READS = /* @__PURE__ */ new Set([
|
|
6398
|
+
"cat",
|
|
6399
|
+
"df",
|
|
6400
|
+
"du",
|
|
6401
|
+
"free",
|
|
6402
|
+
"head",
|
|
6403
|
+
"id",
|
|
6404
|
+
"ls",
|
|
6405
|
+
"ps",
|
|
6406
|
+
"pwd",
|
|
6407
|
+
"stat",
|
|
6408
|
+
"tail",
|
|
6409
|
+
"uname",
|
|
6410
|
+
"uptime",
|
|
6411
|
+
"whoami"
|
|
6412
|
+
]);
|
|
6413
|
+
function collectSSHFindings(args) {
|
|
6414
|
+
const [host, ...remote] = collectSSHOperands(args);
|
|
6415
|
+
if (host === void 0) return [];
|
|
6416
|
+
const command = remote.join(" ");
|
|
6417
|
+
const [program] = command.trim().split(/\s+/u);
|
|
6418
|
+
return SSH_READS.has(program ?? "") && !/[;&|<>`\n\r]|\$\(/u.test(command) ? [] : [{
|
|
6419
|
+
kind: "remote-write",
|
|
6420
|
+
target: "ssh"
|
|
6421
|
+
}];
|
|
6422
|
+
}
|
|
6423
|
+
const LOCAL_HOSTS = /* @__PURE__ */ new Set([
|
|
6424
|
+
"localhost",
|
|
6425
|
+
"127.0.0.1",
|
|
6426
|
+
"[::1]",
|
|
6427
|
+
"0.0.0.0"
|
|
6428
|
+
]);
|
|
6429
|
+
function collectCurlFindings(args) {
|
|
6430
|
+
const joined = args.join(" ");
|
|
6431
|
+
const isUpload = args.some((word) => /^-[a-zA-Z]*[dFT]$|^--(?:data\S*|form|upload-file|json)$/u.test(word));
|
|
6432
|
+
const isWriteMethod = /(?:-X\s*|--request\s+)(?:POST|PUT|PATCH|DELETE)/u.test(joined);
|
|
6433
|
+
if (!isUpload && !isWriteMethod) return [];
|
|
6434
|
+
const hosts = args.filter((word) => /^(?:https?:\/\/|localhost\b|127\.)/u.test(word)).map((word) => /^(?:https?:\/\/)?(?<host>\[[^\]]+\]|[^/:?#\s]+)/u.exec(word)?.groups?.["host"]?.toLowerCase() ?? "");
|
|
6435
|
+
return hosts.length > 0 && hosts.every((host) => LOCAL_HOSTS.has(host)) ? [] : [{
|
|
6436
|
+
kind: "remote-write",
|
|
6437
|
+
target: "curl upload"
|
|
6438
|
+
}];
|
|
6439
|
+
}
|
|
6440
|
+
const GIT_DIRECTORY_WRITES = /* @__PURE__ */ new Set([
|
|
6441
|
+
"clean",
|
|
6442
|
+
"reset",
|
|
6443
|
+
"checkout",
|
|
6444
|
+
"switch",
|
|
6445
|
+
"merge",
|
|
6446
|
+
"commit",
|
|
6447
|
+
"rm",
|
|
6448
|
+
"stash",
|
|
6449
|
+
"push"
|
|
6450
|
+
]);
|
|
6451
|
+
function collectGitFindings(args, directory, cwd, scope) {
|
|
6452
|
+
let gitDirectory = directory;
|
|
6453
|
+
let index = 0;
|
|
6454
|
+
while (index < args.length && args[index]?.startsWith("-") === true) {
|
|
6455
|
+
const option = args[index];
|
|
6456
|
+
if (option === "-C") gitDirectory = resolvePath(gitDirectory, args[index + 1] ?? "", scope.home);
|
|
6457
|
+
index += option === "-C" || option === "-c" ? 2 : 1;
|
|
6458
|
+
}
|
|
6459
|
+
const subcommand = args[index] ?? "";
|
|
6460
|
+
const rest = args.slice(index + 1);
|
|
6461
|
+
const operands = rest.filter((word) => !word.startsWith("-"));
|
|
6462
|
+
const findings = [];
|
|
6463
|
+
if (gitDirectory !== null && GIT_DIRECTORY_WRITES.has(subcommand) && !isInScope(gitDirectory, scope)) findings.push({
|
|
6464
|
+
kind: "path",
|
|
6465
|
+
target: gitDirectory
|
|
6466
|
+
});
|
|
6467
|
+
if (subcommand === "worktree" && operands[0] === "remove" && operands[1] !== void 0) findings.push(...collectTargetFindings(gitDirectory, operands[1], scope));
|
|
6468
|
+
if (subcommand === "branch" && rest.some((word) => /^-(?:d|D|-delete)$/u.test(word))) for (const branch of operands.filter((word) => !scope.branches.includes(word))) findings.push({
|
|
6469
|
+
kind: "branch",
|
|
6470
|
+
target: branch
|
|
6471
|
+
});
|
|
6472
|
+
const isConfigRead = rest.some((word) => /^--(?:get|get-all|get-regexp|list)$|^-l$/u.test(word));
|
|
6473
|
+
if (subcommand === "config" && rest.includes("--global") && !isConfigRead) findings.push({
|
|
6474
|
+
kind: "credential",
|
|
6475
|
+
target: `git config ${operands[0] ?? ""}`
|
|
6426
6476
|
});
|
|
6477
|
+
if (subcommand === "push") {
|
|
6478
|
+
const currentBranch = gitDirectory === cwd ? scope.currentBranch : null;
|
|
6479
|
+
findings.push(...collectPushFindings(rest, operands, currentBranch, scope));
|
|
6480
|
+
}
|
|
6481
|
+
return findings;
|
|
6427
6482
|
}
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
const
|
|
6431
|
-
"
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
const
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6446
|
-
|
|
6447
|
-
}
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
return
|
|
6483
|
+
function collectPushFindings(rest, operands, currentBranch, scope) {
|
|
6484
|
+
const [remote, ...refspecs] = operands;
|
|
6485
|
+
const findings = [];
|
|
6486
|
+
if (rest.includes("--all") || rest.includes("--mirror")) findings.push({
|
|
6487
|
+
kind: "branch",
|
|
6488
|
+
target: "--all"
|
|
6489
|
+
});
|
|
6490
|
+
if (remote !== void 0 && !isUnresolved(remote) && !isOwnedRemote(remote, scope)) findings.push({
|
|
6491
|
+
kind: "remote-write",
|
|
6492
|
+
target: remote
|
|
6493
|
+
});
|
|
6494
|
+
const destinations = refspecs.length === 0 && !rest.includes("--tags") ? ["HEAD"] : refspecs;
|
|
6495
|
+
for (const refspec of destinations) {
|
|
6496
|
+
const named = (refspec.split(":").at(-1) ?? refspec).replace(/^\+/u, "").replace(/^refs\/heads\//u, "");
|
|
6497
|
+
const branch = named === "HEAD" ? currentBranch : named;
|
|
6498
|
+
if (branch !== null && !isUnresolved(branch) && !branch.startsWith("refs/tags/") && !scope.branches.includes(branch)) findings.push({
|
|
6499
|
+
kind: "branch",
|
|
6500
|
+
target: branch
|
|
6501
|
+
});
|
|
6502
|
+
}
|
|
6503
|
+
return findings;
|
|
6504
|
+
}
|
|
6505
|
+
function isUnresolved(word) {
|
|
6506
|
+
return /[$`]/u.test(word);
|
|
6507
|
+
}
|
|
6508
|
+
function isOwnedRemote(remote, scope) {
|
|
6509
|
+
if (scope.remotes.some((entry) => entry.name === remote)) return true;
|
|
6510
|
+
const slug = toRepositorySlug(remote);
|
|
6511
|
+
return slug !== null && scope.remotes.some((entry) => toRepositorySlug(entry.url) === slug);
|
|
6512
|
+
}
|
|
6513
|
+
const GH_PR_WRITES = /* @__PURE__ */ new Set([
|
|
6514
|
+
"comment",
|
|
6515
|
+
"merge",
|
|
6516
|
+
"edit",
|
|
6517
|
+
"close",
|
|
6518
|
+
"review",
|
|
6519
|
+
"ready",
|
|
6520
|
+
"reopen"
|
|
6521
|
+
]);
|
|
6522
|
+
const GH_REMOTE_WRITES = /* @__PURE__ */ new Set([
|
|
6523
|
+
"gist",
|
|
6524
|
+
"release",
|
|
6525
|
+
"repo",
|
|
6526
|
+
"ruleset",
|
|
6527
|
+
"label",
|
|
6528
|
+
"workflow"
|
|
6529
|
+
]);
|
|
6530
|
+
const GH_CREDENTIAL_WRITES = /* @__PURE__ */ new Set([
|
|
6531
|
+
"secret",
|
|
6532
|
+
"variable",
|
|
6533
|
+
"ssh-key",
|
|
6534
|
+
"gpg-key"
|
|
6535
|
+
]);
|
|
6536
|
+
const GH_READS = /* @__PURE__ */ new Set([
|
|
6537
|
+
"list",
|
|
6538
|
+
"view",
|
|
6539
|
+
"clone",
|
|
6540
|
+
"status",
|
|
6541
|
+
"checks",
|
|
6542
|
+
"diff"
|
|
6543
|
+
]);
|
|
6544
|
+
function collectGhFindings(args, scope) {
|
|
6545
|
+
const group = args[0] ?? "";
|
|
6546
|
+
const verb = args[1] ?? "";
|
|
6547
|
+
if (group === "pr" && GH_PR_WRITES.has(verb)) {
|
|
6548
|
+
const number = Number(args.slice(2).find((word) => /^\d+$/u.test(word)));
|
|
6549
|
+
const repoIndex = args.findIndex((word) => word === "--repo" || word === "-R");
|
|
6550
|
+
const repo = repoIndex === -1 ? void 0 : args[repoIndex + 1];
|
|
6551
|
+
return verb !== "merge" && isOwnedPullRequest(number, repo, scope) ? [] : [{
|
|
6552
|
+
kind: "remote-write",
|
|
6553
|
+
target: `gh pr ${verb}`
|
|
6554
|
+
}];
|
|
6555
|
+
}
|
|
6556
|
+
if (group === "issue" && verb !== "" && !GH_READS.has(verb)) return [{
|
|
6557
|
+
kind: "remote-write",
|
|
6558
|
+
target: `gh issue ${verb}`
|
|
6559
|
+
}];
|
|
6560
|
+
if (group === "api") return collectGhAPIFindings(args.slice(1), scope);
|
|
6561
|
+
if (group === "auth" && [
|
|
6562
|
+
"refresh",
|
|
6563
|
+
"login",
|
|
6564
|
+
"logout"
|
|
6565
|
+
].includes(verb)) return [{
|
|
6566
|
+
kind: "credential",
|
|
6567
|
+
target: `gh auth ${verb}`
|
|
6568
|
+
}];
|
|
6569
|
+
if (GH_CREDENTIAL_WRITES.has(group) && !GH_READS.has(verb)) return [{
|
|
6570
|
+
kind: "credential",
|
|
6571
|
+
target: `gh ${group} ${verb}`
|
|
6572
|
+
}];
|
|
6573
|
+
if (GH_REMOTE_WRITES.has(group) && !GH_READS.has(verb)) return [{
|
|
6574
|
+
kind: "remote-write",
|
|
6575
|
+
target: `gh ${group} ${verb}`
|
|
6576
|
+
}];
|
|
6577
|
+
return [];
|
|
6578
|
+
}
|
|
6579
|
+
function isOwnedPullRequest(number, repo, scope) {
|
|
6580
|
+
const wanted = repo?.toLowerCase().replace(/\.git$/u, "");
|
|
6581
|
+
const slugs = new Set(scope.remotes.map((remote) => toRepositorySlug(remote.url)));
|
|
6582
|
+
return scope.pullRequests.some((pull) => pull.number === number && (wanted === void 0 ? slugs.size === 1 && slugs.has(pull.repository) : pull.repository === wanted || pull.repository.endsWith(`/${wanted}`)));
|
|
6583
|
+
}
|
|
6584
|
+
function collectGhAPIFindings(args, scope) {
|
|
6585
|
+
const endpoint = args.find((word) => !word.startsWith("-")) ?? "";
|
|
6586
|
+
if (endpoint === "graphql") return args.some((word) => /\bmutation\b/u.test(word)) ? [{
|
|
6587
|
+
kind: "remote-write",
|
|
6588
|
+
target: "gh api graphql mutation"
|
|
6589
|
+
}] : [];
|
|
6590
|
+
const methodIndex = args.findIndex((word) => word === "-X" || word === "--method");
|
|
6591
|
+
const method = methodIndex === -1 ? null : (args[methodIndex + 1] ?? "").toUpperCase();
|
|
6592
|
+
const hasFields = args.some((word) => /^(?:-f|-F|--field|--raw-field|--input)$/u.test(word));
|
|
6593
|
+
if (method === "GET" || method === null && !hasFields) return [];
|
|
6594
|
+
const pull = /^\/?repos\/(?<repo>[^/]+\/[^/]+)\/(?:pulls|issues)\/(?<number>\d+)(?:\/|$)/u.exec(endpoint)?.groups;
|
|
6595
|
+
if (pull !== void 0 && isOwnedPullRequest(Number(pull["number"]), pull["repo"] ?? "", scope) && !endpoint.includes("/merge")) return [];
|
|
6596
|
+
return [{
|
|
6597
|
+
kind: /secret|collaborator|protection|key|permission/u.test(endpoint) ? "credential" : "remote-write",
|
|
6598
|
+
target: `gh api ${endpoint}`
|
|
6599
|
+
}];
|
|
6600
|
+
}
|
|
6601
|
+
const IAM_WRITE = /^(?:add|attach|change|create|deactivate|delete|detach|enable|put|remove|reset|set|tag|untag|update|upload)-/u;
|
|
6602
|
+
function findProgramFinding(name, args) {
|
|
6603
|
+
const joined = args.join(" ");
|
|
6604
|
+
if (name === "op" && /^(?:item|vault|document) (?:edit|create|delete)\b/u.test(joined)) return {
|
|
6605
|
+
kind: "credential",
|
|
6606
|
+
target: `op ${args[0] ?? ""} ${args[1] ?? ""}`
|
|
6607
|
+
};
|
|
6608
|
+
if (name === "aws" && args[0] === "iam" && IAM_WRITE.test(args[1] ?? "")) return {
|
|
6609
|
+
kind: "credential",
|
|
6610
|
+
target: `aws iam ${args[1] ?? ""}`
|
|
6611
|
+
};
|
|
6612
|
+
if (name === "docker" && /\b(?:volume|system|image|container) prune\b|\bvolume rm\b|\bdown\b.*(?:-v|--volumes)\b/u.test(joined)) return {
|
|
6613
|
+
kind: "prune",
|
|
6614
|
+
target: `docker ${joined}`
|
|
6615
|
+
};
|
|
6616
|
+
if (name === "npm" && args[0] === "publish") return {
|
|
6617
|
+
kind: "remote-write",
|
|
6618
|
+
target: "npm publish"
|
|
6619
|
+
};
|
|
6620
|
+
if (name === "kubectl" && /\b(?:apply|delete|replace|patch|scale)\b/u.test(joined)) return {
|
|
6621
|
+
kind: "remote-write",
|
|
6622
|
+
target: "kubectl write"
|
|
6623
|
+
};
|
|
6624
|
+
if (name === "terraform" && /\b(?:apply|destroy|import)\b/u.test(joined)) return {
|
|
6625
|
+
kind: "remote-write",
|
|
6626
|
+
target: "terraform write"
|
|
6627
|
+
};
|
|
6628
|
+
if (name === "psql" && /\b(?:UPDATE|DELETE|DROP|INSERT|TRUNCATE|ALTER)\b/iu.test(joined)) return {
|
|
6629
|
+
kind: "remote-write",
|
|
6630
|
+
target: "database write"
|
|
6631
|
+
};
|
|
6632
|
+
if (/^(?:bun|npm|pnpm|yarn)$/u.test(name) && /\bdeploy\b/u.test(joined) && /prod/u.test(joined)) return {
|
|
6633
|
+
kind: "remote-write",
|
|
6634
|
+
target: "production deploy"
|
|
6635
|
+
};
|
|
6636
|
+
return null;
|
|
6452
6637
|
}
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
|
|
6638
|
+
function collectRedirectTargets(text) {
|
|
6639
|
+
const targets = [];
|
|
6640
|
+
let quote = null;
|
|
6641
|
+
let index = 0;
|
|
6642
|
+
while (index < text.length) {
|
|
6643
|
+
const ch = text.charAt(index);
|
|
6644
|
+
if (quote !== null) {
|
|
6645
|
+
quote = ch === quote ? null : quote;
|
|
6646
|
+
index += 1;
|
|
6647
|
+
} else if (ch === "'" || ch === "\"") {
|
|
6648
|
+
quote = ch;
|
|
6649
|
+
index += 1;
|
|
6650
|
+
} else if (ch === ">" && text.charAt(index - 1) !== "<") {
|
|
6651
|
+
let next = index + 1;
|
|
6652
|
+
if (text.charAt(next) === ">" || text.charAt(next) === "|") next += 1;
|
|
6653
|
+
if (text.charAt(next) === "&") {
|
|
6654
|
+
index = next + 1;
|
|
6655
|
+
continue;
|
|
6656
|
+
}
|
|
6657
|
+
const [target, end] = readWord(text, next);
|
|
6658
|
+
if (target !== "") targets.push(target);
|
|
6659
|
+
index = end;
|
|
6660
|
+
} else index += 1;
|
|
6466
6661
|
}
|
|
6467
|
-
return
|
|
6468
|
-
}
|
|
6469
|
-
const text = string().min(1);
|
|
6470
|
-
const positive = number().positive();
|
|
6471
|
-
const denialBudgetSchema = strictObject({
|
|
6472
|
-
consecutive: number().int().positive().optional(),
|
|
6473
|
-
perSession: number().int().positive().optional()
|
|
6474
|
-
});
|
|
6475
|
-
const configFileSchema = strictObject({
|
|
6476
|
-
classifiers: record(text, unknown()).optional(),
|
|
6477
|
-
scopeSources: record(text, unknown()).optional(),
|
|
6478
|
-
decision: strictObject({
|
|
6479
|
-
classifier: text.optional(),
|
|
6480
|
-
judge: text.nullable().optional(),
|
|
6481
|
-
minConfidence: number().min(.5).max(1).optional(),
|
|
6482
|
-
onFailure: _enum(["defer", "deny"]).optional(),
|
|
6483
|
-
denialBudget: denialBudgetSchema.optional()
|
|
6484
|
-
}).optional(),
|
|
6485
|
-
policy: strictObject({
|
|
6486
|
-
rulesPath: text.nullable().optional(),
|
|
6487
|
-
frameworkPath: text.nullable().optional(),
|
|
6488
|
-
claudeSettingsPath: text.nullable().optional()
|
|
6489
|
-
}).optional()
|
|
6490
|
-
});
|
|
6491
|
-
function buildConfig(json, path) {
|
|
6492
|
-
const parsed = configFileSchema.safeParse(json);
|
|
6493
|
-
if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
|
|
6494
|
-
const file = parsed.data;
|
|
6495
|
-
const warnings = [];
|
|
6496
|
-
const classifiers = buildRegistry(file.classifiers ?? {}, (id, entry) => parseClassifierEntry(id, entry));
|
|
6497
|
-
const scopeSources = buildRegistry(file.scopeSources ?? {}, (id, entry) => parseScopeSourceEntry(id, entry));
|
|
6498
|
-
warnings.push(...classifiers.dropped.map((line) => `${path}: classifiers.${line}`), ...scopeSources.dropped.map((line) => `${path}: scopeSources.${line}`));
|
|
6499
|
-
const decision = file.decision ?? {};
|
|
6500
|
-
const policy = file.policy ?? {};
|
|
6501
|
-
const judgeID = decision.judge ?? null;
|
|
6502
|
-
return {
|
|
6503
|
-
provider: resolveRole("classifier", decision.classifier ?? "jev", classifiers.entries, classifiers.droppedIDs, path),
|
|
6504
|
-
judge: judgeID === null ? null : resolveRole("judge", judgeID, classifiers.entries, classifiers.droppedIDs, path),
|
|
6505
|
-
scopeSources: scopeSources.entries,
|
|
6506
|
-
classifierPath: expandHomePath(policy.frameworkPath ?? void 0),
|
|
6507
|
-
rulesPath: expandHomePath(policy.rulesPath ?? void 0),
|
|
6508
|
-
onFailure: decision.onFailure ?? DEFAULT_CONFIG.onFailure,
|
|
6509
|
-
claudeSettingsPath: policy.claudeSettingsPath === null ? null : expandHomePath(policy.claudeSettingsPath),
|
|
6510
|
-
minConfidence: decision.minConfidence ?? DEFAULT_CONFIG.minConfidence,
|
|
6511
|
-
denialBudget: {
|
|
6512
|
-
consecutive: decision.denialBudget?.consecutive ?? DEFAULT_DENIAL_BUDGET.consecutive,
|
|
6513
|
-
perSession: decision.denialBudget?.perSession ?? DEFAULT_DENIAL_BUDGET.perSession
|
|
6514
|
-
},
|
|
6515
|
-
warnings
|
|
6516
|
-
};
|
|
6662
|
+
return targets;
|
|
6517
6663
|
}
|
|
6518
|
-
function
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6664
|
+
function readWord(text, start) {
|
|
6665
|
+
let index = start;
|
|
6666
|
+
while (/\s/u.test(text.charAt(index))) index += 1;
|
|
6667
|
+
let word = "";
|
|
6668
|
+
let quote = null;
|
|
6669
|
+
while (index < text.length) {
|
|
6670
|
+
const ch = text.charAt(index);
|
|
6671
|
+
if (quote !== null) {
|
|
6672
|
+
if (ch === quote) quote = null;
|
|
6673
|
+
else word += ch;
|
|
6674
|
+
} else if (ch === "'" || ch === "\"") quote = ch;
|
|
6675
|
+
else if (/[\s;&|<>()]/u.test(ch)) break;
|
|
6676
|
+
else word += ch;
|
|
6677
|
+
index += 1;
|
|
6528
6678
|
}
|
|
6529
|
-
return
|
|
6530
|
-
entries: Object.fromEntries(entries),
|
|
6531
|
-
dropped,
|
|
6532
|
-
droppedIDs
|
|
6533
|
-
};
|
|
6679
|
+
return [word, index];
|
|
6534
6680
|
}
|
|
6535
|
-
|
|
6536
|
-
const
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
const CREDENTIAL_KEYS = /* @__PURE__ */ new Set([
|
|
6547
|
-
"apiKey",
|
|
6548
|
-
"key",
|
|
6549
|
-
"token",
|
|
6550
|
-
"secret"
|
|
6551
|
-
]);
|
|
6552
|
-
function parseClassifierEntry(id, entry) {
|
|
6553
|
-
if (typeof entry === "object" && entry !== null) {
|
|
6554
|
-
const credential = Object.keys(entry).find((key) => CREDENTIAL_KEYS.has(key));
|
|
6555
|
-
if (credential !== void 0) return `holds a literal ${credential}; name the key with apiKeyEnv or apiKeyCommand`;
|
|
6681
|
+
function collectInPlaceFiles(args) {
|
|
6682
|
+
const files = [];
|
|
6683
|
+
let skipNext = false;
|
|
6684
|
+
let sawScript = false;
|
|
6685
|
+
for (const word of args) if (skipNext) {
|
|
6686
|
+
skipNext = false;
|
|
6687
|
+
sawScript = true;
|
|
6688
|
+
} else if (/^-\w*e$/u.test(word)) skipNext = true;
|
|
6689
|
+
else if (!word.startsWith("-")) {
|
|
6690
|
+
if (sawScript) files.push(word);
|
|
6691
|
+
else sawScript = true;
|
|
6556
6692
|
}
|
|
6557
|
-
|
|
6558
|
-
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
6559
|
-
const kind = parsed.data.kind ?? id;
|
|
6560
|
-
if (!CLASSIFIER_KINDS.includes(kind)) return `unknown kind '${kind}'; known kinds are ${CLASSIFIER_KINDS.join(", ")}`;
|
|
6561
|
-
const base = kind === "messages" ? MESSAGES_DEFAULTS : findPreset(kind);
|
|
6562
|
-
const model = parsed.data.model ?? base?.model;
|
|
6563
|
-
if (base === void 0 || model === void 0) return `kind '${kind}' needs a model`;
|
|
6564
|
-
return {
|
|
6565
|
-
protocol: base.protocol ?? "messages",
|
|
6566
|
-
baseURL: parsed.data.baseURL ?? base.baseURL,
|
|
6567
|
-
model,
|
|
6568
|
-
apiKeyEnv: parsed.data.apiKeyEnv ?? base.apiKeyEnv,
|
|
6569
|
-
apiKeyCommand: parsed.data.apiKeyCommand ?? base.apiKeyCommand,
|
|
6570
|
-
reasoning: parsed.data.reasoning ?? base.reasoning,
|
|
6571
|
-
maxTokens: parsed.data.maxTokens ?? base.maxTokens,
|
|
6572
|
-
timeoutMs: parsed.data.timeoutMs ?? base.timeoutMs
|
|
6573
|
-
};
|
|
6693
|
+
return files;
|
|
6574
6694
|
}
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
6582
|
-
const kind = SCOPE_SOURCE_KINDS.find((known) => known === (parsed.data.kind ?? id));
|
|
6583
|
-
if (kind === void 0) return `unknown kind '${parsed.data.kind ?? id}'; known kinds are ${SCOPE_SOURCE_KINDS.join(", ")}`;
|
|
6584
|
-
if (kind === "globs") return parsed.data.paths === void 0 ? "kind 'globs' needs paths" : {
|
|
6585
|
-
kind,
|
|
6586
|
-
paths: parsed.data.paths
|
|
6587
|
-
};
|
|
6588
|
-
return parsed.data.paths === void 0 ? { kind } : `kind '${kind}' takes no paths`;
|
|
6695
|
+
function resolvePath(cwd, path, home) {
|
|
6696
|
+
const expanded = path.replace(/^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/u, home);
|
|
6697
|
+
if (/[$`]|^~/u.test(expanded) || expanded === "") return null;
|
|
6698
|
+
if (cwd === null && !isAbsolute(expanded)) return null;
|
|
6699
|
+
const absolute = isAbsolute(expanded) || cwd === null ? expanded : join(cwd, expanded);
|
|
6700
|
+
return normalize(absolute).replace(/(?<=.)\/$/u, "");
|
|
6589
6701
|
}
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6702
|
+
const SCRATCH_PATHS = [
|
|
6703
|
+
"/tmp",
|
|
6704
|
+
"/dev/null",
|
|
6705
|
+
"/dev/stdout",
|
|
6706
|
+
"/dev/stderr"
|
|
6707
|
+
];
|
|
6708
|
+
function isInScope(path, scope) {
|
|
6709
|
+
if (SCRATCH_PATHS.some((entry) => isUnder(path, entry)) || scope.pathGlobs.some((glob) => matchesGlob(path, glob))) return true;
|
|
6710
|
+
return scope.worktrees.some((worktree) => {
|
|
6711
|
+
const isNested = isUnder(path, join(worktree, ".worktrees"));
|
|
6712
|
+
const isOtherOwned = scope.worktrees.some((other) => other !== worktree && isUnder(path, other));
|
|
6713
|
+
return isUnder(path, worktree) && (!isNested || isOtherOwned);
|
|
6714
|
+
});
|
|
6594
6715
|
}
|
|
6595
|
-
function
|
|
6596
|
-
|
|
6597
|
-
if (entry !== void 0) return entry;
|
|
6598
|
-
if (droppedIDs.includes(id)) throw new Error(`${path}: decision.${role} names '${id}', whose entry was dropped`);
|
|
6599
|
-
if (findPreset(id) === void 0) throw new Error(`${path}: decision.${role} names '${id}', which is neither a classifiers entry nor a built-in kind (${Object.keys(PRESETS).join(", ")})`);
|
|
6600
|
-
return parseBuiltIn(id);
|
|
6716
|
+
function isUnder(path, root) {
|
|
6717
|
+
return path === root || path.startsWith(`${root}/`);
|
|
6601
6718
|
}
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6719
|
+
//#endregion
|
|
6720
|
+
//#region src/containment/check-containment.ts
|
|
6721
|
+
const CONTAINMENT_RULE = "Outside Task Scope";
|
|
6722
|
+
async function checkContainment(request, sources) {
|
|
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
|
+
}
|
|
6733
|
+
const findings = collectScopeFindings({
|
|
6734
|
+
tool: request.toolName,
|
|
6735
|
+
cwd: request.cwd,
|
|
6736
|
+
input: request.toolInput
|
|
6737
|
+
}, scope);
|
|
6738
|
+
if (findings.length === 0) return null;
|
|
6739
|
+
return {
|
|
6740
|
+
rule: CONTAINMENT_RULE,
|
|
6741
|
+
reason: formatContainmentReason(findings, scope),
|
|
6742
|
+
findings
|
|
6743
|
+
};
|
|
6606
6744
|
}
|
|
6607
|
-
|
|
6608
|
-
|
|
6745
|
+
const KIND_LABELS = {
|
|
6746
|
+
path: "path",
|
|
6747
|
+
branch: "branch",
|
|
6748
|
+
"remote-write": "remote target",
|
|
6749
|
+
credential: "credential or global setting",
|
|
6750
|
+
prune: "shared resource"
|
|
6751
|
+
};
|
|
6752
|
+
function formatContainmentReason(findings, scope) {
|
|
6753
|
+
const targets = findings.slice(0, 5).map((finding) => `${KIND_LABELS[finding.kind]} ${finding.target}`).join(", ");
|
|
6754
|
+
const more = findings.length > 5 ? ` and ${findings.length - 5} more` : "";
|
|
6755
|
+
const branches = scope.branches.length === 0 ? "" : ` and ${formatList("branch", "branches", scope.branches)}`;
|
|
6756
|
+
return `This action writes outside the task scope: ${targets}${more}. The task owns ${`${formatList("worktree", "worktrees", scope.worktrees)}${branches}`}. Do the work inside the task's own worktree and branch, or ask the user to extend the task scope to this target.`;
|
|
6609
6757
|
}
|
|
6610
|
-
function
|
|
6611
|
-
|
|
6612
|
-
if (fromEnv !== void 0 && fromEnv !== "") return Promise.resolve(fromEnv);
|
|
6613
|
-
if (provider.apiKeyCommand === void 0 || provider.apiKeyCommand === "") return Promise.resolve(null);
|
|
6614
|
-
return readApiKeyFromCommand(provider.apiKeyCommand, options);
|
|
6758
|
+
function formatList(one, many, values) {
|
|
6759
|
+
return `the ${values.length === 1 ? one : many} ${values.join(", ")}`;
|
|
6615
6760
|
}
|
|
6616
6761
|
//#endregion
|
|
6617
6762
|
//#region src/policy/resolve-shipped-policy-dir.ts
|
|
@@ -7743,7 +7888,7 @@ async function classifyAction(request, config, options = {}) {
|
|
|
7743
7888
|
}
|
|
7744
7889
|
const local = configured !== null && (configured.hard_deny.length > 0 || configured.soft_deny.length > 0) ? { kind: "escalate" } : classifyLocally(request);
|
|
7745
7890
|
if (local.kind === "allow" && local.exception === READ_ONLY_EXCEPTION) return buildLocalAllow(local.exception);
|
|
7746
|
-
const containment = await checkContainment(request);
|
|
7891
|
+
const containment = await checkContainment(request, config.scopeSources ?? DEFAULT_SCOPE_SOURCES);
|
|
7747
7892
|
if (containment !== null) {
|
|
7748
7893
|
const guidance = await tryReadDenialGuidance();
|
|
7749
7894
|
return {
|
|
@@ -7850,4 +7995,4 @@ function renderVerdict(verdict) {
|
|
|
7850
7995
|
})).exhaustive();
|
|
7851
7996
|
}
|
|
7852
7997
|
//#endregion
|
|
7853
|
-
export { classifyWithModel as a,
|
|
7998
|
+
export { resolveConfigPath as C, string as D, strictObject as E, resolveApiKey as S, number as T, loadRepositoryContext as _, classifyWithModel as a, DEFAULT_SCOPE_SOURCES as b, splitShellCommand as c, resolveSessionScopePath as d, loadSessionScope as f, toRepositorySlug as g, findCheckout as h, classifyLocally as i, splitCommandWords as l, loadCheckoutRemotes as m, parseActionRequest as n, parseModelVerdict as o, EMPTY_SESSION_SCOPE as p, classifyAction as r, loadPolicy as s, renderVerdict as t, resolveStateDir as u, DEFAULT_CONFIG as v, PRESETS as w, loadConfig as x, DEFAULT_DENIAL_BUDGET as y };
|