auto-mode 0.10.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.
- package/dist/classify-edit-D8GC6b91.js +17848 -0
- package/dist/cli.js +320 -10
- package/dist/get-edit-fields-BtAvHIsq.js +20 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{render-verdict-1xtCI2is.js → render-verdict-FhSKxlO9.js} +804 -602
- 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 +2 -1
|
@@ -1,8 +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, normalize, parse, relative, resolve } from "node:path";
|
|
4
5
|
import { spawn } from "node:child_process";
|
|
5
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
|
|
6
20
|
//#region node_modules/.bun/zod@4.6.2/node_modules/zod/v4/core/util.js
|
|
7
21
|
function getEnumValues(entries) {
|
|
8
22
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
@@ -2922,7 +2936,7 @@ function bucketFor(state, inst) {
|
|
|
2922
2936
|
return bucket;
|
|
2923
2937
|
}
|
|
2924
2938
|
let handoff;
|
|
2925
|
-
const open = [];
|
|
2939
|
+
const open$1 = [];
|
|
2926
2940
|
const memo = {
|
|
2927
2941
|
alloc(_inst, payload, empty) {
|
|
2928
2942
|
const bucket = handoff;
|
|
@@ -2933,7 +2947,7 @@ const memo = {
|
|
|
2933
2947
|
issues: null
|
|
2934
2948
|
};
|
|
2935
2949
|
bucket.set(payload.value, entry);
|
|
2936
|
-
open.push(entry);
|
|
2950
|
+
open$1.push(entry);
|
|
2937
2951
|
return empty;
|
|
2938
2952
|
},
|
|
2939
2953
|
guard(inst) {
|
|
@@ -2999,10 +3013,10 @@ const memo = {
|
|
|
2999
3013
|
return payload;
|
|
3000
3014
|
}
|
|
3001
3015
|
handoff = bucket;
|
|
3002
|
-
const depth = open.length;
|
|
3016
|
+
const depth = open$1.length;
|
|
3003
3017
|
const result = base(payload, ctx);
|
|
3004
3018
|
handoff = void 0;
|
|
3005
|
-
const entry = open.length > depth ? open.pop() : void 0;
|
|
3019
|
+
const entry = open$1.length > depth ? open$1.pop() : void 0;
|
|
3006
3020
|
if (result instanceof Promise) return result.then((r) => {
|
|
3007
3021
|
if (entry) entry.issues = r.issues.length ? cloneIssues(r.issues) : NO_ISSUES;
|
|
3008
3022
|
return r;
|
|
@@ -5526,79 +5540,414 @@ function refine(fn, _params = {}) {
|
|
|
5526
5540
|
function superRefine(fn, params) {
|
|
5527
5541
|
return /* @__PURE__ */ _superRefine(fn, params);
|
|
5528
5542
|
}
|
|
5543
|
+
const PRESETS = {
|
|
5544
|
+
jev: {
|
|
5545
|
+
protocol: "system-one",
|
|
5546
|
+
baseURL: "https://api.typesafe.ai",
|
|
5547
|
+
model: "jev-1.13.0",
|
|
5548
|
+
apiKeyEnv: "TYPESAFE_API_KEY",
|
|
5549
|
+
reasoning: false,
|
|
5550
|
+
maxTokens: 3e3,
|
|
5551
|
+
timeoutMs: 5e3
|
|
5552
|
+
},
|
|
5553
|
+
spark: {
|
|
5554
|
+
baseURL: "https://api.meta.ai",
|
|
5555
|
+
model: "muse-spark-1.3-contributor",
|
|
5556
|
+
apiKeyEnv: "META_API_KEY",
|
|
5557
|
+
reasoning: true,
|
|
5558
|
+
maxTokens: 3e3,
|
|
5559
|
+
timeoutMs: 45e3
|
|
5560
|
+
},
|
|
5561
|
+
claude: {
|
|
5562
|
+
baseURL: "https://api.anthropic.com",
|
|
5563
|
+
model: "claude-haiku-4-5-20251001",
|
|
5564
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
5565
|
+
reasoning: true,
|
|
5566
|
+
maxTokens: 3e3,
|
|
5567
|
+
timeoutMs: 45e3
|
|
5568
|
+
},
|
|
5569
|
+
glm: {
|
|
5570
|
+
baseURL: "https://api.z.ai/api/anthropic",
|
|
5571
|
+
model: "glm-5.3-flash",
|
|
5572
|
+
apiKeyEnv: "ZAI_API_KEY",
|
|
5573
|
+
reasoning: true,
|
|
5574
|
+
maxTokens: 3e3,
|
|
5575
|
+
timeoutMs: 6e4
|
|
5576
|
+
}
|
|
5577
|
+
};
|
|
5578
|
+
const MESSAGES_DEFAULTS = {
|
|
5579
|
+
protocol: "messages",
|
|
5580
|
+
baseURL: "https://api.anthropic.com",
|
|
5581
|
+
reasoning: true,
|
|
5582
|
+
maxTokens: 3e3,
|
|
5583
|
+
timeoutMs: 45e3
|
|
5584
|
+
};
|
|
5585
|
+
function findPreset(id) {
|
|
5586
|
+
return Object.hasOwn(PRESETS, id) ? PRESETS[id] : void 0;
|
|
5587
|
+
}
|
|
5529
5588
|
//#endregion
|
|
5530
|
-
//#region src/config/
|
|
5531
|
-
|
|
5532
|
-
const
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5589
|
+
//#region src/config/read-api-key-from-command.ts
|
|
5590
|
+
function readApiKeyFromCommand(command, options = {}) {
|
|
5591
|
+
const remainingMs = options.deadlineAt === void 0 ? 5e3 : options.deadlineAt - Date.now();
|
|
5592
|
+
if (remainingMs <= 0 || options.signal?.aborted === true) return Promise.resolve(null);
|
|
5593
|
+
return new Promise((resolve) => {
|
|
5594
|
+
const grouped = process.platform !== "win32";
|
|
5595
|
+
const child = spawn("/bin/sh", ["-c", command], {
|
|
5596
|
+
detached: grouped,
|
|
5597
|
+
stdio: [
|
|
5598
|
+
"ignore",
|
|
5599
|
+
"pipe",
|
|
5600
|
+
"ignore"
|
|
5601
|
+
]
|
|
5602
|
+
});
|
|
5603
|
+
let output = "";
|
|
5604
|
+
let settled = false;
|
|
5605
|
+
let stopped = false;
|
|
5606
|
+
const setResult = (value) => {
|
|
5607
|
+
if (!settled) {
|
|
5608
|
+
settled = true;
|
|
5609
|
+
clearTimeout(timer);
|
|
5610
|
+
options.signal?.removeEventListener("abort", stopChild);
|
|
5611
|
+
resolve(value);
|
|
5612
|
+
}
|
|
5613
|
+
};
|
|
5614
|
+
const stopChild = () => {
|
|
5615
|
+
if (stopped) return;
|
|
5616
|
+
stopped = true;
|
|
5617
|
+
try {
|
|
5618
|
+
if (grouped && child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
5619
|
+
else child.kill("SIGKILL");
|
|
5620
|
+
} catch {
|
|
5621
|
+
child.kill("SIGKILL");
|
|
5622
|
+
}
|
|
5623
|
+
};
|
|
5624
|
+
const timer = setTimeout(stopChild, Math.min(5e3, remainingMs));
|
|
5625
|
+
options.signal?.addEventListener("abort", stopChild, { once: true });
|
|
5626
|
+
child.stdout.setEncoding("utf8");
|
|
5627
|
+
child.stdout.on("data", (chunk) => {
|
|
5628
|
+
if (stopped) return;
|
|
5629
|
+
output += chunk;
|
|
5630
|
+
if (output.length > 65536) stopChild();
|
|
5631
|
+
});
|
|
5632
|
+
child.on("error", () => {
|
|
5633
|
+
setResult(null);
|
|
5634
|
+
});
|
|
5635
|
+
child.on("close", (code) => {
|
|
5636
|
+
const key = !stopped && code === 0 && output.trim() !== "" ? output.trim() : null;
|
|
5637
|
+
setResult(key);
|
|
5638
|
+
});
|
|
5639
|
+
});
|
|
5640
|
+
}
|
|
5641
|
+
//#endregion
|
|
5642
|
+
//#region src/config/config.ts
|
|
5643
|
+
const SCOPE_SOURCE_KINDS = [
|
|
5644
|
+
"cwd",
|
|
5645
|
+
"session",
|
|
5646
|
+
"globs",
|
|
5647
|
+
"atc"
|
|
5648
|
+
];
|
|
5649
|
+
const DEFAULT_PROVIDER = findPreset("jev");
|
|
5650
|
+
if (DEFAULT_PROVIDER === void 0) throw new Error("the jev preset is missing");
|
|
5651
|
+
const DEFAULT_DENIAL_BUDGET = {
|
|
5652
|
+
consecutive: 3,
|
|
5653
|
+
perSession: 20
|
|
5654
|
+
};
|
|
5655
|
+
const DEFAULT_SCOPE_SOURCES = {
|
|
5656
|
+
cwd: { kind: "cwd" },
|
|
5657
|
+
session: { kind: "session" }
|
|
5658
|
+
};
|
|
5659
|
+
const DEFAULT_CONFIG = {
|
|
5660
|
+
provider: DEFAULT_PROVIDER,
|
|
5661
|
+
onFailure: "defer",
|
|
5662
|
+
minConfidence: .8,
|
|
5663
|
+
denialBudget: DEFAULT_DENIAL_BUDGET,
|
|
5664
|
+
scopeSources: DEFAULT_SCOPE_SOURCES
|
|
5665
|
+
};
|
|
5666
|
+
function resolveConfigPath() {
|
|
5667
|
+
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
5668
|
+
const base = xdg !== void 0 && xdg !== "" ? xdg : join(homedir(), ".config");
|
|
5669
|
+
return join(base, "auto-mode", "config.json");
|
|
5670
|
+
}
|
|
5671
|
+
async function loadConfig(path = resolveConfigPath()) {
|
|
5542
5672
|
let raw;
|
|
5543
5673
|
try {
|
|
5544
|
-
raw = await readFile(
|
|
5674
|
+
raw = await readFile(path, "utf8");
|
|
5545
5675
|
} catch (error) {
|
|
5546
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return
|
|
5547
|
-
throw new Error("
|
|
5676
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return DEFAULT_CONFIG;
|
|
5677
|
+
throw new Error("auto-mode configuration unreadable", { cause: error });
|
|
5548
5678
|
}
|
|
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;
|
|
5679
|
+
let json;
|
|
5557
5680
|
try {
|
|
5558
|
-
|
|
5681
|
+
json = JSON.parse(raw);
|
|
5559
5682
|
} catch {
|
|
5560
|
-
throw new Error(
|
|
5683
|
+
throw new Error(`${path} is not valid JSON`);
|
|
5561
5684
|
}
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5685
|
+
return buildConfig(json, path);
|
|
5686
|
+
}
|
|
5687
|
+
const text = string().min(1);
|
|
5688
|
+
const positive = number().positive();
|
|
5689
|
+
const denialBudgetSchema = strictObject({
|
|
5690
|
+
consecutive: number().int().positive().optional(),
|
|
5691
|
+
perSession: number().int().positive().optional()
|
|
5692
|
+
});
|
|
5693
|
+
const configFileSchema = strictObject({
|
|
5694
|
+
classifiers: record(text, unknown()).optional(),
|
|
5695
|
+
scopeSources: record(text, unknown()).optional(),
|
|
5696
|
+
decision: strictObject({
|
|
5697
|
+
classifier: text.optional(),
|
|
5698
|
+
judge: text.nullable().optional(),
|
|
5699
|
+
minConfidence: number().min(.5).max(1).optional(),
|
|
5700
|
+
onFailure: _enum(["defer", "deny"]).optional(),
|
|
5701
|
+
denialBudget: denialBudgetSchema.optional()
|
|
5702
|
+
}).optional(),
|
|
5703
|
+
policy: strictObject({
|
|
5704
|
+
rulesPath: text.nullable().optional(),
|
|
5705
|
+
frameworkPath: text.nullable().optional(),
|
|
5706
|
+
claudeSettingsPath: text.nullable().optional()
|
|
5707
|
+
}).optional()
|
|
5708
|
+
});
|
|
5709
|
+
function buildConfig(json, path) {
|
|
5710
|
+
const parsed = configFileSchema.safeParse(json);
|
|
5711
|
+
if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
|
|
5712
|
+
const file = parsed.data;
|
|
5713
|
+
const warnings = [];
|
|
5714
|
+
const classifiers = buildRegistry(file.classifiers ?? {}, (id, entry) => parseClassifierEntry(id, entry));
|
|
5715
|
+
const scopeSources = buildRegistry(file.scopeSources ?? {}, (id, entry) => parseScopeSourceEntry(id, entry));
|
|
5716
|
+
const configuredSources = file.scopeSources === void 0 ? null : scopeSources.entries;
|
|
5717
|
+
warnings.push(...classifiers.dropped.map((line) => `${path}: classifiers.${line}`), ...scopeSources.dropped.map((line) => `${path}: scopeSources.${line}`), ...collectScopeWarnings(configuredSources).map((line) => `${path}: scopeSources ${line}`));
|
|
5718
|
+
const decision = file.decision ?? {};
|
|
5719
|
+
const policy = file.policy ?? {};
|
|
5720
|
+
const judgeID = decision.judge ?? null;
|
|
5565
5721
|
return {
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5722
|
+
provider: resolveRole("classifier", decision.classifier ?? "jev", classifiers.entries, classifiers.droppedIDs, path),
|
|
5723
|
+
judge: judgeID === null ? null : resolveRole("judge", judgeID, classifiers.entries, classifiers.droppedIDs, path),
|
|
5724
|
+
scopeSources: configuredSources ?? DEFAULT_SCOPE_SOURCES,
|
|
5725
|
+
classifierPath: expandHomePath(policy.frameworkPath ?? void 0),
|
|
5726
|
+
rulesPath: expandHomePath(policy.rulesPath ?? void 0),
|
|
5727
|
+
onFailure: decision.onFailure ?? DEFAULT_CONFIG.onFailure,
|
|
5728
|
+
claudeSettingsPath: policy.claudeSettingsPath === null ? null : expandHomePath(policy.claudeSettingsPath),
|
|
5729
|
+
minConfidence: decision.minConfidence ?? DEFAULT_CONFIG.minConfidence,
|
|
5730
|
+
denialBudget: {
|
|
5731
|
+
consecutive: decision.denialBudget?.consecutive ?? DEFAULT_DENIAL_BUDGET.consecutive,
|
|
5732
|
+
perSession: decision.denialBudget?.perSession ?? DEFAULT_DENIAL_BUDGET.perSession
|
|
5733
|
+
},
|
|
5734
|
+
warnings
|
|
5570
5735
|
};
|
|
5571
5736
|
}
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
const
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5737
|
+
function collectScopeWarnings(entries) {
|
|
5738
|
+
if (entries === null) return [];
|
|
5739
|
+
const sources = Object.values(entries);
|
|
5740
|
+
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`)];
|
|
5741
|
+
}
|
|
5742
|
+
function buildRegistry(raw, parseEntry) {
|
|
5743
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5744
|
+
const dropped = [];
|
|
5745
|
+
const droppedIDs = [];
|
|
5746
|
+
for (const [id, entry] of Object.entries(raw)) {
|
|
5747
|
+
const result = parseEntry(id, entry);
|
|
5748
|
+
if (typeof result === "string") {
|
|
5749
|
+
dropped.push(`${id} dropped: ${result}`);
|
|
5750
|
+
droppedIDs.push(id);
|
|
5751
|
+
} else entries.set(id, result);
|
|
5752
|
+
}
|
|
5753
|
+
return {
|
|
5754
|
+
entries: Object.fromEntries(entries),
|
|
5755
|
+
dropped,
|
|
5756
|
+
droppedIDs
|
|
5584
5757
|
};
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5758
|
+
}
|
|
5759
|
+
const CLASSIFIER_KINDS = [...Object.keys(PRESETS), "messages"];
|
|
5760
|
+
const classifierEntrySchema = strictObject({
|
|
5761
|
+
kind: text.optional(),
|
|
5762
|
+
baseURL: text.optional(),
|
|
5763
|
+
model: text.optional(),
|
|
5764
|
+
apiKeyEnv: text.optional(),
|
|
5765
|
+
apiKeyCommand: text.optional(),
|
|
5766
|
+
reasoning: boolean().optional(),
|
|
5767
|
+
maxTokens: positive.optional(),
|
|
5768
|
+
timeoutMs: positive.optional()
|
|
5769
|
+
});
|
|
5770
|
+
const CREDENTIAL_KEYS = /* @__PURE__ */ new Set([
|
|
5771
|
+
"apiKey",
|
|
5772
|
+
"key",
|
|
5773
|
+
"token",
|
|
5774
|
+
"secret"
|
|
5775
|
+
]);
|
|
5776
|
+
function parseClassifierEntry(id, entry) {
|
|
5777
|
+
if (typeof entry === "object" && entry !== null) {
|
|
5778
|
+
const credential = Object.keys(entry).find((key) => CREDENTIAL_KEYS.has(key));
|
|
5779
|
+
if (credential !== void 0) return `holds a literal ${credential}; name the key with apiKeyEnv or apiKeyCommand`;
|
|
5780
|
+
}
|
|
5781
|
+
const parsed = classifierEntrySchema.safeParse(entry);
|
|
5782
|
+
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
5783
|
+
const kind = parsed.data.kind ?? id;
|
|
5784
|
+
if (!CLASSIFIER_KINDS.includes(kind)) return `unknown kind '${kind}'; known kinds are ${CLASSIFIER_KINDS.join(", ")}`;
|
|
5785
|
+
const base = kind === "messages" ? MESSAGES_DEFAULTS : findPreset(kind);
|
|
5786
|
+
const model = parsed.data.model ?? base?.model;
|
|
5787
|
+
if (base === void 0 || model === void 0) return `kind '${kind}' needs a model`;
|
|
5788
|
+
return {
|
|
5789
|
+
protocol: base.protocol ?? "messages",
|
|
5790
|
+
baseURL: parsed.data.baseURL ?? base.baseURL,
|
|
5791
|
+
model,
|
|
5792
|
+
apiKeyEnv: parsed.data.apiKeyEnv ?? base.apiKeyEnv,
|
|
5793
|
+
apiKeyCommand: parsed.data.apiKeyCommand ?? base.apiKeyCommand,
|
|
5794
|
+
reasoning: parsed.data.reasoning ?? base.reasoning,
|
|
5795
|
+
maxTokens: parsed.data.maxTokens ?? base.maxTokens,
|
|
5796
|
+
timeoutMs: parsed.data.timeoutMs ?? base.timeoutMs
|
|
5797
|
+
};
|
|
5798
|
+
}
|
|
5799
|
+
const scopeSourceEntrySchema = strictObject({
|
|
5800
|
+
kind: text.optional(),
|
|
5801
|
+
paths: array(text).min(1).optional()
|
|
5802
|
+
});
|
|
5803
|
+
function parseScopeSourceEntry(id, entry) {
|
|
5804
|
+
const parsed = scopeSourceEntrySchema.safeParse(entry);
|
|
5805
|
+
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
5806
|
+
const kind = SCOPE_SOURCE_KINDS.find((known) => known === (parsed.data.kind ?? id));
|
|
5807
|
+
if (kind === void 0) return `unknown kind '${parsed.data.kind ?? id}'; known kinds are ${SCOPE_SOURCE_KINDS.join(", ")}`;
|
|
5808
|
+
if (kind === "globs") return parsed.data.paths === void 0 ? "kind 'globs' needs paths" : {
|
|
5809
|
+
kind,
|
|
5810
|
+
paths: parsed.data.paths.map((glob) => expandHomePath(glob) ?? glob)
|
|
5811
|
+
};
|
|
5812
|
+
return parsed.data.paths === void 0 ? { kind } : `kind '${kind}' takes no paths`;
|
|
5813
|
+
}
|
|
5814
|
+
function formatFirstIssue(issues) {
|
|
5815
|
+
const [issue] = issues;
|
|
5816
|
+
if (issue === void 0) return "invalid entry";
|
|
5817
|
+
return issue.path.length === 0 ? issue.message : `${issue.path.map(String).join(".")}: ${issue.message}`;
|
|
5818
|
+
}
|
|
5819
|
+
function resolveRole(role, id, entries, droppedIDs, path) {
|
|
5820
|
+
const entry = Object.hasOwn(entries, id) ? entries[id] : void 0;
|
|
5821
|
+
if (entry !== void 0) return entry;
|
|
5822
|
+
if (droppedIDs.includes(id)) throw new Error(`${path}: decision.${role} names '${id}', whose entry was dropped`);
|
|
5823
|
+
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(", ")})`);
|
|
5824
|
+
return parseBuiltIn(id);
|
|
5825
|
+
}
|
|
5826
|
+
function parseBuiltIn(id) {
|
|
5827
|
+
const resolved = parseClassifierEntry(id, {});
|
|
5828
|
+
if (typeof resolved === "string") throw new TypeError(`built-in kind ${id} does not resolve: ${resolved}`);
|
|
5829
|
+
return resolved;
|
|
5830
|
+
}
|
|
5831
|
+
function expandHomePath(path) {
|
|
5832
|
+
return path?.startsWith("~/") === true ? join(homedir(), path.slice(2)) : path;
|
|
5833
|
+
}
|
|
5834
|
+
function resolveApiKey(provider, options = {}) {
|
|
5835
|
+
const fromEnv = provider.apiKeyEnv === void 0 ? void 0 : process.env[provider.apiKeyEnv];
|
|
5836
|
+
if (fromEnv !== void 0 && fromEnv !== "") return Promise.resolve(fromEnv);
|
|
5837
|
+
if (provider.apiKeyCommand === void 0 || provider.apiKeyCommand === "") return Promise.resolve(null);
|
|
5838
|
+
return readApiKeyFromCommand(provider.apiKeyCommand, options);
|
|
5839
|
+
}
|
|
5840
|
+
//#endregion
|
|
5841
|
+
//#region src/config/load-claude-rules.ts
|
|
5842
|
+
async function loadClaudeRules(path) {
|
|
5843
|
+
const empty = {
|
|
5844
|
+
environment: [],
|
|
5845
|
+
allow: [],
|
|
5846
|
+
soft_deny: [],
|
|
5847
|
+
hard_deny: []
|
|
5848
|
+
};
|
|
5849
|
+
if (path === null) return empty;
|
|
5850
|
+
const configDir = process.env["CLAUDE_CONFIG_DIR"];
|
|
5851
|
+
const base = configDir === void 0 || configDir === "" ? join(homedir(), ".claude") : configDir;
|
|
5852
|
+
const settingsPath = path ?? join(base, "settings.json");
|
|
5853
|
+
let raw;
|
|
5854
|
+
try {
|
|
5855
|
+
raw = await readFile(settingsPath, "utf8");
|
|
5856
|
+
} catch (error) {
|
|
5857
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return empty;
|
|
5858
|
+
throw new Error("Claude settings unreadable", { cause: error });
|
|
5859
|
+
}
|
|
5860
|
+
const entries = array(string()).optional();
|
|
5861
|
+
const schema = object({ autoMode: object({
|
|
5862
|
+
environment: entries,
|
|
5863
|
+
allow: entries,
|
|
5864
|
+
soft_deny: entries,
|
|
5865
|
+
hard_deny: entries
|
|
5866
|
+
}).optional() });
|
|
5867
|
+
let body;
|
|
5868
|
+
try {
|
|
5869
|
+
body = JSON.parse(raw);
|
|
5870
|
+
} catch {
|
|
5871
|
+
throw new Error("Claude settings contain invalid JSON");
|
|
5872
|
+
}
|
|
5873
|
+
const parsed = schema.safeParse(body);
|
|
5874
|
+
if (!parsed.success) throw new Error("Claude autoMode settings are invalid");
|
|
5875
|
+
const rules = parsed.data;
|
|
5876
|
+
return {
|
|
5877
|
+
environment: (rules.autoMode?.environment ?? []).filter((entry) => entry !== "$defaults"),
|
|
5878
|
+
allow: (rules.autoMode?.allow ?? []).filter((entry) => entry !== "$defaults"),
|
|
5879
|
+
soft_deny: (rules.autoMode?.soft_deny ?? []).filter((entry) => entry !== "$defaults"),
|
|
5880
|
+
hard_deny: (rules.autoMode?.hard_deny ?? []).filter((entry) => entry !== "$defaults")
|
|
5881
|
+
};
|
|
5882
|
+
}
|
|
5883
|
+
//#endregion
|
|
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;
|
|
5941
|
+
continue;
|
|
5942
|
+
}
|
|
5943
|
+
if (ch === "'" || ch === "\"") {
|
|
5944
|
+
quote = ch;
|
|
5945
|
+
current += ch;
|
|
5946
|
+
i += 1;
|
|
5947
|
+
continue;
|
|
5948
|
+
}
|
|
5949
|
+
if (ch === "\\") {
|
|
5950
|
+
current += command.slice(i, i + 2);
|
|
5602
5951
|
i += 2;
|
|
5603
5952
|
continue;
|
|
5604
5953
|
}
|
|
@@ -5628,6 +5977,14 @@ function splitShellCommand(command) {
|
|
|
5628
5977
|
};
|
|
5629
5978
|
}
|
|
5630
5979
|
//#endregion
|
|
5980
|
+
//#region src/containment/to-repository-slug.ts
|
|
5981
|
+
function toRepositorySlug(reference) {
|
|
5982
|
+
const match = /^(?:[a-z+]+:\/\/)?(?:[^@/]+@)?(?<host>[^/:]+)[:/](?<owner>[^/]+)\/(?<name>[^/]+?)(?:\.git)?\/?$/iu.exec(reference);
|
|
5983
|
+
if (match?.groups === void 0) return null;
|
|
5984
|
+
const parts = match.groups;
|
|
5985
|
+
return `${parts["host"] ?? ""}/${parts["owner"] ?? ""}/${parts["name"] ?? ""}`.toLowerCase();
|
|
5986
|
+
}
|
|
5987
|
+
//#endregion
|
|
5631
5988
|
//#region src/containment/collect-scope-findings.ts
|
|
5632
5989
|
function collectScopeFindings(action, scope) {
|
|
5633
5990
|
const filePath = action.input["file_path"] ?? action.input["notebook_path"];
|
|
@@ -5672,7 +6029,10 @@ function collectToolFindings(action, scope) {
|
|
|
5672
6029
|
const verb = action.tool.split("__").at(-1) ?? "";
|
|
5673
6030
|
if (!/merge|create|comment|update|delete|push|close|review/u.test(verb)) return [];
|
|
5674
6031
|
const number = action.input["pullNumber"] ?? action.input["pull_number"] ?? action.input["issue_number"];
|
|
5675
|
-
|
|
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) ? [] : [{
|
|
5676
6036
|
kind: "remote-write",
|
|
5677
6037
|
target: action.tool
|
|
5678
6038
|
}];
|
|
@@ -5696,7 +6056,7 @@ function collectCommandFindings(command, cwd, scope) {
|
|
|
5696
6056
|
const hasPipe = /(?<!\|)\|(?!\|)/u.test(command);
|
|
5697
6057
|
let directory = cwd;
|
|
5698
6058
|
for (const segment of splitShellCommand(command).segments) {
|
|
5699
|
-
const [name, ...args] =
|
|
6059
|
+
const [name, ...args] = splitCommandWords(segment.text);
|
|
5700
6060
|
for (const target of collectRedirectTargets(segment.text)) findings.push(...collectTargetFindings(directory, target, scope));
|
|
5701
6061
|
if (name === void 0) continue;
|
|
5702
6062
|
if (name === "cd" && hasPipe) directory = null;
|
|
@@ -5729,6 +6089,11 @@ function collectProgramFindings(name, args, directory, cwd, scope) {
|
|
|
5729
6089
|
return resolve(destination);
|
|
5730
6090
|
}
|
|
5731
6091
|
if (name === "curl") return collectCurlFindings(args);
|
|
6092
|
+
const launcher = [name, ...args].findIndex((word) => word.includes("auto-mode"));
|
|
6093
|
+
if (launcher !== -1 && [name, ...args].slice(launcher + 1).includes("record")) return [{
|
|
6094
|
+
kind: "credential",
|
|
6095
|
+
target: "auto-mode record"
|
|
6096
|
+
}];
|
|
5732
6097
|
const finding = findProgramFinding(name, args);
|
|
5733
6098
|
return finding === null ? [] : [finding];
|
|
5734
6099
|
}
|
|
@@ -5903,12 +6268,6 @@ function isOwnedRemote(remote, scope) {
|
|
|
5903
6268
|
const slug = toRepositorySlug(remote);
|
|
5904
6269
|
return slug !== null && scope.remotes.some((entry) => toRepositorySlug(entry.url) === slug);
|
|
5905
6270
|
}
|
|
5906
|
-
function toRepositorySlug(reference) {
|
|
5907
|
-
const match = /^(?:[a-z+]+:\/\/)?(?:[^@/]+@)?(?<host>[^/:]+)[:/](?<owner>[^/]+)\/(?<name>[^/]+?)(?:\.git)?\/?$/iu.exec(reference);
|
|
5908
|
-
if (match?.groups === void 0) return null;
|
|
5909
|
-
const parts = match.groups;
|
|
5910
|
-
return `${parts["host"] ?? ""}/${parts["owner"] ?? ""}/${parts["name"] ?? ""}`.toLowerCase();
|
|
5911
|
-
}
|
|
5912
6271
|
const GH_PR_WRITES = /* @__PURE__ */ new Set([
|
|
5913
6272
|
"comment",
|
|
5914
6273
|
"merge",
|
|
@@ -5947,7 +6306,7 @@ function collectGhFindings(args, scope) {
|
|
|
5947
6306
|
const number = Number(args.slice(2).find((word) => /^\d+$/u.test(word)));
|
|
5948
6307
|
const repoIndex = args.findIndex((word) => word === "--repo" || word === "-R");
|
|
5949
6308
|
const repo = repoIndex === -1 ? void 0 : args[repoIndex + 1];
|
|
5950
|
-
return verb !== "merge" &&
|
|
6309
|
+
return verb !== "merge" && isOwnedPullRequest(number, repo, scope) ? [] : [{
|
|
5951
6310
|
kind: "remote-write",
|
|
5952
6311
|
target: `gh pr ${verb}`
|
|
5953
6312
|
}];
|
|
@@ -5975,12 +6334,10 @@ function collectGhFindings(args, scope) {
|
|
|
5975
6334
|
}];
|
|
5976
6335
|
return [];
|
|
5977
6336
|
}
|
|
5978
|
-
function
|
|
5979
|
-
const wanted = repo
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
return slug !== null && (slug === wanted || slug.endsWith(`/${wanted}`));
|
|
5983
|
-
});
|
|
6337
|
+
function isOwnedPullRequest(number, repo, scope) {
|
|
6338
|
+
const wanted = repo?.toLowerCase().replace(/\.git$/u, "");
|
|
6339
|
+
const slugs = new Set(scope.remotes.map((remote) => toRepositorySlug(remote.url)));
|
|
6340
|
+
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}`)));
|
|
5984
6341
|
}
|
|
5985
6342
|
function collectGhAPIFindings(args, scope) {
|
|
5986
6343
|
const endpoint = args.find((word) => !word.startsWith("-")) ?? "";
|
|
@@ -5993,7 +6350,7 @@ function collectGhAPIFindings(args, scope) {
|
|
|
5993
6350
|
const hasFields = args.some((word) => /^(?:-f|-F|--field|--raw-field|--input)$/u.test(word));
|
|
5994
6351
|
if (method === "GET" || method === null && !hasFields) return [];
|
|
5995
6352
|
const pull = /^\/?repos\/(?<repo>[^/]+\/[^/]+)\/(?:pulls|issues)\/(?<number>\d+)(?:\/|$)/u.exec(endpoint)?.groups;
|
|
5996
|
-
if (pull !== void 0 &&
|
|
6353
|
+
if (pull !== void 0 && isOwnedPullRequest(Number(pull["number"]), pull["repo"] ?? "", scope) && !endpoint.includes("/merge")) return [];
|
|
5997
6354
|
return [{
|
|
5998
6355
|
kind: /secret|collaborator|protection|key|permission/u.test(endpoint) ? "credential" : "remote-write",
|
|
5999
6356
|
target: `gh api ${endpoint}`
|
|
@@ -6090,528 +6447,64 @@ function collectInPlaceFiles(args) {
|
|
|
6090
6447
|
else if (!word.startsWith("-")) {
|
|
6091
6448
|
if (sawScript) files.push(word);
|
|
6092
6449
|
else sawScript = true;
|
|
6093
|
-
}
|
|
6094
|
-
return files;
|
|
6095
|
-
}
|
|
6096
|
-
function getCommandWords(words) {
|
|
6097
|
-
let index = 0;
|
|
6098
|
-
while (index < words.length) {
|
|
6099
|
-
const word = words[index] ?? "";
|
|
6100
|
-
if (/^\w+=/u.test(word) || word === "sudo" || word === "command") index += 1;
|
|
6101
|
-
else if (word === "env") {
|
|
6102
|
-
index += 1;
|
|
6103
|
-
while (words[index] === "-u" || /^\w+=/u.test(words[index] ?? "")) index += words[index] === "-u" ? 2 : 1;
|
|
6104
|
-
} else if (word === "timeout") index += 2;
|
|
6105
|
-
else break;
|
|
6106
|
-
}
|
|
6107
|
-
return words.slice(index).filter((word, position, all) => !/^\d*[<>]/u.test(word) && !/^\d*>{1,2}\|?$/u.test(all[position - 1] ?? ""));
|
|
6108
|
-
}
|
|
6109
|
-
function splitWords$1(text) {
|
|
6110
|
-
const words = [];
|
|
6111
|
-
let current = "";
|
|
6112
|
-
let quote = null;
|
|
6113
|
-
let started = false;
|
|
6114
|
-
for (const ch of text) if (quote !== null) {
|
|
6115
|
-
if (ch === quote) quote = null;
|
|
6116
|
-
else current += ch;
|
|
6117
|
-
} else if (ch === "'" || ch === "\"") {
|
|
6118
|
-
quote = ch;
|
|
6119
|
-
started = true;
|
|
6120
|
-
} else if (/\s/u.test(ch)) {
|
|
6121
|
-
if (started) words.push(current);
|
|
6122
|
-
current = "";
|
|
6123
|
-
started = false;
|
|
6124
|
-
} else {
|
|
6125
|
-
current += ch;
|
|
6126
|
-
started = true;
|
|
6127
|
-
}
|
|
6128
|
-
if (started) words.push(current);
|
|
6129
|
-
return words;
|
|
6130
|
-
}
|
|
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
|
-
//#endregion
|
|
6156
|
-
//#region src/model/load-repository-context.ts
|
|
6157
|
-
async function loadRepositoryContext(cwd) {
|
|
6158
|
-
if ([
|
|
6159
|
-
"GIT_DIR",
|
|
6160
|
-
"GIT_WORK_TREE",
|
|
6161
|
-
"GIT_COMMON_DIR"
|
|
6162
|
-
].some((name) => process.env[name] !== void 0)) return null;
|
|
6163
|
-
let directory = resolve(cwd);
|
|
6164
|
-
for (;;) {
|
|
6165
|
-
const gitPath = join(directory, ".git");
|
|
6166
|
-
try {
|
|
6167
|
-
await stat(gitPath);
|
|
6168
|
-
} catch (error) {
|
|
6169
|
-
if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) return null;
|
|
6170
|
-
const parent = dirname(directory);
|
|
6171
|
-
if (parent === directory) return null;
|
|
6172
|
-
directory = parent;
|
|
6173
|
-
continue;
|
|
6174
|
-
}
|
|
6175
|
-
let gitDir = gitPath;
|
|
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;
|
|
6196
|
-
}
|
|
6197
|
-
let defaultBranch = null;
|
|
6198
|
-
try {
|
|
6199
|
-
defaultBranch = parseReference(await readFile(join(commonDir, "refs", "remotes", "origin", "HEAD"), "utf8"), "refs/remotes/origin/");
|
|
6200
|
-
} catch {
|
|
6201
|
-
defaultBranch = null;
|
|
6202
|
-
}
|
|
6203
|
-
return {
|
|
6204
|
-
cwd: resolve(cwd),
|
|
6205
|
-
branch: parseReference(head, "refs/heads/"),
|
|
6206
|
-
defaultBranch
|
|
6207
|
-
};
|
|
6208
|
-
}
|
|
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 ?? "");
|
|
6221
|
-
return {
|
|
6222
|
-
home: input.home,
|
|
6223
|
-
worktrees: [input.worktree],
|
|
6224
|
-
branches: input.branch !== null && !isDefault ? [input.branch] : [],
|
|
6225
|
-
currentBranch: input.branch,
|
|
6226
|
-
remotes: input.remotes,
|
|
6227
|
-
pullRequests: []
|
|
6228
|
-
};
|
|
6229
|
-
}
|
|
6230
|
-
const DEFAULT_NAMES = /* @__PURE__ */ new Set([
|
|
6231
|
-
"main",
|
|
6232
|
-
"master",
|
|
6233
|
-
"trunk",
|
|
6234
|
-
"develop"
|
|
6235
|
-
]);
|
|
6236
|
-
//#endregion
|
|
6237
|
-
//#region src/containment/load-task-scope.ts
|
|
6238
|
-
async function loadTaskScope(cwd) {
|
|
6239
|
-
const [checkout, context] = await Promise.all([findCheckout(resolve(cwd)), loadRepositoryContext(cwd)]);
|
|
6240
|
-
return buildCWDScope({
|
|
6241
|
-
home: homedir(),
|
|
6242
|
-
worktree: checkout?.worktree ?? resolve(cwd),
|
|
6243
|
-
branch: context?.branch ?? null,
|
|
6244
|
-
defaultBranch: context?.defaultBranch ?? null,
|
|
6245
|
-
remotes: checkout === null ? [] : await readRemotes(checkout.commonDir)
|
|
6246
|
-
});
|
|
6247
|
-
}
|
|
6248
|
-
const GIT_OVERRIDES = [
|
|
6249
|
-
"GIT_DIR",
|
|
6250
|
-
"GIT_WORK_TREE",
|
|
6251
|
-
"GIT_COMMON_DIR"
|
|
6252
|
-
];
|
|
6253
|
-
async function findCheckout(start) {
|
|
6254
|
-
if (GIT_OVERRIDES.some((name) => process.env[name] !== void 0)) return null;
|
|
6255
|
-
for (let directory = start;; directory = dirname(directory)) {
|
|
6256
|
-
const gitPath = join(directory, ".git");
|
|
6257
|
-
const info = await stat(gitPath).catch(() => null);
|
|
6258
|
-
if (info !== null) {
|
|
6259
|
-
const gitDir = info.isDirectory() ? gitPath : await findLinkedGitDir(directory, gitPath);
|
|
6260
|
-
return gitDir === null ? null : {
|
|
6261
|
-
worktree: directory,
|
|
6262
|
-
commonDir: await findCommonDir(gitDir)
|
|
6263
|
-
};
|
|
6264
|
-
}
|
|
6265
|
-
if (dirname(directory) === directory) return null;
|
|
6266
|
-
}
|
|
6267
|
-
}
|
|
6268
|
-
async function findLinkedGitDir(directory, gitPath) {
|
|
6269
|
-
const link = await readFile(gitPath, "utf8").catch(() => "");
|
|
6270
|
-
const path = /^gitdir: (?<path>.+)$/mu.exec(link)?.groups?.["path"];
|
|
6271
|
-
return path === void 0 ? null : resolve(directory, path.trim());
|
|
6272
|
-
}
|
|
6273
|
-
async function findCommonDir(gitDir) {
|
|
6274
|
-
const common = await readFile(join(gitDir, "commondir"), "utf8").catch(() => null);
|
|
6275
|
-
return common === null ? gitDir : resolve(gitDir, common.trim());
|
|
6276
|
-
}
|
|
6277
|
-
async function readRemotes(commonDir) {
|
|
6278
|
-
const config = await readFile(join(commonDir, "config"), "utf8").catch(() => "");
|
|
6279
|
-
const remotes = [];
|
|
6280
|
-
let section = null;
|
|
6281
|
-
for (const line of config.split("\n")) {
|
|
6282
|
-
const header = /^\s*\[remote "(?<name>[^"]+)"\]/u.exec(line)?.groups?.["name"];
|
|
6283
|
-
if (header !== void 0) section = header;
|
|
6284
|
-
else if (/^\s*\[/u.test(line)) section = null;
|
|
6285
|
-
else if (section !== null) {
|
|
6286
|
-
const url = /^\s*(?:push)?url\s*=\s*(?<url>\S+)/u.exec(line)?.groups?.["url"];
|
|
6287
|
-
if (url !== void 0) remotes.push({
|
|
6288
|
-
name: section,
|
|
6289
|
-
url
|
|
6290
|
-
});
|
|
6291
|
-
}
|
|
6292
|
-
}
|
|
6293
|
-
return remotes;
|
|
6294
|
-
}
|
|
6295
|
-
//#endregion
|
|
6296
|
-
//#region src/containment/check-containment.ts
|
|
6297
|
-
const CONTAINMENT_RULE = "Outside Task Scope";
|
|
6298
|
-
async function checkContainment(request) {
|
|
6299
|
-
let scope;
|
|
6300
|
-
try {
|
|
6301
|
-
scope = await loadTaskScope(request.cwd);
|
|
6302
|
-
} catch {
|
|
6303
|
-
return null;
|
|
6304
|
-
}
|
|
6305
|
-
const findings = collectScopeFindings({
|
|
6306
|
-
tool: request.toolName,
|
|
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
|
-
};
|
|
6316
|
-
}
|
|
6317
|
-
const KIND_LABELS = {
|
|
6318
|
-
path: "path",
|
|
6319
|
-
branch: "branch",
|
|
6320
|
-
"remote-write": "remote target",
|
|
6321
|
-
credential: "credential or global setting",
|
|
6322
|
-
prune: "shared resource"
|
|
6323
|
-
};
|
|
6324
|
-
function formatContainmentReason(findings, scope) {
|
|
6325
|
-
const targets = findings.slice(0, 5).map((finding) => `${KIND_LABELS[finding.kind]} ${finding.target}`).join(", ");
|
|
6326
|
-
const more = findings.length > 5 ? ` and ${findings.length - 5} more` : "";
|
|
6327
|
-
const [branch] = scope.branches;
|
|
6328
|
-
return `This action writes outside the task scope: ${targets}${more}. The task owns ${`the worktree ${scope.worktrees.join(", ")}${branch === void 0 ? "" : ` and the branch ${branch}`}`}. Do the work inside the task's own worktree and branch, or ask the user to extend the task scope to this target.`;
|
|
6329
|
-
}
|
|
6330
|
-
const PRESETS = {
|
|
6331
|
-
jev: {
|
|
6332
|
-
protocol: "system-one",
|
|
6333
|
-
baseURL: "https://api.typesafe.ai",
|
|
6334
|
-
model: "jev-1.13.0",
|
|
6335
|
-
apiKeyEnv: "TYPESAFE_API_KEY",
|
|
6336
|
-
reasoning: false,
|
|
6337
|
-
maxTokens: 3e3,
|
|
6338
|
-
timeoutMs: 5e3
|
|
6339
|
-
},
|
|
6340
|
-
spark: {
|
|
6341
|
-
baseURL: "https://api.meta.ai",
|
|
6342
|
-
model: "muse-spark-1.3-contributor",
|
|
6343
|
-
apiKeyEnv: "META_API_KEY",
|
|
6344
|
-
reasoning: true,
|
|
6345
|
-
maxTokens: 3e3,
|
|
6346
|
-
timeoutMs: 45e3
|
|
6347
|
-
},
|
|
6348
|
-
claude: {
|
|
6349
|
-
baseURL: "https://api.anthropic.com",
|
|
6350
|
-
model: "claude-haiku-4-5-20251001",
|
|
6351
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
6352
|
-
reasoning: true,
|
|
6353
|
-
maxTokens: 3e3,
|
|
6354
|
-
timeoutMs: 45e3
|
|
6355
|
-
},
|
|
6356
|
-
glm: {
|
|
6357
|
-
baseURL: "https://api.z.ai/api/anthropic",
|
|
6358
|
-
model: "glm-5.3-flash",
|
|
6359
|
-
apiKeyEnv: "ZAI_API_KEY",
|
|
6360
|
-
reasoning: true,
|
|
6361
|
-
maxTokens: 3e3,
|
|
6362
|
-
timeoutMs: 6e4
|
|
6363
|
-
}
|
|
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;
|
|
6374
|
-
}
|
|
6375
|
-
//#endregion
|
|
6376
|
-
//#region src/config/read-api-key-from-command.ts
|
|
6377
|
-
function readApiKeyFromCommand(command, options = {}) {
|
|
6378
|
-
const remainingMs = options.deadlineAt === void 0 ? 5e3 : options.deadlineAt - Date.now();
|
|
6379
|
-
if (remainingMs <= 0 || options.signal?.aborted === true) return Promise.resolve(null);
|
|
6380
|
-
return new Promise((resolve) => {
|
|
6381
|
-
const grouped = process.platform !== "win32";
|
|
6382
|
-
const child = spawn("/bin/sh", ["-c", command], {
|
|
6383
|
-
detached: grouped,
|
|
6384
|
-
stdio: [
|
|
6385
|
-
"ignore",
|
|
6386
|
-
"pipe",
|
|
6387
|
-
"ignore"
|
|
6388
|
-
]
|
|
6389
|
-
});
|
|
6390
|
-
let output = "";
|
|
6391
|
-
let settled = false;
|
|
6392
|
-
let stopped = false;
|
|
6393
|
-
const setResult = (value) => {
|
|
6394
|
-
if (!settled) {
|
|
6395
|
-
settled = true;
|
|
6396
|
-
clearTimeout(timer);
|
|
6397
|
-
options.signal?.removeEventListener("abort", stopChild);
|
|
6398
|
-
resolve(value);
|
|
6399
|
-
}
|
|
6400
|
-
};
|
|
6401
|
-
const stopChild = () => {
|
|
6402
|
-
if (stopped) return;
|
|
6403
|
-
stopped = true;
|
|
6404
|
-
try {
|
|
6405
|
-
if (grouped && child.pid !== void 0) process.kill(-child.pid, "SIGKILL");
|
|
6406
|
-
else child.kill("SIGKILL");
|
|
6407
|
-
} catch {
|
|
6408
|
-
child.kill("SIGKILL");
|
|
6409
|
-
}
|
|
6410
|
-
};
|
|
6411
|
-
const timer = setTimeout(stopChild, Math.min(5e3, remainingMs));
|
|
6412
|
-
options.signal?.addEventListener("abort", stopChild, { once: true });
|
|
6413
|
-
child.stdout.setEncoding("utf8");
|
|
6414
|
-
child.stdout.on("data", (chunk) => {
|
|
6415
|
-
if (stopped) return;
|
|
6416
|
-
output += chunk;
|
|
6417
|
-
if (output.length > 65536) stopChild();
|
|
6418
|
-
});
|
|
6419
|
-
child.on("error", () => {
|
|
6420
|
-
setResult(null);
|
|
6421
|
-
});
|
|
6422
|
-
child.on("close", (code) => {
|
|
6423
|
-
const key = !stopped && code === 0 && output.trim() !== "" ? output.trim() : null;
|
|
6424
|
-
setResult(key);
|
|
6425
|
-
});
|
|
6426
|
-
});
|
|
6427
|
-
}
|
|
6428
|
-
//#endregion
|
|
6429
|
-
//#region src/config/config.ts
|
|
6430
|
-
const SCOPE_SOURCE_KINDS = [
|
|
6431
|
-
"cwd",
|
|
6432
|
-
"session",
|
|
6433
|
-
"globs",
|
|
6434
|
-
"atc"
|
|
6435
|
-
];
|
|
6436
|
-
const DEFAULT_PROVIDER = findPreset("jev");
|
|
6437
|
-
if (DEFAULT_PROVIDER === void 0) throw new Error("the jev preset is missing");
|
|
6438
|
-
const DEFAULT_DENIAL_BUDGET = {
|
|
6439
|
-
consecutive: 3,
|
|
6440
|
-
perSession: 20
|
|
6441
|
-
};
|
|
6442
|
-
const DEFAULT_CONFIG = {
|
|
6443
|
-
provider: DEFAULT_PROVIDER,
|
|
6444
|
-
onFailure: "defer",
|
|
6445
|
-
minConfidence: .8,
|
|
6446
|
-
denialBudget: DEFAULT_DENIAL_BUDGET
|
|
6447
|
-
};
|
|
6448
|
-
function resolveConfigPath() {
|
|
6449
|
-
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
6450
|
-
const base = xdg !== void 0 && xdg !== "" ? xdg : join(homedir(), ".config");
|
|
6451
|
-
return join(base, "auto-mode", "config.json");
|
|
6452
|
-
}
|
|
6453
|
-
async function loadConfig(path = resolveConfigPath()) {
|
|
6454
|
-
let raw;
|
|
6455
|
-
try {
|
|
6456
|
-
raw = await readFile(path, "utf8");
|
|
6457
|
-
} catch (error) {
|
|
6458
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") return DEFAULT_CONFIG;
|
|
6459
|
-
throw new Error("auto-mode configuration unreadable", { cause: error });
|
|
6460
|
-
}
|
|
6461
|
-
let json;
|
|
6462
|
-
try {
|
|
6463
|
-
json = JSON.parse(raw);
|
|
6464
|
-
} catch {
|
|
6465
|
-
throw new Error(`${path} is not valid JSON`);
|
|
6466
|
-
}
|
|
6467
|
-
return buildConfig(json, path);
|
|
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
|
-
};
|
|
6517
|
-
}
|
|
6518
|
-
function buildRegistry(raw, parseEntry) {
|
|
6519
|
-
const entries = /* @__PURE__ */ new Map();
|
|
6520
|
-
const dropped = [];
|
|
6521
|
-
const droppedIDs = [];
|
|
6522
|
-
for (const [id, entry] of Object.entries(raw)) {
|
|
6523
|
-
const result = parseEntry(id, entry);
|
|
6524
|
-
if (typeof result === "string") {
|
|
6525
|
-
dropped.push(`${id} dropped: ${result}`);
|
|
6526
|
-
droppedIDs.push(id);
|
|
6527
|
-
} else entries.set(id, result);
|
|
6528
|
-
}
|
|
6529
|
-
return {
|
|
6530
|
-
entries: Object.fromEntries(entries),
|
|
6531
|
-
dropped,
|
|
6532
|
-
droppedIDs
|
|
6533
|
-
};
|
|
6534
|
-
}
|
|
6535
|
-
const CLASSIFIER_KINDS = [...Object.keys(PRESETS), "messages"];
|
|
6536
|
-
const classifierEntrySchema = strictObject({
|
|
6537
|
-
kind: text.optional(),
|
|
6538
|
-
baseURL: text.optional(),
|
|
6539
|
-
model: text.optional(),
|
|
6540
|
-
apiKeyEnv: text.optional(),
|
|
6541
|
-
apiKeyCommand: text.optional(),
|
|
6542
|
-
reasoning: boolean().optional(),
|
|
6543
|
-
maxTokens: positive.optional(),
|
|
6544
|
-
timeoutMs: positive.optional()
|
|
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`;
|
|
6556
|
-
}
|
|
6557
|
-
const parsed = classifierEntrySchema.safeParse(entry);
|
|
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
|
-
};
|
|
6450
|
+
}
|
|
6451
|
+
return files;
|
|
6574
6452
|
}
|
|
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`;
|
|
6453
|
+
function resolvePath(cwd, path, home) {
|
|
6454
|
+
const expanded = path.replace(/^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/u, home);
|
|
6455
|
+
if (/[$`]|^~/u.test(expanded) || expanded === "") return null;
|
|
6456
|
+
if (cwd === null && !isAbsolute(expanded)) return null;
|
|
6457
|
+
const absolute = isAbsolute(expanded) || cwd === null ? expanded : join(cwd, expanded);
|
|
6458
|
+
return normalize(absolute).replace(/(?<=.)\/$/u, "");
|
|
6589
6459
|
}
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6460
|
+
const SCRATCH_PATHS = [
|
|
6461
|
+
"/tmp",
|
|
6462
|
+
"/dev/null",
|
|
6463
|
+
"/dev/stdout",
|
|
6464
|
+
"/dev/stderr"
|
|
6465
|
+
];
|
|
6466
|
+
function isInScope(path, scope) {
|
|
6467
|
+
if (SCRATCH_PATHS.some((entry) => isUnder(path, entry)) || scope.pathGlobs.some((glob) => matchesGlob(path, glob))) return true;
|
|
6468
|
+
return scope.worktrees.some((worktree) => {
|
|
6469
|
+
const isNested = isUnder(path, join(worktree, ".worktrees"));
|
|
6470
|
+
const isOtherOwned = scope.worktrees.some((other) => other !== worktree && isUnder(path, other));
|
|
6471
|
+
return isUnder(path, worktree) && (!isNested || isOtherOwned);
|
|
6472
|
+
});
|
|
6594
6473
|
}
|
|
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);
|
|
6474
|
+
function isUnder(path, root) {
|
|
6475
|
+
return path === root || path.startsWith(`${root}/`);
|
|
6601
6476
|
}
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6477
|
+
//#endregion
|
|
6478
|
+
//#region src/containment/check-containment.ts
|
|
6479
|
+
const CONTAINMENT_RULE = "Outside Task Scope";
|
|
6480
|
+
function checkContainment(request, scope) {
|
|
6481
|
+
const findings = collectScopeFindings({
|
|
6482
|
+
tool: request.toolName,
|
|
6483
|
+
cwd: request.cwd,
|
|
6484
|
+
input: request.toolInput
|
|
6485
|
+
}, scope);
|
|
6486
|
+
if (findings.length === 0) return null;
|
|
6487
|
+
return {
|
|
6488
|
+
rule: CONTAINMENT_RULE,
|
|
6489
|
+
reason: formatContainmentReason(findings, scope),
|
|
6490
|
+
findings
|
|
6491
|
+
};
|
|
6606
6492
|
}
|
|
6607
|
-
|
|
6608
|
-
|
|
6493
|
+
const KIND_LABELS = {
|
|
6494
|
+
path: "path",
|
|
6495
|
+
branch: "branch",
|
|
6496
|
+
"remote-write": "remote target",
|
|
6497
|
+
credential: "credential or global setting",
|
|
6498
|
+
prune: "shared resource"
|
|
6499
|
+
};
|
|
6500
|
+
function formatContainmentReason(findings, scope) {
|
|
6501
|
+
const targets = findings.slice(0, 5).map((finding) => `${KIND_LABELS[finding.kind]} ${finding.target}`).join(", ");
|
|
6502
|
+
const more = findings.length > 5 ? ` and ${findings.length - 5} more` : "";
|
|
6503
|
+
const branches = scope.branches.length === 0 ? "" : ` and ${formatList("branch", "branches", scope.branches)}`;
|
|
6504
|
+
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
6505
|
}
|
|
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);
|
|
6506
|
+
function formatList(one, many, values) {
|
|
6507
|
+
return `the ${values.length === 1 ? one : many} ${values.join(", ")}`;
|
|
6615
6508
|
}
|
|
6616
6509
|
//#endregion
|
|
6617
6510
|
//#region src/policy/resolve-shipped-policy-dir.ts
|
|
@@ -6882,6 +6775,68 @@ function formatClassifierNote(note, apiKey) {
|
|
|
6882
6775
|
return apiKey === null || apiKey === "" ? note : note.split(apiKey).join("[redacted]");
|
|
6883
6776
|
}
|
|
6884
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;
|
|
6838
|
+
}
|
|
6839
|
+
//#endregion
|
|
6885
6840
|
//#region node_modules/.bun/ts-pattern@5.9.0/node_modules/ts-pattern/dist/index.js
|
|
6886
6841
|
const t = Symbol.for("@ts-pattern/matcher");
|
|
6887
6842
|
const e = Symbol.for("@ts-pattern/isVariadic");
|
|
@@ -7726,6 +7681,199 @@ function toUnquoted(word) {
|
|
|
7726
7681
|
return (first === "'" || first === "\"") && word.at(-1) === first ? word.slice(1, -1) : word;
|
|
7727
7682
|
}
|
|
7728
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
|
|
7729
7877
|
//#region src/classify-action.ts
|
|
7730
7878
|
async function classifyAction(request, config, options = {}) {
|
|
7731
7879
|
let configured = null;
|
|
@@ -7741,9 +7889,11 @@ async function classifyAction(request, config, options = {}) {
|
|
|
7741
7889
|
unavailable: true
|
|
7742
7890
|
};
|
|
7743
7891
|
}
|
|
7744
|
-
const
|
|
7892
|
+
const hasConfiguredDenies = configured !== null && (configured.hard_deny.length > 0 || configured.soft_deny.length > 0);
|
|
7893
|
+
const local = hasConfiguredDenies ? { kind: "escalate" } : classifyLocally(request);
|
|
7745
7894
|
if (local.kind === "allow" && local.exception === READ_ONLY_EXCEPTION) return buildLocalAllow(local.exception);
|
|
7746
|
-
const
|
|
7895
|
+
const scope = await tryLoadTaskScope(request, config);
|
|
7896
|
+
const containment = scope === null ? null : checkContainment(request, scope);
|
|
7747
7897
|
if (containment !== null) {
|
|
7748
7898
|
const guidance = await tryReadDenialGuidance();
|
|
7749
7899
|
return {
|
|
@@ -7754,6 +7904,13 @@ async function classifyAction(request, config, options = {}) {
|
|
|
7754
7904
|
};
|
|
7755
7905
|
}
|
|
7756
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
|
+
};
|
|
7757
7914
|
if (options.localOnly === true) return {
|
|
7758
7915
|
verdict: null,
|
|
7759
7916
|
decidingStage: "local",
|
|
@@ -7769,6 +7926,51 @@ async function classifyAction(request, config, options = {}) {
|
|
|
7769
7926
|
};
|
|
7770
7927
|
}
|
|
7771
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
|
+
}
|
|
7772
7974
|
function buildLocalAllow(exception) {
|
|
7773
7975
|
return {
|
|
7774
7976
|
verdict: { kind: "allow" },
|
|
@@ -7850,4 +8052,4 @@ function renderVerdict(verdict) {
|
|
|
7850
8052
|
})).exhaustive();
|
|
7851
8053
|
}
|
|
7852
8054
|
//#endregion
|
|
7853
|
-
export {
|
|
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 };
|