santree 0.7.5 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -3
- package/dist/commands/config.d.ts +12 -0
- package/dist/commands/config.js +444 -0
- package/dist/commands/dashboard.js +165 -61
- package/dist/commands/helpers/statusline.js +0 -30
- package/dist/commands/issue/index.d.ts +1 -1
- package/dist/commands/issue/index.js +1 -1
- package/dist/commands/pr/context.d.ts +7 -0
- package/dist/commands/pr/context.js +36 -0
- package/dist/commands/pr/fix.d.ts +11 -2
- package/dist/commands/pr/fix.js +64 -12
- package/dist/commands/worktree/create.js +6 -1
- package/dist/commands/worktree/open.js +3 -2
- package/dist/lib/ai.d.ts +11 -0
- package/dist/lib/ai.js +71 -2
- package/dist/lib/claude-config.d.ts +18 -4
- package/dist/lib/claude-config.js +58 -23
- package/dist/lib/config/TrackerPicker.d.ts +10 -0
- package/dist/lib/config/TrackerPicker.js +113 -0
- package/dist/lib/config/diagnostics.d.ts +37 -0
- package/dist/lib/config/diagnostics.js +190 -0
- package/dist/lib/config-store.d.ts +23 -0
- package/dist/lib/config-store.js +68 -0
- package/dist/lib/dashboard/DetailPanel.d.ts +4 -2
- package/dist/lib/dashboard/DetailPanel.js +44 -37
- package/dist/lib/dashboard/IssueList.d.ts +5 -1
- package/dist/lib/dashboard/IssueList.js +24 -5
- package/dist/lib/dashboard/MultilineTextArea.js +1 -1
- package/dist/lib/dashboard/Overlays.js +3 -4
- package/dist/lib/dashboard/ReviewDetailPanel.js +11 -2
- package/dist/lib/dashboard/ReviewList.js +6 -3
- package/dist/lib/dashboard/data.js +7 -34
- package/dist/lib/dashboard/external-editor.d.ts +1 -1
- package/dist/lib/dashboard/external-editor.js +3 -2
- package/dist/lib/dashboard/types.d.ts +13 -2
- package/dist/lib/dashboard/types.js +33 -2
- package/dist/lib/exec.d.ts +13 -0
- package/dist/lib/exec.js +11 -1
- package/dist/lib/fix-loop.d.ts +45 -0
- package/dist/lib/fix-loop.js +83 -0
- package/dist/lib/git.d.ts +3 -29
- package/dist/lib/git.js +6 -62
- package/dist/lib/github.d.ts +70 -0
- package/dist/lib/github.js +128 -0
- package/dist/lib/multiplexer/cmux.js +143 -17
- package/dist/lib/multiplexer/none.js +3 -3
- package/dist/lib/multiplexer/tmux.js +5 -29
- package/dist/lib/multiplexer/types.d.ts +34 -1
- package/dist/lib/open-url.d.ts +3 -2
- package/dist/lib/open-url.js +5 -4
- package/dist/lib/prompts.d.ts +35 -1
- package/dist/lib/prompts.js +7 -0
- package/dist/lib/setup/apply.d.ts +2 -2
- package/dist/lib/setup/apply.js +2 -2
- package/dist/lib/setup/gitignore.d.ts +7 -0
- package/dist/lib/setup/gitignore.js +39 -1
- package/dist/lib/setup/steps.d.ts +6 -2
- package/dist/lib/setup/steps.js +65 -60
- package/dist/lib/setup/tools.d.ts +1 -1
- package/dist/lib/setup/tools.js +1 -1
- package/dist/lib/trackers/github/index.js +2 -2
- package/dist/lib/trackers/local/store.js +1 -1
- package/package.json +1 -1
- package/prompts/fix-context.njk +79 -0
- package/prompts/fix-loop.njk +18 -0
- package/prompts/pr.njk +1 -1
- package/dist/commands/doctor.d.ts +0 -2
- package/dist/commands/doctor.js +0 -545
- package/dist/commands/github/auth.d.ts +0 -2
- package/dist/commands/github/auth.js +0 -56
- package/dist/commands/github/index.d.ts +0 -1
- package/dist/commands/github/index.js +0 -1
- package/dist/commands/helpers/english-tutor/index.d.ts +0 -1
- package/dist/commands/helpers/english-tutor/index.js +0 -1
- package/dist/commands/helpers/english-tutor/install.d.ts +0 -8
- package/dist/commands/helpers/english-tutor/install.js +0 -24
- package/dist/commands/helpers/english-tutor/prompt.d.ts +0 -2
- package/dist/commands/helpers/english-tutor/prompt.js +0 -16
- package/dist/commands/helpers/english-tutor/session-start.d.ts +0 -2
- package/dist/commands/helpers/english-tutor/session-start.js +0 -34
- package/dist/commands/helpers/english-tutor/uninstall.d.ts +0 -2
- package/dist/commands/helpers/english-tutor/uninstall.js +0 -15
- package/dist/commands/helpers/session-signal/end.d.ts +0 -2
- package/dist/commands/helpers/session-signal/end.js +0 -13
- package/dist/commands/helpers/session-signal/index.d.ts +0 -1
- package/dist/commands/helpers/session-signal/index.js +0 -1
- package/dist/commands/helpers/session-signal/install.d.ts +0 -8
- package/dist/commands/helpers/session-signal/install.js +0 -24
- package/dist/commands/helpers/session-signal/notification.d.ts +0 -2
- package/dist/commands/helpers/session-signal/notification.js +0 -13
- package/dist/commands/helpers/session-signal/prompt.d.ts +0 -2
- package/dist/commands/helpers/session-signal/prompt.js +0 -13
- package/dist/commands/helpers/session-signal/stop.d.ts +0 -2
- package/dist/commands/helpers/session-signal/stop.js +0 -13
- package/dist/commands/issue/setup.d.ts +0 -2
- package/dist/commands/issue/setup.js +0 -108
- package/dist/commands/issue/switch.d.ts +0 -12
- package/dist/commands/issue/switch.js +0 -38
- package/dist/commands/linear/switch.d.ts +0 -2
- package/dist/commands/linear/switch.js +0 -75
- package/dist/commands/setup.d.ts +0 -11
- package/dist/commands/setup.js +0 -194
- package/dist/lib/english-tutor.d.ts +0 -13
- package/dist/lib/english-tutor.js +0 -125
- package/dist/lib/session-signal.d.ts +0 -16
- package/dist/lib/session-signal.js +0 -104
- package/dist/lib/setup/shell-config.d.ts +0 -38
- package/dist/lib/setup/shell-config.js +0 -131
- package/prompts/english-tutor-prompt.njk +0 -15
|
@@ -9,12 +9,15 @@ import * as path from "path";
|
|
|
9
9
|
const require = createRequire(import.meta.url);
|
|
10
10
|
const { version } = require("../../package.json");
|
|
11
11
|
import { findMainRepoRoot, createWorktree, getDefaultBranch, getBaseBranch, hasInitScript, getInitScriptPath, removeWorktree, getDiffTool, getWorktreeStatus, stageFile, unstageFile, stageAll, unstageAll, discardFile, } from "../lib/git.js";
|
|
12
|
-
import { run, spawnAsync } from "../lib/exec.js";
|
|
12
|
+
import { run, spawnAsync, execFileAsync } from "../lib/exec.js";
|
|
13
|
+
import { getConfiguredEditor } from "../lib/config-store.js";
|
|
13
14
|
import { printCdHint } from "../lib/cd-hint.js";
|
|
14
15
|
import { resolveAgentBinary, resolveClaudeBinary, fillCommitMessage, askTicketQuestion, } from "../lib/ai.js";
|
|
15
16
|
import { readTriageInvestigateConfig, isTriageInvestigateConfigured, buildInvestigatePrompt, buildInvestigateCommand, } from "../lib/triage-config.js";
|
|
16
17
|
import { getInstalledClaudeVersion } from "../lib/version.js";
|
|
17
18
|
import { extractTicketId, getStagedDiffContent } from "../lib/git.js";
|
|
19
|
+
import { santreeSelfArgv } from "../lib/setup/apply.js";
|
|
20
|
+
import { startFixLoop } from "../lib/fix-loop.js";
|
|
18
21
|
import { getMultiplexer } from "../lib/multiplexer/index.js";
|
|
19
22
|
import { shellEscape } from "../lib/multiplexer/types.js";
|
|
20
23
|
import Spinner from "ink-spinner";
|
|
@@ -294,6 +297,32 @@ function Tab({ active, label, mode }) {
|
|
|
294
297
|
}
|
|
295
298
|
return _jsx(Text, { children: ` ${label} ` });
|
|
296
299
|
}
|
|
300
|
+
const TAB_NAMES = {
|
|
301
|
+
triage: "Triage",
|
|
302
|
+
issues: "Issues",
|
|
303
|
+
trees: "Trees",
|
|
304
|
+
reviews: "Reviews",
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* Column ranges (1-based, absolute) of each tab pill in the tab strip, so a
|
|
308
|
+
* row-2 click maps to the right tab. Must mirror the renderer exactly: the strip
|
|
309
|
+
* Box has `paddingX={1}` (first pill at col 2), each `<Tab>` pill is `" label "`
|
|
310
|
+
* (label + 2), and a 1-space separator sits between pills. Labels embed the live
|
|
311
|
+
* counts, so widths shift as data refreshes — always pass current counts.
|
|
312
|
+
*/
|
|
313
|
+
function computeTabHitboxes(order, counts) {
|
|
314
|
+
const boxes = [];
|
|
315
|
+
let col = 2; // paddingX={1} on the row-2 Box → first pill starts at col 2
|
|
316
|
+
order.forEach((tab, i) => {
|
|
317
|
+
if (i > 0)
|
|
318
|
+
col += 1; // 1-space separator between pills
|
|
319
|
+
const label = `${i + 1} ${TAB_NAMES[tab]} (${counts[tab]})`;
|
|
320
|
+
const width = label.length + 2; // pill renders ` label `
|
|
321
|
+
boxes.push({ tab, start: col, end: col + width - 1 });
|
|
322
|
+
col += width;
|
|
323
|
+
});
|
|
324
|
+
return boxes;
|
|
325
|
+
}
|
|
297
326
|
/**
|
|
298
327
|
* Single-line global keymap shown at the bottom-left of the dashboard. The
|
|
299
328
|
* `E workspace` hint only appears when the action is meaningful
|
|
@@ -336,6 +365,10 @@ export default function Dashboard() {
|
|
|
336
365
|
const repoRootRef = useRef(null);
|
|
337
366
|
const stateRef = useRef(state);
|
|
338
367
|
stateRef.current = state;
|
|
368
|
+
// Kept in sync so the long-lived mouse handler can rebuild the tab order
|
|
369
|
+
// (which depends on whether Triage is present) for click-to-switch.
|
|
370
|
+
const supportsTriageRef = useRef(supportsTriage);
|
|
371
|
+
supportsTriageRef.current = supportsTriage;
|
|
339
372
|
const draggingRef = useRef(null);
|
|
340
373
|
const [termSize, setTermSize] = useState({
|
|
341
374
|
columns: stdout?.columns ?? 80,
|
|
@@ -411,10 +444,11 @@ export default function Dashboard() {
|
|
|
411
444
|
return;
|
|
412
445
|
}
|
|
413
446
|
try {
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
// on
|
|
417
|
-
//
|
|
447
|
+
// Refresh the colorscheme FIRST — it's the most visible change, so on a
|
|
448
|
+
// manual `R` (or a light↔dark switch) the theme should flip immediately
|
|
449
|
+
// rather than waiting on the slower gh-backed data fetch below. Detect +
|
|
450
|
+
// apply it, then load everything else.
|
|
451
|
+
// Skip the OSC 11 query when a text-input overlay is active — the
|
|
418
452
|
// terminal's response would otherwise leak into the user's
|
|
419
453
|
// commit/PR/context message via Ink's stdin handler.
|
|
420
454
|
const overlay = stateRef.current.overlay;
|
|
@@ -422,18 +456,17 @@ export default function Dashboard() {
|
|
|
422
456
|
(overlay === "triage-ask" && stateRef.current.triageAskPhase === "input") ||
|
|
423
457
|
(overlay === "pr-create" && stateRef.current.prCreatePhase === "review") ||
|
|
424
458
|
(overlay === "commit" && stateRef.current.commitPhase === "awaiting-message");
|
|
425
|
-
const
|
|
426
|
-
|
|
459
|
+
const themeMode = inTextInput ? null : await detectTerminalTheme();
|
|
460
|
+
if (themeMode !== null)
|
|
461
|
+
setTheme(getThemeForMode(themeMode));
|
|
462
|
+
const [data, reviewData] = await Promise.all([
|
|
427
463
|
loadDashboardData(repoRoot),
|
|
428
464
|
loadReviewsData(repoRoot),
|
|
429
|
-
themeP,
|
|
430
465
|
]);
|
|
431
|
-
if (themeMode !== null)
|
|
432
|
-
setTheme(getThemeForMode(themeMode));
|
|
433
466
|
// Workspace file presence — only meaningful when the editor consumes
|
|
434
467
|
// `.code-workspace` files. Cheap directory read; recomputed each cycle
|
|
435
468
|
// in case the user adds/removes one.
|
|
436
|
-
const editor = (
|
|
469
|
+
const editor = (getConfiguredEditor() ?? "code").toLowerCase();
|
|
437
470
|
const editorAcceptsWorkspace = editor === "code" || editor === "cursor";
|
|
438
471
|
let hasWs = false;
|
|
439
472
|
if (editorAcceptsWorkspace) {
|
|
@@ -599,6 +632,28 @@ export default function Dashboard() {
|
|
|
599
632
|
}
|
|
600
633
|
if (!isPress)
|
|
601
634
|
return;
|
|
635
|
+
// Tab strip click (row 2) — switch tabs by clicking, like the 1–N keys.
|
|
636
|
+
// Ignored while any overlay is up or data isn't ready.
|
|
637
|
+
{
|
|
638
|
+
const s = stateRef.current;
|
|
639
|
+
if (row === 2 && s.overlay === null && !s.loading && !s.error) {
|
|
640
|
+
const order = supportsTriageRef.current
|
|
641
|
+
? ["triage", "issues", "trees", "reviews"]
|
|
642
|
+
: ["issues", "trees", "reviews"];
|
|
643
|
+
const counts = {
|
|
644
|
+
triage: s.flatTriage.length,
|
|
645
|
+
issues: s.flatIssues.length,
|
|
646
|
+
trees: s.flatTrees.length,
|
|
647
|
+
reviews: s.flatReviews.length,
|
|
648
|
+
};
|
|
649
|
+
const hit = computeTabHitboxes(order, counts).find((b) => col >= b.start && col <= b.end);
|
|
650
|
+
if (hit) {
|
|
651
|
+
if (hit.tab !== s.activeTab)
|
|
652
|
+
dispatch({ type: "SET_TAB", tab: hit.tab });
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
602
657
|
// Diff overlay click: drag divider, or select file row in left pane
|
|
603
658
|
{
|
|
604
659
|
const s = stateRef.current;
|
|
@@ -1078,6 +1133,8 @@ export default function Dashboard() {
|
|
|
1078
1133
|
name: windowName,
|
|
1079
1134
|
cwd: worktreePath,
|
|
1080
1135
|
command: cmd,
|
|
1136
|
+
group: di.issue.projectName ?? undefined,
|
|
1137
|
+
tabName: "work",
|
|
1081
1138
|
});
|
|
1082
1139
|
if (created.ok) {
|
|
1083
1140
|
dispatch({
|
|
@@ -1097,7 +1154,7 @@ export default function Dashboard() {
|
|
|
1097
1154
|
// Delayed refresh to pick up session ID created by `santree worktree work`
|
|
1098
1155
|
setTimeout(() => refresh(), 3000);
|
|
1099
1156
|
}, [refresh]);
|
|
1100
|
-
const launchAfterCreation = useCallback(async (mode, worktreePath, ticketId, contextFile) => {
|
|
1157
|
+
const launchAfterCreation = useCallback(async (mode, worktreePath, ticketId, contextFile, group) => {
|
|
1101
1158
|
const mux = getMultiplexer();
|
|
1102
1159
|
if (mux.isActive()) {
|
|
1103
1160
|
const windowName = ticketId;
|
|
@@ -1109,6 +1166,8 @@ export default function Dashboard() {
|
|
|
1109
1166
|
name: windowName,
|
|
1110
1167
|
cwd: worktreePath,
|
|
1111
1168
|
command: workCmd,
|
|
1169
|
+
group,
|
|
1170
|
+
tabName: "work",
|
|
1112
1171
|
});
|
|
1113
1172
|
if (created.ok) {
|
|
1114
1173
|
dispatch({
|
|
@@ -1244,7 +1303,7 @@ export default function Dashboard() {
|
|
|
1244
1303
|
}
|
|
1245
1304
|
// 4. Done — launch work
|
|
1246
1305
|
dispatch({ type: "CREATION_DONE" });
|
|
1247
|
-
launchAfterCreation(mode, result.path, ticketId, contextFile);
|
|
1306
|
+
launchAfterCreation(mode, result.path, ticketId, contextFile, di.issue.projectName ?? undefined);
|
|
1248
1307
|
}, [launchAfterCreation]);
|
|
1249
1308
|
// Holds the context file path through multi-step flows (mode-select → base-select → confirm-setup → create)
|
|
1250
1309
|
const pendingContextFileRef = useRef(null);
|
|
@@ -1414,7 +1473,7 @@ export default function Dashboard() {
|
|
|
1414
1473
|
if (!user) {
|
|
1415
1474
|
dispatch({
|
|
1416
1475
|
type: "TRACKER_SELECT_MESSAGE",
|
|
1417
|
-
message: "GitHub CLI not authenticated. Run:
|
|
1476
|
+
message: "GitHub CLI not authenticated. Run: gh auth login",
|
|
1418
1477
|
});
|
|
1419
1478
|
return;
|
|
1420
1479
|
}
|
|
@@ -1581,7 +1640,9 @@ export default function Dashboard() {
|
|
|
1581
1640
|
const msg = tid && !trimmed.includes(`[${tid}]`) ? `[${tid}] ${trimmed}` : trimmed;
|
|
1582
1641
|
dispatch({ type: "COMMIT_PHASE", phase: "committing" });
|
|
1583
1642
|
try {
|
|
1584
|
-
|
|
1643
|
+
// Array args (no shell) — the message is user-typed and may contain
|
|
1644
|
+
// backticks, `$(…)`, quotes, or backslashes.
|
|
1645
|
+
await execFileAsync("git", ["commit", "-m", msg], {
|
|
1585
1646
|
cwd: s.commitWorktreePath,
|
|
1586
1647
|
});
|
|
1587
1648
|
}
|
|
@@ -1594,7 +1655,9 @@ export default function Dashboard() {
|
|
|
1594
1655
|
}
|
|
1595
1656
|
dispatch({ type: "COMMIT_PHASE", phase: "pushing" });
|
|
1596
1657
|
try {
|
|
1597
|
-
await
|
|
1658
|
+
await execFileAsync("git", ["push", "-u", "origin", s.commitBranch], {
|
|
1659
|
+
cwd: s.commitWorktreePath,
|
|
1660
|
+
});
|
|
1598
1661
|
}
|
|
1599
1662
|
catch (e) {
|
|
1600
1663
|
dispatch({ type: "COMMIT_ERROR", error: e?.stderr?.trim() || e?.message || "Push failed" });
|
|
@@ -1608,7 +1671,7 @@ export default function Dashboard() {
|
|
|
1608
1671
|
}, [refresh]);
|
|
1609
1672
|
// ── Editor actions ───────────────────────────────────────────────
|
|
1610
1673
|
const openInEditor = useCallback((wtPath) => {
|
|
1611
|
-
const editor =
|
|
1674
|
+
const editor = getConfiguredEditor() || "code";
|
|
1612
1675
|
spawn(editor, [wtPath], { detached: true, stdio: "ignore" }).unref();
|
|
1613
1676
|
dispatch({
|
|
1614
1677
|
type: "SET_ACTION_MESSAGE",
|
|
@@ -1619,7 +1682,7 @@ export default function Dashboard() {
|
|
|
1619
1682
|
const repoRoot = repoRootRef.current;
|
|
1620
1683
|
if (!repoRoot)
|
|
1621
1684
|
return;
|
|
1622
|
-
const editor =
|
|
1685
|
+
const editor = getConfiguredEditor() || "code";
|
|
1623
1686
|
try {
|
|
1624
1687
|
const entries = fs.readdirSync(repoRoot);
|
|
1625
1688
|
const wsFile = entries.find((f) => f.endsWith(".code-workspace"));
|
|
@@ -2486,7 +2549,9 @@ export default function Dashboard() {
|
|
|
2486
2549
|
(async () => {
|
|
2487
2550
|
try {
|
|
2488
2551
|
dispatch({ type: "CREATION_LOG", logs: `Fetching ${ri.branch}...\n` });
|
|
2489
|
-
|
|
2552
|
+
// Array args (no shell) — foreign PR branch names are not
|
|
2553
|
+
// locally slugified and could otherwise inject.
|
|
2554
|
+
await execFileAsync("git", ["fetch", "origin", ri.branch], { cwd: repoRoot });
|
|
2490
2555
|
dispatch({ type: "CREATION_LOG", logs: `Creating worktree...\n` });
|
|
2491
2556
|
const result = await createWorktree(ri.branch, ri.baseBranch ?? getDefaultBranch(), repoRoot);
|
|
2492
2557
|
if (!result.success || !result.path) {
|
|
@@ -2537,7 +2602,7 @@ export default function Dashboard() {
|
|
|
2537
2602
|
dispatch({ type: "CREATION_DONE" });
|
|
2538
2603
|
dispatch({ type: "SET_ACTION_MESSAGE", message: `Worktree created for ${ticketId}` });
|
|
2539
2604
|
// Open in editor automatically
|
|
2540
|
-
const editor =
|
|
2605
|
+
const editor = getConfiguredEditor() || "code";
|
|
2541
2606
|
spawn(editor, [result.path], { detached: true, stdio: "ignore" }).unref();
|
|
2542
2607
|
refresh();
|
|
2543
2608
|
}
|
|
@@ -2553,7 +2618,7 @@ export default function Dashboard() {
|
|
|
2553
2618
|
dispatch({ type: "SET_ACTION_MESSAGE", message: "No worktree (press w to checkout)" });
|
|
2554
2619
|
return;
|
|
2555
2620
|
}
|
|
2556
|
-
const editor =
|
|
2621
|
+
const editor = getConfiguredEditor() || "code";
|
|
2557
2622
|
spawn(editor, [ri.worktree.path], { detached: true, stdio: "ignore" }).unref();
|
|
2558
2623
|
dispatch({ type: "SET_ACTION_MESSAGE", message: `Opened in ${editor}` });
|
|
2559
2624
|
return;
|
|
@@ -2569,18 +2634,22 @@ export default function Dashboard() {
|
|
|
2569
2634
|
}
|
|
2570
2635
|
const mux = getMultiplexer();
|
|
2571
2636
|
if (mux.isActive()) {
|
|
2572
|
-
const
|
|
2637
|
+
const ticketId = extractTicketId(ri.branch ?? "") ?? `pr-${ri.pr.number}`;
|
|
2573
2638
|
const cwd = ri.worktree.path;
|
|
2574
2639
|
void (async () => {
|
|
2575
|
-
|
|
2576
|
-
|
|
2640
|
+
// Open review as a `review` tab inside the ticket's workspace
|
|
2641
|
+
// when one exists; addTab falls back to a new window otherwise.
|
|
2642
|
+
const created = await mux.addTab({
|
|
2643
|
+
windowName: ticketId,
|
|
2644
|
+
tabName: "review",
|
|
2577
2645
|
cwd,
|
|
2578
2646
|
command: "santree pr review",
|
|
2647
|
+
group: ri.ticket?.projectName ?? undefined,
|
|
2579
2648
|
});
|
|
2580
2649
|
dispatch({
|
|
2581
2650
|
type: "SET_ACTION_MESSAGE",
|
|
2582
2651
|
message: created.ok
|
|
2583
|
-
? "Launched AI review in
|
|
2652
|
+
? "Launched AI review in review tab"
|
|
2584
2653
|
: `Failed to launch review${created.message ? `: ${created.message}` : ""}`,
|
|
2585
2654
|
});
|
|
2586
2655
|
})();
|
|
@@ -2656,7 +2725,7 @@ export default function Dashboard() {
|
|
|
2656
2725
|
if (di.worktree?.sessionId) {
|
|
2657
2726
|
dispatch({
|
|
2658
2727
|
type: "SET_ACTION_MESSAGE",
|
|
2659
|
-
message: "
|
|
2728
|
+
message: "A Claude session exists — switch to the Trees tab to resume.",
|
|
2660
2729
|
});
|
|
2661
2730
|
return;
|
|
2662
2731
|
}
|
|
@@ -2695,7 +2764,12 @@ export default function Dashboard() {
|
|
|
2695
2764
|
const command = buildInvestigateCommand(resolveClaudeBinary() ?? "claude", prompt);
|
|
2696
2765
|
const windowName = `investigate-${di.issue.identifier}`;
|
|
2697
2766
|
void (async () => {
|
|
2698
|
-
const created = await mux.createWindow({
|
|
2767
|
+
const created = await mux.createWindow({
|
|
2768
|
+
name: windowName,
|
|
2769
|
+
cwd: root,
|
|
2770
|
+
command,
|
|
2771
|
+
group: di.issue.projectName ?? undefined,
|
|
2772
|
+
});
|
|
2699
2773
|
dispatch({
|
|
2700
2774
|
type: "SET_ACTION_MESSAGE",
|
|
2701
2775
|
message: created.ok
|
|
@@ -2758,7 +2832,7 @@ export default function Dashboard() {
|
|
|
2758
2832
|
if (di.worktree?.sessionId) {
|
|
2759
2833
|
dispatch({
|
|
2760
2834
|
type: "SET_ACTION_MESSAGE",
|
|
2761
|
-
message: "
|
|
2835
|
+
message: "A Claude session exists — switch to the Trees tab to resume.",
|
|
2762
2836
|
});
|
|
2763
2837
|
return;
|
|
2764
2838
|
}
|
|
@@ -2825,7 +2899,7 @@ export default function Dashboard() {
|
|
|
2825
2899
|
if (di.worktree?.sessionId) {
|
|
2826
2900
|
dispatch({
|
|
2827
2901
|
type: "SET_ACTION_MESSAGE",
|
|
2828
|
-
message: "
|
|
2902
|
+
message: "A Claude session exists. Press Enter to resume.",
|
|
2829
2903
|
});
|
|
2830
2904
|
return;
|
|
2831
2905
|
}
|
|
@@ -2857,6 +2931,8 @@ export default function Dashboard() {
|
|
|
2857
2931
|
name: windowName,
|
|
2858
2932
|
cwd: worktreePath,
|
|
2859
2933
|
command: cmd,
|
|
2934
|
+
group: di.issue.projectName ?? undefined,
|
|
2935
|
+
tabName: "work",
|
|
2860
2936
|
});
|
|
2861
2937
|
if (!created.ok) {
|
|
2862
2938
|
dispatch({
|
|
@@ -2921,18 +2997,21 @@ export default function Dashboard() {
|
|
|
2921
2997
|
}
|
|
2922
2998
|
const mux = getMultiplexer();
|
|
2923
2999
|
if (mux.isActive()) {
|
|
2924
|
-
const windowName = `review-${di.issue.identifier}`;
|
|
2925
3000
|
const cwd = di.worktree.path;
|
|
2926
3001
|
void (async () => {
|
|
2927
|
-
|
|
2928
|
-
|
|
3002
|
+
// Open review as a `review` tab inside the ticket's workspace
|
|
3003
|
+
// (next to `work`), not as a separate window.
|
|
3004
|
+
const created = await mux.addTab({
|
|
3005
|
+
windowName: di.issue.identifier,
|
|
3006
|
+
tabName: "review",
|
|
2929
3007
|
cwd,
|
|
2930
3008
|
command: "santree pr review",
|
|
3009
|
+
group: di.issue.projectName ?? undefined,
|
|
2931
3010
|
});
|
|
2932
3011
|
dispatch({
|
|
2933
3012
|
type: "SET_ACTION_MESSAGE",
|
|
2934
3013
|
message: created.ok
|
|
2935
|
-
? "Launched review in
|
|
3014
|
+
? "Launched review in review tab"
|
|
2936
3015
|
: `Failed to launch review${created.message ? `: ${created.message}` : ""}`,
|
|
2937
3016
|
});
|
|
2938
3017
|
})();
|
|
@@ -2982,35 +3061,50 @@ export default function Dashboard() {
|
|
|
2982
3061
|
});
|
|
2983
3062
|
return;
|
|
2984
3063
|
}
|
|
2985
|
-
// Fix PR
|
|
3064
|
+
// Fix PR — launch a self-driving fix loop (re-checks CI + conflicts every 5 min)
|
|
2986
3065
|
if (input === "f") {
|
|
2987
3066
|
if (!di.pr || !di.worktree) {
|
|
2988
3067
|
dispatch({ type: "SET_ACTION_MESSAGE", message: "No PR to fix" });
|
|
2989
3068
|
return;
|
|
2990
3069
|
}
|
|
2991
3070
|
const mux = getMultiplexer();
|
|
2992
|
-
if (mux.isActive()) {
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
cwd,
|
|
2999
|
-
command: "santree pr fix",
|
|
3000
|
-
});
|
|
3001
|
-
dispatch({
|
|
3002
|
-
type: "SET_ACTION_MESSAGE",
|
|
3003
|
-
message: created.ok
|
|
3004
|
-
? "Launched PR fix in new window"
|
|
3005
|
-
: `Failed to launch PR fix${created.message ? `: ${created.message}` : ""}`,
|
|
3006
|
-
});
|
|
3007
|
-
})();
|
|
3008
|
-
}
|
|
3009
|
-
else {
|
|
3010
|
-
leaveAltScreen();
|
|
3011
|
-
printCdHint({ path: di.worktree.path });
|
|
3012
|
-
exit();
|
|
3071
|
+
if (!mux.isActive()) {
|
|
3072
|
+
dispatch({
|
|
3073
|
+
type: "SET_ACTION_MESSAGE",
|
|
3074
|
+
message: "Fix loop needs tmux or cmux — run `santree pr fix --loop` in the worktree",
|
|
3075
|
+
});
|
|
3076
|
+
return;
|
|
3013
3077
|
}
|
|
3078
|
+
const ticketId = di.issue.identifier;
|
|
3079
|
+
const cwd = di.worktree.path;
|
|
3080
|
+
// Absolute santree invocation: the new window's shell may not have
|
|
3081
|
+
// `santree` on PATH (cmux workspaces start a fresh login shell).
|
|
3082
|
+
const self = santreeSelfArgv(["pr", "fix", "--loop"]);
|
|
3083
|
+
const command = [self.cmd, ...self.args].join(" ");
|
|
3084
|
+
// Seed the marker + optimistic badge so the loop shows immediately,
|
|
3085
|
+
// before the launched process writes its own marker.
|
|
3086
|
+
const root = repoRootRef.current ?? findMainRepoRoot();
|
|
3087
|
+
if (root)
|
|
3088
|
+
startFixLoop(root, ticketId, 5);
|
|
3089
|
+
dispatch({ type: "FIXLOOP_START", ticketId });
|
|
3090
|
+
void (async () => {
|
|
3091
|
+
// Add the loop as a `fix-loop` tab inside the ticket's workspace
|
|
3092
|
+
// (next to its `work` tab). On tmux this falls back to a separate
|
|
3093
|
+
// `fix-loop-<ticket>` window.
|
|
3094
|
+
const created = await mux.addTab({
|
|
3095
|
+
windowName: ticketId,
|
|
3096
|
+
tabName: "fix-loop",
|
|
3097
|
+
cwd,
|
|
3098
|
+
command,
|
|
3099
|
+
group: di.issue.projectName ?? undefined,
|
|
3100
|
+
});
|
|
3101
|
+
dispatch({
|
|
3102
|
+
type: "SET_ACTION_MESSAGE",
|
|
3103
|
+
message: created.ok
|
|
3104
|
+
? "Started fix loop (re-checks every 5 min) in fix-loop tab"
|
|
3105
|
+
: `Failed to start fix loop${created.message ? `: ${created.message}` : ""}`,
|
|
3106
|
+
});
|
|
3107
|
+
})();
|
|
3014
3108
|
return;
|
|
3015
3109
|
}
|
|
3016
3110
|
// View diff (inline overlay)
|
|
@@ -3068,6 +3162,11 @@ export default function Dashboard() {
|
|
|
3068
3162
|
: state.flatIssues[state.selectedIndex]) ?? null;
|
|
3069
3163
|
const selectedReview = state.flatReviews[state.reviewSelectedIndex] ?? null;
|
|
3070
3164
|
const activeTracker = getIssueTracker(repoRootRef.current);
|
|
3165
|
+
// The tracker actually in use right now — null until the repo is configured,
|
|
3166
|
+
// so the `t` overlay doesn't mark a tracker the user never picked.
|
|
3167
|
+
const configuredTrackerKind = isRepoTrackerConfigured(repoRootRef.current)
|
|
3168
|
+
? activeTracker.kind
|
|
3169
|
+
: null;
|
|
3071
3170
|
// Comments for the selected triage issue (lazily loaded; undefined = still
|
|
3072
3171
|
// fetching, which the detail panel renders as "loading…").
|
|
3073
3172
|
const triageComments = state.activeTab === "triage" && selectedIssue
|
|
@@ -3107,12 +3206,17 @@ export default function Dashboard() {
|
|
|
3107
3206
|
const sel = i === state.trackerSelectIndex;
|
|
3108
3207
|
return (_jsxs(Text, { color: sel ? "cyan" : undefined, bold: sel, children: [sel ? "> " : " ", org.name, " (", org.slug, ")"] }, org.slug));
|
|
3109
3208
|
}), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "j/k to navigate, Enter to link, ESC to go back" })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Select an issue tracker for this repo:" }), _jsx(Text, { children: " " }), [
|
|
3110
|
-
{
|
|
3111
|
-
|
|
3112
|
-
|
|
3209
|
+
{
|
|
3210
|
+
kind: "local",
|
|
3211
|
+
label: "Local",
|
|
3212
|
+
hint: "built-in, file-based — no account needed",
|
|
3213
|
+
},
|
|
3214
|
+
{ kind: "linear", label: "Linear", hint: "OAuth workspace" },
|
|
3215
|
+
{ kind: "github", label: "GitHub", hint: "GitHub Issues via gh CLI" },
|
|
3113
3216
|
].map((t, i) => {
|
|
3114
3217
|
const sel = i === state.trackerSelectIndex;
|
|
3115
|
-
|
|
3218
|
+
const active = t.kind === configuredTrackerKind;
|
|
3219
|
+
return (_jsxs(Text, { children: [_jsxs(Text, { color: sel ? "cyan" : undefined, bold: sel, children: [sel ? "> " : " ", t.label] }), active ? _jsx(Text, { color: "green", children: " \u25CF in use" }) : null, _jsxs(Text, { dimColor: true, children: [" \u2014 ", t.hint] })] }, t.label));
|
|
3116
3220
|
}), _jsx(Text, { children: " " }), state.trackerSelectMessage ? (_jsx(Text, { color: "yellow", children: state.trackerSelectMessage })) : null, _jsx(Text, { dimColor: true, children: "j/k to navigate, Enter to select, q to quit" })] })) }) })) : state.overlay === "confirm-delete-issue" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "red", paddingX: 3, paddingY: 1, children: [_jsx(Text, { bold: true, color: "red", children: "Delete issue?" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [selectedIssue?.issue.identifier, " ", selectedIssue?.issue.title] }), _jsx(Text, { dimColor: true, children: "This removes the issue file from .santree/issues/" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsx(Text, { color: "red", bold: true, children: "y" }), " Confirm"] }), _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "n" }), " Cancel"] })] }) })) : state.overlay === "issue-form" ? (_jsx(Box, { flexGrow: 1, justifyContent: "center", alignItems: "center", children: _jsxs(Box, { flexDirection: "column", paddingX: 2, width: Math.min(columns - 8, 100), children: [_jsxs(Text, { bold: true, color: "cyan", children: [state.issueFormMode === "edit" ? `Edit ${state.issueFormId}` : "New issue", " · ", state.issueFormPhase === "title"
|
|
3117
3221
|
? "title"
|
|
3118
3222
|
: state.issueFormPhase === "description"
|
|
@@ -3144,7 +3248,7 @@ export default function Dashboard() {
|
|
|
3144
3248
|
})(), _jsx(Text, { children: " " }), _jsxs(Text, { dimColor: true, children: [_jsx(Text, { color: "cyan", bold: true, children: "\u21E7\u2191\u2193/j/k" }), " scroll · ", _jsx(Text, { color: "cyan", bold: true, children: "a" }), " ask again · ", _jsx(Text, { color: "cyan", bold: true, children: "q" }), " close"] })] }))] })) : (_jsxs(Box, { flexGrow: 1, children: [_jsx(Box, { width: leftWidth, children: state.activeTab === "reviews" ? (_jsx(ReviewList, { flatReviews: state.flatReviews, selectedIndex: state.reviewSelectedIndex, scrollOffset: state.reviewListScrollOffset, height: contentHeight, width: leftWidth, selectionBg: theme.selectionBg })) : state.activeTab === "triage" ? (state.flatTriage.length === 0 ? (_jsx(Box, { width: leftWidth, height: contentHeight, justifyContent: "center", alignItems: "center", children: _jsx(Text, { color: "yellow", children: "Triage inbox empty" }) })) : (_jsx(IssueList, { groups: state.triageGroups, flatIssues: state.flatTriage, selectedIndex: state.triageSelectedIndex, scrollOffset: state.triageListScrollOffset, height: contentHeight, width: leftWidth, selectionBg: theme.selectionBg, variant: "triage" }))) : (state.activeTab === "trees" ? state.flatTrees : state.flatIssues).length ===
|
|
3145
3249
|
0 ? (_jsxs(Box, { width: leftWidth, height: contentHeight, justifyContent: "center", alignItems: "center", flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: state.activeTab === "trees" ? "No worktrees yet" : "No active issues" }), state.activeTab === "issues" && activeTracker.canMutate ? (_jsx(Text, { dimColor: true, children: "Press n to create one" })) : null] })) : (_jsx(IssueList, { groups: state.activeTab === "trees" ? state.treeGroups : state.groups, flatIssues: state.activeTab === "trees" ? state.flatTrees : state.flatIssues, selectedIndex: state.activeTab === "trees" ? state.treeSelectedIndex : state.selectedIndex, scrollOffset: state.activeTab === "trees"
|
|
3146
3250
|
? state.treeListScrollOffset
|
|
3147
|
-
: state.listScrollOffset, height: contentHeight, width: leftWidth, selectionBg: theme.selectionBg, deletingIds: state.activeTab === "trees" ? deletingIds : undefined, variant: state.activeTab === "issues" ? "issues" : "default" })) }), _jsx(Box, { flexDirection: "column", width: 3, children: Array.from({ length: contentHeight }).map((_, i) => (_jsx(Text, { dimColor: true, children: " │ " }, i))) }), _jsx(Box, { width: rightWidth, children: state.activeTab === "reviews" && state.creatingForTicket ? (_jsxs(Box, { flexDirection: "column", width: rightWidth, height: contentHeight, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Setting up worktree for ", state.creatingForTicket, "..."] }), state.creationLogs
|
|
3251
|
+
: state.listScrollOffset, height: contentHeight, width: leftWidth, selectionBg: theme.selectionBg, deletingIds: state.activeTab === "trees" ? deletingIds : undefined, fixLoops: state.activeTab === "trees" ? state.fixLoopTickets : undefined, variant: state.activeTab === "issues" ? "issues" : "default" })) }), _jsx(Box, { flexDirection: "column", width: 3, children: Array.from({ length: contentHeight }).map((_, i) => (_jsx(Text, { dimColor: true, children: " │ " }, i))) }), _jsx(Box, { width: rightWidth, children: state.activeTab === "reviews" && state.creatingForTicket ? (_jsxs(Box, { flexDirection: "column", width: rightWidth, height: contentHeight, children: [_jsxs(Text, { color: "yellow", bold: true, children: ["Setting up worktree for ", state.creatingForTicket, "..."] }), state.creationLogs
|
|
3148
3252
|
.split("\n")
|
|
3149
3253
|
.slice(-(contentHeight - 1))
|
|
3150
3254
|
.map((line, i) => (_jsx(Box, { children: _jsx(Text, { dimColor: true, children: line }) }, i)))] })) : state.activeTab === "reviews" ? (_jsx(ReviewDetailPanel, { item: selectedReview, scrollOffset: state.reviewDetailScrollOffset, height: contentHeight, width: rightWidth })) : state.overlay === "commit" ? (_jsx(CommitOverlay, { width: rightWidth, height: contentHeight, branch: state.commitBranch, ticketId: state.commitTicketId, gitStatus: state.commitGitStatus, phase: state.commitPhase, message: state.commitMessage, error: state.commitError, dispatch: dispatch, onSubmit: handleCommitSubmit })) : state.overlay === "pr-create" ? (_jsx(PrCreateOverlay, { width: rightWidth, height: contentHeight, branch: state.prCreateBranch, ticketId: state.prCreateTicketId, phase: state.prCreatePhase, error: state.prCreateError, url: state.prCreateUrl, body: state.prCreateBody, title: state.prCreateTitle, draft: state.prCreateDraft, dispatch: dispatch })) : state.activeTab === "triage" && !selectedIssue ? (
|
|
@@ -3155,7 +3259,7 @@ export default function Dashboard() {
|
|
|
3155
3259
|
? state.treeDetailScrollOffset
|
|
3156
3260
|
: state.activeTab === "triage"
|
|
3157
3261
|
? state.triageDetailScrollOffset
|
|
3158
|
-
: state.detailScrollOffset, height: contentHeight, width: rightWidth, creatingForTicket: state.creatingForTicket, creationLogs: state.creationLogs, deleteStatus: selectedDeleteStatus, prSyncing: !!selectedIssue && !!state.pendingPrs[selectedIssue.issue.identifier], triage: state.activeTab === "triage", comments: triageComments, onCall: onCall })) })] })), _jsx(Box, { children: state.overlay === "diff" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "diff" }) })) : state.overlay === "triage-schedule" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "j/k" }), _jsx(Text, { dimColor: true, children: " scroll" }), _jsx(Text, { dimColor: true, children: " · " }), _jsx(Text, { color: "cyan", bold: true, children: "q" }), _jsx(Text, { dimColor: true, children: " close" })] }) })) : (_jsxs(_Fragment, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "default" }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview, overlay: state.overlay, trackerName: activeTracker.displayName, canMutate: activeTracker.canMutate === true, triageInvestigateConfigured: triageInvestigateConfigured }) })] })) })] })] }));
|
|
3262
|
+
: state.detailScrollOffset, height: contentHeight, width: rightWidth, creatingForTicket: state.creatingForTicket, creationLogs: state.creationLogs, deleteStatus: selectedDeleteStatus, prSyncing: !!selectedIssue && !!state.pendingPrs[selectedIssue.issue.identifier], fixLoop: selectedIssue ? state.fixLoopTickets[selectedIssue.issue.identifier] : undefined, triage: state.activeTab === "triage", comments: triageComments, onCall: onCall })) })] })), _jsx(Box, { children: state.overlay === "diff" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "diff" }) })) : state.overlay === "triage-schedule" ? (_jsx(Box, { width: innerWidth, paddingX: 1, children: _jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "j/k" }), _jsx(Text, { dimColor: true, children: " scroll" }), _jsx(Text, { dimColor: true, children: " · " }), _jsx(Text, { color: "cyan", bold: true, children: "q" }), _jsx(Text, { dimColor: true, children: " close" })] }) })) : (_jsxs(_Fragment, { children: [_jsx(Box, { width: leftWidth + separatorWidth, paddingX: 1, children: _jsx(CommandBar, { showWorkspace: hasWorkspaceFile, mode: "default" }) }), _jsx(Box, { width: rightWidth, children: _jsx(ActionRow, { activeTab: state.activeTab, selectedIssue: selectedIssue, selectedReview: selectedReview, overlay: state.overlay, trackerName: activeTracker.displayName, canMutate: activeTracker.canMutate === true, triageInvestigateConfigured: triageInvestigateConfigured }) })] })) })] })] }));
|
|
3159
3263
|
}
|
|
3160
3264
|
/**
|
|
3161
3265
|
* Renders the per-issue action key hints (Resume / Editor / View diff / …)
|
|
@@ -83,28 +83,6 @@ function isWorktree(cwd) {
|
|
|
83
83
|
function isSantreeWorktree(cwd) {
|
|
84
84
|
return cwd.includes("/.santree/worktrees/");
|
|
85
85
|
}
|
|
86
|
-
// Read session state from per-ticket state file
|
|
87
|
-
function readSessionStateFile(cwd) {
|
|
88
|
-
const marker = "/.santree/worktrees/";
|
|
89
|
-
const idx = cwd.indexOf(marker);
|
|
90
|
-
if (idx === -1)
|
|
91
|
-
return null;
|
|
92
|
-
const repoRoot = cwd.slice(0, idx);
|
|
93
|
-
const rest = cwd.slice(idx + marker.length);
|
|
94
|
-
const ticketId = rest.split("/")[0];
|
|
95
|
-
if (!ticketId)
|
|
96
|
-
return null;
|
|
97
|
-
const filePath = path.join(repoRoot, ".santree", "session-states", `${ticketId}.json`);
|
|
98
|
-
try {
|
|
99
|
-
const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
100
|
-
if (data.state === "exited")
|
|
101
|
-
return null;
|
|
102
|
-
return data.state;
|
|
103
|
-
}
|
|
104
|
-
catch {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
86
|
// Extract ticket ID from branch name (e.g., feature/TEAM-123-desc -> TEAM-123)
|
|
109
87
|
function extractTicketId(branch) {
|
|
110
88
|
const match = branch.match(/([a-zA-Z]+)-(\d+)/);
|
|
@@ -181,14 +159,6 @@ function buildSantreeStatusline(cwd, model, usedPercentage) {
|
|
|
181
159
|
// Git changes
|
|
182
160
|
const changes = getGitChanges(cwd);
|
|
183
161
|
parts.push(formatChanges(changes));
|
|
184
|
-
// Session state
|
|
185
|
-
const sessState = readSessionStateFile(cwd);
|
|
186
|
-
if (sessState === "waiting") {
|
|
187
|
-
parts.push(`${c.red}WAITING${c.reset}`);
|
|
188
|
-
}
|
|
189
|
-
else if (sessState === "idle") {
|
|
190
|
-
parts.push(`${c.yellow}idle${c.reset}`);
|
|
191
|
-
}
|
|
192
162
|
// Model
|
|
193
163
|
if (model) {
|
|
194
164
|
parts.push(`${c.blue}${model}${c.reset}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const description = "Issue
|
|
1
|
+
export declare const description = "Issue actions (open the current branch's issue)";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const description = "Issue
|
|
1
|
+
export const description = "Issue actions (open the current branch's issue)";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const description = "Print the fix-loop iteration brief (state + the exact actions to take)";
|
|
2
|
+
/**
|
|
3
|
+
* Read-only command the auto-fix `/loop` runs each iteration to refresh state.
|
|
4
|
+
* No Ink UI — it writes the rendered brief straight to stdout and exits (same
|
|
5
|
+
* pattern as the statusline helper) so the agent can read it cleanly.
|
|
6
|
+
*/
|
|
7
|
+
export default function PRContext(): null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { resolveAIContext, fetchAndRenderFixContext } from "../../lib/ai.js";
|
|
3
|
+
import { santreeSelfArgv } from "../../lib/setup/apply.js";
|
|
4
|
+
export const description = "Print the fix-loop iteration brief (state + the exact actions to take)";
|
|
5
|
+
/**
|
|
6
|
+
* Read-only command the auto-fix `/loop` runs each iteration to refresh state.
|
|
7
|
+
* No Ink UI — it writes the rendered brief straight to stdout and exits (same
|
|
8
|
+
* pattern as the statusline helper) so the agent can read it cleanly.
|
|
9
|
+
*/
|
|
10
|
+
export default function PRContext() {
|
|
11
|
+
const hasRun = useRef(false);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (hasRun.current)
|
|
14
|
+
return;
|
|
15
|
+
hasRun.current = true;
|
|
16
|
+
(async () => {
|
|
17
|
+
const result = await resolveAIContext();
|
|
18
|
+
if (!result.ok) {
|
|
19
|
+
process.stderr.write(result.error + "\n");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
// Embed the absolute santree invocation so the brief's own `--signal` /
|
|
23
|
+
// resolve commands work in a shell that may not have santree on PATH.
|
|
24
|
+
const self = santreeSelfArgv([]);
|
|
25
|
+
const santreeCmd = [self.cmd, ...self.args].join(" ");
|
|
26
|
+
const md = await fetchAndRenderFixContext(result.context.branch, santreeCmd);
|
|
27
|
+
if (!md) {
|
|
28
|
+
process.stderr.write(`No pull request found for branch '${result.context.branch}'\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write(md.trimEnd() + "\n");
|
|
32
|
+
process.exit(0);
|
|
33
|
+
})();
|
|
34
|
+
}, []);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const description = "Run the self-driving fix loop: merge conflicts, fixable CI, and \uD83D\uDC4D-approved review comments";
|
|
3
|
+
export declare const options: z.ZodObject<{
|
|
4
|
+
loop: z.ZodDefault<z.ZodBoolean>;
|
|
5
|
+
signal: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
type Props = {
|
|
8
|
+
options: z.infer<typeof options>;
|
|
9
|
+
};
|
|
10
|
+
export default function Fix({ options: opts }: Props): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|