santree 0.7.4 → 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 +187 -80
- 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 +17 -11
- package/dist/commands/worktree/open.js +3 -2
- package/dist/commands/worktree/switch.js +3 -11
- package/dist/lib/ai.d.ts +11 -0
- package/dist/lib/ai.js +71 -2
- package/dist/lib/cd-hint.d.ts +18 -0
- package/dist/lib/cd-hint.js +27 -0
- package/dist/lib/claude-config.d.ts +26 -0
- package/dist/lib/claude-config.js +121 -0
- 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 +7 -2
- package/dist/lib/dashboard/DetailPanel.js +54 -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 +19 -2
- package/dist/lib/dashboard/types.js +68 -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 +16 -0
- package/dist/lib/setup/apply.js +35 -0
- package/dist/lib/setup/gitignore.d.ts +24 -0
- package/dist/lib/setup/gitignore.js +95 -0
- package/dist/lib/setup/steps.d.ts +39 -0
- package/dist/lib/setup/steps.js +333 -0
- package/dist/lib/setup/tools.d.ts +23 -0
- package/dist/lib/setup/tools.js +47 -0
- package/dist/lib/squirrel-loader.d.ts +3 -1
- package/dist/lib/squirrel-loader.js +2 -2
- package/dist/lib/trackers/github/index.js +2 -2
- package/dist/lib/trackers/local/store.js +1 -1
- package/package.json +1 -2
- 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 -618
- 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/helpers/shell-init.d.ts +0 -11
- package/dist/commands/helpers/shell-init.js +0 -122
- 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/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/prompts/english-tutor-prompt.njk +0 -15
- package/shell/init.bash.njk +0 -129
- package/shell/init.zsh.njk +0 -204
|
@@ -9,11 +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";
|
|
14
|
+
import { printCdHint } from "../lib/cd-hint.js";
|
|
13
15
|
import { resolveAgentBinary, resolveClaudeBinary, fillCommitMessage, askTicketQuestion, } from "../lib/ai.js";
|
|
14
16
|
import { readTriageInvestigateConfig, isTriageInvestigateConfigured, buildInvestigatePrompt, buildInvestigateCommand, } from "../lib/triage-config.js";
|
|
15
17
|
import { getInstalledClaudeVersion } from "../lib/version.js";
|
|
16
18
|
import { extractTicketId, getStagedDiffContent } from "../lib/git.js";
|
|
19
|
+
import { santreeSelfArgv } from "../lib/setup/apply.js";
|
|
20
|
+
import { startFixLoop } from "../lib/fix-loop.js";
|
|
17
21
|
import { getMultiplexer } from "../lib/multiplexer/index.js";
|
|
18
22
|
import { shellEscape } from "../lib/multiplexer/types.js";
|
|
19
23
|
import Spinner from "ink-spinner";
|
|
@@ -293,6 +297,32 @@ function Tab({ active, label, mode }) {
|
|
|
293
297
|
}
|
|
294
298
|
return _jsx(Text, { children: ` ${label} ` });
|
|
295
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
|
+
}
|
|
296
326
|
/**
|
|
297
327
|
* Single-line global keymap shown at the bottom-left of the dashboard. The
|
|
298
328
|
* `E workspace` hint only appears when the action is meaningful
|
|
@@ -335,6 +365,10 @@ export default function Dashboard() {
|
|
|
335
365
|
const repoRootRef = useRef(null);
|
|
336
366
|
const stateRef = useRef(state);
|
|
337
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;
|
|
338
372
|
const draggingRef = useRef(null);
|
|
339
373
|
const [termSize, setTermSize] = useState({
|
|
340
374
|
columns: stdout?.columns ?? 80,
|
|
@@ -410,10 +444,11 @@ export default function Dashboard() {
|
|
|
410
444
|
return;
|
|
411
445
|
}
|
|
412
446
|
try {
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
// on
|
|
416
|
-
//
|
|
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
|
|
417
452
|
// terminal's response would otherwise leak into the user's
|
|
418
453
|
// commit/PR/context message via Ink's stdin handler.
|
|
419
454
|
const overlay = stateRef.current.overlay;
|
|
@@ -421,18 +456,17 @@ export default function Dashboard() {
|
|
|
421
456
|
(overlay === "triage-ask" && stateRef.current.triageAskPhase === "input") ||
|
|
422
457
|
(overlay === "pr-create" && stateRef.current.prCreatePhase === "review") ||
|
|
423
458
|
(overlay === "commit" && stateRef.current.commitPhase === "awaiting-message");
|
|
424
|
-
const
|
|
425
|
-
|
|
459
|
+
const themeMode = inTextInput ? null : await detectTerminalTheme();
|
|
460
|
+
if (themeMode !== null)
|
|
461
|
+
setTheme(getThemeForMode(themeMode));
|
|
462
|
+
const [data, reviewData] = await Promise.all([
|
|
426
463
|
loadDashboardData(repoRoot),
|
|
427
464
|
loadReviewsData(repoRoot),
|
|
428
|
-
themeP,
|
|
429
465
|
]);
|
|
430
|
-
if (themeMode !== null)
|
|
431
|
-
setTheme(getThemeForMode(themeMode));
|
|
432
466
|
// Workspace file presence — only meaningful when the editor consumes
|
|
433
467
|
// `.code-workspace` files. Cheap directory read; recomputed each cycle
|
|
434
468
|
// in case the user adds/removes one.
|
|
435
|
-
const editor = (
|
|
469
|
+
const editor = (getConfiguredEditor() ?? "code").toLowerCase();
|
|
436
470
|
const editorAcceptsWorkspace = editor === "code" || editor === "cursor";
|
|
437
471
|
let hasWs = false;
|
|
438
472
|
if (editorAcceptsWorkspace) {
|
|
@@ -598,6 +632,28 @@ export default function Dashboard() {
|
|
|
598
632
|
}
|
|
599
633
|
if (!isPress)
|
|
600
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
|
+
}
|
|
601
657
|
// Diff overlay click: drag divider, or select file row in left pane
|
|
602
658
|
{
|
|
603
659
|
const s = stateRef.current;
|
|
@@ -1049,7 +1105,9 @@ export default function Dashboard() {
|
|
|
1049
1105
|
? `cd ${shellEscape(sessionCwd)} && ${bin} --resume ${sessionId}`
|
|
1050
1106
|
: null;
|
|
1051
1107
|
const contextArg = contextFile ? ` --context-file "${contextFile}"` : "";
|
|
1052
|
-
const workCmd = mode === "plan"
|
|
1108
|
+
const workCmd = mode === "plan"
|
|
1109
|
+
? `santree worktree work --plan${contextArg}`
|
|
1110
|
+
: `santree worktree work${contextArg}`;
|
|
1053
1111
|
const cmd = resumeCmd ?? workCmd;
|
|
1054
1112
|
const mux = getMultiplexer();
|
|
1055
1113
|
const selected = await mux.selectWindow(windowName);
|
|
@@ -1075,6 +1133,8 @@ export default function Dashboard() {
|
|
|
1075
1133
|
name: windowName,
|
|
1076
1134
|
cwd: worktreePath,
|
|
1077
1135
|
command: cmd,
|
|
1136
|
+
group: di.issue.projectName ?? undefined,
|
|
1137
|
+
tabName: "work",
|
|
1078
1138
|
});
|
|
1079
1139
|
if (created.ok) {
|
|
1080
1140
|
dispatch({
|
|
@@ -1091,21 +1151,23 @@ export default function Dashboard() {
|
|
|
1091
1151
|
});
|
|
1092
1152
|
}
|
|
1093
1153
|
}
|
|
1094
|
-
// Delayed refresh to pick up session ID created by `
|
|
1154
|
+
// Delayed refresh to pick up session ID created by `santree worktree work`
|
|
1095
1155
|
setTimeout(() => refresh(), 3000);
|
|
1096
1156
|
}, [refresh]);
|
|
1097
|
-
const launchAfterCreation = useCallback(async (mode, worktreePath, ticketId, contextFile) => {
|
|
1157
|
+
const launchAfterCreation = useCallback(async (mode, worktreePath, ticketId, contextFile, group) => {
|
|
1098
1158
|
const mux = getMultiplexer();
|
|
1099
1159
|
if (mux.isActive()) {
|
|
1100
1160
|
const windowName = ticketId;
|
|
1101
1161
|
const contextArg = contextFile ? ` --context-file "${contextFile}"` : "";
|
|
1102
1162
|
const workCmd = mode === "plan"
|
|
1103
|
-
? `
|
|
1104
|
-
: `
|
|
1163
|
+
? `santree worktree work --plan${contextArg}`
|
|
1164
|
+
: `santree worktree work${contextArg}`;
|
|
1105
1165
|
const created = await mux.createWindow({
|
|
1106
1166
|
name: windowName,
|
|
1107
1167
|
cwd: worktreePath,
|
|
1108
1168
|
command: workCmd,
|
|
1169
|
+
group,
|
|
1170
|
+
tabName: "work",
|
|
1109
1171
|
});
|
|
1110
1172
|
if (created.ok) {
|
|
1111
1173
|
dispatch({
|
|
@@ -1123,10 +1185,10 @@ export default function Dashboard() {
|
|
|
1123
1185
|
}
|
|
1124
1186
|
else {
|
|
1125
1187
|
leaveAltScreen();
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1188
|
+
printCdHint({
|
|
1189
|
+
path: worktreePath,
|
|
1190
|
+
work: { mode, contextFile: contextFile || undefined },
|
|
1191
|
+
});
|
|
1130
1192
|
exit();
|
|
1131
1193
|
}
|
|
1132
1194
|
}, [exit, refresh]);
|
|
@@ -1241,7 +1303,7 @@ export default function Dashboard() {
|
|
|
1241
1303
|
}
|
|
1242
1304
|
// 4. Done — launch work
|
|
1243
1305
|
dispatch({ type: "CREATION_DONE" });
|
|
1244
|
-
launchAfterCreation(mode, result.path, ticketId, contextFile);
|
|
1306
|
+
launchAfterCreation(mode, result.path, ticketId, contextFile, di.issue.projectName ?? undefined);
|
|
1245
1307
|
}, [launchAfterCreation]);
|
|
1246
1308
|
// Holds the context file path through multi-step flows (mode-select → base-select → confirm-setup → create)
|
|
1247
1309
|
const pendingContextFileRef = useRef(null);
|
|
@@ -1277,10 +1339,10 @@ export default function Dashboard() {
|
|
|
1277
1339
|
}
|
|
1278
1340
|
else {
|
|
1279
1341
|
leaveAltScreen();
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1342
|
+
printCdHint({
|
|
1343
|
+
path: di.worktree.path,
|
|
1344
|
+
work: { mode, contextFile: contextFile || undefined },
|
|
1345
|
+
});
|
|
1284
1346
|
exit();
|
|
1285
1347
|
}
|
|
1286
1348
|
}
|
|
@@ -1411,7 +1473,7 @@ export default function Dashboard() {
|
|
|
1411
1473
|
if (!user) {
|
|
1412
1474
|
dispatch({
|
|
1413
1475
|
type: "TRACKER_SELECT_MESSAGE",
|
|
1414
|
-
message: "GitHub CLI not authenticated. Run:
|
|
1476
|
+
message: "GitHub CLI not authenticated. Run: gh auth login",
|
|
1415
1477
|
});
|
|
1416
1478
|
return;
|
|
1417
1479
|
}
|
|
@@ -1578,7 +1640,9 @@ export default function Dashboard() {
|
|
|
1578
1640
|
const msg = tid && !trimmed.includes(`[${tid}]`) ? `[${tid}] ${trimmed}` : trimmed;
|
|
1579
1641
|
dispatch({ type: "COMMIT_PHASE", phase: "committing" });
|
|
1580
1642
|
try {
|
|
1581
|
-
|
|
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], {
|
|
1582
1646
|
cwd: s.commitWorktreePath,
|
|
1583
1647
|
});
|
|
1584
1648
|
}
|
|
@@ -1591,7 +1655,9 @@ export default function Dashboard() {
|
|
|
1591
1655
|
}
|
|
1592
1656
|
dispatch({ type: "COMMIT_PHASE", phase: "pushing" });
|
|
1593
1657
|
try {
|
|
1594
|
-
await
|
|
1658
|
+
await execFileAsync("git", ["push", "-u", "origin", s.commitBranch], {
|
|
1659
|
+
cwd: s.commitWorktreePath,
|
|
1660
|
+
});
|
|
1595
1661
|
}
|
|
1596
1662
|
catch (e) {
|
|
1597
1663
|
dispatch({ type: "COMMIT_ERROR", error: e?.stderr?.trim() || e?.message || "Push failed" });
|
|
@@ -1605,7 +1671,7 @@ export default function Dashboard() {
|
|
|
1605
1671
|
}, [refresh]);
|
|
1606
1672
|
// ── Editor actions ───────────────────────────────────────────────
|
|
1607
1673
|
const openInEditor = useCallback((wtPath) => {
|
|
1608
|
-
const editor =
|
|
1674
|
+
const editor = getConfiguredEditor() || "code";
|
|
1609
1675
|
spawn(editor, [wtPath], { detached: true, stdio: "ignore" }).unref();
|
|
1610
1676
|
dispatch({
|
|
1611
1677
|
type: "SET_ACTION_MESSAGE",
|
|
@@ -1616,7 +1682,7 @@ export default function Dashboard() {
|
|
|
1616
1682
|
const repoRoot = repoRootRef.current;
|
|
1617
1683
|
if (!repoRoot)
|
|
1618
1684
|
return;
|
|
1619
|
-
const editor =
|
|
1685
|
+
const editor = getConfiguredEditor() || "code";
|
|
1620
1686
|
try {
|
|
1621
1687
|
const entries = fs.readdirSync(repoRoot);
|
|
1622
1688
|
const wsFile = entries.find((f) => f.endsWith(".code-workspace"));
|
|
@@ -2483,7 +2549,9 @@ export default function Dashboard() {
|
|
|
2483
2549
|
(async () => {
|
|
2484
2550
|
try {
|
|
2485
2551
|
dispatch({ type: "CREATION_LOG", logs: `Fetching ${ri.branch}...\n` });
|
|
2486
|
-
|
|
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 });
|
|
2487
2555
|
dispatch({ type: "CREATION_LOG", logs: `Creating worktree...\n` });
|
|
2488
2556
|
const result = await createWorktree(ri.branch, ri.baseBranch ?? getDefaultBranch(), repoRoot);
|
|
2489
2557
|
if (!result.success || !result.path) {
|
|
@@ -2534,7 +2602,7 @@ export default function Dashboard() {
|
|
|
2534
2602
|
dispatch({ type: "CREATION_DONE" });
|
|
2535
2603
|
dispatch({ type: "SET_ACTION_MESSAGE", message: `Worktree created for ${ticketId}` });
|
|
2536
2604
|
// Open in editor automatically
|
|
2537
|
-
const editor =
|
|
2605
|
+
const editor = getConfiguredEditor() || "code";
|
|
2538
2606
|
spawn(editor, [result.path], { detached: true, stdio: "ignore" }).unref();
|
|
2539
2607
|
refresh();
|
|
2540
2608
|
}
|
|
@@ -2550,7 +2618,7 @@ export default function Dashboard() {
|
|
|
2550
2618
|
dispatch({ type: "SET_ACTION_MESSAGE", message: "No worktree (press w to checkout)" });
|
|
2551
2619
|
return;
|
|
2552
2620
|
}
|
|
2553
|
-
const editor =
|
|
2621
|
+
const editor = getConfiguredEditor() || "code";
|
|
2554
2622
|
spawn(editor, [ri.worktree.path], { detached: true, stdio: "ignore" }).unref();
|
|
2555
2623
|
dispatch({ type: "SET_ACTION_MESSAGE", message: `Opened in ${editor}` });
|
|
2556
2624
|
return;
|
|
@@ -2566,25 +2634,29 @@ export default function Dashboard() {
|
|
|
2566
2634
|
}
|
|
2567
2635
|
const mux = getMultiplexer();
|
|
2568
2636
|
if (mux.isActive()) {
|
|
2569
|
-
const
|
|
2637
|
+
const ticketId = extractTicketId(ri.branch ?? "") ?? `pr-${ri.pr.number}`;
|
|
2570
2638
|
const cwd = ri.worktree.path;
|
|
2571
2639
|
void (async () => {
|
|
2572
|
-
|
|
2573
|
-
|
|
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",
|
|
2574
2645
|
cwd,
|
|
2575
|
-
command: "
|
|
2646
|
+
command: "santree pr review",
|
|
2647
|
+
group: ri.ticket?.projectName ?? undefined,
|
|
2576
2648
|
});
|
|
2577
2649
|
dispatch({
|
|
2578
2650
|
type: "SET_ACTION_MESSAGE",
|
|
2579
2651
|
message: created.ok
|
|
2580
|
-
? "Launched AI review in
|
|
2652
|
+
? "Launched AI review in review tab"
|
|
2581
2653
|
: `Failed to launch review${created.message ? `: ${created.message}` : ""}`,
|
|
2582
2654
|
});
|
|
2583
2655
|
})();
|
|
2584
2656
|
}
|
|
2585
2657
|
else {
|
|
2586
2658
|
leaveAltScreen();
|
|
2587
|
-
|
|
2659
|
+
printCdHint({ path: ri.worktree.path });
|
|
2588
2660
|
exit();
|
|
2589
2661
|
}
|
|
2590
2662
|
return;
|
|
@@ -2653,7 +2725,7 @@ export default function Dashboard() {
|
|
|
2653
2725
|
if (di.worktree?.sessionId) {
|
|
2654
2726
|
dispatch({
|
|
2655
2727
|
type: "SET_ACTION_MESSAGE",
|
|
2656
|
-
message: "
|
|
2728
|
+
message: "A Claude session exists — switch to the Trees tab to resume.",
|
|
2657
2729
|
});
|
|
2658
2730
|
return;
|
|
2659
2731
|
}
|
|
@@ -2692,7 +2764,12 @@ export default function Dashboard() {
|
|
|
2692
2764
|
const command = buildInvestigateCommand(resolveClaudeBinary() ?? "claude", prompt);
|
|
2693
2765
|
const windowName = `investigate-${di.issue.identifier}`;
|
|
2694
2766
|
void (async () => {
|
|
2695
|
-
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
|
+
});
|
|
2696
2773
|
dispatch({
|
|
2697
2774
|
type: "SET_ACTION_MESSAGE",
|
|
2698
2775
|
message: created.ok
|
|
@@ -2755,7 +2832,7 @@ export default function Dashboard() {
|
|
|
2755
2832
|
if (di.worktree?.sessionId) {
|
|
2756
2833
|
dispatch({
|
|
2757
2834
|
type: "SET_ACTION_MESSAGE",
|
|
2758
|
-
message: "
|
|
2835
|
+
message: "A Claude session exists — switch to the Trees tab to resume.",
|
|
2759
2836
|
});
|
|
2760
2837
|
return;
|
|
2761
2838
|
}
|
|
@@ -2822,7 +2899,7 @@ export default function Dashboard() {
|
|
|
2822
2899
|
if (di.worktree?.sessionId) {
|
|
2823
2900
|
dispatch({
|
|
2824
2901
|
type: "SET_ACTION_MESSAGE",
|
|
2825
|
-
message: "
|
|
2902
|
+
message: "A Claude session exists. Press Enter to resume.",
|
|
2826
2903
|
});
|
|
2827
2904
|
return;
|
|
2828
2905
|
}
|
|
@@ -2849,11 +2926,13 @@ export default function Dashboard() {
|
|
|
2849
2926
|
const selected = await mux.selectWindow(windowName);
|
|
2850
2927
|
if (selected.ok)
|
|
2851
2928
|
return;
|
|
2852
|
-
const cmd = resumeCmd ?? "
|
|
2929
|
+
const cmd = resumeCmd ?? "santree worktree work";
|
|
2853
2930
|
const created = await mux.createWindow({
|
|
2854
2931
|
name: windowName,
|
|
2855
2932
|
cwd: worktreePath,
|
|
2856
2933
|
command: cmd,
|
|
2934
|
+
group: di.issue.projectName ?? undefined,
|
|
2935
|
+
tabName: "work",
|
|
2857
2936
|
});
|
|
2858
2937
|
if (!created.ok) {
|
|
2859
2938
|
dispatch({
|
|
@@ -2865,7 +2944,7 @@ export default function Dashboard() {
|
|
|
2865
2944
|
}
|
|
2866
2945
|
else {
|
|
2867
2946
|
leaveAltScreen();
|
|
2868
|
-
|
|
2947
|
+
printCdHint({ path: di.worktree.path });
|
|
2869
2948
|
exit();
|
|
2870
2949
|
}
|
|
2871
2950
|
return;
|
|
@@ -2918,25 +2997,28 @@ export default function Dashboard() {
|
|
|
2918
2997
|
}
|
|
2919
2998
|
const mux = getMultiplexer();
|
|
2920
2999
|
if (mux.isActive()) {
|
|
2921
|
-
const windowName = `review-${di.issue.identifier}`;
|
|
2922
3000
|
const cwd = di.worktree.path;
|
|
2923
3001
|
void (async () => {
|
|
2924
|
-
|
|
2925
|
-
|
|
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",
|
|
2926
3007
|
cwd,
|
|
2927
|
-
command: "
|
|
3008
|
+
command: "santree pr review",
|
|
3009
|
+
group: di.issue.projectName ?? undefined,
|
|
2928
3010
|
});
|
|
2929
3011
|
dispatch({
|
|
2930
3012
|
type: "SET_ACTION_MESSAGE",
|
|
2931
3013
|
message: created.ok
|
|
2932
|
-
? "Launched review in
|
|
3014
|
+
? "Launched review in review tab"
|
|
2933
3015
|
: `Failed to launch review${created.message ? `: ${created.message}` : ""}`,
|
|
2934
3016
|
});
|
|
2935
3017
|
})();
|
|
2936
3018
|
}
|
|
2937
3019
|
else {
|
|
2938
3020
|
leaveAltScreen();
|
|
2939
|
-
|
|
3021
|
+
printCdHint({ path: di.worktree.path });
|
|
2940
3022
|
exit();
|
|
2941
3023
|
}
|
|
2942
3024
|
return;
|
|
@@ -2979,35 +3061,50 @@ export default function Dashboard() {
|
|
|
2979
3061
|
});
|
|
2980
3062
|
return;
|
|
2981
3063
|
}
|
|
2982
|
-
// Fix PR
|
|
3064
|
+
// Fix PR — launch a self-driving fix loop (re-checks CI + conflicts every 5 min)
|
|
2983
3065
|
if (input === "f") {
|
|
2984
3066
|
if (!di.pr || !di.worktree) {
|
|
2985
3067
|
dispatch({ type: "SET_ACTION_MESSAGE", message: "No PR to fix" });
|
|
2986
3068
|
return;
|
|
2987
3069
|
}
|
|
2988
3070
|
const mux = getMultiplexer();
|
|
2989
|
-
if (mux.isActive()) {
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
cwd,
|
|
2996
|
-
command: "st pr fix",
|
|
2997
|
-
});
|
|
2998
|
-
dispatch({
|
|
2999
|
-
type: "SET_ACTION_MESSAGE",
|
|
3000
|
-
message: created.ok
|
|
3001
|
-
? "Launched PR fix in new window"
|
|
3002
|
-
: `Failed to launch PR fix${created.message ? `: ${created.message}` : ""}`,
|
|
3003
|
-
});
|
|
3004
|
-
})();
|
|
3005
|
-
}
|
|
3006
|
-
else {
|
|
3007
|
-
leaveAltScreen();
|
|
3008
|
-
console.log(`SANTREE_CD:${di.worktree.path}`);
|
|
3009
|
-
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;
|
|
3010
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
|
+
})();
|
|
3011
3108
|
return;
|
|
3012
3109
|
}
|
|
3013
3110
|
// View diff (inline overlay)
|
|
@@ -3052,7 +3149,7 @@ export default function Dashboard() {
|
|
|
3052
3149
|
});
|
|
3053
3150
|
// ── Render ─────────────────────────────────────────────────────────
|
|
3054
3151
|
if (state.loading) {
|
|
3055
|
-
return (_jsx(Box, { width: columns, height: rows, flexDirection: "column", children: _jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: _jsx(SquirrelLoader, { text: "Loading dashboard..." }) }) }));
|
|
3152
|
+
return (_jsx(Box, { width: columns, height: rows, flexDirection: "column", children: _jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: _jsx(SquirrelLoader, { text: "Loading dashboard...", version: version }) }) }));
|
|
3056
3153
|
}
|
|
3057
3154
|
if (state.error) {
|
|
3058
3155
|
return (_jsx(Box, { width: columns, height: rows, flexDirection: "column", children: _jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, flexDirection: "column", children: [_jsxs(Text, { color: "red", bold: true, children: ["Error: ", state.error] }), _jsx(Text, { dimColor: true, children: "Press R to retry or q to quit" })] }) }));
|
|
@@ -3065,6 +3162,11 @@ export default function Dashboard() {
|
|
|
3065
3162
|
: state.flatIssues[state.selectedIndex]) ?? null;
|
|
3066
3163
|
const selectedReview = state.flatReviews[state.reviewSelectedIndex] ?? null;
|
|
3067
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;
|
|
3068
3170
|
// Comments for the selected triage issue (lazily loaded; undefined = still
|
|
3069
3171
|
// fetching, which the detail panel renders as "loading…").
|
|
3070
3172
|
const triageComments = state.activeTab === "triage" && selectedIssue
|
|
@@ -3104,12 +3206,17 @@ export default function Dashboard() {
|
|
|
3104
3206
|
const sel = i === state.trackerSelectIndex;
|
|
3105
3207
|
return (_jsxs(Text, { color: sel ? "cyan" : undefined, bold: sel, children: [sel ? "> " : " ", org.name, " (", org.slug, ")"] }, org.slug));
|
|
3106
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: " " }), [
|
|
3107
|
-
{
|
|
3108
|
-
|
|
3109
|
-
|
|
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" },
|
|
3110
3216
|
].map((t, i) => {
|
|
3111
3217
|
const sel = i === state.trackerSelectIndex;
|
|
3112
|
-
|
|
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));
|
|
3113
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"
|
|
3114
3221
|
? "title"
|
|
3115
3222
|
: state.issueFormPhase === "description"
|
|
@@ -3141,7 +3248,7 @@ export default function Dashboard() {
|
|
|
3141
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 ===
|
|
3142
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"
|
|
3143
3250
|
? state.treeListScrollOffset
|
|
3144
|
-
: 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
|
|
3145
3252
|
.split("\n")
|
|
3146
3253
|
.slice(-(contentHeight - 1))
|
|
3147
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 ? (
|
|
@@ -3152,7 +3259,7 @@ export default function Dashboard() {
|
|
|
3152
3259
|
? state.treeDetailScrollOffset
|
|
3153
3260
|
: state.activeTab === "triage"
|
|
3154
3261
|
? state.triageDetailScrollOffset
|
|
3155
|
-
: state.detailScrollOffset, height: contentHeight, width: rightWidth, creatingForTicket: state.creatingForTicket, creationLogs: state.creationLogs, deleteStatus: selectedDeleteStatus, 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 }) })] })) })] })] }));
|
|
3156
3263
|
}
|
|
3157
3264
|
/**
|
|
3158
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;
|