auto-mode 0.14.0 → 0.16.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 +4 -4
- package/dist/{classify-edit-D8GC6b91.js → classify-edit-LpoeenGt.js} +4 -1
- package/dist/cli.js +139 -121
- package/dist/index.d.ts +210 -15
- package/dist/index.js +7 -2
- package/dist/{render-verdict-QLyj-g7d.js → render-verdict-u3_ftSEp.js} +534 -450
- package/mods/auto-mode/hooks/testing.d.ts +76 -24
- package/mods/auto-mode/hooks/types.ts +15 -1
- package/package.json +25 -12
- package/policy/classifier.md +33 -22
- package/policy/decision.md +11 -5
- package/policy/rules.md +131 -137
- package/mods/auto-mode/hooks/build-prompt-context.claude-check.ts +0 -93
- package/mods/auto-mode/hooks/register.claude-check.ts +0 -1226
package/README.md
CHANGED
|
@@ -58,10 +58,10 @@ assessment.
|
|
|
58
58
|
|
|
59
59
|
## Decisions and failures
|
|
60
60
|
|
|
61
|
-
Jev evaluates each named rule with a typed choice. auto-mode
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
Jev evaluates each named rule with a typed choice between allow and block. auto-mode allows the
|
|
62
|
+
action unless some rule's block probability reaches the threshold, 0.2 by default. Every denial
|
|
63
|
+
names the rule with the highest block probability, the harm, and what clears it, and the agent
|
|
64
|
+
continues on another path. The threshold is configurable and needs evaluation against your actions.
|
|
65
65
|
|
|
66
66
|
A missing key, failed API call, malformed response, or oversized request follows
|
|
67
67
|
`decision.onFailure`. The default `defer` writes no verdict and keeps the prompt; `deny` fails
|
|
@@ -17717,6 +17717,9 @@ function getRE2(pattern) {
|
|
|
17717
17717
|
compiledRE2.set(pattern, compiled);
|
|
17718
17718
|
return compiled;
|
|
17719
17719
|
}
|
|
17720
|
+
//#endregion
|
|
17721
|
+
//#region src/bypass/classify-edit.ts
|
|
17722
|
+
const MAX_SCANNED_BYTES = 262144;
|
|
17720
17723
|
function classifyEdit(action, scope) {
|
|
17721
17724
|
const fields = getEditFields(action.toolName);
|
|
17722
17725
|
if (fields === null) return {
|
|
@@ -17747,7 +17750,7 @@ function classifyEdit(action, scope) {
|
|
|
17747
17750
|
kind: "jev",
|
|
17748
17751
|
reason: text.reason
|
|
17749
17752
|
};
|
|
17750
|
-
if (Buffer.byteLength(text, "utf8") >
|
|
17753
|
+
if (Buffer.byteLength(text, "utf8") > MAX_SCANNED_BYTES) return {
|
|
17751
17754
|
kind: "jev",
|
|
17752
17755
|
reason: "content larger than the secret scan reads"
|
|
17753
17756
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { D as
|
|
2
|
+
import { C as resolveStateDir, D as DEFAULT_SCOPE_SOURCES, E as DEFAULT_DENIAL_BUDGET, F as string, N as number, O as loadConfig, P as strictObject, a as loadSessionScope, b as splitCommandWords, f as loadRepositoryContext, g as loadPolicy, i as resolveSessionScopePath, j as readHostEnvironment, n as parseActionRequest, o as EMPTY_SESSION_SCOPE, p as loadCheckoutRemotes, r as classifyAction, t as renderVerdict, v as toRepositorySlug, w as findCheckout, y as splitShellCommand } from "./render-verdict-u3_ftSEp.js";
|
|
3
3
|
import { appendFile, mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
4
4
|
import { dirname, isAbsolute, join, normalize } from "node:path";
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
5
|
import { spawn } from "node:child_process";
|
|
7
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
7
|
import { text } from "node:stream/consumers";
|
|
@@ -120,10 +119,10 @@ function formatDenialsLeft(left) {
|
|
|
120
119
|
}
|
|
121
120
|
//#endregion
|
|
122
121
|
//#region src/budget/resolve-denial-state-path.ts
|
|
123
|
-
function resolveDenialStatePath(request) {
|
|
122
|
+
function resolveDenialStatePath(request, stateDir) {
|
|
124
123
|
const agentID = request.decisionContext?.agentID ?? "";
|
|
125
124
|
const key = createHash("sha256").update(`${request.sessionID}\0${agentID}`).digest("hex").slice(0, 32);
|
|
126
|
-
return join(
|
|
125
|
+
return join(stateDir, "denials", `${key}.json`);
|
|
127
126
|
}
|
|
128
127
|
//#endregion
|
|
129
128
|
//#region src/budget/write-denial-state.ts
|
|
@@ -150,8 +149,8 @@ function buildJevOnlyConfig(config) {
|
|
|
150
149
|
}
|
|
151
150
|
//#endregion
|
|
152
151
|
//#region src/diagnostics/write-action-diagnostic.ts
|
|
153
|
-
async function writeActionDiagnostic(payload, entry) {
|
|
154
|
-
const path =
|
|
152
|
+
async function writeActionDiagnostic(payload, entry, host, warnings) {
|
|
153
|
+
const path = host.env["AUTO_MODE_DIAGNOSTICS_PATH"] ?? join(resolveStateDir(host), "actions.jsonl");
|
|
155
154
|
if (path === "") return;
|
|
156
155
|
const record = {
|
|
157
156
|
schemaVersion: 3,
|
|
@@ -173,29 +172,32 @@ async function writeActionDiagnostic(payload, entry) {
|
|
|
173
172
|
});
|
|
174
173
|
await appendFile(path, `${JSON.stringify(record)}\n`, { mode: 384 });
|
|
175
174
|
} catch {
|
|
176
|
-
|
|
175
|
+
warnings.write("auto-mode: diagnostics unavailable\n");
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
178
|
function toHash(value) {
|
|
180
179
|
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
|
181
180
|
}
|
|
182
181
|
//#endregion
|
|
183
|
-
//#region src/request/
|
|
184
|
-
const
|
|
182
|
+
//#region src/request/scope-record-request-schema.ts
|
|
183
|
+
const scopeRecordRequestSchema = strictObject({
|
|
185
184
|
sessionID: string().min(1),
|
|
186
185
|
cwd: string().min(1),
|
|
187
186
|
startedAt: number().int().positive(),
|
|
188
187
|
command: string().min(1),
|
|
189
188
|
resultText: string()
|
|
190
189
|
});
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/request/parse-scope-record-request.ts
|
|
191
192
|
function parseScopeRecordRequest(body) {
|
|
192
|
-
const parsed =
|
|
193
|
+
const parsed = scopeRecordRequestSchema.safeParse(body);
|
|
193
194
|
return parsed.success ? parsed.data : null;
|
|
194
195
|
}
|
|
195
196
|
//#endregion
|
|
196
197
|
//#region src/scope/read-pull-request.ts
|
|
197
198
|
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
198
|
-
function readPullRequest(repository, number) {
|
|
199
|
+
function readPullRequest(repository, number, options = {}) {
|
|
200
|
+
const [file, ...prefix] = options.command ?? ["gh"];
|
|
199
201
|
const args = [
|
|
200
202
|
"pr",
|
|
201
203
|
"view",
|
|
@@ -206,7 +208,8 @@ function readPullRequest(repository, number) {
|
|
|
206
208
|
"headRefName,createdAt"
|
|
207
209
|
];
|
|
208
210
|
return new Promise((resolve) => {
|
|
209
|
-
const child = spawn(
|
|
211
|
+
const child = spawn(file, [
|
|
212
|
+
...prefix,
|
|
210
213
|
...args,
|
|
211
214
|
"--jq",
|
|
212
215
|
"[.headRefName, .createdAt] | @tsv"
|
|
@@ -216,7 +219,7 @@ function readPullRequest(repository, number) {
|
|
|
216
219
|
"pipe",
|
|
217
220
|
"ignore"
|
|
218
221
|
],
|
|
219
|
-
timeout: LOOKUP_TIMEOUT_MS
|
|
222
|
+
timeout: options.timeoutMs ?? LOOKUP_TIMEOUT_MS
|
|
220
223
|
});
|
|
221
224
|
let output = "";
|
|
222
225
|
child.stdout.setEncoding("utf8");
|
|
@@ -365,13 +368,17 @@ async function readBranchCreatedAt(commonDir, branch) {
|
|
|
365
368
|
}
|
|
366
369
|
//#endregion
|
|
367
370
|
//#region src/scope/write-session-scope.ts
|
|
368
|
-
|
|
371
|
+
const SYSTEM_CLOCK = {
|
|
372
|
+
now: Date.now,
|
|
373
|
+
wait: (ms) => setTimeout(ms)
|
|
374
|
+
};
|
|
375
|
+
async function writeSessionScope(path, added, clock = SYSTEM_CLOCK) {
|
|
369
376
|
await mkdir(dirname(path), {
|
|
370
377
|
recursive: true,
|
|
371
378
|
mode: 448
|
|
372
379
|
});
|
|
373
380
|
const lock = `${path}.lock`;
|
|
374
|
-
await claimLock(lock);
|
|
381
|
+
await claimLock(lock, clock);
|
|
375
382
|
try {
|
|
376
383
|
const merged = mergeSessionScope([await loadSessionScope(path), added]);
|
|
377
384
|
const staged = `${path}.${randomUUID()}.tmp`;
|
|
@@ -384,8 +391,8 @@ async function writeSessionScope(path, added) {
|
|
|
384
391
|
const STALE_LOCK_MS = 1e4;
|
|
385
392
|
const LOCK_WAIT_MS = 3e3;
|
|
386
393
|
const LOCK_POLL_MS = 10;
|
|
387
|
-
async function claimLock(lock) {
|
|
388
|
-
const deadline =
|
|
394
|
+
async function claimLock(lock, clock) {
|
|
395
|
+
const deadline = clock.now() + LOCK_WAIT_MS;
|
|
389
396
|
for (;;) {
|
|
390
397
|
try {
|
|
391
398
|
await (await open(lock, "wx", 384)).close();
|
|
@@ -394,9 +401,9 @@ async function claimLock(lock) {
|
|
|
394
401
|
if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
|
|
395
402
|
}
|
|
396
403
|
const held = await stat(lock).catch(() => null);
|
|
397
|
-
if (held !== null &&
|
|
398
|
-
else if (
|
|
399
|
-
else await
|
|
404
|
+
if (held !== null && clock.now() - held.mtimeMs > STALE_LOCK_MS) await rm(lock, { force: true });
|
|
405
|
+
else if (clock.now() > deadline) throw new Error("session scope lock unavailable");
|
|
406
|
+
else await clock.wait(LOCK_POLL_MS);
|
|
400
407
|
}
|
|
401
408
|
}
|
|
402
409
|
//#endregion
|
|
@@ -407,12 +414,12 @@ async function updateSessionScope(request, options) {
|
|
|
407
414
|
if (age < -6e4 || age > MAX_CALL_AGE_MS) return EMPTY_SESSION_SCOPE;
|
|
408
415
|
const events = collectScopeEvents(request.command, request.cwd, options.home);
|
|
409
416
|
const scope = mergeSessionScope(await Promise.all(events.map((event) => tryVerifyScopeEvent(event, request, options))));
|
|
410
|
-
if (scope.worktrees.length + scope.branches.length + scope.pullRequests.length > 0) await writeSessionScope(resolveSessionScopePath(options.stateDir, request.sessionID), scope);
|
|
417
|
+
if (scope.worktrees.length + scope.branches.length + scope.pullRequests.length > 0) await writeSessionScope(resolveSessionScopePath(options.stateDir, request.sessionID), scope, options.lockClock);
|
|
411
418
|
return scope;
|
|
412
419
|
}
|
|
413
420
|
async function tryVerifyScopeEvent(event, request, options) {
|
|
414
421
|
try {
|
|
415
|
-
return await verifyScopeEvent(event, request, options);
|
|
422
|
+
return await (options.verifyEvent === void 0 ? verifyScopeEvent(event, request, options) : options.verifyEvent(event, request));
|
|
416
423
|
} catch {
|
|
417
424
|
return EMPTY_SESSION_SCOPE;
|
|
418
425
|
}
|
|
@@ -422,9 +429,9 @@ const FILE_CLOCK_SLACK_MS = 50;
|
|
|
422
429
|
async function verifyScopeEvent(event, request, options) {
|
|
423
430
|
if (event.kind === "worktree") {
|
|
424
431
|
const link = await stat(join(event.path, ".git"));
|
|
425
|
-
const checkout = await findCheckout(event.path);
|
|
432
|
+
const checkout = await findCheckout(event.path, options.env);
|
|
426
433
|
if (!link.isFile() || link.mtimeMs < request.startedAt - FILE_CLOCK_SLACK_MS || checkout?.worktree !== event.path) return EMPTY_SESSION_SCOPE;
|
|
427
|
-
const branch = (await loadRepositoryContext(event.path))?.branch ?? null;
|
|
434
|
+
const branch = (await loadRepositoryContext(event.path, options.env))?.branch ?? null;
|
|
428
435
|
if (branch === null) return {
|
|
429
436
|
...EMPTY_SESSION_SCOPE,
|
|
430
437
|
worktrees: [event.path]
|
|
@@ -439,7 +446,7 @@ async function verifyScopeEvent(event, request, options) {
|
|
|
439
446
|
}] : []
|
|
440
447
|
};
|
|
441
448
|
}
|
|
442
|
-
const checkout = await findCheckout(event.directory);
|
|
449
|
+
const checkout = await findCheckout(event.directory, options.env);
|
|
443
450
|
if (checkout === null) return EMPTY_SESSION_SCOPE;
|
|
444
451
|
if (event.kind === "branch") return await isBranchCreatedSince(checkout.commonDir, event.name, request.startedAt) ? {
|
|
445
452
|
...EMPTY_SESSION_SCOPE,
|
|
@@ -470,7 +477,7 @@ async function isBranchCreatedSince(commonDir, branch, since) {
|
|
|
470
477
|
return ref !== null && ref.mtimeMs >= since - FILE_CLOCK_SLACK_MS;
|
|
471
478
|
}
|
|
472
479
|
//#endregion
|
|
473
|
-
//#region src/cli.ts
|
|
480
|
+
//#region src/run-cli.ts
|
|
474
481
|
const USAGE = `auto-mode — a permission classifier for the auto-mode Claude Code mod
|
|
475
482
|
|
|
476
483
|
Usage:
|
|
@@ -486,44 +493,85 @@ Options:
|
|
|
486
493
|
--jev-only With run: require Jev and cap its API timeout at 5 seconds
|
|
487
494
|
--evaluation-deadline <unix-ms> With --jev-only: share the helper and API deadline
|
|
488
495
|
`;
|
|
489
|
-
function
|
|
490
|
-
|
|
496
|
+
async function runCLI(argv, io) {
|
|
497
|
+
const args = parseArgs({
|
|
498
|
+
args: [...argv],
|
|
499
|
+
allowPositionals: true,
|
|
500
|
+
options: {
|
|
501
|
+
classifier: { type: "string" },
|
|
502
|
+
rules: { type: "string" },
|
|
503
|
+
explain: { type: "boolean" },
|
|
504
|
+
"local-only": { type: "boolean" },
|
|
505
|
+
"jev-only": { type: "boolean" },
|
|
506
|
+
"evaluation-deadline": { type: "string" },
|
|
507
|
+
help: {
|
|
508
|
+
type: "boolean",
|
|
509
|
+
short: "h"
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
const [command] = args.positionals;
|
|
514
|
+
if (args.values.help === true || command === void 0) {
|
|
515
|
+
io.stdout.write(USAGE);
|
|
516
|
+
return 0;
|
|
517
|
+
}
|
|
518
|
+
if (command === "run") try {
|
|
519
|
+
return await run(io, args.values.explain === true, args.values["local-only"] === true, args.values["jev-only"] === true, args.values["evaluation-deadline"]);
|
|
520
|
+
} catch {
|
|
521
|
+
io.stderr.write("auto-mode: configuration unreadable; no verdict\n");
|
|
522
|
+
return 0;
|
|
523
|
+
}
|
|
524
|
+
if (command === "record") return runRecord(io);
|
|
525
|
+
if (command === "print-prompt") {
|
|
526
|
+
const config = await loadConfig(void 0, io.host);
|
|
527
|
+
printWarnings(io.stderr, config.warnings);
|
|
528
|
+
const framework = config.provider.protocol === "system-one" ? "decision.md" : "classifier.md";
|
|
529
|
+
const prompt = await loadPolicy({
|
|
530
|
+
classifierPath: args.values.classifier ?? config.classifierPath,
|
|
531
|
+
rulesPath: args.values.rules ?? config.rulesPath
|
|
532
|
+
}, framework);
|
|
533
|
+
io.stdout.write(`${prompt}\n`);
|
|
534
|
+
return 0;
|
|
535
|
+
}
|
|
536
|
+
io.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
|
|
537
|
+
return 2;
|
|
491
538
|
}
|
|
492
|
-
async function run(explain, localOnly, jevOnly, deadline) {
|
|
493
|
-
const raw = await
|
|
539
|
+
async function run(io, explain, localOnly, jevOnly, deadline) {
|
|
540
|
+
const raw = await io.stdin();
|
|
494
541
|
let body;
|
|
495
542
|
try {
|
|
496
543
|
body = JSON.parse(raw);
|
|
497
544
|
} catch {
|
|
498
|
-
return printNote(explain, "stdin is not JSON, so no verdict");
|
|
545
|
+
return printNote(io.stderr, explain, "stdin is not JSON, so no verdict");
|
|
499
546
|
}
|
|
500
547
|
const request = parseActionRequest(body);
|
|
501
|
-
if (request === null) return printNote(explain, "not an action request this classifier judges, so no verdict");
|
|
548
|
+
if (request === null) return printNote(io.stderr, explain, "not an action request this classifier judges, so no verdict");
|
|
549
|
+
const host = io.host;
|
|
502
550
|
const invocationID = randomUUID();
|
|
503
551
|
await writeActionDiagnostic(request, {
|
|
504
552
|
invocationID,
|
|
505
553
|
status: "started"
|
|
506
|
-
});
|
|
554
|
+
}, host, io.stderr);
|
|
507
555
|
let loaded;
|
|
508
556
|
try {
|
|
509
|
-
loaded = await loadConfig();
|
|
557
|
+
loaded = await loadConfig(void 0, host);
|
|
510
558
|
} catch {
|
|
511
559
|
await writeActionDiagnostic(request, {
|
|
512
560
|
invocationID,
|
|
513
561
|
status: "failure",
|
|
514
562
|
verdict: "defer"
|
|
515
|
-
});
|
|
516
|
-
return printNote(true, "configuration unreadable; no verdict");
|
|
563
|
+
}, host, io.stderr);
|
|
564
|
+
return printNote(io.stderr, true, "configuration unreadable; no verdict");
|
|
517
565
|
}
|
|
518
|
-
printWarnings(loaded.warnings);
|
|
566
|
+
printWarnings(io.stderr, loaded.warnings);
|
|
519
567
|
const config = jevOnly ? buildJevOnlyConfig(loaded) : loaded;
|
|
520
568
|
if (config === null) {
|
|
521
569
|
await writeActionDiagnostic(request, {
|
|
522
570
|
invocationID,
|
|
523
571
|
status: "failure",
|
|
524
572
|
verdict: "defer"
|
|
525
|
-
});
|
|
526
|
-
return printNote(true, "Jev-only evaluation requires system-one; no verdict");
|
|
573
|
+
}, host, io.stderr);
|
|
574
|
+
return printNote(io.stderr, true, "Jev-only evaluation requires system-one; no verdict");
|
|
527
575
|
}
|
|
528
576
|
const deadlineAt = deadline === void 0 ? void 0 : Number(deadline);
|
|
529
577
|
if (deadlineAt !== void 0 && (!jevOnly || !Number.isSafeInteger(deadlineAt) || deadlineAt <= 0)) {
|
|
@@ -531,26 +579,24 @@ async function run(explain, localOnly, jevOnly, deadline) {
|
|
|
531
579
|
invocationID,
|
|
532
580
|
status: "failure",
|
|
533
581
|
verdict: "defer"
|
|
534
|
-
});
|
|
535
|
-
return printNote(true, "invalid Jev evaluation deadline; no verdict");
|
|
582
|
+
}, host, io.stderr);
|
|
583
|
+
return printNote(io.stderr, true, "invalid Jev evaluation deadline; no verdict");
|
|
536
584
|
}
|
|
537
585
|
const controller = new AbortController();
|
|
538
|
-
const
|
|
586
|
+
const unsubscribe = jevOnly ? io.subscribeToStopSignals(() => {
|
|
539
587
|
controller.abort();
|
|
540
|
-
};
|
|
541
|
-
if (jevOnly) {
|
|
542
|
-
process.on("SIGTERM", stopEvaluation);
|
|
543
|
-
process.on("SIGINT", stopEvaluation);
|
|
544
|
-
}
|
|
588
|
+
}) : null;
|
|
545
589
|
try {
|
|
546
|
-
const statePath = resolveDenialStatePath(request);
|
|
590
|
+
const statePath = resolveDenialStatePath(request, resolveStateDir(host));
|
|
547
591
|
const retryKey = buildRetryKey(request);
|
|
548
592
|
const before = await loadDenialState(statePath);
|
|
549
593
|
const retry = findRetryDeny(before, retryKey);
|
|
550
594
|
const outcome = retry === null ? await classifyAction(request, config, {
|
|
551
595
|
localOnly,
|
|
552
596
|
deadlineAt,
|
|
553
|
-
signal: controller.signal
|
|
597
|
+
signal: controller.signal,
|
|
598
|
+
host,
|
|
599
|
+
stderr: io.stderr
|
|
554
600
|
}) : {
|
|
555
601
|
verdict: retry,
|
|
556
602
|
decidingStage: "retry",
|
|
@@ -559,8 +605,8 @@ async function run(explain, localOnly, jevOnly, deadline) {
|
|
|
559
605
|
};
|
|
560
606
|
const latest = retry === null ? await loadDenialState(statePath) : before;
|
|
561
607
|
const plan = planDenialBudget(latest, outcome.verdict, retryKey, config.denialBudget ?? DEFAULT_DENIAL_BUDGET);
|
|
562
|
-
if (plan.state !== latest) await tryWriteDenialState(statePath, plan.state);
|
|
563
|
-
if (plan.verdict !== null)
|
|
608
|
+
if (plan.state !== latest) await tryWriteDenialState(io.stderr, statePath, plan.state);
|
|
609
|
+
if (plan.verdict !== null) printVerdict(io.stdout, plan.verdict);
|
|
564
610
|
await writeActionDiagnostic(request, {
|
|
565
611
|
invocationID,
|
|
566
612
|
status: outcome.status,
|
|
@@ -572,16 +618,32 @@ async function run(explain, localOnly, jevOnly, deadline) {
|
|
|
572
618
|
},
|
|
573
619
|
escalation: plan.escalation,
|
|
574
620
|
...outcome.diagnostics === void 0 ? {} : { diagnostics: outcome.diagnostics }
|
|
575
|
-
});
|
|
621
|
+
}, host, io.stderr);
|
|
576
622
|
const note = plan.escalation ? "denial budget exhausted; the user decides this action" : outcome.note;
|
|
577
|
-
return printNote(explain || outcome.unavailable === true, note);
|
|
623
|
+
return printNote(io.stderr, explain || outcome.unavailable === true, note);
|
|
578
624
|
} finally {
|
|
579
|
-
|
|
580
|
-
|
|
625
|
+
unsubscribe?.();
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function printNote(stderr, explain, message) {
|
|
629
|
+
if (explain) stderr.write(`auto-mode: ${message}\n`);
|
|
630
|
+
return 0;
|
|
631
|
+
}
|
|
632
|
+
function printWarnings(stderr, warnings = []) {
|
|
633
|
+
for (const warning of warnings) stderr.write(`auto-mode: ${warning}\n`);
|
|
634
|
+
}
|
|
635
|
+
async function tryWriteDenialState(stderr, path, state) {
|
|
636
|
+
try {
|
|
637
|
+
await writeDenialState(path, state);
|
|
638
|
+
} catch {
|
|
639
|
+
stderr.write("auto-mode: denial counts unavailable\n");
|
|
581
640
|
}
|
|
582
641
|
}
|
|
583
|
-
|
|
584
|
-
|
|
642
|
+
function printVerdict(stdout, verdict) {
|
|
643
|
+
stdout.write(renderVerdict(verdict));
|
|
644
|
+
}
|
|
645
|
+
async function runRecord(io) {
|
|
646
|
+
const raw = await io.stdin();
|
|
585
647
|
let body;
|
|
586
648
|
try {
|
|
587
649
|
body = JSON.parse(raw);
|
|
@@ -591,78 +653,34 @@ async function runRecord() {
|
|
|
591
653
|
const request = parseScopeRecordRequest(body);
|
|
592
654
|
if (request === null) return 0;
|
|
593
655
|
try {
|
|
594
|
-
const config = await loadConfig();
|
|
656
|
+
const config = await loadConfig(void 0, io.host);
|
|
595
657
|
if (Object.values(config.scopeSources ?? DEFAULT_SCOPE_SOURCES).some((source) => source.kind === "session")) await updateSessionScope(request, {
|
|
596
658
|
now: Date.now(),
|
|
597
|
-
stateDir: resolveStateDir(),
|
|
598
|
-
home:
|
|
659
|
+
stateDir: resolveStateDir(io.host),
|
|
660
|
+
home: io.host.home,
|
|
661
|
+
env: io.host.env,
|
|
599
662
|
readPullRequest
|
|
600
663
|
});
|
|
601
664
|
} catch {
|
|
602
|
-
|
|
665
|
+
io.stderr.write("auto-mode: session scope unavailable\n");
|
|
603
666
|
}
|
|
604
667
|
return 0;
|
|
605
668
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (explain) process.stderr.write(`auto-mode: ${message}\n`);
|
|
621
|
-
return 0;
|
|
622
|
-
}
|
|
623
|
-
async function main(argv) {
|
|
624
|
-
const args = parseArgs({
|
|
625
|
-
args: [...argv],
|
|
626
|
-
allowPositionals: true,
|
|
627
|
-
options: {
|
|
628
|
-
classifier: { type: "string" },
|
|
629
|
-
rules: { type: "string" },
|
|
630
|
-
explain: { type: "boolean" },
|
|
631
|
-
"local-only": { type: "boolean" },
|
|
632
|
-
"jev-only": { type: "boolean" },
|
|
633
|
-
"evaluation-deadline": { type: "string" },
|
|
634
|
-
help: {
|
|
635
|
-
type: "boolean",
|
|
636
|
-
short: "h"
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
});
|
|
640
|
-
const [command] = args.positionals;
|
|
641
|
-
if (args.values.help === true || command === void 0) {
|
|
642
|
-
process.stdout.write(USAGE);
|
|
643
|
-
return 0;
|
|
644
|
-
}
|
|
645
|
-
if (command === "run") try {
|
|
646
|
-
return await run(args.values.explain === true, args.values["local-only"] === true, args.values["jev-only"] === true, args.values["evaluation-deadline"]);
|
|
647
|
-
} catch {
|
|
648
|
-
process.stderr.write("auto-mode: configuration unreadable; no verdict\n");
|
|
649
|
-
return 0;
|
|
650
|
-
}
|
|
651
|
-
if (command === "record") return runRecord();
|
|
652
|
-
if (command === "print-prompt") {
|
|
653
|
-
const config = await loadConfig();
|
|
654
|
-
printWarnings(config.warnings);
|
|
655
|
-
const framework = config.provider.protocol === "system-one" ? "decision.md" : "classifier.md";
|
|
656
|
-
const prompt = await loadPolicy({
|
|
657
|
-
classifierPath: args.values.classifier ?? config.classifierPath,
|
|
658
|
-
rulesPath: args.values.rules ?? config.rulesPath
|
|
659
|
-
}, framework);
|
|
660
|
-
process.stdout.write(`${prompt}\n`);
|
|
661
|
-
return 0;
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/cli.ts
|
|
671
|
+
process.exitCode = await runCLI(process.argv.slice(2), {
|
|
672
|
+
stdin: () => text(process.stdin),
|
|
673
|
+
stdout: process.stdout,
|
|
674
|
+
stderr: process.stderr,
|
|
675
|
+
host: readHostEnvironment(),
|
|
676
|
+
subscribeToStopSignals: (onStop) => {
|
|
677
|
+
process.on("SIGTERM", onStop);
|
|
678
|
+
process.on("SIGINT", onStop);
|
|
679
|
+
return () => {
|
|
680
|
+
process.off("SIGTERM", onStop);
|
|
681
|
+
process.off("SIGINT", onStop);
|
|
682
|
+
};
|
|
662
683
|
}
|
|
663
|
-
|
|
664
|
-
return 2;
|
|
665
|
-
}
|
|
666
|
-
process.exitCode = await main(process.argv.slice(2));
|
|
684
|
+
});
|
|
667
685
|
//#endregion
|
|
668
686
|
export {};
|