@guilz-dev/belay 0.1.0 → 0.2.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/README.md +59 -12
- package/dist/adapters/shared/gate-runtime.js +12 -4
- package/dist/bundle/claude-runtime.mjs +155 -36
- package/dist/bundle/codex-runtime.mjs +155 -36
- package/dist/bundle/cursor-runtime.mjs +155 -36
- package/dist/cli.js +15 -4
- package/dist/commands/classify-for-report.js +3 -3
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/explain.js +14 -14
- package/dist/commands/init-wizard.d.ts +5 -0
- package/dist/commands/init-wizard.js +24 -11
- package/dist/commands/recover.js +2 -2
- package/dist/core/approval.d.ts +3 -0
- package/dist/core/approval.js +18 -3
- package/dist/core/audit-query.js +5 -1
- package/dist/core/classify-tool.js +1 -1
- package/dist/core/config.d.ts +1 -1
- package/dist/core/config.js +2 -2
- package/dist/core/gate-contract.d.ts +1 -1
- package/dist/core/gate-contract.js +1 -1
- package/dist/core/gate-engine.js +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/core/judge-config.d.ts +5 -1
- package/dist/core/judge-config.js +17 -1
- package/dist/core/judge-doctor.js +2 -2
- package/dist/core/types.d.ts +5 -3
- package/dist/core/{v2 → verdict}/adapter.js +9 -3
- package/dist/core/{v2 → verdict}/egress-classify.js +3 -0
- package/dist/core/{v2 → verdict}/judge.js +10 -12
- package/dist/core/{v2 → verdict}/launcher-resolve.js +72 -1
- package/dist/core/{v2 → verdict}/verdict.js +16 -0
- package/dist/corpus/evaluate.js +2 -2
- package/dist/installer.js +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -1
- package/package.json +5 -2
- package/skills/belay/SKILL.md +19 -5
- /package/dist/core/{v2 → verdict}/adapter.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/containment.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/containment.js +0 -0
- /package/dist/core/{v2 → verdict}/egress-classify.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/fingerprint.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/fingerprint.js +0 -0
- /package/dist/core/{v2 → verdict}/index.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/index.js +0 -0
- /package/dist/core/{v2 → verdict}/judge-audit.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/judge-audit.js +0 -0
- /package/dist/core/{v2 → verdict}/judge-factory.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/judge-factory.js +0 -0
- /package/dist/core/{v2 → verdict}/judge-outbound.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/judge-outbound.js +0 -0
- /package/dist/core/{v2 → verdict}/judge.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/launcher-resolve.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/overrides.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/overrides.js +0 -0
- /package/dist/core/{v2 → verdict}/parser.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/parser.js +0 -0
- /package/dist/core/{v2 → verdict}/types.d.ts +0 -0
- /package/dist/core/{v2 → verdict}/types.js +0 -0
- /package/dist/core/{v2 → verdict}/verdict.d.ts +0 -0
|
@@ -86,7 +86,7 @@ var LEGACY_POLICY_V3 = {
|
|
|
86
86
|
fenceWarnThreshold: DEFAULT_FENCE_WARN_THRESHOLD
|
|
87
87
|
};
|
|
88
88
|
var DEFAULT_POLICY_V3 = {
|
|
89
|
-
unknownLocalEffect: "
|
|
89
|
+
unknownLocalEffect: "allow_flagged",
|
|
90
90
|
unparseableShell: "deny",
|
|
91
91
|
codexUnmappedTool: "deny",
|
|
92
92
|
confidenceThresholds: { ...DEFAULT_CONFIDENCE_THRESHOLDS },
|
|
@@ -703,16 +703,25 @@ import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile2 } from
|
|
|
703
703
|
import path16 from "node:path";
|
|
704
704
|
|
|
705
705
|
// src/core/approval.ts
|
|
706
|
+
var APPROVAL_EXECUTION_LEASE_MS = 6e4;
|
|
706
707
|
function nowIso() {
|
|
707
708
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
708
709
|
}
|
|
709
710
|
function isExpired(approval) {
|
|
710
711
|
return Date.parse(approval.expiresAt) <= Date.now();
|
|
711
712
|
}
|
|
713
|
+
function isExecutionLeaseExpired(approval) {
|
|
714
|
+
if (!approval.executionLeaseExpiresAt) {
|
|
715
|
+
return false;
|
|
716
|
+
}
|
|
717
|
+
return Date.parse(approval.executionLeaseExpiresAt) <= Date.now();
|
|
718
|
+
}
|
|
712
719
|
function compactApprovals(state) {
|
|
713
720
|
return {
|
|
714
721
|
version: state.version,
|
|
715
|
-
approvals: state.approvals.filter(
|
|
722
|
+
approvals: state.approvals.filter(
|
|
723
|
+
(approval) => !isExpired(approval) && !isExecutionLeaseExpired(approval)
|
|
724
|
+
)
|
|
716
725
|
};
|
|
717
726
|
}
|
|
718
727
|
function escapeRegex(value) {
|
|
@@ -721,8 +730,15 @@ function escapeRegex(value) {
|
|
|
721
730
|
}
|
|
722
731
|
function approvalCommandMatch(prompt, tokenPrefix) {
|
|
723
732
|
const escapedPrefix = escapeRegex(tokenPrefix);
|
|
724
|
-
const
|
|
725
|
-
|
|
733
|
+
const linePattern = new RegExp(`^\\s*${escapedPrefix}\\s+(\\S+)\\s*$`, "i");
|
|
734
|
+
for (const line of prompt.split(/\r?\n/)) {
|
|
735
|
+
if (!line.trim()) {
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const match = line.match(linePattern);
|
|
739
|
+
return match?.[1] ?? null;
|
|
740
|
+
}
|
|
741
|
+
return null;
|
|
726
742
|
}
|
|
727
743
|
function buildRetryInstruction(tokenPrefix, approvalId) {
|
|
728
744
|
return `To allow the next matching action once, send ${tokenPrefix} ${approvalId} and then retry the original action unchanged.`;
|
|
@@ -1249,7 +1265,7 @@ function classifyResultToGateVerdict(params) {
|
|
|
1249
1265
|
approvalId,
|
|
1250
1266
|
user_message,
|
|
1251
1267
|
agent_message,
|
|
1252
|
-
|
|
1268
|
+
axes: result.axes
|
|
1253
1269
|
};
|
|
1254
1270
|
}
|
|
1255
1271
|
function unnormalizedGateVerdict(params) {
|
|
@@ -1503,7 +1519,7 @@ function matchesSensitivePath(filePath, patterns) {
|
|
|
1503
1519
|
return false;
|
|
1504
1520
|
}
|
|
1505
1521
|
|
|
1506
|
-
// src/core/
|
|
1522
|
+
// src/core/verdict/judge-audit.ts
|
|
1507
1523
|
function judgeTraceAuditFields(trace) {
|
|
1508
1524
|
if (!trace) {
|
|
1509
1525
|
return {};
|
|
@@ -1518,7 +1534,7 @@ function judgeTraceAuditFields(trace) {
|
|
|
1518
1534
|
};
|
|
1519
1535
|
}
|
|
1520
1536
|
|
|
1521
|
-
// src/core/
|
|
1537
|
+
// src/core/verdict/judge-outbound.ts
|
|
1522
1538
|
var PATH_LIKE = /(?:^|[\s"'`=])(~\/[^\s"'`]+|\/[^\s"'`]+|\.\/[^\s"'`]+|\.\.\/[^\s"'`]+|[A-Za-z]:\\[^\s"'`]+)/g;
|
|
1523
1539
|
var REDACTED_PLACEHOLDER = /^(?:<redacted>|\[REDACTED\]|<secret>|<high-entropy>|<approval-id>)$/i;
|
|
1524
1540
|
var URL_CREDENTIALS_PATTERN2 = /\b[A-Za-z][A-Za-z0-9+.-]*:\/\/([^/\s:@]+):([^@\s/]+)@/gi;
|
|
@@ -1587,16 +1603,16 @@ function scrubOutboundForJudge(text, options) {
|
|
|
1587
1603
|
}
|
|
1588
1604
|
}
|
|
1589
1605
|
|
|
1590
|
-
// src/core/
|
|
1606
|
+
// src/core/verdict/judge.ts
|
|
1591
1607
|
var SECRET_PATTERNS = [/\.env\b/i, /\.pem\b/i, /id_rsa\b/i, /credentials/i, /secrets?\b/i];
|
|
1592
1608
|
var DESTRUCTIVE_VERBS = /\b(rm|rmtree|unlink|delete|truncate|shred|destroy|drop)\b/i;
|
|
1593
1609
|
var GIT_PATTERNS = /\.git\b/i;
|
|
1594
|
-
var TIER1_PROMPT = `Answer ONLY with JSON: {"external_change":boolean,"
|
|
1595
|
-
|
|
1610
|
+
var TIER1_PROMPT = `Answer ONLY with JSON: {"external_change":boolean,"reason":string}
|
|
1611
|
+
reason should be a short snake_case label.
|
|
1596
1612
|
|
|
1597
|
-
external_change:
|
|
1598
|
-
|
|
1599
|
-
|
|
1613
|
+
external_change: true if this operation CREATEs, MODIFYs, DELETEs, or SENDs data to any system outside the local machine and its git repository working tree.
|
|
1614
|
+
READ/QUERY/GET/list/describe/SELECT/HTTP GET is NOT a change. Local build, test, and package install inside the repo are NOT external changes.
|
|
1615
|
+
If unsure, answer true.
|
|
1600
1616
|
|
|
1601
1617
|
Command/code:
|
|
1602
1618
|
`;
|
|
@@ -1611,13 +1627,13 @@ function failClosedVerdict(reason) {
|
|
|
1611
1627
|
function parseTier1Json(raw) {
|
|
1612
1628
|
try {
|
|
1613
1629
|
const parsed = JSON.parse(raw);
|
|
1614
|
-
if (typeof parsed.external_change !== "boolean"
|
|
1630
|
+
if (typeof parsed.external_change !== "boolean") {
|
|
1615
1631
|
return null;
|
|
1616
1632
|
}
|
|
1617
1633
|
return {
|
|
1618
|
-
external_change: parsed.external_change
|
|
1619
|
-
destroys_outside_repo:
|
|
1620
|
-
destroys_history_or_secrets:
|
|
1634
|
+
external_change: parsed.external_change,
|
|
1635
|
+
destroys_outside_repo: false,
|
|
1636
|
+
destroys_history_or_secrets: false,
|
|
1621
1637
|
reason: typeof parsed.reason === "string" ? parsed.reason : "tier1_llm"
|
|
1622
1638
|
};
|
|
1623
1639
|
} catch {
|
|
@@ -1840,10 +1856,10 @@ function createOpenAiCompatibleJudge(options) {
|
|
|
1840
1856
|
return judge;
|
|
1841
1857
|
}
|
|
1842
1858
|
function tier1RequiresAsk(verdict2) {
|
|
1843
|
-
return verdict2.external_change || verdict2.
|
|
1859
|
+
return verdict2.external_change || verdict2.destroys_history_or_secrets;
|
|
1844
1860
|
}
|
|
1845
1861
|
|
|
1846
|
-
// src/core/
|
|
1862
|
+
// src/core/verdict/judge-factory.ts
|
|
1847
1863
|
var FIXTURE_MODELS_URL = new URL("../../../fixtures/judge-models.json", import.meta.url);
|
|
1848
1864
|
function resolveCloudModel(requested, pinned) {
|
|
1849
1865
|
if (requested === "auto") {
|
|
@@ -1890,10 +1906,10 @@ function createJudgeFromConfig(config, options = {}) {
|
|
|
1890
1906
|
return createDeterministicJudgeStub();
|
|
1891
1907
|
}
|
|
1892
1908
|
|
|
1893
|
-
// src/core/
|
|
1909
|
+
// src/core/verdict/verdict.ts
|
|
1894
1910
|
import path11 from "node:path";
|
|
1895
1911
|
|
|
1896
|
-
// src/core/
|
|
1912
|
+
// src/core/verdict/containment.ts
|
|
1897
1913
|
import path8 from "node:path";
|
|
1898
1914
|
function expandHome(token) {
|
|
1899
1915
|
if (token === "~" || token.startsWith("~/")) {
|
|
@@ -1985,7 +2001,7 @@ function cwdRelative(repoRoot, cwd) {
|
|
|
1985
2001
|
return relativeWithinRepo(repoRoot, cwd) ?? cwd;
|
|
1986
2002
|
}
|
|
1987
2003
|
|
|
1988
|
-
// src/core/
|
|
2004
|
+
// src/core/verdict/egress-classify.ts
|
|
1989
2005
|
var EGRESS_TOOL_HEADS = /* @__PURE__ */ new Set([
|
|
1990
2006
|
"aws",
|
|
1991
2007
|
"curl",
|
|
@@ -2083,6 +2099,9 @@ function classifyAws(tokens) {
|
|
|
2083
2099
|
if (/\bs3\s+rm\b/.test(joined)) {
|
|
2084
2100
|
return "destructive";
|
|
2085
2101
|
}
|
|
2102
|
+
if (/\bs3\s+mb\b/.test(joined)) {
|
|
2103
|
+
return "destructive";
|
|
2104
|
+
}
|
|
2086
2105
|
if (/\bs3\s+sync\b/.test(joined)) {
|
|
2087
2106
|
return "destructive";
|
|
2088
2107
|
}
|
|
@@ -2193,15 +2212,59 @@ function classifyNetlify(tokens) {
|
|
|
2193
2212
|
return "ambiguous";
|
|
2194
2213
|
}
|
|
2195
2214
|
|
|
2196
|
-
// src/core/
|
|
2215
|
+
// src/core/verdict/fingerprint.ts
|
|
2197
2216
|
function verdictFingerprint(cwdRelative2, commandRedacted) {
|
|
2198
2217
|
return hashValue(`v2:${cwdRelative2}:${commandRedacted}`);
|
|
2199
2218
|
}
|
|
2200
2219
|
|
|
2201
|
-
// src/core/
|
|
2220
|
+
// src/core/verdict/launcher-resolve.ts
|
|
2202
2221
|
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
|
|
2203
2222
|
import path9 from "node:path";
|
|
2204
2223
|
var MAX_RESOLVE_DEPTH = 8;
|
|
2224
|
+
var PNPM_BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
|
|
2225
|
+
"add",
|
|
2226
|
+
"audit",
|
|
2227
|
+
"cache",
|
|
2228
|
+
"config",
|
|
2229
|
+
"deploy",
|
|
2230
|
+
"dlx",
|
|
2231
|
+
"exec",
|
|
2232
|
+
"fetch",
|
|
2233
|
+
"help",
|
|
2234
|
+
"import",
|
|
2235
|
+
"init",
|
|
2236
|
+
"install",
|
|
2237
|
+
"i",
|
|
2238
|
+
"licenses",
|
|
2239
|
+
"link",
|
|
2240
|
+
"list",
|
|
2241
|
+
"outdated",
|
|
2242
|
+
"pack",
|
|
2243
|
+
"patch",
|
|
2244
|
+
"patch-commit",
|
|
2245
|
+
"patch-remove",
|
|
2246
|
+
"publish",
|
|
2247
|
+
"prune",
|
|
2248
|
+
"rebuild",
|
|
2249
|
+
"remove",
|
|
2250
|
+
"rm",
|
|
2251
|
+
"store",
|
|
2252
|
+
"unlink",
|
|
2253
|
+
"update",
|
|
2254
|
+
"up",
|
|
2255
|
+
"why"
|
|
2256
|
+
]);
|
|
2257
|
+
var PNPM_EXEC_LIKE_HEADS = /* @__PURE__ */ new Set([
|
|
2258
|
+
"vitest",
|
|
2259
|
+
"vite",
|
|
2260
|
+
"biome",
|
|
2261
|
+
"eslint",
|
|
2262
|
+
"jest",
|
|
2263
|
+
"mocha",
|
|
2264
|
+
"tsc",
|
|
2265
|
+
"tsx",
|
|
2266
|
+
"node"
|
|
2267
|
+
]);
|
|
2205
2268
|
function readPackageJson(dir) {
|
|
2206
2269
|
const packagePath = path9.join(dir, "package.json");
|
|
2207
2270
|
if (!existsSync4(packagePath)) {
|
|
@@ -2256,6 +2319,12 @@ function npmScriptName(tokens) {
|
|
|
2256
2319
|
if (launcher[0] === "pnpm" && launcher[1] === "run" && launcher[2]) {
|
|
2257
2320
|
return launcher[2];
|
|
2258
2321
|
}
|
|
2322
|
+
if (launcher[0] === "pnpm" && launcher[1] === "test") {
|
|
2323
|
+
return "test";
|
|
2324
|
+
}
|
|
2325
|
+
if (launcher[0] === "pnpm" && launcher[1] && !launcher[1].startsWith("-") && !PNPM_BUILTIN_COMMANDS.has(launcher[1])) {
|
|
2326
|
+
return launcher[1];
|
|
2327
|
+
}
|
|
2259
2328
|
if (launcher[0] === "npm" && launcher[1] && launcher[1] !== "run" && launcher[1] !== "install") {
|
|
2260
2329
|
return null;
|
|
2261
2330
|
}
|
|
@@ -2377,11 +2446,31 @@ function resolveLauncherRecipe(params) {
|
|
|
2377
2446
|
const tokens = params.tokens;
|
|
2378
2447
|
const scriptName = npmScriptName(tokens);
|
|
2379
2448
|
if (scriptName) {
|
|
2380
|
-
|
|
2449
|
+
const resolution = resolveNpmRecipe(
|
|
2450
|
+
params.cwd,
|
|
2451
|
+
params.repoRoot,
|
|
2452
|
+
scriptName,
|
|
2453
|
+
forwardedArgs(tokens)
|
|
2454
|
+
);
|
|
2455
|
+
if (tokens[0] === "pnpm" && tokens[1] && PNPM_EXEC_LIKE_HEADS.has(tokens[1]) && resolution.reason === "npm_script_undefined") {
|
|
2456
|
+
return {
|
|
2457
|
+
recipes: [tokens.slice(1).join(" ")],
|
|
2458
|
+
opaque: false,
|
|
2459
|
+
reason: "pnpm_exec_like"
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
return resolution;
|
|
2381
2463
|
}
|
|
2382
2464
|
if (tokens[0] === "make" && tokens[1] && !tokens[1].startsWith("-")) {
|
|
2383
2465
|
return resolveMakeRecipe(params.cwd, params.repoRoot, tokens[1]);
|
|
2384
2466
|
}
|
|
2467
|
+
if (tokens[0] === "pnpm" && tokens[1] && PNPM_EXEC_LIKE_HEADS.has(tokens[1])) {
|
|
2468
|
+
return {
|
|
2469
|
+
recipes: [tokens.slice(1).join(" ")],
|
|
2470
|
+
opaque: false,
|
|
2471
|
+
reason: "pnpm_exec_like"
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2385
2474
|
return null;
|
|
2386
2475
|
}
|
|
2387
2476
|
function isRoutineLauncher(tokens) {
|
|
@@ -2397,7 +2486,7 @@ function matchesCustomCommand(normalizedCommand, key, pattern) {
|
|
|
2397
2486
|
return normalizedCommand === trimmed || key === trimmed;
|
|
2398
2487
|
}
|
|
2399
2488
|
|
|
2400
|
-
// src/core/
|
|
2489
|
+
// src/core/verdict/overrides.ts
|
|
2401
2490
|
function matchesCustomPatterns(command, segment, patterns) {
|
|
2402
2491
|
if (!patterns || patterns.length === 0) {
|
|
2403
2492
|
return false;
|
|
@@ -2436,7 +2525,7 @@ function askFromCustomExternal(opacity) {
|
|
|
2436
2525
|
};
|
|
2437
2526
|
}
|
|
2438
2527
|
|
|
2439
|
-
// src/core/
|
|
2528
|
+
// src/core/verdict/parser.ts
|
|
2440
2529
|
import path10 from "node:path";
|
|
2441
2530
|
|
|
2442
2531
|
// src/core/shell-substitution.ts
|
|
@@ -2660,7 +2749,7 @@ function hasUnbalancedDollarParen(command) {
|
|
|
2660
2749
|
return depth > 0;
|
|
2661
2750
|
}
|
|
2662
2751
|
|
|
2663
|
-
// src/core/
|
|
2752
|
+
// src/core/verdict/parser.ts
|
|
2664
2753
|
var ENV_PREFIX_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|"[^"]*"|\S+)$/;
|
|
2665
2754
|
var TRANSPARENT_WRAPPERS = /* @__PURE__ */ new Set([
|
|
2666
2755
|
"sudo",
|
|
@@ -2860,7 +2949,7 @@ function redactCommand(command) {
|
|
|
2860
2949
|
return command.replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]").replace(/sk-[A-Za-z0-9]{8,}/g, "sk-[REDACTED]").trim();
|
|
2861
2950
|
}
|
|
2862
2951
|
|
|
2863
|
-
// src/core/
|
|
2952
|
+
// src/core/verdict/verdict.ts
|
|
2864
2953
|
var DEFAULT_MAX_DEPTH = 8;
|
|
2865
2954
|
var TIER0_EXTERNAL_KEYS = /* @__PURE__ */ new Set([
|
|
2866
2955
|
"git push",
|
|
@@ -2943,6 +3032,7 @@ var LOCAL_ROUTINE_HEADS = /* @__PURE__ */ new Set([
|
|
|
2943
3032
|
"make",
|
|
2944
3033
|
"cmake"
|
|
2945
3034
|
]);
|
|
3035
|
+
var BELAY_SELF_COMMANDS = /* @__PURE__ */ new Set(["approve", "revoke"]);
|
|
2946
3036
|
var FIND_DANGEROUS_FLAGS = /* @__PURE__ */ new Set(["-delete", "-exec", "-execdir", "-ok", "-okdir"]);
|
|
2947
3037
|
function isFindDangerous(tokens) {
|
|
2948
3038
|
return tokens.some(
|
|
@@ -3090,6 +3180,11 @@ function tier0ExternalMatch(key, head, tokens) {
|
|
|
3090
3180
|
}
|
|
3091
3181
|
return false;
|
|
3092
3182
|
}
|
|
3183
|
+
function isBelaySelfCommand(tokens) {
|
|
3184
|
+
const head = tokens[0] ?? "";
|
|
3185
|
+
const subcommand = tokens[1] ?? "";
|
|
3186
|
+
return head === "belay" && BELAY_SELF_COMMANDS.has(subcommand);
|
|
3187
|
+
}
|
|
3093
3188
|
function tier0HighStakesRm(tokens, context) {
|
|
3094
3189
|
const head = tokens[0] ?? "";
|
|
3095
3190
|
if (head !== "rm") {
|
|
@@ -3321,6 +3416,16 @@ async function evaluateSegment(command, context, depth) {
|
|
|
3321
3416
|
if (rmVerdict) {
|
|
3322
3417
|
return rmVerdict;
|
|
3323
3418
|
}
|
|
3419
|
+
if (isBelaySelfCommand(peeled)) {
|
|
3420
|
+
return allowVerdict({
|
|
3421
|
+
location: "unknown",
|
|
3422
|
+
opacity: "transparent",
|
|
3423
|
+
effect: "local_mutation",
|
|
3424
|
+
confidence: "deterministic",
|
|
3425
|
+
reason: "belay_control_plane_command",
|
|
3426
|
+
signals: ["belay_control_plane_command", segment.head]
|
|
3427
|
+
});
|
|
3428
|
+
}
|
|
3324
3429
|
let effect = "unknown";
|
|
3325
3430
|
if (READ_ONLY_KEYS.has(segment.key) || READ_ONLY_KEYS.has(segment.head)) {
|
|
3326
3431
|
effect = "read_only";
|
|
@@ -3541,7 +3646,7 @@ async function verdict(command, context) {
|
|
|
3541
3646
|
);
|
|
3542
3647
|
}
|
|
3543
3648
|
|
|
3544
|
-
// src/core/
|
|
3649
|
+
// src/core/verdict/adapter.ts
|
|
3545
3650
|
function buildVerdictContext(params) {
|
|
3546
3651
|
const protectedArtifactRoots2 = [
|
|
3547
3652
|
...params.options?.protectedArtifactRoots ?? [],
|
|
@@ -3588,6 +3693,12 @@ function mapLegacyReason(result) {
|
|
|
3588
3693
|
if (result.reason === "repo_local_mutation") {
|
|
3589
3694
|
return "local_mutation";
|
|
3590
3695
|
}
|
|
3696
|
+
if (result.reason === "tier1_not_restorable") {
|
|
3697
|
+
return "tier1_catastrophic";
|
|
3698
|
+
}
|
|
3699
|
+
if (result.reason === "tier0_restorable" || result.reason === "tier1_restorable") {
|
|
3700
|
+
return result.effect === "local_mutation" ? "local_mutation" : result.reason;
|
|
3701
|
+
}
|
|
3591
3702
|
return result.reason;
|
|
3592
3703
|
}
|
|
3593
3704
|
function verdictToClassifyResult(result) {
|
|
@@ -3608,13 +3719,13 @@ function verdictToClassifyResult(result) {
|
|
|
3608
3719
|
assessment,
|
|
3609
3720
|
normalizedCommand: result.commandRedacted,
|
|
3610
3721
|
summary: result.commandRedacted,
|
|
3611
|
-
|
|
3722
|
+
axes: {
|
|
3612
3723
|
location: result.location,
|
|
3613
3724
|
opacity: result.opacity,
|
|
3614
3725
|
effect: result.effect,
|
|
3615
3726
|
confidence: result.confidence,
|
|
3616
3727
|
would: result.permission,
|
|
3617
|
-
by: "
|
|
3728
|
+
by: "verdict",
|
|
3618
3729
|
commandRedacted: result.commandRedacted,
|
|
3619
3730
|
commandFingerprint: result.fingerprint,
|
|
3620
3731
|
signals: result.signals,
|
|
@@ -4064,7 +4175,7 @@ function normalizeGatedAction(params) {
|
|
|
4064
4175
|
};
|
|
4065
4176
|
}
|
|
4066
4177
|
function applyShellPeripheralPolicy(command, action, result, options) {
|
|
4067
|
-
if (options.brokerFsScope && result.verdict === "deny_pending_approval" && (result.reason === "outside_repo_mutation" || result.reason === "outside_repo_redirect" || result.reason === "repo_outside_mutation" || result.
|
|
4178
|
+
if (options.brokerFsScope && result.verdict === "deny_pending_approval" && (result.reason === "outside_repo_mutation" || result.reason === "outside_repo_redirect" || result.reason === "repo_outside_mutation" || result.axes?.location === "repo_outside")) {
|
|
4068
4179
|
const outsideRepoPaths = collectOutsideRepoPaths(command, action.cwd, action.repoRoot);
|
|
4069
4180
|
if (outsideRepoPaths.length > 0 && options.fsScopeAllowlist && allPathsAllowlisted(outsideRepoPaths, options.fsScopeAllowlist)) {
|
|
4070
4181
|
return {
|
|
@@ -4743,7 +4854,15 @@ async function consumeApprovedApproval(ctx, deps, kind, fingerprint) {
|
|
|
4743
4854
|
await deps.writeApprovals(approved.filePath, approved.state);
|
|
4744
4855
|
return null;
|
|
4745
4856
|
}
|
|
4746
|
-
const
|
|
4857
|
+
const approval = approved.state.approvals[index];
|
|
4858
|
+
if (approval.executionLeaseExpiresAt) {
|
|
4859
|
+
await deps.writeApprovals(approved.filePath, approved.state);
|
|
4860
|
+
return approval;
|
|
4861
|
+
}
|
|
4862
|
+
approved.state.approvals[index] = {
|
|
4863
|
+
...approval,
|
|
4864
|
+
executionLeaseExpiresAt: new Date(Date.now() + APPROVAL_EXECUTION_LEASE_MS).toISOString()
|
|
4865
|
+
};
|
|
4747
4866
|
await deps.writeApprovals(approved.filePath, approved.state);
|
|
4748
4867
|
return approval;
|
|
4749
4868
|
}
|
|
@@ -4859,8 +4978,8 @@ async function gateDecisionToVerdict(ctx, deps, kind, result, auditExtras = {})
|
|
|
4859
4978
|
predictedAssessment: auditExtras.predictedAssessment,
|
|
4860
4979
|
observedAssessment: auditExtras.observedAssessment,
|
|
4861
4980
|
mode: ctx.config.mode,
|
|
4862
|
-
schemaVersion: result.
|
|
4863
|
-
...result.
|
|
4981
|
+
schemaVersion: result.axes ? 2 : 1,
|
|
4982
|
+
...result.axes ?? {},
|
|
4864
4983
|
...auditExtras.transactionalLayer
|
|
4865
4984
|
};
|
|
4866
4985
|
if (result.reason === TRANSACTIONAL_ALREADY_APPLIED) {
|
package/dist/cli.js
CHANGED
|
@@ -16,9 +16,16 @@ import { loadConfigFile } from './config-io.js';
|
|
|
16
16
|
import { initProject, upgradeProject } from './installer.js';
|
|
17
17
|
import { egressEnv, egressStatus, formatEgressStatusReport, startEgressProxy, stopEgressProxy, } from './services/egress-service.js';
|
|
18
18
|
import { formatSandboxStatusReport, sandboxStatus } from './services/sandbox-service.js';
|
|
19
|
+
import { PACKAGE_VERSION } from './version.js';
|
|
19
20
|
function parseArgs(argv) {
|
|
20
21
|
const [command, ...rest] = argv;
|
|
21
22
|
const options = {};
|
|
23
|
+
if (!command || command === '--help' || command === '-h') {
|
|
24
|
+
return { command: 'help', options };
|
|
25
|
+
}
|
|
26
|
+
if (command === '--version' || command === '-V') {
|
|
27
|
+
return { command: 'version', options };
|
|
28
|
+
}
|
|
22
29
|
for (let index = 0; index < rest.length; index += 1) {
|
|
23
30
|
const token = rest[index];
|
|
24
31
|
if (token === '--with-skill') {
|
|
@@ -58,10 +65,10 @@ function parseArgs(argv) {
|
|
|
58
65
|
}
|
|
59
66
|
if (token === '--judge-profile') {
|
|
60
67
|
const next = rest[index + 1];
|
|
61
|
-
if (!next ||
|
|
62
|
-
throw new Error('--judge-profile requires local-ollama.');
|
|
68
|
+
if (!next || !['local-ollama', 'cursor', 'claude', 'codex'].includes(next)) {
|
|
69
|
+
throw new Error('--judge-profile requires local-ollama, cursor, claude, or codex.');
|
|
63
70
|
}
|
|
64
|
-
options.judgeProfile =
|
|
71
|
+
options.judgeProfile = next;
|
|
65
72
|
index += 1;
|
|
66
73
|
continue;
|
|
67
74
|
}
|
|
@@ -322,7 +329,7 @@ function printHelp() {
|
|
|
322
329
|
process.stdout.write(`${c}
|
|
323
330
|
|
|
324
331
|
Usage:
|
|
325
|
-
${c} init [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--preset strict|standard|audit-first|l1-full-recommended] [--judge-profile local-ollama] [--judge-provider ollama|openai-compatible] [--judge-model <id|auto>] [--judge-endpoint <url>] [--accept-cloud-judge] [--with-skill] [--dogfood]
|
|
332
|
+
${c} init [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--preset strict|standard|audit-first|l1-full-recommended] [--judge-profile local-ollama|cursor|claude|codex] [--judge-provider ollama|openai-compatible] [--judge-model <id|auto>] [--judge-endpoint <url>] [--accept-cloud-judge] [--with-skill] [--dogfood]
|
|
326
333
|
${c} init-wizard [--target <dir>]
|
|
327
334
|
(--dogfood runs after --preset and sets mode: audit, overriding preset enforce mode)
|
|
328
335
|
${c} upgrade [--target <dir>] [--adapter cursor|claude|codex] [--scope project|global] [--with-skill]
|
|
@@ -349,6 +356,10 @@ async function main() {
|
|
|
349
356
|
printHelp();
|
|
350
357
|
return;
|
|
351
358
|
}
|
|
359
|
+
if (command === 'version') {
|
|
360
|
+
process.stdout.write(`${PACKAGE_VERSION}\n`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
352
363
|
if (command === 'init-wizard') {
|
|
353
364
|
const { runInitWizard } = await import('./commands/init-wizard.js');
|
|
354
365
|
const result = await runInitWizard({ targetDir: options.targetDir });
|
|
@@ -5,7 +5,7 @@ import { detectAdapterName, loadConfigFile } from '../config-io.js';
|
|
|
5
5
|
import { isCapabilityBrokerDemotionActive } from '../core/capability/broker.js';
|
|
6
6
|
import { classifySubagent, classifyToolUse } from '../core/index.js';
|
|
7
7
|
import { isTransactionalEligible } from '../core/transactional/index.js';
|
|
8
|
-
import { classifyShell } from '../core/
|
|
8
|
+
import { classifyShell } from '../core/verdict/adapter.js';
|
|
9
9
|
import { egressStatus } from '../services/egress-service.js';
|
|
10
10
|
import { sandboxStatus } from '../services/sandbox-service.js';
|
|
11
11
|
export async function classifyForReport(params) {
|
|
@@ -57,11 +57,11 @@ export async function classifyForReport(params) {
|
|
|
57
57
|
throw new Error(`Unknown classify kind: ${kind}`);
|
|
58
58
|
}
|
|
59
59
|
const transactionalEligible = kind === 'shell' && isTransactionalEligible(config, 'shell', result);
|
|
60
|
-
const permission = result.
|
|
60
|
+
const permission = result.axes?.would ??
|
|
61
61
|
(result.verdict === 'allow' || result.verdict === 'allow_flagged' ? 'allow' : 'ask');
|
|
62
62
|
const tier = result.reason.startsWith('tier0_') || result.reason === 'external_effect'
|
|
63
63
|
? 'Tier0'
|
|
64
|
-
: result.
|
|
64
|
+
: result.axes?.confidence === 'llm' || result.reason === 'unknown_local_effect'
|
|
65
65
|
? 'Tier1'
|
|
66
66
|
: 'deterministic';
|
|
67
67
|
return {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -90,7 +90,7 @@ export async function doctorProject(options = {}) {
|
|
|
90
90
|
? `Install scope: global (hooks/runtime at ${scopedPaths.hooksDir})`
|
|
91
91
|
: 'Install scope: project');
|
|
92
92
|
notes.push(`Config mode: ${loadedConfig.mode}`);
|
|
93
|
-
notes.push('Verdict engine
|
|
93
|
+
notes.push('Verdict engine (Tier0 + Tier1; location × opacity × effect × confidence). Audit records include schemaVersion 2 axes when available.');
|
|
94
94
|
const repoLocalDir = repoLocalStateDirFor(repoRoot, loadedConfig);
|
|
95
95
|
if (loadedConfig.controlPlane.enabled) {
|
|
96
96
|
notes.push(`Control plane: ${belayStateDir(loadedConfig, repoLocalDir)}`);
|
package/dist/commands/explain.js
CHANGED
|
@@ -70,15 +70,15 @@ export async function explainCommand(options) {
|
|
|
70
70
|
}
|
|
71
71
|
export function formatExplainReport(report) {
|
|
72
72
|
const { result } = report;
|
|
73
|
-
const judgeFields = result.
|
|
73
|
+
const judgeFields = result.axes
|
|
74
74
|
? [
|
|
75
|
-
result.
|
|
76
|
-
result.
|
|
77
|
-
result.
|
|
78
|
-
? ` judgeLatencyMs: ${result.
|
|
75
|
+
result.axes.judgeProvider ? ` judgeProvider: ${result.axes.judgeProvider}` : null,
|
|
76
|
+
result.axes.judgeModelResolved ? ` judgeModel: ${result.axes.judgeModelResolved}` : null,
|
|
77
|
+
result.axes.judgeLatencyMs !== undefined
|
|
78
|
+
? ` judgeLatencyMs: ${result.axes.judgeLatencyMs}`
|
|
79
79
|
: null,
|
|
80
|
-
result.
|
|
81
|
-
? ` judgeFallbackReason: ${result.
|
|
80
|
+
result.axes.judgeFallbackReason
|
|
81
|
+
? ` judgeFallbackReason: ${result.axes.judgeFallbackReason}`
|
|
82
82
|
: null,
|
|
83
83
|
].filter((line) => line !== null)
|
|
84
84
|
: [];
|
|
@@ -106,15 +106,15 @@ export function formatExplainReport(report) {
|
|
|
106
106
|
`Verdict: ${result.verdict}`,
|
|
107
107
|
`Reason: ${result.reason}`,
|
|
108
108
|
`Fingerprint: ${result.fingerprint}`,
|
|
109
|
-
...(result.
|
|
109
|
+
...(result.axes
|
|
110
110
|
? [
|
|
111
111
|
'',
|
|
112
|
-
'
|
|
113
|
-
` location: ${result.
|
|
114
|
-
` opacity: ${result.
|
|
115
|
-
` effect: ${result.
|
|
116
|
-
` confidence: ${result.
|
|
117
|
-
` would: ${result.
|
|
112
|
+
'verdict axes:',
|
|
113
|
+
` location: ${result.axes.location}`,
|
|
114
|
+
` opacity: ${result.axes.opacity}`,
|
|
115
|
+
` effect: ${result.axes.effect}`,
|
|
116
|
+
` confidence: ${result.axes.confidence}`,
|
|
117
|
+
` would: ${result.axes.would}`,
|
|
118
118
|
...(judgeFields.length > 0 ? ['judgeTrace:', ...judgeFields] : []),
|
|
119
119
|
]
|
|
120
120
|
: []),
|
|
@@ -3,8 +3,13 @@ export interface WizardAnswers {
|
|
|
3
3
|
adapter: AdapterName;
|
|
4
4
|
scope: 'project' | 'global';
|
|
5
5
|
withSkill: boolean;
|
|
6
|
+
judgeProfile: 'local-ollama' | 'cursor' | 'claude' | 'codex';
|
|
6
7
|
dogfood: boolean;
|
|
7
8
|
}
|
|
9
|
+
export declare function parseAdapter(value: string | undefined): AdapterName;
|
|
10
|
+
export declare function parseScope(value: string | undefined): 'project' | 'global';
|
|
11
|
+
export declare function parseYesNo(value: string | undefined, defaultValue: boolean): boolean;
|
|
12
|
+
export declare function parseJudgeProfile(value: string | undefined, defaultProfile: 'local-ollama' | 'cursor' | 'claude' | 'codex'): 'local-ollama' | 'cursor' | 'claude' | 'codex';
|
|
8
13
|
export declare function buildInitOptionsFromWizard(answers: WizardAnswers, targetDir?: string): InitOptions;
|
|
9
14
|
export declare function runInitWizard(options?: {
|
|
10
15
|
targetDir?: string;
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { stdin as input, stdout as output } from 'node:process';
|
|
2
2
|
import readline from 'node:readline/promises';
|
|
3
3
|
import { initProject } from '../installer.js';
|
|
4
|
-
function parseAdapter(value) {
|
|
5
|
-
const normalized = (value
|
|
4
|
+
export function parseAdapter(value) {
|
|
5
|
+
const normalized = (value?.trim() || 'cursor').toLowerCase();
|
|
6
6
|
if (normalized === 'claude' || normalized === 'codex' || normalized === 'cursor') {
|
|
7
7
|
return normalized;
|
|
8
8
|
}
|
|
9
9
|
throw new Error(`Unknown adapter: ${value ?? '(empty)'}`);
|
|
10
10
|
}
|
|
11
|
-
function parseScope(value) {
|
|
12
|
-
const normalized = (value
|
|
11
|
+
export function parseScope(value) {
|
|
12
|
+
const normalized = (value?.trim() || 'project').toLowerCase();
|
|
13
13
|
if (normalized === 'global' || normalized === 'project') {
|
|
14
14
|
return normalized;
|
|
15
15
|
}
|
|
16
16
|
throw new Error(`Unknown scope: ${value ?? '(empty)'}`);
|
|
17
17
|
}
|
|
18
|
-
function parseYesNo(value, defaultValue) {
|
|
19
|
-
const normalized = (value
|
|
18
|
+
export function parseYesNo(value, defaultValue) {
|
|
19
|
+
const normalized = (value?.trim() || (defaultValue ? 'y' : 'n')).toLowerCase();
|
|
20
20
|
if (['y', 'yes', 'true', '1'].includes(normalized)) {
|
|
21
21
|
return true;
|
|
22
22
|
}
|
|
@@ -25,12 +25,23 @@ function parseYesNo(value, defaultValue) {
|
|
|
25
25
|
}
|
|
26
26
|
return defaultValue;
|
|
27
27
|
}
|
|
28
|
+
export function parseJudgeProfile(value, defaultProfile) {
|
|
29
|
+
const normalized = (value?.trim() || defaultProfile).toLowerCase();
|
|
30
|
+
if (normalized === 'local-ollama' ||
|
|
31
|
+
normalized === 'cursor' ||
|
|
32
|
+
normalized === 'claude' ||
|
|
33
|
+
normalized === 'codex') {
|
|
34
|
+
return normalized;
|
|
35
|
+
}
|
|
36
|
+
throw new Error(`Unknown judge profile: ${value ?? '(empty)'}`);
|
|
37
|
+
}
|
|
28
38
|
export function buildInitOptionsFromWizard(answers, targetDir) {
|
|
29
39
|
return {
|
|
30
40
|
targetDir,
|
|
31
41
|
adapter: answers.adapter,
|
|
32
42
|
scope: answers.scope,
|
|
33
43
|
withSkill: answers.withSkill,
|
|
44
|
+
judgeProfile: answers.judgeProfile,
|
|
34
45
|
dogfood: answers.dogfood,
|
|
35
46
|
};
|
|
36
47
|
}
|
|
@@ -38,11 +49,13 @@ export async function runInitWizard(options = {}) {
|
|
|
38
49
|
const rl = readline.createInterface({ input, output });
|
|
39
50
|
try {
|
|
40
51
|
output.write('belay init wizard\n');
|
|
41
|
-
const adapter = parseAdapter(await rl.question('Adapter
|
|
42
|
-
const scope = parseScope(await rl.question('Install scope
|
|
43
|
-
const withSkill = parseYesNo(await rl.question('Install SKILL.md and slash commands?
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
const adapter = parseAdapter(await rl.question('Adapter [cursor | claude | codex] (cursor): '));
|
|
53
|
+
const scope = parseScope(await rl.question('Install scope [project | global] (project): '));
|
|
54
|
+
const withSkill = parseYesNo(await rl.question('Install SKILL.md and slash commands? [y | n] (y): '), true);
|
|
55
|
+
// Show Tier1 judge choice explicitly so init defaults are visible in wizard UX.
|
|
56
|
+
const defaultJudgeProfile = adapter;
|
|
57
|
+
const judgeProfile = parseJudgeProfile(await rl.question(`Tier1 judge profile [cursor | claude | codex | local-ollama] (${defaultJudgeProfile}): `), defaultJudgeProfile);
|
|
58
|
+
return initProject(buildInitOptionsFromWizard({ adapter, scope, withSkill, judgeProfile, dogfood: false }, options.targetDir));
|
|
46
59
|
}
|
|
47
60
|
finally {
|
|
48
61
|
rl.close();
|
package/dist/commands/recover.js
CHANGED
|
@@ -20,8 +20,8 @@ export async function recoverProject(options = {}) {
|
|
|
20
20
|
target = {
|
|
21
21
|
summary: classified.input,
|
|
22
22
|
reason: classified.result.reason,
|
|
23
|
-
effect: classified.result.
|
|
24
|
-
location: classified.result.
|
|
23
|
+
effect: classified.result.axes?.effect,
|
|
24
|
+
location: classified.result.axes?.location,
|
|
25
25
|
permission: classified.permission,
|
|
26
26
|
assessment: classified.result.assessment,
|
|
27
27
|
};
|
package/dist/core/approval.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { ApprovalRecord, ApprovalStateFile } from './types.js';
|
|
2
|
+
/** Cursor may invoke the same shell gate more than once per retry; lease covers that window. */
|
|
3
|
+
export declare const APPROVAL_EXECUTION_LEASE_MS = 60000;
|
|
2
4
|
export declare function nowIso(): string;
|
|
3
5
|
export declare function isExpired(approval: ApprovalRecord): boolean;
|
|
6
|
+
export declare function isExecutionLeaseExpired(approval: ApprovalRecord): boolean;
|
|
4
7
|
export declare function compactApprovals(state: ApprovalStateFile): ApprovalStateFile;
|
|
5
8
|
export declare function mergeApprovalStates(target: ApprovalStateFile, source: ApprovalStateFile): ApprovalStateFile;
|
|
6
9
|
export declare function escapeRegex(value: string): string;
|