finitemonkey 0.1.0-next.16 → 0.1.0-next.17

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.
@@ -69,7 +69,7 @@ var resolveWindowsCommand = (command, env, exists) => {
69
69
  }
70
70
  return { command, shell: false };
71
71
  };
72
- var windowsCommandLine = (resolved, args) => [quoteForCmd(resolved.command), ...args.map(quoteForCmd)].join(" ");
72
+ var windowsCommandLine = (resolved, args2) => [quoteForCmd(resolved.command), ...args2.map(quoteForCmd)].join(" ");
73
73
 
74
74
  // src/core/node-exec.ts
75
75
  var SHELL_NOT_FOUND = /* @__PURE__ */ new Set([127, 9009]);
@@ -117,9 +117,9 @@ var nodeAppendText = (path, content) => {
117
117
  return reject(error instanceof Error ? error.message : String(error));
118
118
  }
119
119
  };
120
- var nodeSpawnSelf = (selfPath) => (args, cwd) => {
120
+ var nodeSpawnSelf = (selfPath) => (args2, cwd) => {
121
121
  try {
122
- const child2 = spawn(process.execPath, [selfPath, ...args], {
122
+ const child2 = spawn(process.execPath, [selfPath, ...args2], {
123
123
  cwd,
124
124
  detached: true,
125
125
  stdio: "ignore",
@@ -290,8 +290,8 @@ import { createInterface } from "node:readline";
290
290
  var MAX_BUFFER2 = 256 * 1024 * 1024;
291
291
  var GIT_ENV = { ...process.env, GIT_PAGER: "cat", GIT_TERMINAL_PROMPT: "0", LC_ALL: "C" };
292
292
  var notFound = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
293
- var nodeGit = (args, cwd) => {
294
- const result = spawnSync2("git", [...args], {
293
+ var nodeGit = (args2, cwd) => {
294
+ const result = spawnSync2("git", [...args2], {
295
295
  cwd,
296
296
  encoding: "utf8",
297
297
  maxBuffer: MAX_BUFFER2,
@@ -305,8 +305,8 @@ var nodeGit = (args, cwd) => {
305
305
  stdout: typeof stdout === "string" ? stdout : ""
306
306
  };
307
307
  };
308
- var nodeGitLines = (args, cwd, onLine) => new Promise((resolve2) => {
309
- const child2 = spawn2("git", [...args], {
308
+ var nodeGitLines = (args2, cwd, onLine) => new Promise((resolve2) => {
309
+ const child2 = spawn2("git", [...args2], {
310
310
  cwd,
311
311
  windowsHide: true,
312
312
  env: GIT_ENV,
@@ -323,8 +323,8 @@ var nodeGitLines = (args, cwd, onLine) => new Promise((resolve2) => {
323
323
  });
324
324
  });
325
325
  var textOf = (value) => typeof value === "string" ? value : "";
326
- var nodeGitWith = (args, cwd, options) => {
327
- const result = spawnSync2("git", [...args], {
326
+ var nodeGitWith = (args2, cwd, options) => {
327
+ const result = spawnSync2("git", [...args2], {
328
328
  cwd,
329
329
  encoding: "utf8",
330
330
  maxBuffer: MAX_BUFFER2,
@@ -350,15 +350,15 @@ var errnoCode2 = (error) => {
350
350
  if (!(error instanceof Error) || !("code" in error)) return void 0;
351
351
  return typeof error.code === "string" ? error.code : void 0;
352
352
  };
353
- var spawnProbe = (command, args, timeout) => {
353
+ var spawnProbe = (command, args2, timeout) => {
354
354
  const options = { encoding: "utf8", timeout, windowsHide: true };
355
- return process.platform === "win32" ? spawnSync3([command, ...args].join(" "), { ...options, shell: true }) : spawnSync3(command, [...args], options);
355
+ return process.platform === "win32" ? spawnSync3([command, ...args2].join(" "), { ...options, shell: true }) : spawnSync3(command, [...args2], options);
356
356
  };
357
- var nodeProbe = (command, args, timeoutMs) => {
358
- if (![command, ...args].every((token) => SAFE_TOKEN.test(token))) {
357
+ var nodeProbe = (command, args2, timeoutMs) => {
358
+ if (![command, ...args2].every((token) => SAFE_TOKEN.test(token))) {
359
359
  return { found: false, ok: false, stdout: "", timedOut: false };
360
360
  }
361
- const result = spawnProbe(command, args, timeoutMs);
361
+ const result = spawnProbe(command, args2, timeoutMs);
362
362
  const code = errnoCode2(result.error);
363
363
  const stdout = result.stdout;
364
364
  return {
@@ -409,14 +409,155 @@ var nodeRuntime = () => ({
409
409
  exec: nodeExec,
410
410
  appendText: nodeAppendText,
411
411
  spawnSelf: nodeSpawnSelf(SELF_PATH),
412
+ execSelf: (args2, cwd, timeoutMs) => nodeExec({ command: process.execPath, args: [SELF_PATH, ...args2], cwd, timeoutMs }),
412
413
  gitWith: nodeGitWith,
413
414
  realpath: nodeRealpath,
414
415
  homeDir: homedir,
415
416
  systemLocale: () => Intl.DateTimeFormat().resolvedOptions().locale
416
417
  });
417
418
 
418
- // src/commands/doctor.ts
419
- import { dirname as dirname7 } from "node:path";
419
+ // src/app/node-process.ts
420
+ import { spawn as spawn3, spawnSync as spawnSync4 } from "node:child_process";
421
+ import { connect } from "node:net";
422
+ var MAX_OUTPUT = 64 * 1024;
423
+ var POLL_MS = 150;
424
+ var STOP_GRACE_MS = 3e3;
425
+ var sleep = (ms) => new Promise((resolve2) => {
426
+ setTimeout(resolve2, ms);
427
+ });
428
+ var portOpen = (host, port) => new Promise((resolve2) => {
429
+ const socket = connect({ host, port });
430
+ const done = (open) => {
431
+ socket.destroy();
432
+ resolve2(open);
433
+ };
434
+ socket.setTimeout(1e3, () => {
435
+ done(false);
436
+ });
437
+ socket.once("connect", () => {
438
+ done(true);
439
+ });
440
+ socket.once("error", () => {
441
+ done(false);
442
+ });
443
+ });
444
+ var startApp = (command, cwd, port) => {
445
+ const child2 = spawn3(command, {
446
+ cwd,
447
+ shell: true,
448
+ detached: process.platform !== "win32",
449
+ windowsHide: true,
450
+ stdio: ["ignore", "pipe", "pipe"],
451
+ env: { ...process.env, PORT: String(port) }
452
+ });
453
+ let output = "";
454
+ let exit;
455
+ const collect4 = (chunk) => {
456
+ output = `${output}${chunk.toString("utf8")}`.slice(-MAX_OUTPUT);
457
+ };
458
+ child2.stdout.on("data", collect4);
459
+ child2.stderr.on("data", collect4);
460
+ child2.once("error", (error) => {
461
+ output = `${output}${error.message}`;
462
+ exit = { code: null };
463
+ });
464
+ child2.once("exit", (code) => {
465
+ exit = { code };
466
+ });
467
+ return { child: child2, output: () => output, exited: () => exit };
468
+ };
469
+ var waitForPort = async (app, host, port, deadline) => {
470
+ while (Date.now() < deadline) {
471
+ if (await portOpen(host, port)) return "open";
472
+ if (app.exited() !== void 0) return "exited";
473
+ await sleep(POLL_MS);
474
+ }
475
+ return "timeout";
476
+ };
477
+ var signalGroup = (pid, signal) => {
478
+ try {
479
+ process.kill(-pid, signal);
480
+ } catch {
481
+ return;
482
+ }
483
+ };
484
+ var alive = (pid) => {
485
+ try {
486
+ process.kill(-pid, 0);
487
+ return true;
488
+ } catch {
489
+ return false;
490
+ }
491
+ };
492
+ var stopApp = async (app) => {
493
+ const pid = app.child.pid;
494
+ if (pid === void 0) return true;
495
+ if (process.platform === "win32") {
496
+ spawnSync4("taskkill", ["/pid", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" });
497
+ return true;
498
+ }
499
+ signalGroup(pid, "SIGTERM");
500
+ const deadline = Date.now() + STOP_GRACE_MS;
501
+ while (alive(pid) && Date.now() < deadline) await sleep(POLL_MS);
502
+ if (alive(pid)) signalGroup(pid, "SIGKILL");
503
+ return !alive(pid);
504
+ };
505
+
506
+ // src/app/node-probe.ts
507
+ var BODY_EXCERPT = 200;
508
+ var MAX_BODY = 256 * 1024;
509
+ var url = (request) => {
510
+ const host = request.host.includes(":") ? `[${request.host}]` : request.host;
511
+ return `http://${host}:${String(request.port)}${request.route.startsWith("/") ? "" : "/"}${request.route}`;
512
+ };
513
+ var ask = async (request, deadline) => {
514
+ try {
515
+ const response = await fetch(url(request), {
516
+ redirect: "manual",
517
+ signal: AbortSignal.timeout(Math.max(1e3, deadline - Date.now()))
518
+ });
519
+ const body2 = (await response.text()).slice(0, MAX_BODY);
520
+ const excerpt = body2.slice(0, BODY_EXCERPT);
521
+ if (response.status !== request.status)
522
+ return { kind: "wrong_status", status: response.status, bodyExcerpt: excerpt };
523
+ if (request.body !== void 0 && !body2.includes(request.body)) {
524
+ return { kind: "body_mismatch", status: response.status, bodyExcerpt: excerpt };
525
+ }
526
+ return { kind: "ok", status: response.status };
527
+ } catch (error) {
528
+ return { kind: "request_failed", bodyExcerpt: error instanceof Error ? error.message : String(error) };
529
+ }
530
+ };
531
+ var finish = async (app, answer, started) => {
532
+ const stopped = app === void 0 ? true : await stopApp(app);
533
+ return {
534
+ ...answer,
535
+ reused: app === void 0,
536
+ output: app?.output() ?? "",
537
+ stopped,
538
+ durationMs: Date.now() - started
539
+ };
540
+ };
541
+ var probeApp = async (request) => {
542
+ const started = Date.now();
543
+ const deadline = started + request.timeoutMs;
544
+ if (await portOpen(request.host, request.port)) return finish(void 0, await ask(request, deadline), started);
545
+ if (request.start === void 0) return finish(void 0, { kind: "no_start" }, started);
546
+ const app = startApp(request.start, request.cwd, request.port);
547
+ const port = await waitForPort(app, request.host, request.port, deadline);
548
+ if (port === "exited") return finish(app, { kind: "crashed", exitCode: app.exited()?.code ?? null }, started);
549
+ if (port === "timeout") return finish(app, { kind: "no_port" }, started);
550
+ return finish(app, await ask(request, deadline), started);
551
+ };
552
+
553
+ // src/app/types.ts
554
+ var DEFAULT_HOST = "127.0.0.1";
555
+ var DEFAULT_ROUTE = "/";
556
+ var DEFAULT_STATUS = 200;
557
+ var DEFAULT_APP_TIMEOUT_S = 60;
558
+
559
+ // src/core/args.ts
560
+ import { parseArgs } from "node:util";
420
561
 
421
562
  // src/exit-codes.ts
422
563
  var ExitCode = {
@@ -444,6 +585,89 @@ var exitCodeDocs = {
444
585
  5: "exit.internal"
445
586
  };
446
587
 
588
+ // src/core/problem.ts
589
+ var usageProblem = (code, messageKey, fix, params2 = {}) => ({
590
+ code,
591
+ messageKey,
592
+ params: params2,
593
+ fix,
594
+ exitCode: ExitCode.Usage
595
+ });
596
+ var needsPersonProblem = (messageKey, fix, params2 = {}) => ({
597
+ code: "needs_confirmation",
598
+ messageKey,
599
+ params: params2,
600
+ fix,
601
+ exitCode: ExitCode.NeedsPerson
602
+ });
603
+ var FmError = class extends Error {
604
+ problem;
605
+ constructor(problem4) {
606
+ super(problem4.code);
607
+ this.name = "FmError";
608
+ this.problem = problem4;
609
+ }
610
+ };
611
+
612
+ // src/core/args.ts
613
+ var GLOBAL_OPTIONS = {
614
+ json: { type: "boolean" },
615
+ lang: { type: "string" },
616
+ yes: { type: "boolean", short: "y" },
617
+ "dry-run": { type: "boolean" },
618
+ version: { type: "boolean", short: "v" },
619
+ help: { type: "boolean", short: "h" }
620
+ };
621
+ var flag = (values, name) => values[name] === true;
622
+ var looseArgs = (argv) => {
623
+ const { values, positionals } = parseArgs({
624
+ args: [...argv],
625
+ options: GLOBAL_OPTIONS,
626
+ strict: false,
627
+ allowPositionals: true
628
+ });
629
+ const lang = values.lang;
630
+ return {
631
+ positionals,
632
+ json: flag(values, "json"),
633
+ yes: flag(values, "yes"),
634
+ dryRun: flag(values, "dry-run"),
635
+ version: flag(values, "version"),
636
+ help: flag(values, "help"),
637
+ lang: typeof lang === "string" ? lang : void 0
638
+ };
639
+ };
640
+ var resolveCommand = (registry, positionals) => registry.filter((spec2) => spec2.path.every((part2, index) => positionals[index] === part2)).reduce(
641
+ (best, spec2) => best === void 0 || spec2.path.length > best.consumed ? { spec: spec2, consumed: spec2.path.length } : best,
642
+ void 0
643
+ );
644
+ var strictArgs = (argv, spec2) => {
645
+ const fix = `finitemonkey help ${spec2.path.join(" ")}`;
646
+ let parsed;
647
+ try {
648
+ parsed = parseArgs({
649
+ args: [...argv],
650
+ options: { ...GLOBAL_OPTIONS, ...spec2.options },
651
+ strict: true,
652
+ allowPositionals: true
653
+ });
654
+ } catch (error) {
655
+ const detail = error instanceof Error ? error.message : String(error);
656
+ return reject(usageProblem("invalid_arguments", "error.invalid_arguments", fix, { detail }));
657
+ }
658
+ const rest = parsed.positionals.slice(spec2.path.length);
659
+ const max = spec2.maxPositionals ?? 0;
660
+ const extra = rest[max];
661
+ if (extra !== void 0) {
662
+ return reject(usageProblem("unexpected_argument", "error.unexpected_argument", fix, { argument: extra }));
663
+ }
664
+ return accept({ values: parsed.values, positionals: rest });
665
+ };
666
+ var stringOption = (values, name) => {
667
+ const value = values[name];
668
+ return typeof value === "string" ? value : void 0;
669
+ };
670
+
447
671
  // src/core/result.ts
448
672
  var SCHEMA_VERSION = 1;
449
673
  var success = (data, human2 = []) => ({
@@ -468,6 +692,87 @@ var toEnvelope = (command, result) => ({
468
692
  ...result.next === void 0 ? {} : { next: result.next }
469
693
  });
470
694
 
695
+ // src/commands/app-check.ts
696
+ var APP_CHECK_SCHEMA = "finitemonkey/app-check@1";
697
+ var positive = (raw, fallback) => {
698
+ if (raw === void 0) return fallback;
699
+ const value = Number(raw);
700
+ return Number.isInteger(value) && value > 0 ? value : void 0;
701
+ };
702
+ var requestFrom = (ctx, values) => {
703
+ const port = positive(stringOption(values, "port"), 0);
704
+ const status = positive(stringOption(values, "status"), DEFAULT_STATUS);
705
+ const timeout = positive(stringOption(values, "timeout"), DEFAULT_APP_TIMEOUT_S);
706
+ if (port === void 0 || port === 0 || port > 65535 || status === void 0 || timeout === void 0)
707
+ return void 0;
708
+ return {
709
+ host: stringOption(values, "host") ?? DEFAULT_HOST,
710
+ port,
711
+ route: stringOption(values, "route") ?? DEFAULT_ROUTE,
712
+ status,
713
+ body: stringOption(values, "body"),
714
+ start: stringOption(values, "start"),
715
+ cwd: ctx.cwd,
716
+ timeoutMs: timeout * 1e3
717
+ };
718
+ };
719
+ var humanLine = (ctx, data) => ctx.t(`app.check.${data.kind}`, {
720
+ url: data.url,
721
+ status: data.status ?? 0,
722
+ code: data.exitCode ?? -1
723
+ });
724
+ var toResult = (ctx, request, data) => {
725
+ const human2 = [humanLine(ctx, data)];
726
+ if (data.kind === "ok") return success(data, human2);
727
+ return {
728
+ ok: false,
729
+ exitCode: ExitCode.CheckFailed,
730
+ data,
731
+ human: human2,
732
+ error: {
733
+ code: "app_check_failed",
734
+ message: human2.join(" "),
735
+ fix: `finitemonkey app check --port ${String(request.port)}`
736
+ }
737
+ };
738
+ };
739
+ var appCheckCommand = {
740
+ path: ["app", "check"],
741
+ summaryKey: "command.app_check.summary",
742
+ usage: "finitemonkey app check --port <n> [--start <command>] [--route <path>] [--status <code>] [--body <text>] [--timeout <seconds>] [--host <host>]",
743
+ status: "ready",
744
+ options: {
745
+ port: { type: "string" },
746
+ start: { type: "string" },
747
+ route: { type: "string" },
748
+ status: { type: "string" },
749
+ body: { type: "string" },
750
+ timeout: { type: "string" },
751
+ host: { type: "string" }
752
+ },
753
+ run: async (ctx, args2) => {
754
+ const request = requestFrom(ctx, args2.values);
755
+ if (request === void 0) {
756
+ return {
757
+ ok: false,
758
+ exitCode: ExitCode.Usage,
759
+ human: [ctx.t("app.check.usage")],
760
+ error: {
761
+ code: "app_check_failed",
762
+ message: ctx.t("app.check.usage"),
763
+ fix: "finitemonkey app check --port 3000"
764
+ }
765
+ };
766
+ }
767
+ const result = await probeApp(request);
768
+ const url2 = `http://${request.host}:${String(request.port)}${request.route}`;
769
+ return toResult(ctx, request, { schema: APP_CHECK_SCHEMA, url: url2, ...result });
770
+ }
771
+ };
772
+
773
+ // src/commands/doctor.ts
774
+ import { dirname as dirname7 } from "node:path";
775
+
471
776
  // src/install/apply.ts
472
777
  import { dirname as dirname3 } from "node:path";
473
778
 
@@ -512,12 +817,12 @@ var parseAdded = (value) => {
512
817
  const deny2 = strings(value["deny"]);
513
818
  const { schema, attribution } = value;
514
819
  if (deny2 === void 0 || typeof schema !== "boolean" || typeof attribution !== "boolean") return void 0;
515
- const ask = strings(value["ask"]) ?? [];
820
+ const ask2 = strings(value["ask"]) ?? [];
516
821
  const blockReads = value["blockReads"] === true;
517
822
  const marketplace = value["marketplace"] === true;
518
823
  const plugin = value["plugin"] === true;
519
824
  const language = value["language"];
520
- const added = { schema, attribution, deny: deny2, ask, blockReads, marketplace, plugin };
825
+ const added = { schema, attribution, deny: deny2, ask: ask2, blockReads, marketplace, plugin };
521
826
  return typeof language === "string" ? { ...added, language } : added;
522
827
  };
523
828
  var parseEntry = (value) => {
@@ -837,8 +1142,8 @@ var execForm = (runner, event) => {
837
1142
  };
838
1143
  var hookHandler = (runner, spec2, failClosedSupported) => {
839
1144
  const failure4 = spec2.guard && failClosedSupported ? { onFailure: "block" } : {};
840
- const { command, args } = execForm(runner, spec2.event);
841
- return { type: "command", command, args, timeout: spec2.timeout, ...failure4 };
1145
+ const { command, args: args2 } = execForm(runner, spec2.event);
1146
+ return { type: "command", command, args: args2, timeout: spec2.timeout, ...failure4 };
842
1147
  };
843
1148
  var OWN_SHELL = /(^|[\s/"'])finitemonkey(@[^\s"]+|\.mjs|\.cmd|\.exe)?"?\s+hook\s/;
844
1149
  var OWN_SCRIPT = /finitemonkey(\.mjs|\.cmd|\.exe)?$/;
@@ -847,19 +1152,19 @@ var isOwnHandler = (handler) => {
847
1152
  if (!isRecord(handler)) return false;
848
1153
  const command = handler["command"];
849
1154
  if (typeof command !== "string") return false;
850
- const args = stringsOf(handler["args"]);
851
- if (args.length === 0) return OWN_SHELL.test(`${command} `);
852
- const hookAt = args.indexOf("hook");
1155
+ const args2 = stringsOf(handler["args"]);
1156
+ if (args2.length === 0) return OWN_SHELL.test(`${command} `);
1157
+ const hookAt = args2.indexOf("hook");
853
1158
  if (hookAt < 0) return false;
854
- const target = hookAt === 0 ? command : args[hookAt - 1] ?? "";
1159
+ const target = hookAt === 0 ? command : args2[hookAt - 1] ?? "";
855
1160
  return OWN_SCRIPT.test(target);
856
1161
  };
857
1162
 
858
1163
  // src/install/skill-template.ts
859
- var cliCommand = (runner, args) => {
860
- if (runner.kind === "local") return `node ${LOCAL_BIN} ${args}`;
861
- if (runner.kind === "vendored") return `node ${VENDOR_BIN} ${args}`;
862
- return isScript(runner.path) ? `node "${runner.path}" ${args}` : `"${runner.path}" ${args}`;
1164
+ var cliCommand = (runner, args2) => {
1165
+ if (runner.kind === "local") return `node ${LOCAL_BIN} ${args2}`;
1166
+ if (runner.kind === "vendored") return `node ${VENDOR_BIN} ${args2}`;
1167
+ return isScript(runner.path) ? `node "${runner.path}" ${args2}` : `"${runner.path}" ${args2}`;
863
1168
  };
864
1169
  var verifySkillTemplate = (runner, lang) => {
865
1170
  const skill = TEMPLATES[lang].verifySkill;
@@ -1258,7 +1563,7 @@ var mergeSettings = (current2, wanted, recorded) => {
1258
1563
  const existingDeny = asList(permissions["deny"]);
1259
1564
  const existingAsk = asList(permissions["ask"]);
1260
1565
  const deny2 = missing(wanted.deny, existingDeny);
1261
- const ask = missing(wanted.ask, existingAsk);
1566
+ const ask2 = missing(wanted.ask, existingAsk);
1262
1567
  const blockReads = !(BLOCK_READS in permissions);
1263
1568
  const attribution = wanted.attributionOff && !("attribution" in base);
1264
1569
  const marketplace = wanted.marketplace && !hasEntry(base, "extraKnownMarketplaces", MARKETPLACE_KEY);
@@ -1266,14 +1571,14 @@ var mergeSettings = (current2, wanted, recorded) => {
1266
1571
  const language = "language" in base ? void 0 : wanted.language;
1267
1572
  let settings = withPermissions(withHooks(base, hooks), {
1268
1573
  deny: [...existingDeny, ...deny2],
1269
- ask: [...existingAsk, ...ask],
1574
+ ask: [...existingAsk, ...ask2],
1270
1575
  blockReads: blockReads ? true : permissions[BLOCK_READS]
1271
1576
  });
1272
1577
  if (attribution) settings = { ...settings, attribution: ATTRIBUTION_OFF };
1273
1578
  if (marketplace) settings = withEntry(settings, "extraKnownMarketplaces", MARKETPLACE_KEY, MARKETPLACE_ENTRY);
1274
1579
  if (plugin) settings = withEntry(settings, "enabledPlugins", PLUGIN_ID, true);
1275
1580
  if (language !== void 0) settings = { ...settings, language };
1276
- const added = { schema, deny: deny2, ask, blockReads, attribution, marketplace, plugin };
1581
+ const added = { schema, deny: deny2, ask: ask2, blockReads, attribution, marketplace, plugin };
1277
1582
  return { settings, added: language === void 0 ? added : { ...added, language } };
1278
1583
  };
1279
1584
  var sameAttribution = (value) => JSON.stringify(value) === JSON.stringify(ATTRIBUTION_OFF);
@@ -2192,8 +2497,8 @@ var fmPaths = (root) => {
2192
2497
  var HOOK_FAILURE_EVENT = "hook.failed";
2193
2498
  var FAILURES = ["failed_open", "failed_closed", "stop_released"];
2194
2499
  var MAX_DETAIL = 500;
2195
- var logHookFailure = (ctx, event, outcome) => {
2196
- if (!FAILURES.includes(outcome.verdict)) return;
2500
+ var logHookFailure = (ctx, event, outcome2) => {
2501
+ if (!FAILURES.includes(outcome2.verdict)) return;
2197
2502
  const fromEnv = ctx.env["CLAUDE_PROJECT_DIR"];
2198
2503
  const root = fromEnv === void 0 ? findGitRoot(ctx.cwd, ctx.runtime) : toAbsolutePath(fromEnv);
2199
2504
  if (root === void 0) return;
@@ -2201,8 +2506,8 @@ var logHookFailure = (ctx, event, outcome) => {
2201
2506
  if (ctx.runtime.stat(paths.dir)?.kind !== "dir") return;
2202
2507
  logEvent(ctx.runtime, paths, HOOK_FAILURE_EVENT, {
2203
2508
  hook: event,
2204
- verdict: outcome.verdict,
2205
- detail: maskText(outcome.stderr.trim()).slice(0, MAX_DETAIL)
2509
+ verdict: outcome2.verdict,
2510
+ detail: maskText(outcome2.stderr.trim()).slice(0, MAX_DETAIL)
2206
2511
  });
2207
2512
  };
2208
2513
 
@@ -2615,7 +2920,7 @@ var checkSandbox = (env) => {
2615
2920
  }
2616
2921
  if (os === "macos") return [finding("sandbox", "ok", "doctor.sandbox.ok")];
2617
2922
  if (os === "other") return [];
2618
- const missing2 = SANDBOX_TOOLS.filter(({ command, args }) => !env.runtime.probe(command, args, PROBE_MS2).found).map(
2923
+ const missing2 = SANDBOX_TOOLS.filter(({ command, args: args2 }) => !env.runtime.probe(command, args2, PROBE_MS2).found).map(
2619
2924
  ({ command }) => command
2620
2925
  );
2621
2926
  if (missing2.length === 0) return [finding("sandbox", "ok", "doctor.sandbox.ok")];
@@ -3336,10 +3641,10 @@ var withAssignments = (stage) => {
3336
3641
  }
3337
3642
  return { ...stage, argv: stage.argv.slice(index), env };
3338
3643
  };
3339
- var skipOptions = (args, wrapper) => {
3644
+ var skipOptions = (args2, wrapper) => {
3340
3645
  let index = 0;
3341
- while (index < args.length) {
3342
- const arg = args[index] ?? "";
3646
+ while (index < args2.length) {
3647
+ const arg = args2[index] ?? "";
3343
3648
  if (arg === "--") return index + 1 + wrapper.positionals;
3344
3649
  if (!arg.startsWith("-") || arg === "-") break;
3345
3650
  if (wrapper.queries.has(arg)) return void 0;
@@ -3369,33 +3674,33 @@ var envCommand = (stage) => {
3369
3674
  return printsEnvironment ? { kind: "command", stage, extra: [] } : shift(stage, index, "env");
3370
3675
  };
3371
3676
  var shellScript = (stage, name, stdin) => {
3372
- const args = stage.argv.slice(1);
3677
+ const args2 = stage.argv.slice(1);
3373
3678
  let index = 0;
3374
3679
  let command = false;
3375
- while (index < args.length && /^[-+]/.test(args[index] ?? "")) {
3376
- const arg = args[index] ?? "";
3680
+ while (index < args2.length && /^[-+]/.test(args2[index] ?? "")) {
3681
+ const arg = args2[index] ?? "";
3377
3682
  if (/^-[A-Za-z]*c[A-Za-z]*$/.test(arg)) command = true;
3378
3683
  index += SHELL_WITH_VALUE.has(arg) ? 2 : 1;
3379
3684
  }
3380
- const script = args[index];
3685
+ const script = args2[index];
3381
3686
  if (command && script !== void 0) return { kind: "script", script, dialect: "bash", stage: shift(stage, 0, name) };
3382
3687
  if (script === void 0 && stdin !== void 0) return { kind: "script", script: stdin, dialect: "bash", stage };
3383
3688
  return void 0;
3384
3689
  };
3385
3690
  var decodeEncoded = (value) => Buffer.from(value, "base64").toString("utf16le");
3386
3691
  var powershellScript = (stage, name) => {
3387
- const args = stage.argv.slice(1);
3388
- const at = args.findIndex((arg) => /^[-/](?:c|co|com|comm|comma|comman|command)$/i.test(arg));
3389
- const encoded = args.findIndex((arg) => /^[-/](?:e|ec|enc|encodedcommand)$/i.test(arg));
3692
+ const args2 = stage.argv.slice(1);
3693
+ const at = args2.findIndex((arg) => /^[-/](?:c|co|com|comm|comma|comman|command)$/i.test(arg));
3694
+ const encoded = args2.findIndex((arg) => /^[-/](?:e|ec|enc|encodedcommand)$/i.test(arg));
3390
3695
  if (at >= 0) {
3391
3696
  return {
3392
3697
  kind: "script",
3393
- script: args.slice(at + 1).join(" "),
3698
+ script: args2.slice(at + 1).join(" "),
3394
3699
  dialect: "powershell",
3395
3700
  stage: shift(stage, 0, name)
3396
3701
  };
3397
3702
  }
3398
- const value = encoded >= 0 ? args[encoded + 1] : void 0;
3703
+ const value = encoded >= 0 ? args2[encoded + 1] : void 0;
3399
3704
  if (value !== void 0) {
3400
3705
  return { kind: "script", script: decodeEncoded(value), dialect: "powershell", stage: shift(stage, 0, name) };
3401
3706
  }
@@ -3574,20 +3879,20 @@ var envFindings = (command) => {
3574
3879
  const exported = command.argv[0] === "export" ? command.argv.slice(1).map((arg) => arg.split("=", 2)) : [];
3575
3880
  return [...pairs2, ...exported].filter(([name = ""]) => HOOK_ENV.test(name)).map(([name = "", value = ""]) => ({ kind: "hook_env", detail: `${name}=${value}` }));
3576
3881
  };
3577
- var splitGit = (args) => {
3882
+ var splitGit = (args2) => {
3578
3883
  let index = 0;
3579
3884
  const globals = [];
3580
- while (index < args.length && (args[index] ?? "").startsWith("-")) {
3581
- const arg = args[index] ?? "";
3885
+ while (index < args2.length && (args2[index] ?? "").startsWith("-")) {
3886
+ const arg = args2[index] ?? "";
3582
3887
  globals.push(arg);
3583
- if (GIT_VALUE_OPTIONS.has(arg)) globals.push(args[index + 1] ?? "");
3888
+ if (GIT_VALUE_OPTIONS.has(arg)) globals.push(args2[index + 1] ?? "");
3584
3889
  index += GIT_VALUE_OPTIONS.has(arg) ? 2 : 1;
3585
3890
  }
3586
- return { globals, rest: args.slice(index) };
3891
+ return { globals, rest: args2.slice(index) };
3587
3892
  };
3588
- var commitHasShortN = (args) => {
3589
- for (let index = 0; index < args.length; index += 1) {
3590
- const arg = args[index] ?? "";
3893
+ var commitHasShortN = (args2) => {
3894
+ for (let index = 0; index < args2.length; index += 1) {
3895
+ const arg = args2[index] ?? "";
3591
3896
  if (arg === "--") return false;
3592
3897
  if (!/^-[A-Za-z]/.test(arg)) continue;
3593
3898
  for (const char of arg.slice(1)) {
@@ -3600,8 +3905,8 @@ var commitHasShortN = (args) => {
3600
3905
  }
3601
3906
  return false;
3602
3907
  };
3603
- var gitFindings = (args) => {
3604
- const { globals, rest } = splitGit(args);
3908
+ var gitFindings = (args2) => {
3909
+ const { globals, rest } = splitGit(args2);
3605
3910
  const found = [];
3606
3911
  globals.forEach((arg, index) => {
3607
3912
  if (arg === "-c" && isHooksPath(globals[index + 1] ?? "") || /^--config-env=core\.hookspath/i.test(arg)) {
@@ -3678,16 +3983,16 @@ var GIT_WRITERS = /* @__PURE__ */ new Set(["checkout", "restore", "rm", "mv", "c
3678
3983
  var FM_NAMES2 = /* @__PURE__ */ new Set(["finitemonkey", "fm"]);
3679
3984
  var inline = (argv) => argv.some((arg) => arg === "-e" || arg === "-c" || arg === "--eval");
3680
3985
  var writtenPaths = (command) => {
3681
- const [name = "", ...args] = command.argv;
3682
- const operands2 = args.filter((arg) => !arg.startsWith("-"));
3683
- const ddTargets = args.filter((arg) => arg.startsWith("of=")).map((arg) => arg.slice(3));
3986
+ const [name = "", ...args2] = command.argv;
3987
+ const operands2 = args2.filter((arg) => !arg.startsWith("-"));
3988
+ const ddTargets = args2.filter((arg) => arg.startsWith("of=")).map((arg) => arg.slice(3));
3684
3989
  if (FM_NAMES2.has(name)) return command.redirects;
3685
3990
  if (COPIERS.has(name)) return [...command.redirects, ...operands2.slice(-1)];
3686
3991
  if (WRITERS.has(name)) return [...command.redirects, ...operands2, ...ddTargets];
3687
- if (IN_PLACE.has(name) && args.some((arg) => /^-[a-zA-Z]*i/.test(arg) || arg.startsWith("--in-place"))) {
3992
+ if (IN_PLACE.has(name) && args2.some((arg) => /^-[a-zA-Z]*i/.test(arg) || arg.startsWith("--in-place"))) {
3688
3993
  return [...command.redirects, ...operands2];
3689
3994
  }
3690
- if (INTERPRETERS.has(name) && inline(args)) return [...command.redirects, ...args];
3995
+ if (INTERPRETERS.has(name) && inline(args2)) return [...command.redirects, ...args2];
3691
3996
  if (name === "git" && GIT_WRITERS.has(operands2[0] ?? "")) return [...command.redirects, ...operands2.slice(1)];
3692
3997
  return command.redirects;
3693
3998
  };
@@ -3706,14 +4011,14 @@ var bashSelfTargets = (root, commandLine) => parseShell(commandLine).flatMap(
3706
4011
  // src/guards/self/hook.ts
3707
4012
  var targets = (input) => {
3708
4013
  const tool = toolName(input.payload.raw);
3709
- const args = toolInput(input.payload.raw);
4014
+ const args2 = toolInput(input.payload.raw);
3710
4015
  const root = guardRoot(input);
3711
4016
  if (FILE_TOOLS.has(tool)) {
3712
- const path = textField(args, "file_path", "notebook_path") ?? "";
4017
+ const path = textField(args2, "file_path", "notebook_path") ?? "";
3713
4018
  const kind = path === "" ? void 0 : selfTarget(root, path);
3714
4019
  return kind === void 0 ? [] : [{ kind, path }];
3715
4020
  }
3716
- return SHELL_TOOLS.has(tool) ? bashSelfTargets(root, textField(args, "command") ?? "") : [];
4021
+ return SHELL_TOOLS.has(tool) ? bashSelfTargets(root, textField(args2, "command") ?? "") : [];
3717
4022
  };
3718
4023
  var selfGuard = (input) => {
3719
4024
  const found = targets(input);
@@ -3897,19 +4202,19 @@ var read = (input, path) => {
3897
4202
  const text6 = input.runtime.readText(path);
3898
4203
  return text6.ok ? text6.value : void 0;
3899
4204
  };
3900
- var proposed = (tool, args, before) => {
3901
- if (tool === "Write") return textField(args, "content") ?? "";
3902
- return before === void 0 ? void 0 : applyEdits(before, editsOf(args));
4205
+ var proposed = (tool, args2, before) => {
4206
+ if (tool === "Write") return textField(args2, "content") ?? "";
4207
+ return before === void 0 ? void 0 : applyEdits(before, editsOf(args2));
3903
4208
  };
3904
4209
  var fileViolations = (input, root) => {
3905
4210
  const tool = toolName(input.payload.raw);
3906
- const args = toolInput(input.payload.raw);
3907
- const file = textField(args, "file_path") ?? "";
4211
+ const args2 = toolInput(input.payload.raw);
4212
+ const file = textField(args2, "file_path") ?? "";
3908
4213
  if (tool === "NotebookEdit" || file === "") return [];
3909
4214
  const local = localPath(root, file);
3910
4215
  if (!isTestFile(local) && !isProtectedConfig(local)) return [];
3911
4216
  const before = read(input, toAbsolutePath(file, root));
3912
- const after = proposed(tool, args, before);
4217
+ const after = proposed(tool, args2, before);
3913
4218
  if (after === void 0) return [];
3914
4219
  const accepted2 = readTrust(input.runtime, fmPaths(root)).configs[local];
3915
4220
  if (before === void 0) return compareNewTestFile(local, after);
@@ -4426,7 +4731,7 @@ var str = (record3, key, at, issues, required) => {
4426
4731
  }
4427
4732
  return value;
4428
4733
  };
4429
- var positive = (record3, key, at, issues) => {
4734
+ var positive2 = (record3, key, at, issues) => {
4430
4735
  const value = record3[key];
4431
4736
  if (value === void 0 || value === null) return void 0;
4432
4737
  if (typeof value === "number" && Number.isInteger(value) && value > 0) return value;
@@ -4446,11 +4751,26 @@ var gitRequire = (value, at, issues) => {
4446
4751
  if (known2.some((item) => item === void 0)) issues.push(`${at}: use ${GIT_REQUIREMENTS.join(", ")}`);
4447
4752
  return known2.filter((item) => item !== void 0);
4448
4753
  };
4754
+ var appFields = (record3, kind, at, issues) => {
4755
+ const port = positive2(record3, "port", at, issues);
4756
+ if (kind === "runtime" && port === void 0) issues.push(`${at}port: required`);
4757
+ if (port !== void 0 && port > 65535) issues.push(`${at}port: must be at most 65535`);
4758
+ const status = positive2(record3, "status", at, issues);
4759
+ const route = str(record3, "route", at, issues, false);
4760
+ const body2 = str(record3, "body", at, issues, false);
4761
+ return {
4762
+ ...port === void 0 ? {} : { port },
4763
+ ...route === void 0 ? {} : { route },
4764
+ ...status === void 0 ? {} : { status },
4765
+ ...body2 === void 0 ? {} : { body: body2 }
4766
+ };
4767
+ };
4449
4768
  var checkFields = (record3, kind, at, issues) => {
4450
4769
  const run4 = str(record3, "run", at, issues, kind === "command");
4451
- const timeoutS = positive(record3, "timeout_s", at, issues);
4770
+ const timeoutS = positive2(record3, "timeout_s", at, issues);
4452
4771
  const description = str(record3, "description", at, issues, kind === "manual");
4453
4772
  const require2 = kind === "git" ? gitRequire(record3["require"] ?? ["committed"], `${at}require`, issues) : void 0;
4773
+ const app = appFields(record3, kind, at, issues);
4454
4774
  const open = record3["require_open"];
4455
4775
  if (open !== void 0 && typeof open !== "boolean") issues.push(`${at}require_open: must be true or false`);
4456
4776
  return {
@@ -4458,7 +4778,8 @@ var checkFields = (record3, kind, at, issues) => {
4458
4778
  ...timeoutS === void 0 ? {} : { timeoutS },
4459
4779
  ...description === void 0 ? {} : { description },
4460
4780
  ...require2 === void 0 ? {} : { require: require2 },
4461
- ...typeof open === "boolean" ? { requireOpen: open } : {}
4781
+ ...typeof open === "boolean" ? { requireOpen: open } : {},
4782
+ ...app
4462
4783
  };
4463
4784
  };
4464
4785
  var acceptanceItem = (value, index, issues) => {
@@ -4496,8 +4817,8 @@ var scope = (value, issues) => {
4496
4817
  ...files === void 0 ? {} : { files },
4497
4818
  ...lines3 === void 0 ? {} : { lines: lines3 }
4498
4819
  }))({
4499
- files: positive(budget, "files", "scope.diff_budget.", issues),
4500
- lines: positive(budget, "lines", "scope.diff_budget.", issues)
4820
+ files: positive2(budget, "files", "scope.diff_budget.", issues),
4821
+ lines: positive2(budget, "lines", "scope.diff_budget.", issues)
4501
4822
  }) : void 0;
4502
4823
  if (budget !== void 0 && budget !== null && !isRecord(budget)) issues.push("scope.diff_budget: must be a mapping");
4503
4824
  const protectedValue = value["tests_protected"];
@@ -4622,12 +4943,12 @@ var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
4622
4943
  var GIT_TIMEOUT_MS = 15e3;
4623
4944
  var EXCLUDE_FM = `:(exclude)${FM_DIR}`;
4624
4945
  var gitAt = (runtime, cwd) => {
4625
- const run4 = (args) => runtime.exec({ command: "git", args: ["-c", "core.quotepath=off", ...args], cwd, timeoutMs: GIT_TIMEOUT_MS });
4946
+ const run4 = (args2) => runtime.exec({ command: "git", args: ["-c", "core.quotepath=off", ...args2], cwd, timeoutMs: GIT_TIMEOUT_MS });
4626
4947
  return {
4627
4948
  cwd,
4628
4949
  run: run4,
4629
- out: (args) => {
4630
- const result = run4(args);
4950
+ out: (args2) => {
4951
+ const result = run4(args2);
4631
4952
  return result.found && result.exitCode === 0 ? result.stdout : void 0;
4632
4953
  }
4633
4954
  };
@@ -4774,7 +5095,7 @@ var parseMergeTreeConflicts = (stdout) => {
4774
5095
  var PR_FIELDS = "number,url,state,mergeable,mergeStateStatus,baseRefName,headRefOid,statusCheckRollup";
4775
5096
  var PR_CACHE_TTL_MS = 6e4;
4776
5097
  var GH_TIMEOUT_MS = 8e3;
4777
- var gh = (runtime, cwd, args, timeoutMs = GH_TIMEOUT_MS) => runtime.exec({ command: "gh", args, cwd, timeoutMs });
5098
+ var gh = (runtime, cwd, args2, timeoutMs = GH_TIMEOUT_MS) => runtime.exec({ command: "gh", args: args2, cwd, timeoutMs });
4778
5099
  var ghAuthenticated = (runtime, cwd, timeoutMs = 4e3) => {
4779
5100
  const result = gh(runtime, cwd, ["auth", "status"], timeoutMs);
4780
5101
  if (result.timedOut) return "timeout";
@@ -5787,7 +6108,28 @@ var en_default = {
5787
6108
  "gate.callers.warning": "warning only: {percent}% of callers findings were released with callers_ignore ({released} of {total}), above the 5% ceiling",
5788
6109
  "command.policy_false_positive.summary": "Record a false positive of a guard in shadow so it is not promoted",
5789
6110
  "policy.error.person_only": "Only the person can record a false positive, and this does not look like their terminal (no TTY, or running inside Claude Code)",
5790
- "policy.false_positive.recorded": "Recorded for {guard}: {count, plural, one {# false positive} other {# false positives}}; it stays in shadow until you set it in .finitemonkey/policy.yml."
6111
+ "policy.false_positive.recorded": "Recorded for {guard}: {count, plural, one {# false positive} other {# false positives}}; it stays in shadow until you set it in .finitemonkey/policy.yml.",
6112
+ "command.app_check.summary": "Start the app or reuse the running one, call one route and check the answer",
6113
+ "app.check.ok": "{url} answered {status}",
6114
+ "app.check.crashed": "the app exited with code {code} before opening the port",
6115
+ "app.check.no_port": "the port of {url} did not open before the timeout",
6116
+ "app.check.no_start": "nothing listens on {url} and no start command was given",
6117
+ "app.check.wrong_status": "{url} answered {status}",
6118
+ "app.check.body_mismatch": "{url} answered {status} without the expected text",
6119
+ "app.check.request_failed": "the request to {url} failed",
6120
+ "app.check.usage": "give --port as a number from 1 to 65535; --status and --timeout must be positive numbers",
6121
+ "gate.require.runtime": "the app starts and `{command}` answers as declared",
6122
+ "gate.runtime.ok": "started, {route} answered {status}, stopped",
6123
+ "gate.runtime.reused": "reused the running app, {route} answered {status}",
6124
+ "gate.runtime.crashed": "the app exited with code {code} before opening its port",
6125
+ "gate.runtime.no_port": "port {port} did not open before the timeout",
6126
+ "gate.runtime.no_start": "nothing listens on port {port} and the check has no `run` to start the app",
6127
+ "gate.runtime.no_port_declared": "the runtime check needs a `port`",
6128
+ "gate.runtime.wrong_status": "{route} answered {status}, expected {expected}",
6129
+ "gate.runtime.body_mismatch": '{route} did not contain "{body}"',
6130
+ "gate.runtime.request_failed": "the request to {route} failed: {detail}",
6131
+ "gate.runtime.orphan": "the app on port {port} could not be stopped",
6132
+ "gate.runtime.probe_failed": "the app check did not report a result: {line}"
5791
6133
  };
5792
6134
 
5793
6135
  // src/i18n/es.json
@@ -6319,7 +6661,28 @@ var es_default = {
6319
6661
  "gate.callers.warning": "solo aviso: {percent}% de los hallazgos de callers se liberaron con callers_ignore ({released} de {total}), por encima del techo de 5%",
6320
6662
  "command.policy_false_positive.summary": "Registra un falso positivo de un guard en shadow para que no se promueva",
6321
6663
  "policy.error.person_only": "Solo la persona puede registrar un falso positivo, y esto no parece su terminal (sin TTY, o dentro de Claude Code)",
6322
- "policy.false_positive.recorded": "Registrado para {guard}: {count, plural, one {# falso positivo} other {# falsos positivos}}; se queda en shadow hasta que fijes el modo en .finitemonkey/policy.yml."
6664
+ "policy.false_positive.recorded": "Registrado para {guard}: {count, plural, one {# falso positivo} other {# falsos positivos}}; se queda en shadow hasta que fijes el modo en .finitemonkey/policy.yml.",
6665
+ "command.app_check.summary": "Arranca la app o reutiliza la que ya corre, llama a una ruta y comprueba la respuesta",
6666
+ "app.check.ok": "{url} respondió {status}",
6667
+ "app.check.crashed": "la app salió con código {code} antes de abrir el puerto",
6668
+ "app.check.no_port": "el puerto de {url} no se abrió antes del tiempo límite",
6669
+ "app.check.no_start": "nada escucha en {url} y no se indicó comando de arranque",
6670
+ "app.check.wrong_status": "{url} respondió {status}",
6671
+ "app.check.body_mismatch": "{url} respondió {status} sin el texto esperado",
6672
+ "app.check.request_failed": "la petición a {url} falló",
6673
+ "app.check.usage": "indica --port como número de 1 a 65535; --status y --timeout deben ser números positivos",
6674
+ "gate.require.runtime": "la app arranca y `{command}` responde como se declaró",
6675
+ "gate.runtime.ok": "arrancó, {route} respondió {status}, detenida",
6676
+ "gate.runtime.reused": "reutilizó la app en marcha, {route} respondió {status}",
6677
+ "gate.runtime.crashed": "la app salió con código {code} antes de abrir su puerto",
6678
+ "gate.runtime.no_port": "el puerto {port} no se abrió antes del tiempo límite",
6679
+ "gate.runtime.no_start": "nada escucha en el puerto {port} y la comprobación no tiene `run` para arrancar la app",
6680
+ "gate.runtime.no_port_declared": "la comprobación runtime necesita `port`",
6681
+ "gate.runtime.wrong_status": "{route} respondió {status}, se esperaba {expected}",
6682
+ "gate.runtime.body_mismatch": '{route} no contenía "{body}"',
6683
+ "gate.runtime.request_failed": "la petición a {route} falló: {detail}",
6684
+ "gate.runtime.orphan": "no se pudo detener la app del puerto {port}",
6685
+ "gate.runtime.probe_failed": "la comprobación de la app no devolvió resultado: {line}"
6323
6686
  };
6324
6687
 
6325
6688
  // src/i18n/pt-BR.json
@@ -6851,7 +7214,28 @@ var pt_BR_default = {
6851
7214
  "gate.callers.warning": "só aviso: {percent}% dos achados de callers foram liberados com callers_ignore ({released} de {total}), acima do teto de 5%",
6852
7215
  "command.policy_false_positive.summary": "Registra um falso positivo de um guard em shadow para ele não ser promovido",
6853
7216
  "policy.error.person_only": "Só a pessoa pode registrar um falso positivo, e isto não parece o terminal dela (sem TTY, ou rodando dentro do Claude Code)",
6854
- "policy.false_positive.recorded": "Registrado para {guard}: {count, plural, one {# falso positivo} other {# falsos positivos}}; ele fica em shadow até você definir o modo em .finitemonkey/policy.yml."
7217
+ "policy.false_positive.recorded": "Registrado para {guard}: {count, plural, one {# falso positivo} other {# falsos positivos}}; ele fica em shadow até você definir o modo em .finitemonkey/policy.yml.",
7218
+ "command.app_check.summary": "Sobe o app ou reaproveita o que está rodando, chama uma rota e confere a resposta",
7219
+ "app.check.ok": "{url} respondeu {status}",
7220
+ "app.check.crashed": "o app saiu com código {code} antes de abrir a porta",
7221
+ "app.check.no_port": "a porta de {url} não abriu antes do tempo limite",
7222
+ "app.check.no_start": "nada escuta em {url} e nenhum comando de início foi dado",
7223
+ "app.check.wrong_status": "{url} respondeu {status}",
7224
+ "app.check.body_mismatch": "{url} respondeu {status} sem o texto esperado",
7225
+ "app.check.request_failed": "a requisição para {url} falhou",
7226
+ "app.check.usage": "passe --port como número de 1 a 65535; --status e --timeout precisam ser números positivos",
7227
+ "gate.require.runtime": "o app sobe e `{command}` responde como declarado",
7228
+ "gate.runtime.ok": "subiu, {route} respondeu {status}, parado",
7229
+ "gate.runtime.reused": "reaproveitou o app rodando, {route} respondeu {status}",
7230
+ "gate.runtime.crashed": "o app saiu com código {code} antes de abrir a porta",
7231
+ "gate.runtime.no_port": "a porta {port} não abriu antes do tempo limite",
7232
+ "gate.runtime.no_start": "nada escuta na porta {port} e a checagem não tem `run` para subir o app",
7233
+ "gate.runtime.no_port_declared": "a checagem runtime precisa de `port`",
7234
+ "gate.runtime.wrong_status": "{route} respondeu {status}, esperado {expected}",
7235
+ "gate.runtime.body_mismatch": '{route} não continha "{body}"',
7236
+ "gate.runtime.request_failed": "a requisição para {route} falhou: {detail}",
7237
+ "gate.runtime.orphan": "o app na porta {port} não pôde ser parado",
7238
+ "gate.runtime.probe_failed": "a checagem do app não devolveu resultado: {line}"
6855
7239
  };
6856
7240
 
6857
7241
  // src/i18n/review.json
@@ -7384,7 +7768,28 @@ var review_default = {
7384
7768
  "gate.callers.warning": "machine",
7385
7769
  "command.policy_false_positive.summary": "machine",
7386
7770
  "policy.error.person_only": "machine",
7387
- "policy.false_positive.recorded": "machine"
7771
+ "policy.false_positive.recorded": "machine",
7772
+ "command.app_check.summary": "machine",
7773
+ "app.check.ok": "machine",
7774
+ "app.check.crashed": "machine",
7775
+ "app.check.no_port": "machine",
7776
+ "app.check.no_start": "machine",
7777
+ "app.check.wrong_status": "machine",
7778
+ "app.check.body_mismatch": "machine",
7779
+ "app.check.request_failed": "machine",
7780
+ "app.check.usage": "machine",
7781
+ "gate.require.runtime": "machine",
7782
+ "gate.runtime.ok": "machine",
7783
+ "gate.runtime.reused": "machine",
7784
+ "gate.runtime.crashed": "machine",
7785
+ "gate.runtime.no_port": "machine",
7786
+ "gate.runtime.no_start": "machine",
7787
+ "gate.runtime.no_port_declared": "machine",
7788
+ "gate.runtime.wrong_status": "machine",
7789
+ "gate.runtime.body_mismatch": "machine",
7790
+ "gate.runtime.request_failed": "machine",
7791
+ "gate.runtime.orphan": "machine",
7792
+ "gate.runtime.probe_failed": "machine"
7388
7793
  },
7389
7794
  es: {
7390
7795
  "command.init.summary": "machine",
@@ -7914,7 +8319,28 @@ var review_default = {
7914
8319
  "gate.callers.warning": "machine",
7915
8320
  "command.policy_false_positive.summary": "machine",
7916
8321
  "policy.error.person_only": "machine",
7917
- "policy.false_positive.recorded": "machine"
8322
+ "policy.false_positive.recorded": "machine",
8323
+ "command.app_check.summary": "machine",
8324
+ "app.check.ok": "machine",
8325
+ "app.check.crashed": "machine",
8326
+ "app.check.no_port": "machine",
8327
+ "app.check.no_start": "machine",
8328
+ "app.check.wrong_status": "machine",
8329
+ "app.check.body_mismatch": "machine",
8330
+ "app.check.request_failed": "machine",
8331
+ "app.check.usage": "machine",
8332
+ "gate.require.runtime": "machine",
8333
+ "gate.runtime.ok": "machine",
8334
+ "gate.runtime.reused": "machine",
8335
+ "gate.runtime.crashed": "machine",
8336
+ "gate.runtime.no_port": "machine",
8337
+ "gate.runtime.no_start": "machine",
8338
+ "gate.runtime.no_port_declared": "machine",
8339
+ "gate.runtime.wrong_status": "machine",
8340
+ "gate.runtime.body_mismatch": "machine",
8341
+ "gate.runtime.request_failed": "machine",
8342
+ "gate.runtime.orphan": "machine",
8343
+ "gate.runtime.probe_failed": "machine"
7918
8344
  }
7919
8345
  };
7920
8346
 
@@ -9294,9 +9720,9 @@ var isFlagged = (report2) => report2.spans.length > 0;
9294
9720
  var MARKDOWN = /\.(?:md|markdown|mdx)$/i;
9295
9721
  var RECORD_FILE = /(?:^|\/)(?:CHANGELOG|RELEASE[-_]?NOTES|HISTORY)[^/]*$/i;
9296
9722
  var contextOf = (file) => RECORD_FILE.test(toPosixPath(file)) ? "record" : "docs";
9297
- var listed = (runtime, root, args) => {
9298
- const result = runtime.git(args, root);
9299
- if (!result.ok) return reject(`git ${args.join(" ")} failed`);
9723
+ var listed = (runtime, root, args2) => {
9724
+ const result = runtime.git(args2, root);
9725
+ if (!result.ok) return reject(`git ${args2.join(" ")} failed`);
9300
9726
  return accept(result.stdout.split("\0").map(toPosixPath));
9301
9727
  };
9302
9728
  var changedMarkdown = (runtime, root) => {
@@ -9376,6 +9802,121 @@ var reviewCheck = (check2, ctx) => {
9376
9802
  return failed([...sizeNotes(ctx), note("gate.review.missing", { check: check2.id })]);
9377
9803
  };
9378
9804
 
9805
+ // src/app/parse.ts
9806
+ var KINDS2 = [
9807
+ "ok",
9808
+ "crashed",
9809
+ "no_port",
9810
+ "wrong_status",
9811
+ "body_mismatch",
9812
+ "request_failed",
9813
+ "no_start"
9814
+ ];
9815
+ var optionalNumber = (value) => typeof value === "number" ? value : void 0;
9816
+ var parseAppCheck = (stdout) => {
9817
+ const envelope = parseJsonObject(stdout.trim());
9818
+ if (!envelope.ok) return void 0;
9819
+ const data = envelope.value["data"];
9820
+ if (!isRecord(data)) return void 0;
9821
+ const kind = KINDS2.find((item) => item === data["kind"]);
9822
+ const { output, reused, stopped, durationMs, exitCode, bodyExcerpt } = data;
9823
+ if (kind === void 0 || typeof output !== "string" || typeof reused !== "boolean" || typeof stopped !== "boolean")
9824
+ return void 0;
9825
+ const status = optionalNumber(data["status"]);
9826
+ return {
9827
+ kind,
9828
+ output,
9829
+ reused,
9830
+ stopped,
9831
+ durationMs: optionalNumber(durationMs) ?? 0,
9832
+ ...status === void 0 ? {} : { status },
9833
+ ...typeof exitCode === "number" || exitCode === null ? { exitCode } : {},
9834
+ ...typeof bodyExcerpt === "string" ? { bodyExcerpt } : {}
9835
+ };
9836
+ };
9837
+
9838
+ // src/gate/checks/runtime.ts
9839
+ var PROBE_MARGIN_MS = 15e3;
9840
+ var MIN_REMAINING_MS2 = 5e3;
9841
+ var TAIL2 = 160;
9842
+ var lastLine = (text6) => {
9843
+ const rows = text6.split(/\r?\n/).filter((row) => row.trim() !== "" && !/^\s*at\s|^Node\.js v\d/.test(row));
9844
+ const line2 = (rows.findLast((row) => /error/i.test(row)) ?? rows.at(-1))?.trim() ?? "";
9845
+ return line2.length > TAIL2 ? `${line2.slice(0, TAIL2)}…` : line2;
9846
+ };
9847
+ var args = (check2, timeoutS) => [
9848
+ "app",
9849
+ "check",
9850
+ "--json",
9851
+ "--port",
9852
+ String(check2.port ?? 0),
9853
+ "--route",
9854
+ check2.route ?? DEFAULT_ROUTE,
9855
+ "--status",
9856
+ String(check2.status ?? DEFAULT_STATUS),
9857
+ "--timeout",
9858
+ String(timeoutS),
9859
+ ...check2.run === void 0 ? [] : ["--start", check2.run],
9860
+ ...check2.body === void 0 ? [] : ["--body", check2.body]
9861
+ ];
9862
+ var resultNotes = (check2, result, ctx) => {
9863
+ const route = check2.route ?? DEFAULT_ROUTE;
9864
+ const line2 = ctx.mask(lastLine(result.output));
9865
+ const output = line2 === "" ? [] : [note("gate.note.output", { line: line2 })];
9866
+ switch (result.kind) {
9867
+ case "ok":
9868
+ return [note(result.reused ? "gate.runtime.reused" : "gate.runtime.ok", { route, status: result.status ?? 0 })];
9869
+ case "crashed":
9870
+ return [note("gate.runtime.crashed", { code: result.exitCode ?? -1 }), ...output];
9871
+ case "no_port":
9872
+ return [note("gate.runtime.no_port", { port: check2.port ?? 0 }), ...output];
9873
+ case "no_start":
9874
+ return [note("gate.runtime.no_start", { port: check2.port ?? 0 })];
9875
+ case "wrong_status":
9876
+ return [
9877
+ note("gate.runtime.wrong_status", {
9878
+ route,
9879
+ status: result.status ?? 0,
9880
+ expected: check2.status ?? DEFAULT_STATUS
9881
+ })
9882
+ ];
9883
+ case "body_mismatch":
9884
+ return [note("gate.runtime.body_mismatch", { route, body: ctx.mask(check2.body ?? "") })];
9885
+ case "request_failed":
9886
+ return [note("gate.runtime.request_failed", { route, detail: ctx.mask(result.bodyExcerpt ?? "") })];
9887
+ }
9888
+ };
9889
+ var outcome = (check2, result, ctx) => {
9890
+ const notes2 = [
9891
+ ...resultNotes(check2, result, ctx),
9892
+ ...result.stopped ? [] : [note("gate.runtime.orphan", { port: check2.port ?? 0 })]
9893
+ ];
9894
+ const base = result.kind === "ok" && result.stopped ? passed(notes2) : failed(notes2);
9895
+ return {
9896
+ ...base,
9897
+ ...check2.run === void 0 ? {} : { command: check2.run },
9898
+ exitCode: result.exitCode ?? (result.kind === "ok" ? 0 : null),
9899
+ stdoutSha256: sha256Hex(result.output),
9900
+ durationMs: result.durationMs
9901
+ };
9902
+ };
9903
+ var runtimeCheck = (check2, ctx) => {
9904
+ if (check2.port === void 0) return failed([note("gate.runtime.no_port_declared")]);
9905
+ if (check2.run !== void 0 && !ctx.trusted)
9906
+ return { ...unverified("untrusted", [note("gate.note.untrusted", { task: ctx.contract.id })]), command: check2.run };
9907
+ const remaining = ctx.deadline - nowMs(ctx.runtime) - PROBE_MARGIN_MS;
9908
+ if (remaining < MIN_REMAINING_MS2) return unverified("budget", [note("gate.note.budget")]);
9909
+ const timeoutS = Math.max(1, Math.min(check2.timeoutS ?? DEFAULT_APP_TIMEOUT_S, Math.floor(remaining / 1e3)));
9910
+ const run4 = ctx.runtime.execSelf(args(check2, timeoutS), ctx.paths.root, timeoutS * 1e3 + PROBE_MARGIN_MS);
9911
+ if (run4.timedOut) return unverified("timeout", [note("gate.note.timeout", { seconds: timeoutS })]);
9912
+ const result = parseAppCheck(run4.stdout);
9913
+ if (result === void 0) {
9914
+ return failed([note("gate.runtime.probe_failed", { line: ctx.mask(lastLine(`${run4.stdout}
9915
+ ${run4.stderr}`)) })]);
9916
+ }
9917
+ return outcome(check2, result, ctx);
9918
+ };
9919
+
9379
9920
  // src/secrets/baseline.ts
9380
9921
  import { join as join18 } from "node:path";
9381
9922
  var BASELINE_FILE2 = ".finitemonkey/findings/baseline.json";
@@ -9670,15 +10211,15 @@ var diffFindings = (diff) => {
9670
10211
  return scanner.end().filter((finding3) => !SKIPPED.test(finding3.file));
9671
10212
  };
9672
10213
  var scanStaged = (runtime, root, includeUnstaged = false) => {
9673
- const args = includeUnstaged ? ["diff", "HEAD", ...DIFF_FLAGS] : ["diff", "--cached", ...DIFF_FLAGS];
9674
- const git = runtime.git(args, root);
10214
+ const args2 = includeUnstaged ? ["diff", "HEAD", ...DIFF_FLAGS] : ["diff", "--cached", ...DIFF_FLAGS];
10215
+ const git = runtime.git(args2, root);
9675
10216
  if (!git.ok) return reject(git.found ? "git diff failed" : "git not found");
9676
10217
  return accept(diffFindings(git.stdout));
9677
10218
  };
9678
10219
  var scanHistory = async (runtime, root) => {
9679
10220
  const scanner = diffScanner();
9680
- const args = ["log", "-p", "--all", "--format=commit %H", ...DIFF_FLAGS];
9681
- const git = await runtime.gitLines(args, root, scanner.push);
10221
+ const args2 = ["log", "-p", "--all", "--format=commit %H", ...DIFF_FLAGS];
10222
+ const git = await runtime.gitLines(args2, root, scanner.push);
9682
10223
  if (!git.ok) return reject(git.found ? "git log failed" : "git not found");
9683
10224
  return accept(scanner.end().filter((finding3) => !SKIPPED.test(finding3.file)));
9684
10225
  };
@@ -9694,11 +10235,11 @@ var secretsGateStatus = (runtime, root, since) => {
9694
10235
  if (!changed.ok) return changed;
9695
10236
  const baseline = loadBaseline(runtime, root);
9696
10237
  if (!baseline.ok) return baseline;
9697
- const outcome = ratchet([...changed.value, ...untracked2(runtime, root)], baseline.value, runtime.now(), void 0);
10238
+ const outcome2 = ratchet([...changed.value, ...untracked2(runtime, root)], baseline.value, runtime.now(), void 0);
9698
10239
  const openIncidents = listIncidents(runtime, root).filter(
9699
10240
  (incident) => incident.createdAt >= since && incident.state !== "rotacionado" && incident.state !== "fechado"
9700
10241
  );
9701
- return accept({ findings: [...outcome.fresh, ...outcome.expired], openIncidents });
10242
+ return accept({ findings: [...outcome2.fresh, ...outcome2.expired], openIncidents });
9702
10243
  };
9703
10244
 
9704
10245
  // src/gate/checks/secrets.ts
@@ -9770,8 +10311,8 @@ var testsCheck = (check2, ctx) => {
9770
10311
  const released2 = grant === void 0 ? {} : { releasedBy: `person@${grant.grantedAt}` };
9771
10312
  const releaseNotes = grant === void 0 ? [] : [...notes2, note("gate.note.released", { guard: "tests", reason: grant.reason })];
9772
10313
  if (check2.run === void 0) return { ...passed([note("gate.tests.clean"), ...releaseNotes]), ...released2 };
9773
- const outcome = runShellCheck(check2, check2.run, ctx);
9774
- return { ...outcome, notes: [...outcome.notes, ...releaseNotes], ...released2 };
10314
+ const outcome2 = runShellCheck(check2, check2.run, ctx);
10315
+ return { ...outcome2, notes: [...outcome2.notes, ...releaseNotes], ...released2 };
9775
10316
  };
9776
10317
 
9777
10318
  // src/git/tree.ts
@@ -9797,13 +10338,13 @@ var IDENTITY_ENV = {
9797
10338
  GIT_COMMITTER_EMAIL: "finitemonkey@localhost"
9798
10339
  };
9799
10340
  var failure = (step3, result) => result.timedOut ? `${step3}: timed out` : `${step3}: ${result.stderr.trim() || "git failed"}`;
9800
- var run = (runtime, repo, args, env, options, input) => {
9801
- const result = runtime.gitWith(args, repo.root, {
10341
+ var run = (runtime, repo, args2, env, options, input) => {
10342
+ const result = runtime.gitWith(args2, repo.root, {
9802
10343
  env,
9803
10344
  timeoutMs: options.timeoutMs,
9804
10345
  ...input === void 0 ? {} : { input }
9805
10346
  });
9806
- return result.ok ? accept(result.stdout.trim()) : reject(failure(args[0] ?? "git", result));
10347
+ return result.ok ? accept(result.stdout.trim()) : reject(failure(args2[0] ?? "git", result));
9807
10348
  };
9808
10349
  var dropSensitive = (runtime, repo, env, options) => {
9809
10350
  const listed4 = run(runtime, repo, ["ls-files", "-z"], env, options);
@@ -9866,7 +10407,7 @@ var flushHunk = (draft) => {
9866
10407
  if (draft.current !== void 0) draft.hunks.push(draft.current);
9867
10408
  draft.current = void 0;
9868
10409
  };
9869
- var finish = (draft, out) => {
10410
+ var finish2 = (draft, out) => {
9870
10411
  if (draft === void 0) return;
9871
10412
  flushHunk(draft);
9872
10413
  out.push({
@@ -9926,7 +10467,7 @@ var parseUnifiedDiff = (text6) => {
9926
10467
  const line2 = withoutCarriageReturn(raw);
9927
10468
  const start = DIFF_GIT.exec(line2);
9928
10469
  if (start !== null) {
9929
- finish(draft, out);
10470
+ finish2(draft, out);
9930
10471
  draft = {
9931
10472
  oldPath: start[1],
9932
10473
  newPath: start[2],
@@ -9937,7 +10478,7 @@ var parseUnifiedDiff = (text6) => {
9937
10478
  };
9938
10479
  } else if (draft !== void 0) bodyLine(draft, line2);
9939
10480
  }
9940
- finish(draft, out);
10481
+ finish2(draft, out);
9941
10482
  return out;
9942
10483
  };
9943
10484
  var DIFF_ARGS = [
@@ -10654,8 +11195,8 @@ var openRepo = (runtime, dir) => {
10654
11195
  };
10655
11196
  var globRegex2 = (pattern) => new RegExp(`^${pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`);
10656
11197
  var matchesBranch = (branch, patterns) => patterns.some((pattern) => globRegex2(pattern).test(branch));
10657
- var line = (runtime, root, args) => {
10658
- const result = runtime.git(args, root);
11198
+ var line = (runtime, root, args2) => {
11199
+ const result = runtime.git(args2, root);
10659
11200
  const value = result.stdout.trim();
10660
11201
  return result.ok && value !== "" ? value : void 0;
10661
11202
  };
@@ -10781,20 +11322,21 @@ var DEFAULT_CHECKS = {
10781
11322
  worktree: worktreeCheck,
10782
11323
  merge: mergeCheck,
10783
11324
  review: reviewCheck,
10784
- callers: callersCheck
11325
+ callers: callersCheck,
11326
+ runtime: runtimeCheck
10785
11327
  };
10786
11328
 
10787
11329
  // src/gate/run.ts
10788
11330
  var FAST_PATH_MS = 10 * 6e4;
10789
- var toEvidence = (ctx, check2, outcome) => ({
11331
+ var toEvidence = (ctx, check2, outcome2) => ({
10790
11332
  schema: EVIDENCE_SCHEMA,
10791
11333
  task: ctx.contract.id,
10792
11334
  check: check2.id,
10793
11335
  kind: check2.kind,
10794
11336
  treeHash: ctx.treeHash,
10795
11337
  at: ctx.runtime.now(),
10796
- ...outcome,
10797
- ...check2.run === void 0 || outcome.command !== void 0 ? {} : { command: check2.run }
11338
+ ...outcome2,
11339
+ ...check2.run === void 0 || outcome2.command !== void 0 ? {} : { command: check2.run }
10798
11340
  });
10799
11341
  var reusable = (ledger, ctx, check2) => {
10800
11342
  if (!REUSABLE_KINDS.includes(check2.kind)) return false;
@@ -10990,7 +11532,8 @@ var KNOWN_KINDS = /* @__PURE__ */ new Set([
10990
11532
  "worktree",
10991
11533
  "merge",
10992
11534
  "review",
10993
- "callers"
11535
+ "callers",
11536
+ "runtime"
10994
11537
  ]);
10995
11538
  var requirementText = (t, check2) => {
10996
11539
  const key = `gate.require.${check2.kind}`;
@@ -11183,10 +11726,10 @@ var HELM_READ = /* @__PURE__ */ new Set([
11183
11726
  "env"
11184
11727
  ]);
11185
11728
  var CURL_WRITE = /^(?:-X|--request|-d|--data.*|-F|--form|-T|--upload-file|-o|--output|-O|--remote-name)$/;
11186
- var firstPositional = (args, valueFlags = /* @__PURE__ */ new Set()) => {
11729
+ var firstPositional = (args2, valueFlags = /* @__PURE__ */ new Set()) => {
11187
11730
  const out = [];
11188
- for (let index = 0; index < args.length; index += 1) {
11189
- const arg = args[index] ?? "";
11731
+ for (let index = 0; index < args2.length; index += 1) {
11732
+ const arg = args2[index] ?? "";
11190
11733
  if (arg.startsWith("-")) {
11191
11734
  if (valueFlags.has(arg)) index += 1;
11192
11735
  } else out.push(arg);
@@ -11221,16 +11764,16 @@ var KUBE_VALUE_FLAGS = /* @__PURE__ */ new Set([
11221
11764
  "--container",
11222
11765
  "--kube-context"
11223
11766
  ]);
11224
- var dockerReadOnly = (args) => {
11225
- const words = firstPositional(args, DOCKER_VALUE_FLAGS);
11767
+ var dockerReadOnly = (args2) => {
11768
+ const words = firstPositional(args2, DOCKER_VALUE_FLAGS);
11226
11769
  const [first, second] = words;
11227
11770
  if (first === void 0) return true;
11228
11771
  if (DOCKER_GROUPS.has(first))
11229
11772
  return second === void 0 || DOCKER_READ.has(second) || first === "compose" && second === "top";
11230
11773
  return DOCKER_READ.has(first);
11231
11774
  };
11232
- var kubectlReadOnly = (program, args) => {
11233
- const [verb, object] = firstPositional(args, KUBE_VALUE_FLAGS);
11775
+ var kubectlReadOnly = (program, args2) => {
11776
+ const [verb, object] = firstPositional(args2, KUBE_VALUE_FLAGS);
11234
11777
  if (verb === void 0) return true;
11235
11778
  if (program === "helm") return HELM_READ.has(verb);
11236
11779
  if (verb === "config") return object === "view" || object === "get-contexts" || object === "current-context";
@@ -11240,24 +11783,24 @@ var kubectlReadOnly = (program, args) => {
11240
11783
  var writesFile = (command) => command.redirects.some(
11241
11784
  (redirect) => (redirect.op === ">" || redirect.op === ">>" || redirect.op === ">|") && !/^(?:\/dev\/null|&?\d)$/.test(redirect.target) && !redirect.op.startsWith("<")
11242
11785
  );
11243
- var gitReadOnly = (args) => {
11244
- const [verb, ...rest] = firstPositional(args, /* @__PURE__ */ new Set(["-C", "-c"]));
11786
+ var gitReadOnly = (args2) => {
11787
+ const [verb, ...rest] = firstPositional(args2, /* @__PURE__ */ new Set(["-C", "-c"]));
11245
11788
  if (verb === void 0) return true;
11246
11789
  if (GIT_READ.has(verb)) return true;
11247
11790
  return GIT_LIST.has(verb) && (rest.length === 0 || verb === "stash" && rest[0] === "list");
11248
11791
  };
11249
11792
  var programReadOnly = (command) => {
11250
- const { program, args } = command;
11251
- if (program === "git") return gitReadOnly(args);
11793
+ const { program, args: args2 } = command;
11794
+ if (program === "git") return gitReadOnly(args2);
11252
11795
  if (program === "docker" || program === "docker-compose")
11253
- return dockerReadOnly(program === "docker-compose" ? ["compose", ...args] : args);
11254
- if (program === "systemctl") return SYSTEMCTL_READ.has(firstPositional(args)[0] ?? "status");
11255
- if (program === "kubectl" || program === "helm") return kubectlReadOnly(program, args);
11256
- if (program === "curl") return !args.some((arg) => CURL_WRITE.test(arg));
11796
+ return dockerReadOnly(program === "docker-compose" ? ["compose", ...args2] : args2);
11797
+ if (program === "systemctl") return SYSTEMCTL_READ.has(firstPositional(args2)[0] ?? "status");
11798
+ if (program === "kubectl" || program === "helm") return kubectlReadOnly(program, args2);
11799
+ if (program === "curl") return !args2.some((arg) => CURL_WRITE.test(arg));
11257
11800
  if (program === "find")
11258
- return !args.some((arg) => arg === "-delete" || arg.startsWith("-exec") || arg.startsWith("-ok"));
11259
- if (program === "sed") return !args.some((arg) => arg.startsWith("-i") || arg === "--in-place");
11260
- if (program === "awk") return !args.some((arg) => arg.includes("system(") || arg.includes(">"));
11801
+ return !args2.some((arg) => arg === "-delete" || arg.startsWith("-exec") || arg.startsWith("-ok"));
11802
+ if (program === "sed") return !args2.some((arg) => arg.startsWith("-i") || arg === "--in-place");
11803
+ if (program === "awk") return !args2.some((arg) => arg.includes("system(") || arg.includes(">"));
11261
11804
  return READ_PROGRAMS.has(program);
11262
11805
  };
11263
11806
  var readOnlyCommand = (command) => !writesFile(command) && programReadOnly(command);
@@ -11317,11 +11860,11 @@ var RSYNC_LONG_VALUE = /* @__PURE__ */ new Set([
11317
11860
  "--files-from"
11318
11861
  ]);
11319
11862
  var parseSsh = (command) => {
11320
- const { args } = command;
11863
+ const { args: args2 } = command;
11321
11864
  let index = 0;
11322
11865
  let noCommand = false;
11323
- while (index < args.length && (args[index] ?? "").startsWith("-")) {
11324
- const arg = args[index] ?? "";
11866
+ while (index < args2.length && (args2[index] ?? "").startsWith("-")) {
11867
+ const arg = args2[index] ?? "";
11325
11868
  if (arg === "--") {
11326
11869
  index += 1;
11327
11870
  break;
@@ -11330,17 +11873,17 @@ var parseSsh = (command) => {
11330
11873
  const last = arg.charAt(arg.length - 1);
11331
11874
  index += arg.length === 2 && SSH_VALUE.includes(last) ? 2 : 1;
11332
11875
  }
11333
- const destination = args[index];
11876
+ const destination = args2[index];
11334
11877
  if (destination === void 0) return void 0;
11335
- return { destination: destination.replace(/^ssh:\/\//, ""), remote: args.slice(index + 1), noCommand };
11878
+ return { destination: destination.replace(/^ssh:\/\//, ""), remote: args2.slice(index + 1), noCommand };
11336
11879
  };
11337
11880
  var REMOTE_PATH = /^(?:(?:scp|rsync|sftp):\/\/)?(?:[^@/:\s]+@)?(\[[^\]]+\]|[A-Za-z0-9][A-Za-z0-9.-]*)(?::|::|\/)/;
11338
11881
  var remoteHostOf = (operand) => {
11339
11882
  if (/^[A-Za-z]:[\\/]/.test(operand) || operand.startsWith("/") || operand.startsWith(".")) return void 0;
11340
- const url = /^(?:scp|rsync|sftp):\/\//.test(operand);
11883
+ const url2 = /^(?:scp|rsync|sftp):\/\//.test(operand);
11341
11884
  const match = REMOTE_PATH.exec(operand);
11342
11885
  if (match === null) return void 0;
11343
- if (!url && !operand.slice(match[0].length - 1).startsWith(":")) return void 0;
11886
+ if (!url2 && !operand.slice(match[0].length - 1).startsWith(":")) return void 0;
11344
11887
  return (match[1] ?? "").replace(/^\[|\]$/g, "");
11345
11888
  };
11346
11889
  var copyOperands = (command, shortValue) => {
@@ -11445,12 +11988,12 @@ var copyHit = (command, isProd) => {
11445
11988
  const source = call.sources.map(remoteHostOf).find((host) => host !== void 0 && isProd(host));
11446
11989
  return source === void 0 ? void 0 : hit2("read", hostKey(source), command.program);
11447
11990
  };
11448
- var valueAfter = (args, ...names) => {
11449
- for (let index = 0; index < args.length; index += 1) {
11450
- const arg = args[index] ?? "";
11991
+ var valueAfter = (args2, ...names) => {
11992
+ for (let index = 0; index < args2.length; index += 1) {
11993
+ const arg = args2[index] ?? "";
11451
11994
  const equals = names.find((name) => arg.startsWith(`${name}=`));
11452
11995
  if (equals !== void 0) return arg.slice(equals.length + 1);
11453
- if (names.includes(arg)) return args[index + 1];
11996
+ if (names.includes(arg)) return args2[index + 1];
11454
11997
  }
11455
11998
  return void 0;
11456
11999
  };
@@ -11492,8 +12035,8 @@ var DUMPS = /* @__PURE__ */ new Set(["pg_dump", "mysqldump"]);
11492
12035
  var databaseHost = (command) => {
11493
12036
  const flag2 = valueAfter(command.args, "-h", "--host");
11494
12037
  if (flag2 !== void 0) return flag2;
11495
- const url = [...command.args, command.env["DATABASE_URL"] ?? ""].map((arg) => URL_HOST.exec(arg)?.[1]).find((host) => host !== void 0);
11496
- return url ?? command.env["PGHOST"];
12038
+ const url2 = [...command.args, command.env["DATABASE_URL"] ?? ""].map((arg) => URL_HOST.exec(arg)?.[1]).find((host) => host !== void 0);
12039
+ return url2 ?? command.env["PGHOST"];
11497
12040
  };
11498
12041
  var databaseReadOnly = (command) => {
11499
12042
  if (DUMPS.has(command.program)) return true;
@@ -11667,14 +12210,14 @@ var globalOption = (globals, name, value) => {
11667
12210
  var gitInvocation = (command) => {
11668
12211
  if (command.program !== "git") return void 0;
11669
12212
  const globals = { dirs: [], config: {} };
11670
- const { args } = command;
12213
+ const { args: args2 } = command;
11671
12214
  let index = 0;
11672
- while (index < args.length && (args[index] ?? "").startsWith("-")) {
11673
- const arg = args[index] ?? "";
12215
+ while (index < args2.length && (args2[index] ?? "").startsWith("-")) {
12216
+ const arg = args2[index] ?? "";
11674
12217
  const equals = arg.indexOf("=");
11675
12218
  const name = equals > 2 && arg.startsWith("--") ? arg.slice(0, equals) : arg;
11676
12219
  if (WITH_VALUE.has(name) && equals < 0) {
11677
- globalOption(globals, name, args[index + 1] ?? "");
12220
+ globalOption(globals, name, args2[index + 1] ?? "");
11678
12221
  index += 2;
11679
12222
  } else {
11680
12223
  if (equals > 0) globalOption(globals, name, arg.slice(equals + 1));
@@ -11684,8 +12227,8 @@ var gitInvocation = (command) => {
11684
12227
  return {
11685
12228
  dirs: globals.dirs,
11686
12229
  config: globals.config,
11687
- subcommand: args[index] ?? "",
11688
- args: args.slice(index + 1),
12230
+ subcommand: args2[index] ?? "",
12231
+ args: args2.slice(index + 1),
11689
12232
  command
11690
12233
  };
11691
12234
  };
@@ -11819,14 +12362,14 @@ var shortCluster = (building, arg, next2, spec2) => {
11819
12362
  }
11820
12363
  return 1;
11821
12364
  };
11822
- var parseOptions = (args, spec2) => {
12365
+ var parseOptions = (args2, spec2) => {
11823
12366
  const building = { flags: /* @__PURE__ */ new Set(), values: /* @__PURE__ */ new Map(), positionals: [] };
11824
12367
  let index = 0;
11825
- while (index < args.length) {
11826
- const arg = args[index] ?? "";
11827
- const next2 = args[index + 1];
12368
+ while (index < args2.length) {
12369
+ const arg = args2[index] ?? "";
12370
+ const next2 = args2[index + 1];
11828
12371
  if (arg === "--") {
11829
- return { ...building, paths: args.slice(index + 1), dashdash: true };
12372
+ return { ...building, paths: args2.slice(index + 1), dashdash: true };
11830
12373
  }
11831
12374
  if (arg.startsWith("--")) index += longOption(building, arg, next2, spec2);
11832
12375
  else if (arg.startsWith("-") && arg.length > 1 && !/^-\d+$/.test(arg)) {
@@ -12264,9 +12807,9 @@ var gitVerdict = (invocation, facts) => {
12264
12807
 
12265
12808
  // src/git/switch-target.ts
12266
12809
  var CREATES = /* @__PURE__ */ new Set(["-c", "-C", "-b", "-B", "--create", "--force-create", "--orphan"]);
12267
- var operands = (args) => {
12268
- if (args.includes("--")) return void 0;
12269
- return args.filter((arg) => !arg.startsWith("-"));
12810
+ var operands = (args2) => {
12811
+ if (args2.includes("--")) return void 0;
12812
+ return args2.filter((arg) => !arg.startsWith("-"));
12270
12813
  };
12271
12814
  var switchTarget = (invocation, facts) => {
12272
12815
  if (invocation.subcommand !== "switch" && invocation.subcommand !== "checkout") return void 0;
@@ -12460,8 +13003,8 @@ var BACKUP_PREFIX = "refs/finitemonkey/backup/";
12460
13003
  var DEFAULT_SNAPSHOT_TIMEOUT_MS = 2500;
12461
13004
  var stampOf = (iso) => iso.replace(/[:.]/g, "-");
12462
13005
  var slug = (op) => op.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "manual";
12463
- var optionalLine = (runtime, repo, args) => {
12464
- const result = runtime.git(args, repo.root);
13006
+ var optionalLine = (runtime, repo, args2) => {
13007
+ const result = runtime.git(args2, repo.root);
12465
13008
  const value = result.stdout.trim();
12466
13009
  return result.ok && value !== "" ? value : void 0;
12467
13010
  };
@@ -12566,17 +13109,17 @@ var fileList = (items) => {
12566
13109
  return items.length > MAX_FILES2 ? `${head} (+${String(items.length - MAX_FILES2)})` : head;
12567
13110
  };
12568
13111
  var foreignLabel = (t, file) => t(file.reason === "before-session" ? "git.foreign.before" : "git.foreign.other", { path: file.path });
12569
- var outcomeMessage = (t, outcome) => {
12570
- switch (outcome.kind) {
13112
+ var outcomeMessage = (t, outcome2) => {
13113
+ switch (outcome2.kind) {
12571
13114
  case "block":
12572
- return ruleMessage(t, outcome.rule, outcome.command, outcome.subject);
13115
+ return ruleMessage(t, outcome2.rule, outcome2.command, outcome2.subject);
12573
13116
  case "foreign":
12574
13117
  return t("git.block.foreign", {
12575
- command: outcome.command,
12576
- files: fileList(outcome.files.map((file) => foreignLabel(t, file)))
13118
+ command: outcome2.command,
13119
+ files: fileList(outcome2.files.map((file) => foreignLabel(t, file)))
12577
13120
  });
12578
13121
  case "unknown":
12579
- return t("git.block.unknown", { command: outcome.command });
13122
+ return t("git.block.unknown", { command: outcome2.command });
12580
13123
  case "pass":
12581
13124
  case "snapshot":
12582
13125
  return void 0;
@@ -12620,10 +13163,10 @@ var review = (input, commands, owner) => {
12620
13163
  const decision = PROD_PROGRAMS.has(command.program) ? prod().decide(command) : void 0;
12621
13164
  if (decision?.kind === "block") return { blocked: decision.reason, snapshots, reads };
12622
13165
  if (decision !== void 0) reads.push(decision);
12623
- for (const outcome of guardGitCommand({ runtime: input.runtime, cwd: input.cwd, owner, chain }, command)) {
12624
- const message = outcomeMessage(input.t, outcome);
13166
+ for (const outcome2 of guardGitCommand({ runtime: input.runtime, cwd: input.cwd, owner, chain }, command)) {
13167
+ const message = outcomeMessage(input.t, outcome2);
12625
13168
  if (message !== void 0) return { blocked: message, snapshots, reads };
12626
- if (outcome.kind === "snapshot") snapshots.push(outcome);
13169
+ if (outcome2.kind === "snapshot") snapshots.push(outcome2);
12627
13170
  }
12628
13171
  }
12629
13172
  return { blocked: void 0, snapshots, reads };
@@ -12869,7 +13412,7 @@ var addsMarker = (input, tool, inputs, file) => {
12869
13412
  return false;
12870
13413
  }
12871
13414
  };
12872
- var isReadOnly = (program, args) => READ_ONLY.has(program) || program === "git" && GIT_READ_ONLY.has(args[0] ?? "");
13415
+ var isReadOnly = (program, args2) => READ_ONLY.has(program) || program === "git" && GIT_READ_ONLY.has(args2[0] ?? "");
12873
13416
  var shellAddsMarker = (command) => markerCount(command) > 0 && shellWords(command).segments.some((segment) => !isReadOnly(segment.program, segment.args));
12874
13417
  var shown2 = (cwd, file) => {
12875
13418
  const rel = toPosixPath(relative6(cwd, file));
@@ -12907,17 +13450,17 @@ var GIT_FLAGS = { title: [], body: ["-m", "--message"], file: ["-F", "--file"] }
12907
13450
  var GH_FLAGS = { title: ["-t", "--title"], body: ["-b", "--body"], file: ["-F", "--body-file"] };
12908
13451
  var GIT_GLOBAL_WITH_VALUE = /* @__PURE__ */ new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace"]);
12909
13452
  var CLUSTER_WITH_MESSAGE = /^-[a-zA-Z]*m$/;
12910
- var flagValues = (args, spec2) => {
13453
+ var flagValues = (args2, spec2) => {
12911
13454
  const known2 = [...spec2.title, ...spec2.body, ...spec2.file];
12912
13455
  const out = [];
12913
- for (let index = 0; index < args.length; index += 1) {
12914
- const arg = args[index] ?? "";
13456
+ for (let index = 0; index < args2.length; index += 1) {
13457
+ const arg = args2[index] ?? "";
12915
13458
  const equals = arg.indexOf("=");
12916
13459
  const name = arg.startsWith("--") && equals > 0 ? arg.slice(0, equals) : arg;
12917
13460
  if (name !== arg && known2.includes(name)) {
12918
13461
  out.push({ flag: name, value: arg.slice(equals + 1) });
12919
13462
  } else if (known2.includes(arg) || spec2 === GIT_FLAGS && CLUSTER_WITH_MESSAGE.test(arg)) {
12920
- out.push({ flag: known2.includes(arg) ? arg : "-m", value: args[index + 1] ?? "" });
13463
+ out.push({ flag: known2.includes(arg) ? arg : "-m", value: args2[index + 1] ?? "" });
12921
13464
  index += 1;
12922
13465
  }
12923
13466
  }
@@ -12931,17 +13474,17 @@ var sourceOf = (value, isFile, segment, heredocs) => {
12931
13474
  const piped = segment.args.map((arg) => heredocIn(arg, heredocs)).find((text6) => text6 !== void 0);
12932
13475
  return { kind: "inline", text: piped ?? "" };
12933
13476
  };
12934
- var collect3 = (target, segment, args, spec2, heredocs) => {
12935
- const values = flagValues(args, spec2);
13477
+ var collect3 = (target, segment, args2, spec2, heredocs) => {
13478
+ const values = flagValues(args2, spec2);
12936
13479
  if (values.length === 0) return void 0;
12937
13480
  const title = values.find((item) => spec2.title.includes(item.flag));
12938
13481
  const bodies = values.filter((item) => !spec2.title.includes(item.flag)).map((item) => sourceOf(item.value, spec2.file.includes(item.flag), segment, heredocs));
12939
13482
  return { target, title: title === void 0 ? void 0 : sourceOf(title.value, false, segment, heredocs), bodies };
12940
13483
  };
12941
- var gitSubcommand = (args) => {
13484
+ var gitSubcommand = (args2) => {
12942
13485
  let index = 0;
12943
- while (index < args.length && (args[index] ?? "").startsWith("-")) {
12944
- index += GIT_GLOBAL_WITH_VALUE.has(args[index] ?? "") ? 2 : 1;
13486
+ while (index < args2.length && (args2[index] ?? "").startsWith("-")) {
13487
+ index += GIT_GLOBAL_WITH_VALUE.has(args2[index] ?? "") ? 2 : 1;
12945
13488
  }
12946
13489
  return index;
12947
13490
  };
@@ -13157,7 +13700,7 @@ var READERS = /* @__PURE__ */ new Set([
13157
13700
  var SECRET_NAME = /(?:KEY|SECRET|TOKEN|PASSWORD|PASSWD|PASS|PWD|CREDENTIAL)/i;
13158
13701
  var PERSON_ONLY = /\b(?:finitemonkey|fm)(?:\.mjs)?\s+secrets\s+(?:accept\b|incident\s+(?:rotated|close)\b)/;
13159
13702
  var LISTS_ENV = /* @__PURE__ */ new Set(["get-childitem", "gci", "dir", "ls"]);
13160
- var onlyFlags = (args) => args.every((arg) => arg.startsWith("-"));
13703
+ var onlyFlags = (args2) => args2.every((arg) => arg.startsWith("-"));
13161
13704
  var readTarget = (segment) => {
13162
13705
  const redirected = segment.redirectedFrom.find(isSensitivePath);
13163
13706
  if (redirected !== void 0) return redirected;
@@ -13291,17 +13834,17 @@ var secretsPreToolUse = (input) => {
13291
13834
  // src/secrets/remotes.ts
13292
13835
  var USER_AND_SECRET = /^[a-z][a-z0-9+.-]*:\/\/[^\s/@:]+:[^\s/@]+@/i;
13293
13836
  var USERINFO = /^[a-z][a-z0-9+.-]*:\/\/(?<user>[^\s/@]+)@/i;
13294
- var hasCredential = (url) => {
13295
- if (USER_AND_SECRET.test(url)) return true;
13296
- const user = USERINFO.exec(url)?.groups?.["user"];
13837
+ var hasCredential = (url2) => {
13838
+ if (USER_AND_SECRET.test(url2)) return true;
13839
+ const user = USERINFO.exec(url2)?.groups?.["user"];
13297
13840
  return user !== void 0 && scanText(user, { rules: CREDENTIAL_RULES }).length > 0;
13298
13841
  };
13299
13842
  var parseRemotes = (output) => {
13300
13843
  const remotes = /* @__PURE__ */ new Map();
13301
13844
  for (const line2 of output.split(/\r?\n/)) {
13302
- const [name, url] = line2.trim().split(/\s+/);
13303
- if (name === void 0 || url === void 0 || name === "") continue;
13304
- remotes.set(name, [...remotes.get(name) ?? [], url]);
13845
+ const [name, url2] = line2.trim().split(/\s+/);
13846
+ if (name === void 0 || url2 === void 0 || name === "") continue;
13847
+ remotes.set(name, [...remotes.get(name) ?? [], url2]);
13305
13848
  }
13306
13849
  return remotes;
13307
13850
  };
@@ -13550,9 +14093,9 @@ var invocationOf = (entry2, vars) => {
13550
14093
  const seconds = typeof timeout === "number" && timeout > 0 ? timeout : DEFAULT_TIMEOUT_S;
13551
14094
  const timeoutMs = Math.min(seconds * 1e3, PROBE_TIMEOUT_MS2);
13552
14095
  const command = expand(raw, vars);
13553
- const args = strings3(entry2.handler["args"]).map((arg) => expand(arg, vars));
13554
- const shell = args.length === 0;
13555
- return { command, args, shell, timeoutMs, display: [command, ...args].join(" ") };
14096
+ const args2 = strings3(entry2.handler["args"]).map((arg) => expand(arg, vars));
14097
+ const shell = args2.length === 0;
14098
+ return { command, args: args2, shell, timeoutMs, display: [command, ...args2].join(" ") };
13556
14099
  };
13557
14100
  var versionInvocation = (invocation) => {
13558
14101
  if (invocation.shell) {
@@ -13561,8 +14104,8 @@ var versionInvocation = (invocation) => {
13561
14104
  }
13562
14105
  const at = invocation.args.indexOf("hook");
13563
14106
  if (at < 0) return void 0;
13564
- const args = [...invocation.args.slice(0, at), "version", "--json"];
13565
- return { ...invocation, args, display: [invocation.command, ...args].join(" ") };
14107
+ const args2 = [...invocation.args.slice(0, at), "version", "--json"];
14108
+ return { ...invocation, args: args2, display: [invocation.command, ...args2].join(" ") };
13566
14109
  };
13567
14110
  var syntheticPayload = (event, cwd) => {
13568
14111
  const base = { session_id: "finitemonkey-doctor", transcript_path: "", cwd, hook_event_name: event };
@@ -13763,10 +14306,10 @@ var handlerProblems = (at, handler, claude) => {
13763
14306
  const type = handler["type"];
13764
14307
  if (typeof type !== "string" || !HANDLER_TYPES.some((known2) => known2 === type)) return [`${at}.type: unknown`];
13765
14308
  const problems = [];
13766
- const { command, args, timeout, onFailure } = handler;
14309
+ const { command, args: args2, timeout, onFailure } = handler;
13767
14310
  if (type === "command" && (typeof command !== "string" || command.trim() === ""))
13768
14311
  problems.push(`${at}.command: empty`);
13769
- if (args !== void 0 && !(Array.isArray(args) && args.every((arg) => typeof arg === "string"))) {
14312
+ if (args2 !== void 0 && !(Array.isArray(args2) && args2.every((arg) => typeof arg === "string"))) {
13770
14313
  problems.push(`${at}.args: not a list of strings`);
13771
14314
  }
13772
14315
  if (timeout !== void 0 && !(typeof timeout === "number" && timeout > 0))
@@ -14185,9 +14728,9 @@ var CHECKS = [
14185
14728
  ];
14186
14729
  var probeOnce = (probe) => {
14187
14730
  const seen = /* @__PURE__ */ new Map();
14188
- return (command, args, timeoutMs) => {
14189
- const key = [command, ...args].join(" ");
14190
- const known2 = seen.get(key) ?? probe(command, args, timeoutMs);
14731
+ return (command, args2, timeoutMs) => {
14732
+ const key = [command, ...args2].join(" ");
14733
+ const known2 = seen.get(key) ?? probe(command, args2, timeoutMs);
14191
14734
  seen.set(key, known2);
14192
14735
  return known2;
14193
14736
  };
@@ -14298,93 +14841,7 @@ var doctorCommand = {
14298
14841
  usage: "finitemonkey doctor [--fix] [--report] [--dry-run] [--json]",
14299
14842
  status: "ready",
14300
14843
  options: { fix: { type: "boolean" }, report: { type: "boolean" } },
14301
- run: (ctx, args) => Promise.resolve(runDoctor(ctx, args.values["fix"] === true, args.values["report"] === true))
14302
- };
14303
-
14304
- // src/core/args.ts
14305
- import { parseArgs } from "node:util";
14306
-
14307
- // src/core/problem.ts
14308
- var usageProblem = (code, messageKey, fix, params2 = {}) => ({
14309
- code,
14310
- messageKey,
14311
- params: params2,
14312
- fix,
14313
- exitCode: ExitCode.Usage
14314
- });
14315
- var needsPersonProblem = (messageKey, fix, params2 = {}) => ({
14316
- code: "needs_confirmation",
14317
- messageKey,
14318
- params: params2,
14319
- fix,
14320
- exitCode: ExitCode.NeedsPerson
14321
- });
14322
- var FmError = class extends Error {
14323
- problem;
14324
- constructor(problem4) {
14325
- super(problem4.code);
14326
- this.name = "FmError";
14327
- this.problem = problem4;
14328
- }
14329
- };
14330
-
14331
- // src/core/args.ts
14332
- var GLOBAL_OPTIONS = {
14333
- json: { type: "boolean" },
14334
- lang: { type: "string" },
14335
- yes: { type: "boolean", short: "y" },
14336
- "dry-run": { type: "boolean" },
14337
- version: { type: "boolean", short: "v" },
14338
- help: { type: "boolean", short: "h" }
14339
- };
14340
- var flag = (values, name) => values[name] === true;
14341
- var looseArgs = (argv) => {
14342
- const { values, positionals } = parseArgs({
14343
- args: [...argv],
14344
- options: GLOBAL_OPTIONS,
14345
- strict: false,
14346
- allowPositionals: true
14347
- });
14348
- const lang = values.lang;
14349
- return {
14350
- positionals,
14351
- json: flag(values, "json"),
14352
- yes: flag(values, "yes"),
14353
- dryRun: flag(values, "dry-run"),
14354
- version: flag(values, "version"),
14355
- help: flag(values, "help"),
14356
- lang: typeof lang === "string" ? lang : void 0
14357
- };
14358
- };
14359
- var resolveCommand = (registry, positionals) => registry.filter((spec2) => spec2.path.every((part2, index) => positionals[index] === part2)).reduce(
14360
- (best, spec2) => best === void 0 || spec2.path.length > best.consumed ? { spec: spec2, consumed: spec2.path.length } : best,
14361
- void 0
14362
- );
14363
- var strictArgs = (argv, spec2) => {
14364
- const fix = `finitemonkey help ${spec2.path.join(" ")}`;
14365
- let parsed;
14366
- try {
14367
- parsed = parseArgs({
14368
- args: [...argv],
14369
- options: { ...GLOBAL_OPTIONS, ...spec2.options },
14370
- strict: true,
14371
- allowPositionals: true
14372
- });
14373
- } catch (error) {
14374
- const detail = error instanceof Error ? error.message : String(error);
14375
- return reject(usageProblem("invalid_arguments", "error.invalid_arguments", fix, { detail }));
14376
- }
14377
- const rest = parsed.positionals.slice(spec2.path.length);
14378
- const max = spec2.maxPositionals ?? 0;
14379
- const extra = rest[max];
14380
- if (extra !== void 0) {
14381
- return reject(usageProblem("unexpected_argument", "error.unexpected_argument", fix, { argument: extra }));
14382
- }
14383
- return accept({ values: parsed.values, positionals: rest });
14384
- };
14385
- var stringOption = (values, name) => {
14386
- const value = values[name];
14387
- return typeof value === "string" ? value : void 0;
14844
+ run: (ctx, args2) => Promise.resolve(runDoctor(ctx, args2.values["fix"] === true, args2.values["report"] === true))
14388
14845
  };
14389
14846
 
14390
14847
  // src/gate/baseline-run.ts
@@ -14399,8 +14856,8 @@ var runBaseline = (options) => {
14399
14856
  if (commands.length === 0) return { kind: "skipped", reason: "no_commands" };
14400
14857
  const checks = {};
14401
14858
  for (const check2 of commands) {
14402
- const outcome = runShellCheck(check2, check2.run ?? "", ctx);
14403
- checks[check2.id] = { exitCode: outcome.exitCode ?? null, failures: outcome.failures ?? [] };
14859
+ const outcome2 = runShellCheck(check2, check2.run ?? "", ctx);
14860
+ checks[check2.id] = { exitCode: outcome2.exitCode ?? null, failures: outcome2.failures ?? [] };
14404
14861
  }
14405
14862
  if (treeHash(gitAt(ctx.runtime, ctx.paths.root)) !== ctx.treeHash) return { kind: "skipped", reason: "tree_moved" };
14406
14863
  writeBaseline(ctx.runtime, ctx.paths, ctx.treeHash, checks);
@@ -14459,19 +14916,19 @@ var gateCommand = {
14459
14916
  baseline: { type: "boolean" },
14460
14917
  prs: { type: "boolean" }
14461
14918
  },
14462
- run: (ctx, args) => {
14463
- const taskId = stringOption(args.values, "task");
14919
+ run: (ctx, args2) => {
14920
+ const taskId = stringOption(args2.values, "task");
14464
14921
  const options = {
14465
14922
  runtime: ctx.runtime,
14466
14923
  cwd: ctx.cwd,
14467
14924
  env: ctx.env,
14468
14925
  sessionId: void 0,
14469
- budgetMs: budgetMs(stringOption(args.values, "budget")),
14470
- fast: args.values["fast"] === true,
14926
+ budgetMs: budgetMs(stringOption(args2.values, "budget")),
14927
+ fast: args2.values["fast"] === true,
14471
14928
  ...taskId === void 0 ? {} : { taskId }
14472
14929
  };
14473
- if (args.values["prs"] === true) return Promise.resolve(prsResult(ctx));
14474
- if (args.values["baseline"] === true) {
14930
+ if (args2.values["prs"] === true) return Promise.resolve(prsResult(ctx));
14931
+ if (args2.values["baseline"] === true) {
14475
14932
  const baseline = runBaseline(options);
14476
14933
  return Promise.resolve(
14477
14934
  success(baseline, [
@@ -14481,7 +14938,7 @@ var gateCommand = {
14481
14938
  ])
14482
14939
  );
14483
14940
  }
14484
- return Promise.resolve(runResult(ctx, runGate(options), args.values["strict"] === true));
14941
+ return Promise.resolve(runResult(ctx, runGate(options), args2.values["strict"] === true));
14485
14942
  }
14486
14943
  };
14487
14944
 
@@ -14562,11 +15019,11 @@ var mergeVerifyCommand = {
14562
15019
  status: "ready",
14563
15020
  options: { since: { type: "string" }, dropped: { type: "string" } },
14564
15021
  maxPositionals: 1,
14565
- run: (ctx, args) => {
15022
+ run: (ctx, args2) => {
14566
15023
  const repo = repoOf2(ctx);
14567
15024
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
14568
- const dropped = stringOption(args.values, "dropped");
14569
- const targets2 = targetsOf2(ctx, repo, args.positionals[0], stringOption(args.values, "since"));
15025
+ const dropped = stringOption(args2.values, "dropped");
15026
+ const targets2 = targetsOf2(ctx, repo, args2.positionals[0], stringOption(args2.values, "since"));
14570
15027
  if (targets2.length === 0) {
14571
15028
  const data = { schema: MERGE_VERIFY_RUN_SCHEMA, merges: [] };
14572
15029
  return Promise.resolve(success(data, [ctx.t("git.merge.none", { detail: "HEAD" })]));
@@ -14674,11 +15131,11 @@ var rescueCommand = {
14674
15131
  usage: "finitemonkey rescue [--restore <sha> --branch <name>] [--json]",
14675
15132
  status: "ready",
14676
15133
  options: { restore: { type: "string" }, branch: { type: "string" } },
14677
- run: (ctx, args) => {
15134
+ run: (ctx, args2) => {
14678
15135
  const repo = repoOf2(ctx);
14679
15136
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
14680
- const sha = stringOption(args.values, "restore");
14681
- const branch = stringOption(args.values, "branch");
15137
+ const sha = stringOption(args2.values, "restore");
15138
+ const branch = stringOption(args2.values, "branch");
14682
15139
  if (sha === void 0 && branch === void 0) {
14683
15140
  const report2 = rescueReport(ctx.runtime, repo);
14684
15141
  return Promise.resolve(success({ schema: RESCUE_SCHEMA, action: "list", ...report2 }, listing(ctx.t, report2)));
@@ -14709,8 +15166,8 @@ var rescueCommand = {
14709
15166
  // src/git/undo.ts
14710
15167
  import { basename as basename3, dirname as dirname8, join as join36 } from "node:path";
14711
15168
  var INDEX_SUBJECT = "finitemonkey index:";
14712
- var gitLine = (runtime, repo, args) => {
14713
- const result = runtime.git(args, repo.root);
15169
+ var gitLine = (runtime, repo, args2) => {
15170
+ const result = runtime.git(args2, repo.root);
14714
15171
  const value = result.stdout.trim();
14715
15172
  return result.ok && value !== "" ? value : void 0;
14716
15173
  };
@@ -14758,9 +15215,9 @@ var intoWorktree = (runtime, repo, view2) => {
14758
15215
  const added = runtime.gitWith(["worktree", "add", "--detach", path, view2.commit], repo.root, {});
14759
15216
  return added.ok ? accept(path) : reject(failure2("worktree add", added));
14760
15217
  };
14761
- var step2 = (runtime, repo, args) => {
14762
- const result = runtime.gitWith(args, repo.root, {});
14763
- return result.ok ? accept(void 0) : reject(failure2(args.join(" "), result));
15218
+ var step2 = (runtime, repo, args2) => {
15219
+ const result = runtime.gitWith(args2, repo.root, {});
15220
+ return result.ok ? accept(void 0) : reject(failure2(args2.join(" "), result));
14764
15221
  };
14765
15222
  var inPlace = (runtime, repo, view2) => {
14766
15223
  const current2 = gitLine(runtime, repo, ["rev-parse", "--verify", "--quiet", "HEAD"]);
@@ -14793,10 +15250,10 @@ var snapshotCommand = {
14793
15250
  usage: "finitemonkey snapshot [--message <text>] [--json]",
14794
15251
  status: "ready",
14795
15252
  options: { message: { type: "string", short: "m" } },
14796
- run: (ctx, args) => {
15253
+ run: (ctx, args2) => {
14797
15254
  const repo = repoOf2(ctx);
14798
15255
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
14799
- const message = args.values["message"];
15256
+ const message = args2.values["message"];
14800
15257
  const op = typeof message === "string" && message.trim() !== "" ? message.trim() : "manual";
14801
15258
  const taken = takeSnapshot(ctx.runtime, repo, { op });
14802
15259
  if (!taken.ok) {
@@ -14826,12 +15283,12 @@ var undoCommand = {
14826
15283
  status: "ready",
14827
15284
  options: { list: { type: "boolean" }, "in-place": { type: "boolean" } },
14828
15285
  maxPositionals: 1,
14829
- run: (ctx, args) => {
15286
+ run: (ctx, args2) => {
14830
15287
  const repo = repoOf2(ctx);
14831
15288
  if (repo === void 0) return Promise.resolve(failureFrom(ctx.t, notRepository(ctx.cwd)));
14832
15289
  const views = snapshotViews(ctx.runtime, repo);
14833
- if (args.values["list"] === true) return Promise.resolve(listResult(ctx, views));
14834
- const wanted = args.positionals[0];
15290
+ if (args2.values["list"] === true) return Promise.resolve(listResult(ctx, views));
15291
+ const wanted = args2.positionals[0];
14835
15292
  const view2 = findSnapshot(views, wanted);
14836
15293
  if (view2 === void 0) {
14837
15294
  return Promise.resolve(
@@ -14844,7 +15301,7 @@ var undoCommand = {
14844
15301
  })
14845
15302
  );
14846
15303
  }
14847
- const inPlace2 = args.values["in-place"] === true;
15304
+ const inPlace2 = args2.values["in-place"] === true;
14848
15305
  if (inPlace2 && !(ctx.yes && isPerson(ctx))) {
14849
15306
  return Promise.resolve(
14850
15307
  failureFrom(ctx.t, needsPersonProblem("git.undo.confirm", `finitemonkey undo ${view2.ref} --in-place --yes`))
@@ -14933,8 +15390,8 @@ var helpCommand = {
14933
15390
  usage: "finitemonkey help [command]",
14934
15391
  status: "ready",
14935
15392
  maxPositionals: 3,
14936
- run: (ctx, args, registry) => Promise.resolve(
14937
- args.positionals.length === 0 ? overview(ctx, registry) : topicHelp(ctx, registry, args.positionals)
15393
+ run: (ctx, args2, registry) => Promise.resolve(
15394
+ args2.positionals.length === 0 ? overview(ctx, registry) : topicHelp(ctx, registry, args2.positionals)
14938
15395
  )
14939
15396
  };
14940
15397
 
@@ -14946,8 +15403,8 @@ var hookCommand = {
14946
15403
  status: "ready",
14947
15404
  maxPositionals: 1,
14948
15405
  options: { via: { type: "string" } },
14949
- run: async (ctx, args) => {
14950
- const raw = args.positionals[0] ?? "";
15406
+ run: async (ctx, args2) => {
15407
+ const raw = args2.positionals[0] ?? "";
14951
15408
  const event = parseHookEvent(raw);
14952
15409
  if (event === void 0) {
14953
15410
  const params2 = { event: raw, events: HOOK_EVENTS.join(", ") };
@@ -14956,10 +15413,10 @@ var hookCommand = {
14956
15413
  usageProblem("unknown_hook_event", "error.unknown_hook_event", "finitemonkey help hook", params2)
14957
15414
  );
14958
15415
  }
14959
- if (stringOption(args.values, "via") === PLUGIN_VIA && projectOwnsHooks(ctx)) {
15416
+ if (stringOption(args2.values, "via") === PLUGIN_VIA && projectOwnsHooks(ctx)) {
14960
15417
  return { ok: true, exitCode: 0, data: { event, verdict: "allow" }, raw: { stdout: YIELD_STDOUT, stderr: "" } };
14961
15418
  }
14962
- const outcome = await runHook(event, {
15419
+ const outcome2 = await runHook(event, {
14963
15420
  stdin: ctx.io.stdin,
14964
15421
  deadlineMs: deadlineFor(event, ctx.env["FINITEMONKEY_HOOK_DEADLINE_MS"]),
14965
15422
  handlers: DEFAULT_HANDLERS,
@@ -14969,11 +15426,11 @@ var hookCommand = {
14969
15426
  runtime: ctx.runtime,
14970
15427
  env: ctx.env
14971
15428
  });
14972
- logHookFailure(ctx, event, outcome);
14973
- const data = { event, verdict: outcome.verdict };
14974
- const output = { stdout: outcome.stdout, stderr: outcome.stderr };
14975
- if (outcome.exitCode === 0) return { ok: true, exitCode: 0, data, raw: output };
14976
- const message = outcome.stderr.trim();
15429
+ logHookFailure(ctx, event, outcome2);
15430
+ const data = { event, verdict: outcome2.verdict };
15431
+ const output = { stdout: outcome2.stdout, stderr: outcome2.stderr };
15432
+ if (outcome2.exitCode === 0) return { ok: true, exitCode: 0, data, raw: output };
15433
+ const message = outcome2.stderr.trim();
14977
15434
  return {
14978
15435
  ok: false,
14979
15436
  exitCode: 2,
@@ -15244,8 +15701,8 @@ var i18nCheckCommand = {
15244
15701
  usage: "finitemonkey i18n check [--dir <path>]",
15245
15702
  status: "ready",
15246
15703
  options: { dir: { type: "string" } },
15247
- run: (ctx, args) => {
15248
- const dir = stringOption(args.values, "dir");
15704
+ run: (ctx, args2) => {
15705
+ const dir = stringOption(args2.values, "dir");
15249
15706
  return Promise.resolve(runI18nCheck(ctx, dir === void 0 ? void 0 : toAbsolutePath(dir, ctx.cwd)));
15250
15707
  }
15251
15708
  };
@@ -15589,11 +16046,11 @@ var runInstall = (ctx, request, presetFallback) => {
15589
16046
  if (ctx.dryRun || !ctx.yes || !needsWrite(plan.value)) return presentPlan(ctx, request, plan.value);
15590
16047
  return applyAndReport(ctx, request, plan.value);
15591
16048
  };
15592
- var initRequest = (args) => ({
16049
+ var initRequest = (args2) => ({
15593
16050
  mode: "init",
15594
- preset: stringOption(args.values, "preset"),
15595
- bin: stringOption(args.values, "bin"),
15596
- marketplace: args.values["marketplace"] === true,
16051
+ preset: stringOption(args2.values, "preset"),
16052
+ bin: stringOption(args2.values, "bin"),
16053
+ marketplace: args2.values["marketplace"] === true,
15597
16054
  showDiff: false
15598
16055
  });
15599
16056
  var withLookBack = (ctx, result) => {
@@ -15618,9 +16075,9 @@ var initCommand = {
15618
16075
  marketplace: { type: "boolean" },
15619
16076
  "look-back": { type: "boolean" }
15620
16077
  },
15621
- run: (ctx, args) => {
15622
- const result = runInstall(ctx, initRequest(args), "neutral");
15623
- return Promise.resolve(args.values["look-back"] === true ? withLookBack(ctx, result) : result);
16078
+ run: (ctx, args2) => {
16079
+ const result = runInstall(ctx, initRequest(args2), "neutral");
16080
+ return Promise.resolve(args2.values["look-back"] === true ? withLookBack(ctx, result) : result);
15624
16081
  }
15625
16082
  };
15626
16083
 
@@ -15647,7 +16104,7 @@ var policyFalsePositiveCommand = {
15647
16104
  usage: FIX,
15648
16105
  status: "ready",
15649
16106
  maxPositionals: 1,
15650
- run: (ctx, args) => Promise.resolve(recordFalsePositive(ctx, args.positionals[0] ?? ""))
16107
+ run: (ctx, args2) => Promise.resolve(recordFalsePositive(ctx, args2.positionals[0] ?? ""))
15651
16108
  };
15652
16109
 
15653
16110
  // src/commands/prose-check.ts
@@ -15773,10 +16230,10 @@ var proseCheckCommand = {
15773
16230
  status: "ready",
15774
16231
  options: { stdin: { type: "boolean" }, record: { type: "boolean" } },
15775
16232
  maxPositionals: 1e3,
15776
- run: async (ctx, args) => {
16233
+ run: async (ctx, args2) => {
15777
16234
  const root = projectRoot(ctx);
15778
- const mode = { stdin: args.values["stdin"] === true, record: args.values["record"] === true };
15779
- const reports = await reportsOf(ctx, root, args.positionals, mode);
16235
+ const mode = { stdin: args2.values["stdin"] === true, record: args2.values["record"] === true };
16236
+ const reports = await reportsOf(ctx, root, args2.positionals, mode);
15780
16237
  if (!reports.ok) return failureFrom(ctx.t, reports.error);
15781
16238
  return resultOf2(ctx, reports.value);
15782
16239
  }
@@ -15831,7 +16288,7 @@ var inputProblem = (ctx, id, reason, days) => {
15831
16288
  }
15832
16289
  return void 0;
15833
16290
  };
15834
- var entryOf3 = (ctx, finding3, args) => {
16291
+ var entryOf3 = (ctx, finding3, args2) => {
15835
16292
  const now = ctx.runtime.now();
15836
16293
  return {
15837
16294
  id: finding3.id,
@@ -15839,30 +16296,30 @@ var entryOf3 = (ctx, finding3, args) => {
15839
16296
  file: finding3.file,
15840
16297
  line: finding3.line,
15841
16298
  fingerprint: finding3.fingerprint,
15842
- state: args.falsePositive ? "falso-positivo" : "aceito",
16299
+ state: args2.falsePositive ? "falso-positivo" : "aceito",
15843
16300
  owner: personName(ctx),
15844
- reason: args.reason,
16301
+ reason: args2.reason,
15845
16302
  acceptedAt: now,
15846
- expiresAt: plusDays(now, args.days)
16303
+ expiresAt: plusDays(now, args2.days)
15847
16304
  };
15848
16305
  };
15849
- var acceptEntry = (ctx, args) => {
16306
+ var acceptEntry = (ctx, args2) => {
15850
16307
  const root = projectRoot(ctx);
15851
16308
  const baseline = loadBaseline(ctx.runtime, root);
15852
16309
  if (!baseline.ok) {
15853
16310
  return failure3(ctx, baselineInvalid(baseline.error));
15854
16311
  }
15855
16312
  const scanned = scanPath(ctx.runtime, root, ".");
15856
- const finding3 = scanned.ok ? scanned.value.find((item) => item.id === args.id) : void 0;
16313
+ const finding3 = scanned.ok ? scanned.value.find((item) => item.id === args2.id) : void 0;
15857
16314
  if (finding3 === void 0) {
15858
16315
  return failure3(
15859
16316
  ctx,
15860
16317
  usageProblem("secrets_finding_not_found", "secrets.error.finding_not_found", "finitemonkey secrets scan", {
15861
- id: args.id
16318
+ id: args2.id
15862
16319
  })
15863
16320
  );
15864
16321
  }
15865
- const entry2 = entryOf3(ctx, finding3, args);
16322
+ const entry2 = entryOf3(ctx, finding3, args2);
15866
16323
  const entries = [...baseline.value.entries.filter((item) => item.id !== entry2.id), entry2];
15867
16324
  const saved = saveBaseline(ctx.runtime, root, { schema: baseline.value.schema, entries });
15868
16325
  if (!saved.ok) {
@@ -15885,10 +16342,10 @@ var secretsAcceptCommand = {
15885
16342
  status: "ready",
15886
16343
  maxPositionals: 1,
15887
16344
  options: { reason: { type: "string" }, days: { type: "string" }, "false-positive": { type: "boolean" } },
15888
- run: (ctx, args) => {
15889
- const id = args.positionals[0] ?? "";
15890
- const reason = stringOption(args.values, "reason");
15891
- const days = daysOf(args.values);
16345
+ run: (ctx, args2) => {
16346
+ const id = args2.positionals[0] ?? "";
16347
+ const reason = stringOption(args2.values, "reason");
16348
+ const days = daysOf(args2.values);
15892
16349
  const problem4 = inputProblem(ctx, id, reason, days);
15893
16350
  if (problem4 !== void 0 || reason === void 0 || days === void 0) {
15894
16351
  return Promise.resolve(
@@ -15899,7 +16356,7 @@ var secretsAcceptCommand = {
15899
16356
  );
15900
16357
  }
15901
16358
  return Promise.resolve(
15902
- acceptEntry(ctx, { id, reason, days, falsePositive: args.values["false-positive"] === true })
16359
+ acceptEntry(ctx, { id, reason, days, falsePositive: args2.values["false-positive"] === true })
15903
16360
  );
15904
16361
  }
15905
16362
  };
@@ -15980,18 +16437,18 @@ var secretsIncidentCommand = {
15980
16437
  usage: USAGE,
15981
16438
  status: "ready",
15982
16439
  maxPositionals: 2,
15983
- run: (ctx, args) => {
15984
- const action = actionOf(args.positionals[0]);
16440
+ run: (ctx, args2) => {
16441
+ const action = actionOf(args2.positionals[0]);
15985
16442
  const root = projectRoot(ctx);
15986
16443
  if (action === void 0)
15987
16444
  return Promise.resolve(
15988
16445
  fail2(
15989
16446
  ctx,
15990
- usageProblem("invalid_arguments", "error.invalid_arguments", USAGE, { detail: args.positionals[0] ?? "" })
16447
+ usageProblem("invalid_arguments", "error.invalid_arguments", USAGE, { detail: args2.positionals[0] ?? "" })
15991
16448
  )
15992
16449
  );
15993
16450
  if (action === "list") return Promise.resolve(list2(ctx, root));
15994
- const id = args.positionals[1] ?? "";
16451
+ const id = args2.positionals[1] ?? "";
15995
16452
  const incident = readIncident(ctx.runtime, root, id);
15996
16453
  if (!incident.ok) return Promise.resolve(notFound2(ctx, id));
15997
16454
  return Promise.resolve(action === "purge" ? purge(ctx, incident.value) : move(ctx, root, incident.value, action));
@@ -16041,25 +16498,25 @@ var historyIncidents = (ctx, root, fresh3) => fresh3.flatMap((finding3) => {
16041
16498
  const saved = recordIncident(ctx.runtime, root, draft, HOOK_ACTOR);
16042
16499
  return saved.ok ? [saved.value.id] : [];
16043
16500
  });
16044
- var dataOf = (mode, outcome, incidents) => {
16501
+ var dataOf = (mode, outcome2, incidents) => {
16045
16502
  const findings = [
16046
- ...outcome.fresh.map((finding3) => view(finding3, "new")),
16047
- ...outcome.known.map((finding3) => view(finding3, "known")),
16048
- ...outcome.expired.map((finding3) => view(finding3, "expired"))
16503
+ ...outcome2.fresh.map((finding3) => view(finding3, "new")),
16504
+ ...outcome2.known.map((finding3) => view(finding3, "known")),
16505
+ ...outcome2.expired.map((finding3) => view(finding3, "expired"))
16049
16506
  ];
16050
16507
  return {
16051
16508
  schema: SECRETS_SCAN_SCHEMA,
16052
16509
  mode,
16053
16510
  baseline: BASELINE_FILE2,
16054
16511
  findings,
16055
- fixed: outcome.fixed.map((entry2) => entry2.id),
16512
+ fixed: outcome2.fixed.map((entry2) => entry2.id),
16056
16513
  incidents,
16057
16514
  summary: {
16058
16515
  total: findings.length,
16059
- new: outcome.fresh.length,
16060
- known: outcome.known.length,
16061
- expired: outcome.expired.length,
16062
- fixed: outcome.fixed.length
16516
+ new: outcome2.fresh.length,
16517
+ known: outcome2.known.length,
16518
+ expired: outcome2.expired.length,
16519
+ fixed: outcome2.fixed.length
16063
16520
  }
16064
16521
  };
16065
16522
  };
@@ -16093,8 +16550,8 @@ var resultOf3 = (ctx, data) => {
16093
16550
  human2
16094
16551
  );
16095
16552
  };
16096
- var prune = (ctx, root, baseline, outcome) => {
16097
- if (outcome.fixed.length > 0) saveBaseline(ctx.runtime, root, withoutFixed(baseline, outcome.fixed));
16553
+ var prune = (ctx, root, baseline, outcome2) => {
16554
+ if (outcome2.fixed.length > 0) saveBaseline(ctx.runtime, root, withoutFixed(baseline, outcome2.fixed));
16098
16555
  };
16099
16556
  var secretsScanCommand = {
16100
16557
  path: ["secrets", "scan"],
@@ -16102,25 +16559,25 @@ var secretsScanCommand = {
16102
16559
  usage: "finitemonkey secrets scan [--staged | --history | --path <dir>] [--json]",
16103
16560
  status: "ready",
16104
16561
  options: { staged: { type: "boolean" }, history: { type: "boolean" }, path: { type: "string" } },
16105
- run: async (ctx, args) => {
16106
- const mode = modeOf3(args.values);
16562
+ run: async (ctx, args2) => {
16563
+ const mode = modeOf3(args2.values);
16107
16564
  const fix = "finitemonkey help secrets scan";
16108
16565
  if (mode === void 0) return failureFrom(ctx.t, usageProblem("invalid_arguments", "secrets.error.one_mode", fix));
16109
16566
  const root = projectRoot(ctx);
16110
- const target = stringOption(args.values, "path") ?? ".";
16567
+ const target = stringOption(args2.values, "path") ?? ".";
16111
16568
  const scanned = await runScan(ctx, root, mode, target);
16112
16569
  if (!scanned.ok) return failureFrom(ctx.t, gitUnavailable(scanned.error));
16113
16570
  const baseline = loadBaseline(ctx.runtime, root);
16114
16571
  if (!baseline.ok) return failureFrom(ctx.t, baselineInvalid(baseline.error));
16115
- const outcome = ratchet(
16572
+ const outcome2 = ratchet(
16116
16573
  scanned.value,
16117
16574
  baseline.value,
16118
16575
  ctx.runtime.now(),
16119
16576
  mode === "path" ? scopeOf(target) : void 0
16120
16577
  );
16121
- if (mode === "path") prune(ctx, root, baseline.value, outcome);
16122
- const incidents = mode === "history" ? historyIncidents(ctx, root, outcome.fresh) : [];
16123
- return resultOf3(ctx, dataOf(mode, outcome, incidents));
16578
+ if (mode === "path") prune(ctx, root, baseline.value, outcome2);
16579
+ const incidents = mode === "history" ? historyIncidents(ctx, root, outcome2.fresh) : [];
16580
+ return resultOf3(ctx, dataOf(mode, outcome2, incidents));
16124
16581
  }
16125
16582
  };
16126
16583
 
@@ -16171,7 +16628,11 @@ var checkYaml = (check2) => ({
16171
16628
  ...check2.timeoutS === void 0 ? {} : { timeout_s: check2.timeoutS },
16172
16629
  ...check2.require === void 0 ? {} : { require: [...check2.require] },
16173
16630
  ...check2.requireOpen === void 0 ? {} : { require_open: check2.requireOpen },
16174
- ...check2.description === void 0 ? {} : { description: check2.description }
16631
+ ...check2.description === void 0 ? {} : { description: check2.description },
16632
+ ...check2.port === void 0 ? {} : { port: check2.port },
16633
+ ...check2.route === void 0 ? {} : { route: check2.route },
16634
+ ...check2.status === void 0 ? {} : { status: check2.status },
16635
+ ...check2.body === void 0 ? {} : { body: check2.body }
16175
16636
  });
16176
16637
  var contractYaml = (contract) => ({
16177
16638
  id: contract.id,
@@ -16287,13 +16748,13 @@ var list3 = (values, name) => {
16287
16748
  return raw.filter((item) => typeof item === "string" && item.trim() !== "");
16288
16749
  };
16289
16750
  var csv = (values, name) => list3(values, name).flatMap((item) => item.split(",").map((part2) => part2.trim())).filter((part2) => part2 !== "");
16290
- var positive2 = (values, name) => {
16751
+ var positive3 = (values, name) => {
16291
16752
  const parsed = Number.parseInt(stringOption(values, name) ?? "", 10);
16292
16753
  return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
16293
16754
  };
16294
16755
  var draftFrom = (ctx, paths, id, values) => {
16295
- const files = positive2(values, "budget-files");
16296
- const lines3 = positive2(values, "budget-lines");
16756
+ const files = positive3(values, "budget-files");
16757
+ const lines3 = positive3(values, "budget-lines");
16297
16758
  const size = SIZES.find((item) => item === stringOption(values, "size")) ?? "S";
16298
16759
  return {
16299
16760
  id,
@@ -16396,7 +16857,7 @@ var taskNewCommand = {
16396
16857
  status: "ready",
16397
16858
  options: TASK_OPTIONS,
16398
16859
  maxPositionals: 1,
16399
- run: (ctx, args) => Promise.resolve(newTask(ctx, args.positionals[0], args.values))
16860
+ run: (ctx, args2) => Promise.resolve(newTask(ctx, args2.positionals[0], args2.values))
16400
16861
  };
16401
16862
  var taskShowCommand = {
16402
16863
  path: ["task", "show"],
@@ -16404,7 +16865,7 @@ var taskShowCommand = {
16404
16865
  usage: "finitemonkey task show [<id>]",
16405
16866
  status: "ready",
16406
16867
  maxPositionals: 1,
16407
- run: (ctx, args) => Promise.resolve(showTask(ctx, args.positionals[0]))
16868
+ run: (ctx, args2) => Promise.resolve(showTask(ctx, args2.positionals[0]))
16408
16869
  };
16409
16870
  var taskDoneCommand = {
16410
16871
  path: ["task", "done"],
@@ -16412,7 +16873,7 @@ var taskDoneCommand = {
16412
16873
  usage: "finitemonkey task done [<id>]",
16413
16874
  status: "ready",
16414
16875
  maxPositionals: 1,
16415
- run: (ctx, args) => Promise.resolve(doneTask(ctx, args.positionals[0]))
16876
+ run: (ctx, args2) => Promise.resolve(doneTask(ctx, args2.positionals[0]))
16416
16877
  };
16417
16878
 
16418
16879
  // src/install/uninstall.ts
@@ -16462,7 +16923,7 @@ var isEmptyDir = (project, relative9) => {
16462
16923
  const listed4 = project.runtime.listDir(project.abs(relative9));
16463
16924
  return listed4.ok && listed4.value.length === 0;
16464
16925
  };
16465
- var finish2 = (project, plan) => {
16926
+ var finish3 = (project, plan) => {
16466
16927
  const target = plan.purge ? STATE_DIR : LOCK_FILE;
16467
16928
  const removed = project.runtime.remove(project.abs(target));
16468
16929
  if (!removed.ok) return reject({ path: target, detail: removed.error });
@@ -16477,7 +16938,7 @@ var applyRemoval = (project, plan) => {
16477
16938
  }
16478
16939
  return accept(void 0);
16479
16940
  });
16480
- return changed.ok ? finish2(project, plan) : changed;
16941
+ return changed.ok ? finish3(project, plan) : changed;
16481
16942
  };
16482
16943
 
16483
16944
  // src/commands/uninstall.ts
@@ -16508,22 +16969,22 @@ var linesOf2 = (ctx, data) => [
16508
16969
  ctx.t("install.uninstall.outside"),
16509
16970
  ...OUTSIDE.map(({ key, command }) => ` ${ctx.t(key, { command })}`)
16510
16971
  ];
16511
- var fixFor = (args) => [
16972
+ var fixFor = (args2) => [
16512
16973
  "finitemonkey uninstall --yes",
16513
- ...args.values["purge"] === true ? ["--purge"] : [],
16514
- ...args.values["keep-generated"] === true ? ["--keep-generated"] : []
16974
+ ...args2.values["purge"] === true ? ["--purge"] : [],
16975
+ ...args2.values["keep-generated"] === true ? ["--keep-generated"] : []
16515
16976
  ].join(" ");
16516
- var runUninstall = (ctx, args) => {
16977
+ var runUninstall = (ctx, args2) => {
16517
16978
  const { project } = openProject(ctx);
16518
16979
  const lock = readPreviousLock(project);
16519
16980
  if (!lock.ok) return failureFrom(ctx.t, planProblem(lock.error));
16520
16981
  if (lock.value === void 0) return failureFrom(ctx.t, notInstalled());
16521
- const options = { purge: args.values["purge"] === true, keepGenerated: args.values["keep-generated"] === true };
16982
+ const options = { purge: args2.values["purge"] === true, keepGenerated: args2.values["keep-generated"] === true };
16522
16983
  const plan = planRemoval(project, lock.value, options);
16523
16984
  const preview = dataOf2(project.root, plan, false);
16524
16985
  if (ctx.dryRun) return success(preview, [...linesOf2(ctx, preview), ctx.t("install.dry_run")]);
16525
16986
  if (!ctx.yes) {
16526
- const next2 = fixFor(args);
16987
+ const next2 = fixFor(args2);
16527
16988
  const problem4 = confirmProblem("uninstall", ctx.t("install.uninstall.question"), next2);
16528
16989
  return { ...failureFrom(ctx.t, { ...problem4, data: preview }, linesOf2(ctx, preview)), next: next2 };
16529
16990
  }
@@ -16538,7 +16999,7 @@ var uninstallCommand = {
16538
16999
  usage: "finitemonkey uninstall [--purge] [--keep-generated] [--dry-run] [--yes]",
16539
17000
  status: "ready",
16540
17001
  options: { purge: { type: "boolean" }, "keep-generated": { type: "boolean" } },
16541
- run: (ctx, args) => Promise.resolve(runUninstall(ctx, args))
17002
+ run: (ctx, args2) => Promise.resolve(runUninstall(ctx, args2))
16542
17003
  };
16543
17004
 
16544
17005
  // src/commands/update.ts
@@ -16561,14 +17022,14 @@ var check = (ctx, bin) => {
16561
17022
  error: { code: "install_outdated", message: ctx.t("error.install_outdated"), fix: "finitemonkey update --yes" }
16562
17023
  };
16563
17024
  };
16564
- var runUpdate = (ctx, args) => {
16565
- const bin = stringOption(args.values, "bin");
16566
- if (args.values["check"] === true) return check(ctx, bin);
17025
+ var runUpdate = (ctx, args2) => {
17026
+ const bin = stringOption(args2.values, "bin");
17027
+ if (args2.values["check"] === true) return check(ctx, bin);
16567
17028
  const { project } = openProject(ctx);
16568
17029
  const previous = readPreviousLock(project);
16569
17030
  if (!previous.ok) return failureFrom(ctx.t, planProblem(previous.error));
16570
17031
  if (previous.value === void 0) return failureFrom(ctx.t, notInstalled());
16571
- const preset = stringOption(args.values, "preset");
17032
+ const preset = stringOption(args2.values, "preset");
16572
17033
  const request = { mode: "update", preset, bin, marketplace: false, showDiff: true };
16573
17034
  return runInstall(ctx, request, previous.value.preset);
16574
17035
  };
@@ -16578,7 +17039,7 @@ var updateCommand = {
16578
17039
  usage: "finitemonkey update [--check] [--preset neutral|gabriel] [--bin <path>] [--yes]",
16579
17040
  status: "ready",
16580
17041
  options: { check: { type: "boolean" }, preset: { type: "string" }, bin: { type: "string" } },
16581
- run: (ctx, args) => Promise.resolve(runUpdate(ctx, args))
17042
+ run: (ctx, args2) => Promise.resolve(runUpdate(ctx, args2))
16582
17043
  };
16583
17044
 
16584
17045
  // src/commands/version.ts
@@ -16614,6 +17075,7 @@ var REGISTRY = [
16614
17075
  undoCommand,
16615
17076
  rescueCommand,
16616
17077
  mergeVerifyCommand,
17078
+ appCheckCommand,
16617
17079
  versionCommand,
16618
17080
  helpCommand
16619
17081
  ];
@@ -16692,9 +17154,9 @@ var failWith = (ctx, name, problem4) => ({
16692
17154
  });
16693
17155
  var invoke = (ctx, argv, spec2, registry) => {
16694
17156
  const name = commandName(spec2);
16695
- const args = strictArgs(argv, spec2);
16696
- if (!args.ok) return failWith(ctx, name, args.error);
16697
- return { name, execute: () => spec2.run(ctx, args.value, registry) };
17157
+ const args2 = strictArgs(argv, spec2);
17158
+ if (!args2.ok) return failWith(ctx, name, args2.error);
17159
+ return { name, execute: () => spec2.run(ctx, args2.value, registry) };
16698
17160
  };
16699
17161
  var builtin = (registry, name) => registry.find((spec2) => commandName(spec2) === name);
16700
17162
  var dispatch = (ctx, argv, loose, registry) => {