@dadado/agent-kit-cli 6.1.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dashboard/README.md +3 -1
  2. package/dashboard/dashboard-data.mjs +13 -2
  3. package/dashboard/dashboard.html +95 -2
  4. package/dashboard/lib/gate-exit-tickets.d.mts +45 -0
  5. package/dashboard/lib/gate-exit-tickets.mjs +233 -0
  6. package/dashboard/lib/semantic-model.d.mts +21 -0
  7. package/dashboard/lib/semantic-model.mjs +47 -0
  8. package/dist/{add-YTCDLPUS.js → add-Y2426TGU.js} +3 -3
  9. package/dist/{chunk-PCCQUWE5.js → chunk-3LLXDHIU.js} +1 -1
  10. package/dist/{chunk-YP4OOK4T.js → chunk-BEKQ6PVD.js} +2 -2
  11. package/dist/{chunk-3DGTPONS.js → chunk-C2SJDYAM.js} +1 -1
  12. package/dist/{chunk-C26I6HTO.js → chunk-C4Q77GGJ.js} +0 -1
  13. package/dist/{chunk-DRND2KYV.js → chunk-CER2APV2.js} +1 -1
  14. package/dist/{chunk-BBVBYQJO.js → chunk-CWDXNBEU.js} +2 -2
  15. package/dist/{chunk-U5F6RJN4.js → chunk-GO3K752H.js} +4 -1
  16. package/dist/chunk-GUQYNUV4.js +61 -0
  17. package/dist/{chunk-TMFOLRU2.js → chunk-IR7FFPUU.js} +8 -8
  18. package/dist/{chunk-ESNAFDYT.js → chunk-JEI6HACW.js} +44 -1
  19. package/dist/chunk-KVY425NM.js +519 -0
  20. package/dist/{chunk-WUWRGOUV.js → chunk-L5V3W3DY.js} +13 -1
  21. package/dist/chunk-MO63KZBP.js +516 -0
  22. package/dist/chunk-MUTTNRX4.js +53 -0
  23. package/dist/{chunk-DBPNGZNH.js → chunk-NSSXSZLL.js} +13 -2
  24. package/dist/chunk-QTBTKEXK.js +77 -0
  25. package/dist/{chunk-PPKX55GI.js → chunk-SPYIP76F.js} +1 -1
  26. package/dist/{chunk-DBXDV5O7.js → chunk-TGNFGMPV.js} +2 -2
  27. package/dist/{chunk-2FQX2O4I.js → chunk-TW4BF5OE.js} +2 -2
  28. package/dist/{chunk-GN4SAZF2.js → chunk-VO73OL5V.js} +2 -2
  29. package/dist/{chunk-VTOUQAVR.js → chunk-X45O2AIE.js} +9 -1
  30. package/dist/{chunk-HFUH3HVS.js → chunk-XDCCYBCJ.js} +7 -1
  31. package/dist/{chunk-TPCCSWK6.js → chunk-YRFSTHR5.js} +30 -526
  32. package/dist/{chunk-3XKVQS2K.js → chunk-Z7ABX5QI.js} +13 -9
  33. package/dist/{contribute-A7NCWTUM.js → contribute-TVST3CJO.js} +4 -4
  34. package/dist/countdown-QXJD6TDB.js +479 -0
  35. package/dist/{dashboard-YSNUGBFY.js → dashboard-YNCFISNO.js} +1 -1
  36. package/dist/{dashboard-broadcast-M4CMW7RX.js → dashboard-broadcast-NK7PU72L.js} +3 -2
  37. package/dist/{diff-4MDU5SLE.js → diff-MECE5RZB.js} +5 -5
  38. package/dist/{doctor-SS6Y2T6H.js → doctor-VLZNXJNA.js} +9 -9
  39. package/dist/ground-control-NMYYIVCV.js +434 -0
  40. package/dist/{handoff-MK5JG3J5.js → handoff-5AGE2D3T.js} +3 -2
  41. package/dist/{hook-D2WHBKRN.js → hook-XCZWX462.js} +7 -6
  42. package/dist/index.js +33 -23
  43. package/dist/{init-GVVBA2BM.js → init-HKMRU2V4.js} +12 -12
  44. package/dist/{install-R4HPI7VK.js → install-X2J6KWZ5.js} +12 -12
  45. package/dist/{mission-control-LJACEVVN.js → mission-control-MHQU73XC.js} +2 -2
  46. package/dist/{plan-index-V7T5MKL6.js → plan-index-BNLUU6T5.js} +3 -2
  47. package/dist/{run-YV6XTFZY.js → run-R66G276Q.js} +63 -15
  48. package/dist/{run-plan-KT443FO6.js → run-plan-24Z5K4P3.js} +17 -10
  49. package/dist/{scan-K6TQGV4S.js → scan-UX4HARMJ.js} +1 -1
  50. package/dist/{setup-global-AT4DD3MG.js → setup-global-ADJFMOZX.js} +6 -6
  51. package/dist/{status-MPJHI5RP.js → status-UB6NL3Q3.js} +8 -8
  52. package/dist/{update-P62637ZI.js → update-PFBFY7GT.js} +9 -9
  53. package/package.json +1 -1
@@ -1,30 +1,29 @@
1
+ import {
2
+ classifyGateExit,
3
+ landGateExit
4
+ } from "./chunk-KVY425NM.js";
1
5
  import {
2
6
  InputLine,
3
7
  ScrollRegion,
4
8
  renderMcLive,
5
9
  runMcTuiLoop
6
- } from "./chunk-PCCQUWE5.js";
10
+ } from "./chunk-3LLXDHIU.js";
7
11
  import {
8
12
  extractHandoffNamedPlans,
9
13
  namedPlanCandidatePaths,
10
14
  parsePlanFrontmatter
11
- } from "./chunk-WUWRGOUV.js";
15
+ } from "./chunk-L5V3W3DY.js";
12
16
  import {
13
17
  LineSplitter,
14
18
  RedactingStreamBuffer,
15
19
  claudeRedactions,
16
- findHitlGateInText,
17
20
  formatHitlGateLine,
18
21
  formatHitlSummary,
19
22
  operatorQuitStop,
20
23
  parseStreamLine,
21
24
  redactSecrets,
22
- renderStreamEvent,
23
- whichBinary
24
- } from "./chunk-ESNAFDYT.js";
25
- import {
26
- findOnPath
27
- } from "./chunk-WGLPZXNU.js";
25
+ renderStreamEvent
26
+ } from "./chunk-JEI6HACW.js";
28
27
  import {
29
28
  TtySpinner,
30
29
  shouldUseVisualMotion,
@@ -39,7 +38,7 @@ import {
39
38
  STATE_ROOT_ENV,
40
39
  ensureLoopLogsDir,
41
40
  runnerStatePaths
42
- } from "./chunk-C26I6HTO.js";
41
+ } from "./chunk-C4Q77GGJ.js";
43
42
  import {
44
43
  fileExists,
45
44
  readJson
@@ -492,496 +491,6 @@ async function withLiveTui(opts, run) {
492
491
  }
493
492
  }
494
493
 
495
- // src/plan-loop/detect.ts
496
- var DETECT_ORDER = ["cursor-agent", "claude"];
497
- var EXPLICIT_ONLY_BACKENDS = ["codex", "cursor-acp"];
498
- var INSTALL_HINTS = {
499
- "cursor-agent": "Install the Cursor Agent CLI and ensure `cursor-agent` is on PATH (https://cursor.com/docs/cli).",
500
- claude: "Install Claude Code and ensure `claude` is on PATH (https://code.claude.com/docs/en/quickstart).",
501
- "cursor-acp": "Install the Cursor Agent CLI and ensure `cursor-agent` is on PATH (https://cursor.com/docs/cli/acp); sign in with `cursor-agent login`.",
502
- codex: "Install the Codex CLI and ensure `codex` is on PATH (https://developers.openai.com/codex/cli)."
503
- };
504
- function isBackendId(id) {
505
- return id === "cursor-agent" || id === "claude" || id === "codex" || id === "cursor-acp";
506
- }
507
- function isRequestedBackend(id) {
508
- return id === "auto" || isBackendId(id);
509
- }
510
- function listDetectBackendIds() {
511
- return ["auto", ...DETECT_ORDER, ...EXPLICIT_ONLY_BACKENDS];
512
- }
513
- async function detectAgentBackend(requested, whichFn = whichBinary) {
514
- const id = requested.trim().toLowerCase();
515
- if (id === "glm" || id.startsWith("glm-") || id === "glm4") {
516
- return {
517
- ok: false,
518
- message: "No documented GLM CLI binary is registered. Refusing a stub that would claim to work. Use --backend auto, cursor-agent, or claude."
519
- };
520
- }
521
- if (!isRequestedBackend(id)) {
522
- return {
523
- ok: false,
524
- message: `Unknown backend '${requested}'. Supported: ${listDetectBackendIds().join(", ")}.`
525
- };
526
- }
527
- if (id === "auto") {
528
- for (const candidate of DETECT_ORDER) {
529
- const bin2 = await whichFn(candidate);
530
- if (bin2) return { ok: true, id: candidate, bin: bin2 };
531
- }
532
- return {
533
- ok: false,
534
- message: [
535
- "No agent CLI found on PATH (tried: cursor-agent, claude).",
536
- INSTALL_HINTS["cursor-agent"],
537
- INSTALL_HINTS.claude
538
- ].join(" ")
539
- };
540
- }
541
- const bin = await whichFn(id);
542
- if (!bin) {
543
- return { ok: false, message: `${id} not found on PATH. ${INSTALL_HINTS[id]}` };
544
- }
545
- return { ok: true, id, bin };
546
- }
547
-
548
- // src/plan-loop/gate-exit.ts
549
- import { execFile } from "child_process";
550
- import { mkdir, mkdtemp, readFile as readFile3, rename, rm, writeFile } from "fs/promises";
551
- import { tmpdir } from "os";
552
- import path3 from "path";
553
- var GATE_EXIT_BRANCH_PREFIX = "gate-exit/";
554
- var GATE_EXIT_MARKER_RELPATH = ".cursor/context/gate-exit.json";
555
- var GATE_EXIT_NOTE_DIR = ".cursor/gate-exit";
556
- var GATE_EXIT_DEFAULT_BASE = "staging";
557
- var GATE_EXIT_MARKER_SCHEMA = 1;
558
- var GATE_EXIT_STAMP_RE = /^\d{8}T\d{6}Z$/;
559
- var GATE_EXIT_SUMMARY_MAX_CHARS = 8 * 1024;
560
- var SAFE_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]*$/;
561
- var FALLBACK_GIT_IDENTITY = [
562
- "-c",
563
- "user.name=agent-kit",
564
- "-c",
565
- "user.email=agent-kit@noreply.invalid"
566
- ];
567
- var LAND_ON_GATE_DESCRIPTION = "Gate exit: when the run stops at an unanswered gate, is stopped by the operator, or fails, push the outcome note to a gate-exit/<UTC stamp> branch from a temporary worktree and open a PR/MR when gh/glab is authenticated (root tree untouched; off by default)";
568
- function gateExitStamp(now = /* @__PURE__ */ new Date()) {
569
- return now.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/[-:]/g, "");
570
- }
571
- function isGateExitStamp(value) {
572
- return GATE_EXIT_STAMP_RE.test(value);
573
- }
574
- function gateExitBranch(stamp2) {
575
- if (!isGateExitStamp(stamp2)) throw new Error(`invalid gate-exit stamp: ${stamp2}`);
576
- return `${GATE_EXIT_BRANCH_PREFIX}${stamp2}`;
577
- }
578
- function isSafeBaseRef(ref) {
579
- return SAFE_REF_RE.test(ref) && !ref.includes("..") && !ref.endsWith("/") && !ref.endsWith(".lock");
580
- }
581
- function classifyGateExit(input) {
582
- if (input.stop) {
583
- return input.stop.message.startsWith("stopped-by-operator") ? "stopped-by-operator" : "gate";
584
- }
585
- return input.failed ? "failure" : null;
586
- }
587
- function gateExitMarkerPath(root) {
588
- return path3.join(root, GATE_EXIT_MARKER_RELPATH);
589
- }
590
- async function readGateExitMarker(root) {
591
- let raw;
592
- try {
593
- raw = await readFile3(gateExitMarkerPath(root), "utf8");
594
- } catch (err) {
595
- if (err.code === "ENOENT") return { kind: "absent" };
596
- return { kind: "invalid", detail: String(err) };
597
- }
598
- try {
599
- const parsed = JSON.parse(raw);
600
- if (parsed?.schema !== GATE_EXIT_MARKER_SCHEMA || !Array.isArray(parsed.pending) || !Array.isArray(parsed.skipped)) {
601
- return { kind: "invalid", detail: "unexpected marker shape" };
602
- }
603
- const pending = parsed.pending.filter(
604
- (e) => typeof e?.stamp === "string" && isGateExitStamp(e.stamp)
605
- );
606
- return {
607
- kind: "ok",
608
- marker: { schema: GATE_EXIT_MARKER_SCHEMA, pending, skipped: parsed.skipped.map(String) }
609
- };
610
- } catch (err) {
611
- return { kind: "invalid", detail: String(err) };
612
- }
613
- }
614
- async function writeGateExitMarker(root, marker) {
615
- const file = gateExitMarkerPath(root);
616
- await mkdir(path3.dirname(file), { recursive: true });
617
- const tmp = `${file}.${process.pid}.tmp`;
618
- await writeFile(tmp, `${JSON.stringify(marker, null, 2)}
619
- `, "utf8");
620
- await rename(tmp, file);
621
- }
622
- function gateExitRedactions(env) {
623
- const entries = claudeRedactions(env);
624
- const seen = new Set(entries.map((e) => e.value));
625
- for (const key of ["GH_TOKEN", "GITHUB_TOKEN", "GITLAB_TOKEN", "GLAB_TOKEN"]) {
626
- const value = env[key];
627
- if (value && !seen.has(value)) {
628
- seen.add(value);
629
- entries.push({ label: key, value });
630
- }
631
- }
632
- return entries;
633
- }
634
- function gateExitHeadline(stamp2, outcome) {
635
- const what = outcome.trigger === "failure" ? "run failed" : outcome.trigger === "stopped-by-operator" ? `stopped by operator at gate \`${outcome.askId ?? "none"}\`` : `unanswered gate \`${outcome.askId ?? "none"}\``;
636
- return `# Gate exit ${stamp2}: ${what}`;
637
- }
638
- function buildGateExitNote(input) {
639
- const { outcome } = input;
640
- const gateLine = outcome.askId && input.labels.length > 0 ? `\`HITL_GATE: ${outcome.askId} | ${input.labels.join(" | ")}\`` : "(no `HITL_GATE:` line recorded)";
641
- const plan = outcome.plan !== void 0 ? `${outcome.plan} (ticks: ${outcome.ticks ?? 0}, pending to-dos: ${outcome.pending ?? "unknown"})` : "(none)";
642
- const lines = [
643
- gateExitHeadline(input.stamp, outcome),
644
- "",
645
- "- **Provenance:** agent-inferred. Written by `agent-kit` at the end of a headless run; no operator reply exists for this gate and none was inferred.",
646
- `- **Trigger:** ${outcome.trigger}`,
647
- `- **Ask id:** ${outcome.askId ? `\`${outcome.askId}\`` : "(none)"}`,
648
- `- **Gate:** ${gateLine}`,
649
- `- **Stop:** ${outcome.reason}`,
650
- `- **Exit code:** ${outcome.exitCode}`,
651
- `- **Command:** \`agent-kit ${outcome.command}\` (backend ${outcome.backend})`,
652
- `- **Plan:** ${plan}`,
653
- `- **Log:** ${input.logRel ? `\`${input.logRel}\` (on the machine that ran; not pushed)` : "(none)"}`,
654
- `- **Root tree:** ${input.dirtyPaths > 0 ? `dirty (${input.dirtyPaths} path(s)), reported and not cleaned` : "clean"}`,
655
- `- **Base:** \`${input.base}\``,
656
- "",
657
- "Next: answer the gate in a live session (`/continue-plan` or `/run-plan`), then accept or discard this ticket (`agent-kit ground-control`). Never `/git-prod` from a headless run."
658
- ];
659
- const summary = outcome.summary?.trim();
660
- if (summary) {
661
- const capped = summary.length > GATE_EXIT_SUMMARY_MAX_CHARS ? `${summary.slice(0, GATE_EXIT_SUMMARY_MAX_CHARS)}
662
-
663
- (summary truncated at ${GATE_EXIT_SUMMARY_MAX_CHARS} characters)` : summary;
664
- lines.push("", "## Run summary", "", "```text", capped.replaceAll("```", "'''"), "```");
665
- }
666
- return `${lines.join("\n")}
667
- `;
668
- }
669
- function collectStrings(value, out, depth) {
670
- if (depth > 8) return;
671
- if (typeof value === "string") out.push(value);
672
- else if (Array.isArray(value)) for (const v of value) collectStrings(v, out, depth + 1);
673
- else if (value && typeof value === "object") {
674
- for (const v of Object.values(value)) collectStrings(v, out, depth + 1);
675
- }
676
- }
677
- function decodeRunLog(raw) {
678
- const out = [];
679
- for (const line of raw.split(/\r?\n/)) {
680
- out.push(line);
681
- if (!line.startsWith("{")) continue;
682
- try {
683
- collectStrings(JSON.parse(line), out, 0);
684
- } catch {
685
- }
686
- }
687
- return out.join("\n");
688
- }
689
- async function findGateLabels(askId, sources) {
690
- const fromText = sources.gateText ? findHitlGateInText(sources.gateText) : null;
691
- if (fromText?.askId === askId) return fromText.labels;
692
- if (!sources.logPath) return [];
693
- try {
694
- const gate = findHitlGateInText(decodeRunLog(await readFile3(sources.logPath, "utf8")));
695
- return gate?.askId === askId ? gate.labels : [];
696
- } catch {
697
- return [];
698
- }
699
- }
700
- var defaultExec = (bin, args, opts) => new Promise((resolve) => {
701
- execFile(
702
- bin,
703
- [...args],
704
- {
705
- cwd: opts.cwd,
706
- env: { ...opts.env ?? process.env, GIT_TERMINAL_PROMPT: "0" },
707
- encoding: "utf8",
708
- maxBuffer: 4 * 1024 * 1024,
709
- timeout: 12e4
710
- },
711
- (err, stdout, stderr) => {
712
- const code = err ? typeof err.code === "number" ? err.code : 1 : 0;
713
- resolve({
714
- code,
715
- stdout: String(stdout ?? ""),
716
- stderr: String(stderr ?? err?.message ?? "")
717
- });
718
- }
719
- );
720
- });
721
- function isOutside(parent, child) {
722
- const rel = path3.relative(parent, child);
723
- return rel.startsWith("..") || path3.isAbsolute(rel);
724
- }
725
- async function landGateExit(opts) {
726
- const env = opts.env ?? process.env;
727
- const exec = opts.exec ?? defaultExec;
728
- const which = opts.which ?? ((bin) => findOnPath(bin, { env }));
729
- const redactions = gateExitRedactions(env);
730
- const red2 = (text) => redactSecrets(text, redactions);
731
- const log = (line) => opts.log?.(red2(`gate-exit: ${line}`));
732
- const now = opts.now ?? /* @__PURE__ */ new Date();
733
- const stamp2 = gateExitStamp(now);
734
- const branch = gateExitBranch(stamp2);
735
- const outcome = opts.outcome;
736
- const requestedBase = opts.base ?? GATE_EXIT_DEFAULT_BASE;
737
- const result = {
738
- stamp: stamp2,
739
- branch: null,
740
- committed: false,
741
- pushed: false,
742
- url: null,
743
- markerWritten: false,
744
- note: ""
745
- };
746
- const git = (args, cwd) => exec("git", args, { cwd, env });
747
- const labels = outcome.askId ? await findGateLabels(outcome.askId, { gateText: outcome.gateText, logPath: outcome.logPath }) : [];
748
- const top = await git(["rev-parse", "--show-toplevel"], opts.root);
749
- const repo = top.code === 0 ? top.stdout.trim() : null;
750
- let dirtyPaths = 0;
751
- if (repo) {
752
- const status = await git(["status", "--porcelain"], repo);
753
- dirtyPaths = status.stdout.split("\n").filter((l) => l.trim()).length;
754
- if (dirtyPaths > 0) {
755
- log(`root tree is dirty (${dirtyPaths} path(s)); reported, not cleaned`);
756
- }
757
- }
758
- let base = isSafeBaseRef(requestedBase) ? requestedBase : GATE_EXIT_DEFAULT_BASE;
759
- const logRel = outcome.logPath ? path3.relative(opts.root, outcome.logPath) : null;
760
- const buildNote = () => red2(buildGateExitNote({ stamp: stamp2, outcome, labels, base, logRel, dirtyPaths }));
761
- const writeMarker = async () => {
762
- const entry = {
763
- stamp: stamp2,
764
- openedAt: now.toISOString(),
765
- trigger: outcome.trigger,
766
- askId: outcome.askId ?? null,
767
- labels: labels.map(red2),
768
- base,
769
- branch: result.branch,
770
- pushed: result.pushed,
771
- url: result.url
772
- };
773
- const current = await readGateExitMarker(opts.root);
774
- if (current.kind === "invalid") {
775
- log(
776
- `${GATE_EXIT_MARKER_RELPATH} is unreadable (${current.detail}); left as is, entry ${stamp2} not recorded`
777
- );
778
- return;
779
- }
780
- const marker = current.kind === "ok" ? { ...current.marker, pending: [...current.marker.pending, entry] } : { schema: GATE_EXIT_MARKER_SCHEMA, pending: [entry], skipped: [] };
781
- try {
782
- await writeGateExitMarker(opts.root, marker);
783
- result.markerWritten = true;
784
- } catch (err) {
785
- log(`could not write ${GATE_EXIT_MARKER_RELPATH}: ${String(err)}`);
786
- return;
787
- }
788
- if (repo) {
789
- const ignored = await git(["check-ignore", "-q", "--", GATE_EXIT_MARKER_RELPATH], opts.root);
790
- if (ignored.code !== 0) {
791
- log(
792
- `${GATE_EXIT_MARKER_RELPATH} is not gitignored here; add it to .gitignore (agent-kit update adds it)`
793
- );
794
- }
795
- }
796
- };
797
- if (!repo) {
798
- log("not a git repository; no branch landed");
799
- result.note = buildNote();
800
- await writeMarker();
801
- return result;
802
- }
803
- const origin = await git(["remote", "get-url", "origin"], repo);
804
- const hasOrigin = origin.code === 0 && origin.stdout.trim().length > 0;
805
- const originUrl = hasOrigin ? origin.stdout.trim() : "";
806
- let startRef = null;
807
- if (hasOrigin) {
808
- const fetched = await git(["fetch", "--quiet", "origin", base], repo);
809
- if (fetched.code !== 0)
810
- log(`fetch origin ${base} failed: ${fetched.stderr.trim().split("\n")[0] ?? ""}`);
811
- const verify = await git(
812
- ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${base}^{commit}`],
813
- repo
814
- );
815
- if (verify.code === 0) {
816
- startRef = `refs/remotes/origin/${base}`;
817
- } else {
818
- const head = await git(
819
- ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"],
820
- repo
821
- );
822
- const fallback = head.stdout.trim().replace(/^origin\//, "");
823
- if (head.code === 0 && isSafeBaseRef(fallback)) {
824
- log(`origin/${base} not found; using the remote default branch ${fallback}`);
825
- base = fallback;
826
- startRef = `refs/remotes/origin/${fallback}`;
827
- }
828
- }
829
- } else {
830
- log("no `origin` remote: the branch stays local, nothing is pushed");
831
- const local = await git(
832
- ["rev-parse", "--verify", "--quiet", `refs/heads/${base}^{commit}`],
833
- repo
834
- );
835
- startRef = local.code === 0 ? `refs/heads/${base}` : "HEAD";
836
- }
837
- result.note = buildNote();
838
- if (!startRef) {
839
- log(`no base to branch from (origin/${base} missing); note not landed`);
840
- await writeMarker();
841
- return result;
842
- }
843
- let tmp = null;
844
- let wt = null;
845
- try {
846
- tmp = await mkdtemp(path3.join(opts.tmpRoot ?? tmpdir(), "agent-kit-gate-exit-"));
847
- if (!isOutside(repo, tmp)) {
848
- log("temporary directory resolves inside the repository; refusing to land there");
849
- return result;
850
- }
851
- wt = path3.join(tmp, "wt");
852
- const added = await git(["worktree", "add", "--detach", "--quiet", wt, startRef], repo);
853
- if (added.code !== 0) {
854
- wt = null;
855
- log(`worktree add failed: ${added.stderr.trim().split("\n")[0] ?? ""}`);
856
- return result;
857
- }
858
- const noteRel = `${GATE_EXIT_NOTE_DIR}/${stamp2}.md`;
859
- await mkdir(path3.join(wt, GATE_EXIT_NOTE_DIR), { recursive: true });
860
- await writeFile(path3.join(wt, noteRel), result.note, "utf8");
861
- const add = await git(["add", "-f", "--", noteRel], wt);
862
- if (add.code !== 0) {
863
- log(`git add failed: ${add.stderr.trim().split("\n")[0] ?? ""}`);
864
- return result;
865
- }
866
- const email = await git(["config", "user.email"], wt);
867
- const identity = email.code === 0 && email.stdout.trim() ? [] : FALLBACK_GIT_IDENTITY;
868
- const subject = `docs(gate-exit): record the ${outcome.trigger} of ${stamp2}`;
869
- const body = `Ask id: ${outcome.askId ?? "none"}. Provenance: agent-inferred.`;
870
- const commit = await git([...identity, "commit", "--quiet", "-m", subject, "-m", body], wt);
871
- if (commit.code !== 0) {
872
- log(`commit failed: ${commit.stderr.trim().split("\n")[0] ?? ""}`);
873
- return result;
874
- }
875
- result.committed = true;
876
- if (hasOrigin) {
877
- const push = await git(["push", "--quiet", "origin", `HEAD:refs/heads/${branch}`], wt);
878
- if (push.code === 0) {
879
- result.pushed = true;
880
- result.branch = branch;
881
- log(`pushed ${branch}`);
882
- } else {
883
- log(`push failed: ${push.stderr.trim().split("\n")[0] ?? ""}`);
884
- }
885
- }
886
- if (!result.pushed) {
887
- const keep = await git(["branch", branch, "HEAD"], wt);
888
- if (keep.code === 0) {
889
- result.branch = branch;
890
- log(`branch ${branch} kept locally; push it with: git push origin ${branch}`);
891
- }
892
- }
893
- if (result.pushed) {
894
- result.url = await openReview({
895
- branch,
896
- base,
897
- title: `${outcome.trigger === "failure" ? "Gate exit: run failed" : `Gate exit: ${outcome.askId ?? "gate"}`} (${stamp2})`,
898
- body: result.note,
899
- cwd: wt,
900
- tmp,
901
- originUrl,
902
- exec,
903
- which,
904
- env,
905
- log
906
- });
907
- }
908
- } catch (err) {
909
- log(`landing failed: ${String(err)}`);
910
- } finally {
911
- if (wt) await git(["worktree", "remove", "--force", wt], repo);
912
- if (tmp) await rm(tmp, { recursive: true, force: true }).catch(() => void 0);
913
- await git(["worktree", "prune"], repo);
914
- await writeMarker();
915
- }
916
- return result;
917
- }
918
- function lastUrl(text) {
919
- const urls = text.match(/https?:\/\/\S+/g);
920
- return urls?.at(-1) ?? null;
921
- }
922
- async function openReview(input) {
923
- const forges = /gitlab/i.test(input.originUrl) ? ["glab", "gh"] : ["gh", "glab"];
924
- for (const forge of forges) {
925
- const bin = await input.which(forge);
926
- if (!bin) continue;
927
- const auth = await input.exec(bin, ["auth", "status"], { cwd: input.cwd, env: input.env });
928
- if (auth.code !== 0) {
929
- input.log(`${forge} is not authenticated; skipping the ${forge === "gh" ? "PR" : "MR"}`);
930
- continue;
931
- }
932
- let created;
933
- if (forge === "gh") {
934
- const bodyFile = path3.join(input.tmp, "pr-body.md");
935
- await writeFile(bodyFile, input.body, "utf8");
936
- created = await input.exec(
937
- bin,
938
- [
939
- "pr",
940
- "create",
941
- "--base",
942
- input.base,
943
- "--head",
944
- input.branch,
945
- "--title",
946
- input.title,
947
- "--body-file",
948
- bodyFile
949
- ],
950
- { cwd: input.cwd, env: input.env }
951
- );
952
- } else {
953
- created = await input.exec(
954
- bin,
955
- [
956
- "mr",
957
- "create",
958
- "--source-branch",
959
- input.branch,
960
- "--target-branch",
961
- input.base,
962
- "--title",
963
- input.title,
964
- "--description",
965
- input.body,
966
- "--yes"
967
- ],
968
- { cwd: input.cwd, env: input.env }
969
- );
970
- }
971
- if (created.code === 0) {
972
- const url = lastUrl(created.stdout);
973
- input.log(`${forge === "gh" ? "PR" : "MR"} opened${url ? `: ${url}` : ""}`);
974
- return url;
975
- }
976
- input.log(`${forge} could not open the review: ${created.stderr.trim().split("\n")[0] ?? ""}`);
977
- return null;
978
- }
979
- input.log(
980
- `no authenticated gh/glab; open the review by hand: gh pr create --base ${input.base} --head ${input.branch} (or glab mr create --source-branch ${input.branch} --target-branch ${input.base})`
981
- );
982
- return null;
983
- }
984
-
985
494
  // src/plan-loop/driver-events.ts
986
495
  import { createInterface } from "readline";
987
496
  import { StringDecoder as StringDecoder2 } from "string_decoder";
@@ -1195,14 +704,14 @@ var DriverAgentSink = class {
1195
704
  };
1196
705
 
1197
706
  // src/plan-loop/run-loop.ts
1198
- import { readFile as readFile5, rm as rm2, unlink } from "fs/promises";
1199
- import path6 from "path";
707
+ import { readFile as readFile4, rm, unlink } from "fs/promises";
708
+ import path5 from "path";
1200
709
 
1201
710
  // src/plan-loop/external-review.ts
1202
711
  import { spawn } from "child_process";
1203
- import path4 from "path";
1204
- var CANONICAL_REL = path4.join(".cursor", "scripts", "plan-external-review.sh");
1205
- var FALLBACK_REL = path4.join("scripts", "plan-external-review.sh");
712
+ import path3 from "path";
713
+ var CANONICAL_REL = path3.join(".cursor", "scripts", "plan-external-review.sh");
714
+ var FALLBACK_REL = path3.join("scripts", "plan-external-review.sh");
1206
715
  function isPlanExhaustedReason(reason) {
1207
716
  const r = reason.trim().toLowerCase();
1208
717
  if (!r) return false;
@@ -1224,8 +733,8 @@ async function armExternalPlanReview(root, options = {}) {
1224
733
  const existsFn = options.existsFn ?? fileExists;
1225
734
  const log = options.log ?? ((line) => console.log(line));
1226
735
  const force = options.force === true;
1227
- const canonicalPath = path4.join(root, CANONICAL_REL);
1228
- const fallbackPath = path4.join(root, FALLBACK_REL);
736
+ const canonicalPath = path3.join(root, CANONICAL_REL);
737
+ const fallbackPath = path3.join(root, FALLBACK_REL);
1229
738
  let scriptPath = null;
1230
739
  let scriptRel = CANONICAL_REL;
1231
740
  if (await existsFn(canonicalPath)) {
@@ -1292,7 +801,7 @@ async function armExternalPlanReview(root, options = {}) {
1292
801
  }
1293
802
 
1294
803
  // src/plan-loop/persona-banners.ts
1295
- import path5 from "path";
804
+ import path4 from "path";
1296
805
  import {
1297
806
  blue,
1298
807
  cyan,
@@ -1328,7 +837,7 @@ function resolveColor(name, fallback) {
1328
837
  async function resolveCliPersonaId(root) {
1329
838
  try {
1330
839
  const cfg = await readJson(
1331
- path5.join(root, ".cursor", "context", "config.json")
840
+ path4.join(root, ".cursor", "context", "config.json")
1332
841
  );
1333
842
  const modes = cfg?.agentPersona?.modes ?? cfg?.workspaceSkin?.modes;
1334
843
  const id = modes?.[CLI_RUN_PLAN_MODE];
@@ -1339,7 +848,7 @@ async function resolveCliPersonaId(root) {
1339
848
  }
1340
849
  async function loadPersonaPack(root, personaId) {
1341
850
  try {
1342
- const personaPath = path5.join(root, "registry", "personas", "core", personaId, "persona.json");
851
+ const personaPath = path4.join(root, "registry", "personas", "core", personaId, "persona.json");
1343
852
  const pack = await readJson(personaPath);
1344
853
  if (!pack || typeof pack.id !== "string") return null;
1345
854
  return pack;
@@ -1388,7 +897,7 @@ function createPersonaBannerPrinter(persona) {
1388
897
  }
1389
898
 
1390
899
  // src/plan-loop/sentinel.ts
1391
- import { readFile as readFile4 } from "fs/promises";
900
+ import { readFile as readFile3 } from "fs/promises";
1392
901
  var SENTINEL_RE = /LOOP_TICK_RESULT:\s*(continue|stop(?:\s*[—-].*)?)/i;
1393
902
  function takeFromText(text) {
1394
903
  if (!text) return null;
@@ -1457,14 +966,14 @@ function parseTickResultStatus(content) {
1457
966
  }
1458
967
  async function parseTickResultStatusFromLogFile(logPath) {
1459
968
  try {
1460
- return parseTickResultStatus(await readFile4(logPath, "utf8"));
969
+ return parseTickResultStatus(await readFile3(logPath, "utf8"));
1461
970
  } catch {
1462
971
  return null;
1463
972
  }
1464
973
  }
1465
974
  async function parseSentinelFromLogFile(logPath) {
1466
975
  try {
1467
- const content = await readFile4(logPath, "utf8");
976
+ const content = await readFile3(logPath, "utf8");
1468
977
  return parseSentinelFromLog(content);
1469
978
  } catch {
1470
979
  return { kind: "missing" };
@@ -1696,7 +1205,7 @@ async function runPlanLoop(opts) {
1696
1205
  return finishRun(1, "no active plan", null);
1697
1206
  }
1698
1207
  const pending = async () => countPendingTodos(await readPlan(planPath));
1699
- landPlan = path6.basename(planPath);
1208
+ landPlan = path5.basename(planPath);
1700
1209
  if (statePaths.invalid !== void 0) {
1701
1210
  say(
1702
1211
  `${STATE_ROOT_ENV}=${statePaths.invalid} is not supported (use .cursor or .agent-kit); using .cursor.`,
@@ -1733,12 +1242,12 @@ async function runPlanLoop(opts) {
1733
1242
  type: "run_start",
1734
1243
  protocol: DRIVER_PROTOCOL_VERSION,
1735
1244
  backend: opts.backend.id,
1736
- plan: path6.basename(planPath),
1245
+ plan: path5.basename(planPath),
1737
1246
  pending: pendingAtStart,
1738
1247
  maxTicks: opts.maxTicks,
1739
1248
  hitl: opts.noHitl ? "off" : "driver"
1740
1249
  });
1741
- say(`Active plan: ${path6.basename(planPath)}`);
1250
+ say(`Active plan: ${path5.basename(planPath)}`);
1742
1251
  say(`Pending to-dos: ${pendingAtStart} | max ticks: ${opts.maxTicks}`);
1743
1252
  say(`Backend: ${opts.backend.id}`);
1744
1253
  if (persona) {
@@ -1775,7 +1284,7 @@ async function runPlanLoop(opts) {
1775
1284
  else say(msg);
1776
1285
  endReason = "stop file found";
1777
1286
  try {
1778
- await rm2(stopFile, { force: true });
1287
+ await rm(stopFile, { force: true });
1779
1288
  } catch {
1780
1289
  }
1781
1290
  break;
@@ -1787,10 +1296,10 @@ async function runPlanLoop(opts) {
1787
1296
  endReason = "plan exhausted";
1788
1297
  break;
1789
1298
  }
1790
- const logPath = path6.join(logDir, `tick-${stamp()}.log`);
1299
+ const logPath = path5.join(logDir, `tick-${stamp()}.log`);
1791
1300
  landLogPath = logPath;
1792
1301
  landBackend = backend.id;
1793
- const relLog = path6.relative(opts.root, logPath);
1302
+ const relLog = path5.relative(opts.root, logPath);
1794
1303
  if (!driver) console.log("");
1795
1304
  const tickLine = `=== tick ${tick}/${opts.maxTicks} - pending: ${before} - log: ${relLog} ===`;
1796
1305
  if (banners) banners.tickStart(tickLine);
@@ -1884,7 +1393,7 @@ async function runPlanLoop(opts) {
1884
1393
  }
1885
1394
  let logText = "";
1886
1395
  try {
1887
- logText = await readFile5(logPath, "utf8");
1396
+ logText = await readFile4(logPath, "utf8");
1888
1397
  } catch {
1889
1398
  }
1890
1399
  limit = detectUsageLimit(logText);
@@ -1998,7 +1507,7 @@ async function runPlanLoop(opts) {
1998
1507
  const finishDetail = `after ${tick} tick(s); pending: ${pendingNow}`;
1999
1508
  if (banners) banners.phaseComplete(finishDetail);
2000
1509
  say(
2001
- `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path6.relative(opts.root, logDir)}/`
1510
+ `Loop finished after ${tick} tick(s). Pending now: ${pendingNow}. Logs in ${path5.relative(opts.root, logDir)}/`
2002
1511
  );
2003
1512
  if (planExhausted || shouldArmExternalPlanReview({ pending: pendingNow, stopReason })) {
2004
1513
  await armExternalPlanReview(
@@ -2016,11 +1525,6 @@ async function runPlanLoop(opts) {
2016
1525
  export {
2017
1526
  shouldUseLiveTui,
2018
1527
  withLiveTui,
2019
- listDetectBackendIds,
2020
- detectAgentBackend,
2021
- LAND_ON_GATE_DESCRIPTION,
2022
- classifyGateExit,
2023
- landGateExit,
2024
1528
  DRIVER_EVENTS_FORMATS,
2025
1529
  runPlanLoop
2026
1530
  };