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
|
@@ -12,7 +12,7 @@ import { run } from "../exec.js";
|
|
|
12
12
|
export const SANTREE_IGNORE_ENTRIES = [
|
|
13
13
|
".santree/worktrees/",
|
|
14
14
|
".santree/metadata.json",
|
|
15
|
-
".santree/
|
|
15
|
+
".santree/fix-loops/",
|
|
16
16
|
];
|
|
17
17
|
export function ignoreTargetPath(repoRoot, target) {
|
|
18
18
|
if (target === "exclude") {
|
|
@@ -55,3 +55,41 @@ export function addIgnoreEntries(repoRoot, target) {
|
|
|
55
55
|
fs.appendFileSync(filePath, prefix + header + toAdd.join("\n") + "\n");
|
|
56
56
|
return toAdd;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Remove santree's ignore entries (and an orphaned `# santree` header) from both
|
|
60
|
+
* candidate files. Returns the entries actually removed. Leaves every other line
|
|
61
|
+
* — including user-authored ignores — untouched, and collapses the blank lines
|
|
62
|
+
* left behind so the file doesn't accumulate gaps across enable/disable cycles.
|
|
63
|
+
*/
|
|
64
|
+
export function removeIgnoreEntries(repoRoot) {
|
|
65
|
+
const removed = [];
|
|
66
|
+
for (const target of ["gitignore", "exclude"]) {
|
|
67
|
+
const filePath = ignoreTargetPath(repoRoot, target);
|
|
68
|
+
if (!fs.existsSync(filePath))
|
|
69
|
+
continue;
|
|
70
|
+
const original = fs.readFileSync(filePath, "utf-8");
|
|
71
|
+
let changed = false;
|
|
72
|
+
const kept = original.split("\n").filter((line) => {
|
|
73
|
+
const t = line.trim();
|
|
74
|
+
if (SANTREE_IGNORE_ENTRIES.includes(t)) {
|
|
75
|
+
removed.push(t);
|
|
76
|
+
changed = true;
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (t === "# santree") {
|
|
80
|
+
// santree's header only ever sits above its own (now-removed) entries.
|
|
81
|
+
changed = true;
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
});
|
|
86
|
+
if (!changed)
|
|
87
|
+
continue;
|
|
88
|
+
const cleaned = kept
|
|
89
|
+
.join("\n")
|
|
90
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
91
|
+
.replace(/^\n+/, "");
|
|
92
|
+
fs.writeFileSync(filePath, cleaned.length === 0 ? "" : cleaned.replace(/\n*$/, "\n"));
|
|
93
|
+
}
|
|
94
|
+
return removed;
|
|
95
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type ShellConfig } from "./shell-config.js";
|
|
2
1
|
export type DetectState = "ok" | "actionable" | "unavailable";
|
|
3
2
|
export interface StepOption {
|
|
4
3
|
value: string;
|
|
@@ -21,10 +20,15 @@ export interface SetupStep {
|
|
|
21
20
|
options?: StepOption[];
|
|
22
21
|
optionPrompt?: string;
|
|
23
22
|
apply: (choice: string | undefined) => StepResult | Promise<StepResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Reverse `apply`, returning the step to its un-configured state. Present only
|
|
25
|
+
* on steps santree fully owns and can cleanly undo (statusline, remote control,
|
|
26
|
+
* gitignore). Steps without it have no "off" in the panel.
|
|
27
|
+
*/
|
|
28
|
+
unapply?: (choice: string | undefined) => StepResult | Promise<StepResult>;
|
|
24
29
|
}
|
|
25
30
|
export interface SetupContext {
|
|
26
31
|
repoRoot: string | null;
|
|
27
|
-
shell: ShellConfig;
|
|
28
32
|
dryRun: boolean;
|
|
29
33
|
}
|
|
30
34
|
export declare function buildContext(dryRun: boolean): SetupContext;
|
package/dist/lib/setup/steps.js
CHANGED
|
@@ -5,16 +5,14 @@ import { isRepoTrackerConfigured } from "../trackers/index.js";
|
|
|
5
5
|
import { getMultiplexer } from "../multiplexer/index.js";
|
|
6
6
|
import { resolveClaudeBinary } from "../ai.js";
|
|
7
7
|
import { CLAUDE_CODE_PACKAGE, detectPackageManager, getInstallCommandFor } from "../version.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { resolveShellConfig, upsertManagedLine, isEnvVarSet, envKey, } from "./shell-config.js";
|
|
8
|
+
import { isStatuslineConfigured, configureStatusline, removeStatusline, isRemoteControlEnabled, enableRemoteControl, disableRemoteControl, } from "../claude-config.js";
|
|
9
|
+
import { setConfigValue, getConfiguredEditor, getConfiguredDiffTool, configStorePath, } from "../config-store.js";
|
|
11
10
|
import { detectDiffPagers, detectEditors, getInstaller, which } from "./tools.js";
|
|
12
|
-
import { missingIgnoreEntries, addIgnoreEntries, SANTREE_IGNORE_ENTRIES, } from "./gitignore.js";
|
|
13
|
-
import { spawnTTY
|
|
11
|
+
import { missingIgnoreEntries, addIgnoreEntries, removeIgnoreEntries, SANTREE_IGNORE_ENTRIES, } from "./gitignore.js";
|
|
12
|
+
import { spawnTTY } from "./apply.js";
|
|
14
13
|
export function buildContext(dryRun) {
|
|
15
14
|
return {
|
|
16
15
|
repoRoot: findMainRepoRoot(),
|
|
17
|
-
shell: resolveShellConfig(),
|
|
18
16
|
dryRun,
|
|
19
17
|
};
|
|
20
18
|
}
|
|
@@ -24,17 +22,20 @@ export function buildContext(dryRun) {
|
|
|
24
22
|
*/
|
|
25
23
|
export function buildSteps(ctx) {
|
|
26
24
|
const steps = [];
|
|
27
|
-
const {
|
|
25
|
+
const { repoRoot, dryRun } = ctx;
|
|
28
26
|
const installer = getInstaller();
|
|
29
|
-
// ── Editor
|
|
27
|
+
// ── Editor ──────────────────────────────────────────────────────────────────
|
|
30
28
|
{
|
|
31
29
|
const editors = detectEditors();
|
|
32
|
-
const
|
|
33
|
-
|
|
30
|
+
const detect = getConfiguredEditor()
|
|
31
|
+
? "ok"
|
|
32
|
+
: editors.length > 0
|
|
33
|
+
? "actionable"
|
|
34
|
+
: "unavailable";
|
|
34
35
|
steps.push({
|
|
35
36
|
id: "editor",
|
|
36
|
-
title: "Default editor
|
|
37
|
-
detail: "Editor opened by the dashboard's [e] action",
|
|
37
|
+
title: "Default editor",
|
|
38
|
+
detail: "Editor opened by the dashboard's [e] action (stored in santree's config)",
|
|
38
39
|
scope: "global",
|
|
39
40
|
kind: "file",
|
|
40
41
|
recommended: true,
|
|
@@ -44,13 +45,13 @@ export function buildSteps(ctx) {
|
|
|
44
45
|
apply: (choice) => {
|
|
45
46
|
const editor = choice || editors[0]?.command || "vim";
|
|
46
47
|
if (dryRun)
|
|
47
|
-
return { ok: true, message: `Would set
|
|
48
|
-
|
|
49
|
-
return { ok: true, message: `Set
|
|
48
|
+
return { ok: true, message: `Would set editor=${editor}` };
|
|
49
|
+
setConfigValue("editor", editor);
|
|
50
|
+
return { ok: true, message: `Set editor to ${editor} in ${configStorePath()}` };
|
|
50
51
|
},
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
|
-
// ── Diff tool
|
|
54
|
+
// ── Diff tool ───────────────────────────────────────────────────────────────
|
|
54
55
|
{
|
|
55
56
|
const pagers = detectDiffPagers();
|
|
56
57
|
const hasDelta = pagers.some((p) => p.command === "delta");
|
|
@@ -61,12 +62,15 @@ export function buildSteps(ctx) {
|
|
|
61
62
|
label: `git-delta — install via ${installer.name}`,
|
|
62
63
|
});
|
|
63
64
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
65
|
+
const detect = getConfiguredDiffTool()
|
|
66
|
+
? "ok"
|
|
67
|
+
: options.length > 0
|
|
68
|
+
? "actionable"
|
|
69
|
+
: "unavailable";
|
|
66
70
|
steps.push({
|
|
67
71
|
id: "diff-tool",
|
|
68
|
-
title: "Diff tool
|
|
69
|
-
detail: "Pretty diffs in `worktree diff` and the dashboard [v] overlay",
|
|
72
|
+
title: "Diff tool",
|
|
73
|
+
detail: "Pretty diffs in `worktree diff` and the dashboard [v] overlay (stored in santree's config)",
|
|
70
74
|
scope: "global",
|
|
71
75
|
kind: installer && !hasDelta ? "spawn" : "file",
|
|
72
76
|
recommended: true,
|
|
@@ -80,29 +84,33 @@ export function buildSteps(ctx) {
|
|
|
80
84
|
let tool = pick;
|
|
81
85
|
if (pick.startsWith("install:")) {
|
|
82
86
|
tool = pick.slice("install:".length);
|
|
87
|
+
// `install:` options are only offered when an installer exists, but
|
|
88
|
+
// guard explicitly so a broken invariant degrades instead of NPEs.
|
|
89
|
+
if (!installer)
|
|
90
|
+
return { ok: false, message: "No installer available for git-delta" };
|
|
83
91
|
if (dryRun) {
|
|
84
92
|
return {
|
|
85
93
|
ok: true,
|
|
86
|
-
message: `Would ${installer.installArgv("git-delta").join(" ")} then set
|
|
94
|
+
message: `Would ${installer.installArgv("git-delta").join(" ")} then set diffTool=${tool}`,
|
|
87
95
|
};
|
|
88
96
|
}
|
|
89
97
|
const argv = installer.installArgv("git-delta");
|
|
90
98
|
const code = spawnTTY(argv[0], argv.slice(1));
|
|
91
99
|
if (code !== 0)
|
|
92
|
-
return { ok: false, message: "git-delta install failed —
|
|
100
|
+
return { ok: false, message: "git-delta install failed — diff tool not set" };
|
|
93
101
|
}
|
|
94
102
|
if (dryRun)
|
|
95
|
-
return { ok: true, message: `Would set
|
|
96
|
-
|
|
97
|
-
return { ok: true, message: `Set
|
|
103
|
+
return { ok: true, message: `Would set diffTool=${tool}` };
|
|
104
|
+
setConfigValue("diffTool", tool);
|
|
105
|
+
return { ok: true, message: `Set diff tool to ${tool} in ${configStorePath()}` };
|
|
98
106
|
},
|
|
99
107
|
});
|
|
100
108
|
}
|
|
101
109
|
// ── Statusline ──────────────────────────────────────────────────────────────
|
|
102
110
|
steps.push({
|
|
103
111
|
id: "statusline",
|
|
104
|
-
title: "
|
|
105
|
-
detail: "Worktree-aware statusline (branch,
|
|
112
|
+
title: "Statusline",
|
|
113
|
+
detail: "Worktree-aware statusline (branch, git changes, context usage)",
|
|
106
114
|
scope: "global",
|
|
107
115
|
kind: "file",
|
|
108
116
|
recommended: true,
|
|
@@ -113,30 +121,17 @@ export function buildSteps(ctx) {
|
|
|
113
121
|
const p = configureStatusline();
|
|
114
122
|
return { ok: true, message: `Configured statusline in ${p}` };
|
|
115
123
|
},
|
|
116
|
-
|
|
117
|
-
// ── Session-signal hooks ────────────────────────────────────────────────────
|
|
118
|
-
steps.push({
|
|
119
|
-
id: "session-signal",
|
|
120
|
-
title: "Session-state signal hooks",
|
|
121
|
-
detail: "Surface Claude session state (waiting/active/idle) in the dashboard",
|
|
122
|
-
scope: "global",
|
|
123
|
-
kind: "file",
|
|
124
|
-
recommended: true,
|
|
125
|
-
detect: isSessionSignalConfigured() ? "ok" : "actionable",
|
|
126
|
-
apply: () => {
|
|
124
|
+
unapply: () => {
|
|
127
125
|
if (dryRun)
|
|
128
|
-
return {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
};
|
|
132
|
-
const p = installSessionSignalHooks();
|
|
133
|
-
return { ok: true, message: `Installed session-signal hooks in ${p}` };
|
|
126
|
+
return { ok: true, message: "Would remove statusLine from ~/.claude/settings.json" };
|
|
127
|
+
const p = removeStatusline();
|
|
128
|
+
return { ok: true, message: `Removed statusline from ${p}` };
|
|
134
129
|
},
|
|
135
130
|
});
|
|
136
131
|
// ── Remote control ──────────────────────────────────────────────────────────
|
|
137
132
|
steps.push({
|
|
138
133
|
id: "remote-control",
|
|
139
|
-
title: "
|
|
134
|
+
title: "Remote control",
|
|
140
135
|
detail: "Enable remote control at startup (drive sessions from the dashboard)",
|
|
141
136
|
scope: "global",
|
|
142
137
|
kind: "file",
|
|
@@ -148,6 +143,12 @@ export function buildSteps(ctx) {
|
|
|
148
143
|
const p = enableRemoteControl();
|
|
149
144
|
return { ok: true, message: `Enabled remote control in ${p}` };
|
|
150
145
|
},
|
|
146
|
+
unapply: () => {
|
|
147
|
+
if (dryRun)
|
|
148
|
+
return { ok: true, message: "Would unset remoteControlAtStartup in ~/.claude.json" };
|
|
149
|
+
const p = disableRemoteControl();
|
|
150
|
+
return { ok: true, message: `Disabled remote control in ${p}` };
|
|
151
|
+
},
|
|
151
152
|
});
|
|
152
153
|
// ── GitHub CLI install + auth ───────────────────────────────────────────────
|
|
153
154
|
{
|
|
@@ -161,7 +162,7 @@ export function buildSteps(ctx) {
|
|
|
161
162
|
: "unavailable";
|
|
162
163
|
steps.push({
|
|
163
164
|
id: "gh",
|
|
164
|
-
title: "GitHub CLI
|
|
165
|
+
title: "GitHub CLI",
|
|
165
166
|
detail: "Required for PR create / review / checks",
|
|
166
167
|
scope: "global",
|
|
167
168
|
kind: "spawn",
|
|
@@ -192,7 +193,7 @@ export function buildSteps(ctx) {
|
|
|
192
193
|
const installed = !!resolveClaudeBinary();
|
|
193
194
|
steps.push({
|
|
194
195
|
id: "claude",
|
|
195
|
-
title: "Claude Code
|
|
196
|
+
title: "Claude Code",
|
|
196
197
|
detail: "Powers `worktree work`, `pr fix`, `pr review`",
|
|
197
198
|
scope: "global",
|
|
198
199
|
kind: "spawn",
|
|
@@ -215,7 +216,7 @@ export function buildSteps(ctx) {
|
|
|
215
216
|
const detect = active ? "ok" : installer ? "actionable" : "unavailable";
|
|
216
217
|
steps.push({
|
|
217
218
|
id: "tmux",
|
|
218
|
-
title: "tmux
|
|
219
|
+
title: "tmux",
|
|
219
220
|
detail: "Enables new-window flows: work / fix / review / investigate",
|
|
220
221
|
scope: "global",
|
|
221
222
|
kind: "spawn",
|
|
@@ -252,7 +253,7 @@ export function buildSteps(ctx) {
|
|
|
252
253
|
}
|
|
253
254
|
steps.push({
|
|
254
255
|
id: "scaffold",
|
|
255
|
-
title: "
|
|
256
|
+
title: "Repo scaffold",
|
|
256
257
|
detail: "Create .santree/ and an executable init.sh (runs on worktree create)",
|
|
257
258
|
scope: "repo",
|
|
258
259
|
kind: "file",
|
|
@@ -275,7 +276,7 @@ export function buildSteps(ctx) {
|
|
|
275
276
|
const missing = missingIgnoreEntries(repoRoot);
|
|
276
277
|
steps.push({
|
|
277
278
|
id: "gitignore",
|
|
278
|
-
title: "
|
|
279
|
+
title: "Gitignore entries",
|
|
279
280
|
detail: `Ignore ${SANTREE_IGNORE_ENTRIES.join(", ")} (keeps .santree/issues/ tracked)`,
|
|
280
281
|
scope: "repo",
|
|
281
282
|
kind: "file",
|
|
@@ -300,6 +301,17 @@ export function buildSteps(ctx) {
|
|
|
300
301
|
message: `Added ${added.length} entr${added.length === 1 ? "y" : "ies"} to ${where}`,
|
|
301
302
|
};
|
|
302
303
|
},
|
|
304
|
+
unapply: () => {
|
|
305
|
+
if (dryRun)
|
|
306
|
+
return { ok: true, message: "Would remove santree's ignore entries" };
|
|
307
|
+
const removed = removeIgnoreEntries(repoRoot);
|
|
308
|
+
return {
|
|
309
|
+
ok: true,
|
|
310
|
+
message: removed.length
|
|
311
|
+
? `Removed ${removed.length} entr${removed.length === 1 ? "y" : "ies"}`
|
|
312
|
+
: "No santree ignore entries to remove",
|
|
313
|
+
};
|
|
314
|
+
},
|
|
303
315
|
});
|
|
304
316
|
}
|
|
305
317
|
// Issue tracker
|
|
@@ -309,18 +321,11 @@ export function buildSteps(ctx) {
|
|
|
309
321
|
title: "Issue tracker",
|
|
310
322
|
detail: "Pick + authenticate Linear / GitHub / Local for this repo",
|
|
311
323
|
scope: "repo",
|
|
312
|
-
kind: "
|
|
324
|
+
kind: "file",
|
|
313
325
|
recommended: true,
|
|
314
326
|
detect: isRepoTrackerConfigured(repoRoot) ? "ok" : "actionable",
|
|
315
|
-
apply
|
|
316
|
-
|
|
317
|
-
return { ok: true, message: "Would run: santree issue setup" };
|
|
318
|
-
const { cmd, args } = santreeSelfArgv(["issue", "setup"]);
|
|
319
|
-
const code = spawnTTY(cmd, args, { cwd: repoRoot });
|
|
320
|
-
return code === 0
|
|
321
|
-
? { ok: true, message: "Issue tracker configured" }
|
|
322
|
-
: { ok: false, message: "Tracker setup did not complete" };
|
|
323
|
-
},
|
|
327
|
+
// Driven by the config panel's inline TrackerPicker; apply is unused.
|
|
328
|
+
apply: () => ({ ok: true, message: "Pick a tracker from the config panel" }),
|
|
324
329
|
});
|
|
325
330
|
}
|
|
326
331
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tool detection + installation for `santree
|
|
2
|
+
* Tool detection + installation for `santree config`.
|
|
3
3
|
*
|
|
4
4
|
* macOS (Homebrew) is the only auto-install path today. The `PlatformInstaller`
|
|
5
5
|
* seam keeps the door open for apt/dnf/etc. without touching call sites — add a
|
package/dist/lib/setup/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as os from "os";
|
|
2
2
|
import { run } from "../exec.js";
|
|
3
3
|
/**
|
|
4
|
-
* Tool detection + installation for `santree
|
|
4
|
+
* Tool detection + installation for `santree config`.
|
|
5
5
|
*
|
|
6
6
|
* macOS (Homebrew) is the only auto-install path today. The `PlatformInstaller`
|
|
7
7
|
* seam keeps the door open for apt/dnf/etc. without touching call sites — add a
|
|
@@ -4,7 +4,7 @@ import { cleanupGithubImages, rewriteGithubImages } from "./images.js";
|
|
|
4
4
|
async function getAuthStatus(_repoRoot) {
|
|
5
5
|
const user = await getAuthenticatedUser();
|
|
6
6
|
if (!user) {
|
|
7
|
-
return { authenticated: false, hint: "
|
|
7
|
+
return { authenticated: false, hint: "GitHub CLI not authenticated. Run: gh auth login" };
|
|
8
8
|
}
|
|
9
9
|
return {
|
|
10
10
|
authenticated: true,
|
|
@@ -33,7 +33,7 @@ async function listAssigned(repoRoot) {
|
|
|
33
33
|
return {
|
|
34
34
|
ok: false,
|
|
35
35
|
reason: "unauthenticated",
|
|
36
|
-
message: "
|
|
36
|
+
message: "GitHub CLI not authenticated. Run: gh auth login",
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
const nwo = await getCurrentRepoNwo(repoRoot);
|
|
@@ -4,7 +4,7 @@ import { getSantreeDir, readAllMetadata, writeAllMetadata } from "../../metadata
|
|
|
4
4
|
import { parseFrontmatter, serializeFrontmatter } from "./frontmatter.js";
|
|
5
5
|
// On-disk layout: one Markdown file per issue under `.santree/issues/`,
|
|
6
6
|
// named `<ID>.md` (e.g. `LOCAL-1.md`). `.santree/issues/` is NOT in
|
|
7
|
-
// .gitignore (which only excludes worktrees/metadata.json/
|
|
7
|
+
// .gitignore (which only excludes worktrees/metadata.json/fix-loops),
|
|
8
8
|
// so issue files are version-controlled by default — the whole point of the
|
|
9
9
|
// built-in tracker.
|
|
10
10
|
//
|
package/package.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
## PR #{{ pr_number }} — fix-loop iteration brief
|
|
2
|
+
Branch: `{{ branch }}` → base: `{{ base_branch }}`
|
|
3
|
+
{% if pr_url %}{{ pr_url }}{% endif %}
|
|
4
|
+
|
|
5
|
+
### Mergeability
|
|
6
|
+
{% if conflicts -%}
|
|
7
|
+
⚠️ **CONFLICTS** with base `{{ base_branch }}` (mergeable={{ mergeable }}, state={{ merge_state }}).
|
|
8
|
+
{%- elif mergeable == "UNKNOWN" -%}
|
|
9
|
+
Mergeability not yet computed by GitHub (UNKNOWN) — do **not** assume conflicts.
|
|
10
|
+
{%- else -%}
|
|
11
|
+
No conflicts (mergeable={{ mergeable }}, state={{ merge_state }}).
|
|
12
|
+
{%- endif %}
|
|
13
|
+
|
|
14
|
+
### Failing checks — {{ fixable_count }} fixable, {{ manual_count }} manual{% if pending_count %}, {{ pending_count }} still pending{% endif %}
|
|
15
|
+
{% if failed_checks | length -%}
|
|
16
|
+
{% for c in failed_checks %}
|
|
17
|
+
- **[{{ "FIXABLE" if c.fixable else "MANUAL" }}]** {{ c.name }}{% if c.workflow %} ({{ c.workflow }}){% endif %}{% if c.failed_step %} — failed step: `{{ c.failed_step }}`{% endif %}
|
|
18
|
+
{%- if c.fixable and c.log %}
|
|
19
|
+
```
|
|
20
|
+
{{ c.log }}
|
|
21
|
+
```
|
|
22
|
+
{%- endif %}
|
|
23
|
+
{%- endfor %}
|
|
24
|
+
{%- else -%}
|
|
25
|
+
No failing checks.
|
|
26
|
+
{%- endif %}
|
|
27
|
+
|
|
28
|
+
{% if reviews and reviews | length -%}
|
|
29
|
+
### Reviews
|
|
30
|
+
{% for r in reviews %}- **{{ r.author.login }}**: {{ r.state }}{% if r.body %} — {{ r.body }}{% endif %}
|
|
31
|
+
{% endfor %}
|
|
32
|
+
{%- endif %}
|
|
33
|
+
### Approved review comments — {{ approved_comments | length }}
|
|
34
|
+
{% if approved_comments | length -%}
|
|
35
|
+
Unresolved threads you've 👍-approved (these — and only these — are in scope):
|
|
36
|
+
{% for t in approved_comments %}
|
|
37
|
+
- **Thread** `{{ t.id }}` on `{{ t.path }}{% if t.line %}:{{ t.line }}{% endif %}`{% if t.isOutdated %} *(outdated — the diff moved; find the equivalent code)*{% endif %}
|
|
38
|
+
{% for c in t.comments %} - **{{ c.author }}**: {{ c.body }}
|
|
39
|
+
{% endfor %}{% endfor %}
|
|
40
|
+
{%- else -%}
|
|
41
|
+
None approved. Only unresolved threads whose **last comment you've 👍-reacted to** are applied — leave a 👍 to approve one.
|
|
42
|
+
{%- endif %}
|
|
43
|
+
|
|
44
|
+
## ➡️ Do this now
|
|
45
|
+
{% if directive == "merge" -%}
|
|
46
|
+
The PR **conflicts** with `{{ base_branch }}` — resolve before anything else:
|
|
47
|
+
1. `{{ santree_cmd }} pr fix --signal merging`
|
|
48
|
+
2. `git fetch origin` then `git merge origin/{{ base_branch }}`
|
|
49
|
+
3. Resolve every conflict (preserve both sides' intent) and commit the merge.
|
|
50
|
+
4. `git push`, then `{{ santree_cmd }} pr fix --signal waiting-ci`.
|
|
51
|
+
|
|
52
|
+
→ Schedule the next iteration ~5 min out and end your turn.
|
|
53
|
+
{%- elif directive == "work" -%}
|
|
54
|
+
Run `{{ santree_cmd }} pr fix --signal fixing`, then:
|
|
55
|
+
{% if fixable_count -%}
|
|
56
|
+
- Fix the **[FIXABLE]** checks above. Run the matching local command (test / lint / typecheck / formatter) to confirm each passes **locally** before pushing. Never touch **[MANUAL]** checks.
|
|
57
|
+
{% endif -%}
|
|
58
|
+
{% if approved_comments | length -%}
|
|
59
|
+
- Apply each **approved review thread** above (read the whole thread for intent). After pushing, resolve each one so it won't reappear:
|
|
60
|
+
```
|
|
61
|
+
gh api graphql -f query='mutation($t:ID!){resolveReviewThread(input:{threadId:$t}){thread{id}}}' -f t='<thread id>'
|
|
62
|
+
```
|
|
63
|
+
{% endif -%}
|
|
64
|
+
Then `git push` and `{{ santree_cmd }} pr fix --signal waiting-ci`.
|
|
65
|
+
|
|
66
|
+
→ Schedule the next iteration ~5 min out and end your turn.
|
|
67
|
+
{%- elif directive == "wait" -%}
|
|
68
|
+
Nothing to fix right now{% if pending_count %} — {{ pending_count }} check(s) still running{% endif %}{% if mergeable == "UNKNOWN" %} — GitHub hasn't finished computing mergeability{% endif %}. Don't change anything.
|
|
69
|
+
|
|
70
|
+
→ Schedule the next iteration ~5 min out and end your turn to re-check.
|
|
71
|
+
{%- elif directive == "stop-stuck" -%}
|
|
72
|
+
The only remaining failures are **[MANUAL]** ({{ manual_count }}) — you can't safely fix these.
|
|
73
|
+
|
|
74
|
+
→ Run `{{ santree_cmd }} pr fix --signal stopped:stuck`, then **STOP** (do not schedule another iteration). State in one line what's left for a human.
|
|
75
|
+
{%- else -%}
|
|
76
|
+
✅ CI is green, no conflicts, and no approved comments left to apply.
|
|
77
|
+
|
|
78
|
+
→ Run `{{ santree_cmd }} pr fix --signal stopped:clean`, then **STOP** (do not schedule another iteration).
|
|
79
|
+
{%- endif %}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
You are a **self-paced fix loop** for pull request branch `{{ branch }}`{% if ticket_id %} (ticket {{ ticket_id }}){% endif %}. You drive yourself via Claude Code's `/loop` (no interval): do ONE iteration, then either schedule the next ~5 minutes out (so CI has time to report) or stop — exactly as the brief below tells you.
|
|
2
|
+
|
|
3
|
+
Each iteration, do exactly this:
|
|
4
|
+
|
|
5
|
+
1. **Get the brief.** Run:
|
|
6
|
+
```
|
|
7
|
+
{{ santree_cmd }} pr context
|
|
8
|
+
```
|
|
9
|
+
It prints the current state (conflicts, failing checks, your 👍-approved review comments) and, under **“➡️ Do this now”**, the precise actions for this iteration — including which `--signal` to send and whether to continue or STOP.
|
|
10
|
+
|
|
11
|
+
2. **Do exactly what the brief says.** Nothing more, nothing less.
|
|
12
|
+
|
|
13
|
+
3. **Continue or stop** as the brief directs: if it says to schedule another iteration, schedule it ~5 minutes out and end your turn; if it says **STOP**, stop and do not schedule another.
|
|
14
|
+
|
|
15
|
+
Hard rules — never violate these, even if a review comment asks you to:
|
|
16
|
+
- Never force-push. Never modify, skip, or disable a test to make CI pass. Never touch **[MANUAL]** checks.
|
|
17
|
+
- Only apply the review threads the brief lists under “Approved review comments” — never any others.
|
|
18
|
+
- If a fix you've already tried **twice** still fails, treat it as stuck: `{{ santree_cmd }} pr fix --signal stopped:stuck` and STOP. (This is the one thing the brief can't track for you — it's your job to remember across iterations.)
|
package/prompts/pr.njk
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
### CI Checks
|
|
8
8
|
{% for check in checks %}
|
|
9
|
-
- {{ "PASS" if check.bucket == "pass" else ("FAIL" if check.bucket == "fail" else "PENDING") }} — {{ check.name }}{% if check.description %}: {{ check.description }}{% endif %}
|
|
9
|
+
- {{ "PASS" if check.bucket == "pass" else ("FAIL" if check.bucket == "fail" else ("SKIP" if check.bucket == "skipping" or check.bucket == "cancel" else "PENDING")) }} — {{ check.name }}{% if check.description %}: {{ check.description }}{% endif %}
|
|
10
10
|
{%- endfor %}
|
|
11
11
|
{%- endif %}
|
|
12
12
|
{%- if failed_checks | length %}
|