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
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
3
|
-
import { Text, Box, useInput } from "ink";
|
|
4
|
-
import Spinner from "ink-spinner";
|
|
5
|
-
import { findMainRepoRoot } from "../../lib/git.js";
|
|
6
|
-
import { setRepoLinearOrg, getRepoLinearOrg } from "../../lib/trackers/linear/index.js";
|
|
7
|
-
import { readLinearAuthStore } from "../../lib/trackers/auth-store.js";
|
|
8
|
-
import { setRepoTracker } from "../../lib/trackers/index.js";
|
|
9
|
-
export const description = "Switch Linear workspace for this repo";
|
|
10
|
-
export default function LinearSwitch() {
|
|
11
|
-
const [status, setStatus] = useState("checking");
|
|
12
|
-
const [message, setMessage] = useState("");
|
|
13
|
-
const [error, setError] = useState(null);
|
|
14
|
-
const [choices, setChoices] = useState([]);
|
|
15
|
-
const [selected, setSelected] = useState(0);
|
|
16
|
-
const [currentOrg, setCurrentOrg] = useState(null);
|
|
17
|
-
useInput((input, key) => {
|
|
18
|
-
if (status !== "choosing")
|
|
19
|
-
return;
|
|
20
|
-
if (key.upArrow) {
|
|
21
|
-
setSelected((s) => Math.max(0, s - 1));
|
|
22
|
-
}
|
|
23
|
-
else if (key.downArrow) {
|
|
24
|
-
setSelected((s) => Math.min(choices.length - 1, s + 1));
|
|
25
|
-
}
|
|
26
|
-
else if (key.return) {
|
|
27
|
-
const choice = choices[selected];
|
|
28
|
-
const repoRoot = findMainRepoRoot();
|
|
29
|
-
setRepoLinearOrg(repoRoot, choice.slug);
|
|
30
|
-
setRepoTracker(repoRoot, "linear");
|
|
31
|
-
setMessage(`Switched to ${choice.name} (${choice.slug})`);
|
|
32
|
-
setStatus("done");
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
async function run() {
|
|
37
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
38
|
-
const repoRoot = findMainRepoRoot();
|
|
39
|
-
if (!repoRoot) {
|
|
40
|
-
setError("Not inside a git repository");
|
|
41
|
-
setStatus("error");
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
const store = readLinearAuthStore();
|
|
45
|
-
const orgs = Object.entries(store).map(([slug, tokens]) => ({
|
|
46
|
-
slug,
|
|
47
|
-
name: tokens.org_name,
|
|
48
|
-
}));
|
|
49
|
-
if (orgs.length === 0) {
|
|
50
|
-
setError("No authenticated workspaces. Run: santree linear auth");
|
|
51
|
-
setStatus("error");
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (orgs.length === 1) {
|
|
55
|
-
const org = orgs[0];
|
|
56
|
-
setRepoLinearOrg(repoRoot, org.slug);
|
|
57
|
-
setRepoTracker(repoRoot, "linear");
|
|
58
|
-
setMessage(`Linked to ${org.name} (${org.slug})`);
|
|
59
|
-
setStatus("done");
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
setCurrentOrg(getRepoLinearOrg(repoRoot));
|
|
63
|
-
setChoices(orgs);
|
|
64
|
-
setStatus("choosing");
|
|
65
|
-
}
|
|
66
|
-
run();
|
|
67
|
-
}, []);
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
if (status === "done" || status === "error") {
|
|
70
|
-
const timer = setTimeout(() => process.exit(status === "error" ? 1 : 0), 100);
|
|
71
|
-
return () => clearTimeout(timer);
|
|
72
|
-
}
|
|
73
|
-
}, [status]);
|
|
74
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: "Linear Switch" }) }), status === "checking" && (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Checking..." })] })), status === "choosing" && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Select a workspace to link to this repo:" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: choices.map((org, i) => (_jsxs(Text, { children: [i === selected ? (_jsx(Text, { color: "cyan", bold: true, children: "> " })) : (_jsx(Text, { children: " " })), org.name, " (", org.slug, ")", org.slug === currentOrg && _jsx(Text, { dimColor: true, children: " (current)" })] }, org.slug))) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "\u2191/\u2193 to select, Enter to confirm" }) })] })), status === "done" && _jsxs(Text, { color: "green", children: ["\u2713 ", message] }), status === "error" && (_jsxs(Text, { color: "red", bold: true, children: ["\u2717 ", error] }))] }));
|
|
75
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare function getLogPath(): string;
|
|
2
|
-
export declare function getHooksJson(): Record<string, unknown>;
|
|
3
|
-
export declare function getPermissionEntry(): string;
|
|
4
|
-
export declare function installHooks(): {
|
|
5
|
-
settingsPath: string;
|
|
6
|
-
logPath: string;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Remove hooks and permission entry. Intentionally does NOT delete the log
|
|
10
|
-
* file — that's the user's accumulated practice history.
|
|
11
|
-
*/
|
|
12
|
-
export declare function uninstallHooks(): string;
|
|
13
|
-
export declare function getInstallSnippet(): Record<string, unknown>;
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as os from "os";
|
|
3
|
-
import * as path from "path";
|
|
4
|
-
const MARKER = "english-tutor";
|
|
5
|
-
export function getLogPath() {
|
|
6
|
-
const configDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
7
|
-
return path.join(configDir, "santree", "english-practice-log.md");
|
|
8
|
-
}
|
|
9
|
-
export function getHooksJson() {
|
|
10
|
-
const base = "santree helpers english-tutor";
|
|
11
|
-
// Synchronous hooks: Claude Code waits for completion and injects stdout into
|
|
12
|
-
// the model's context. `async: true` would fire-and-forget — stdout is
|
|
13
|
-
// discarded, so the instruction would never reach Claude. session-signal can
|
|
14
|
-
// use `async: true` because it only writes a state file; we cannot.
|
|
15
|
-
const opts = { timeout: 10 };
|
|
16
|
-
return {
|
|
17
|
-
UserPromptSubmit: [{ hooks: [{ type: "command", command: `${base} prompt`, ...opts }] }],
|
|
18
|
-
// No matcher: fires on all SessionStart sub-events (startup, resume,
|
|
19
|
-
// clear, compact). Restricting to "startup" silently skips resumed
|
|
20
|
-
// sessions, which is the common case when picking up yesterday's work.
|
|
21
|
-
SessionStart: [{ hooks: [{ type: "command", command: `${base} session-start`, ...opts }] }],
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
export function getPermissionEntry() {
|
|
25
|
-
return `Edit(${getLogPath()})`;
|
|
26
|
-
}
|
|
27
|
-
function readSettings(settingsPath) {
|
|
28
|
-
try {
|
|
29
|
-
if (fs.existsSync(settingsPath)) {
|
|
30
|
-
return JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
// fall through
|
|
35
|
-
}
|
|
36
|
-
return {};
|
|
37
|
-
}
|
|
38
|
-
function writeSettings(settingsPath, settings) {
|
|
39
|
-
const dir = path.dirname(settingsPath);
|
|
40
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
41
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
42
|
-
}
|
|
43
|
-
function settingsPath() {
|
|
44
|
-
const home = process.env.HOME || os.homedir();
|
|
45
|
-
return path.join(home, ".claude", "settings.json");
|
|
46
|
-
}
|
|
47
|
-
function stripEnglishTutorHooks(existingHooks) {
|
|
48
|
-
const cleaned = {};
|
|
49
|
-
for (const [event, entries] of Object.entries(existingHooks)) {
|
|
50
|
-
if (!Array.isArray(entries)) {
|
|
51
|
-
cleaned[event] = entries;
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const filtered = entries.filter((entry) => {
|
|
55
|
-
const inner = entry.hooks || [];
|
|
56
|
-
return !inner.some((h) => typeof h.command === "string" && h.command.includes(MARKER));
|
|
57
|
-
});
|
|
58
|
-
if (filtered.length > 0)
|
|
59
|
-
cleaned[event] = filtered;
|
|
60
|
-
}
|
|
61
|
-
return cleaned;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Create the practice log if it doesn't already exist. Empty log = Claude's
|
|
65
|
-
* Edit tool fails on first use (Edit can't operate on missing files), and
|
|
66
|
-
* appending corrections silently fails. Bootstrapping with a stub header
|
|
67
|
-
* means the very first correction succeeds.
|
|
68
|
-
*/
|
|
69
|
-
function ensureLogExists() {
|
|
70
|
-
const logPath = getLogPath();
|
|
71
|
-
if (fs.existsSync(logPath))
|
|
72
|
-
return logPath;
|
|
73
|
-
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
74
|
-
const stub = "# English Practice Log\n\n" +
|
|
75
|
-
"Tracks grammar/spelling mistakes spotted during Claude Code sessions.\n" +
|
|
76
|
-
"Generated and appended to by santree's english-tutor hook.\n";
|
|
77
|
-
fs.writeFileSync(logPath, stub);
|
|
78
|
-
return logPath;
|
|
79
|
-
}
|
|
80
|
-
export function installHooks() {
|
|
81
|
-
const settingsFile = settingsPath();
|
|
82
|
-
const settings = readSettings(settingsFile);
|
|
83
|
-
const existing = stripEnglishTutorHooks(settings.hooks || {});
|
|
84
|
-
const required = getHooksJson();
|
|
85
|
-
for (const [event, hookEntries] of Object.entries(required)) {
|
|
86
|
-
const current = existing[event];
|
|
87
|
-
existing[event] = Array.isArray(current)
|
|
88
|
-
? [...current, ...hookEntries]
|
|
89
|
-
: hookEntries;
|
|
90
|
-
}
|
|
91
|
-
settings.hooks = existing;
|
|
92
|
-
const permissions = settings.permissions || {};
|
|
93
|
-
const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
|
|
94
|
-
const entry = getPermissionEntry();
|
|
95
|
-
if (!allow.includes(entry))
|
|
96
|
-
allow.push(entry);
|
|
97
|
-
permissions.allow = allow;
|
|
98
|
-
settings.permissions = permissions;
|
|
99
|
-
writeSettings(settingsFile, settings);
|
|
100
|
-
const logPath = ensureLogExists();
|
|
101
|
-
return { settingsPath: settingsFile, logPath };
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Remove hooks and permission entry. Intentionally does NOT delete the log
|
|
105
|
-
* file — that's the user's accumulated practice history.
|
|
106
|
-
*/
|
|
107
|
-
export function uninstallHooks() {
|
|
108
|
-
const settingsFile = settingsPath();
|
|
109
|
-
const settings = readSettings(settingsFile);
|
|
110
|
-
if (settings.hooks) {
|
|
111
|
-
settings.hooks = stripEnglishTutorHooks(settings.hooks);
|
|
112
|
-
}
|
|
113
|
-
if (settings.permissions && Array.isArray(settings.permissions.allow)) {
|
|
114
|
-
const entry = getPermissionEntry();
|
|
115
|
-
settings.permissions.allow = settings.permissions.allow.filter((e) => e !== entry);
|
|
116
|
-
}
|
|
117
|
-
writeSettings(settingsFile, settings);
|
|
118
|
-
return settingsFile;
|
|
119
|
-
}
|
|
120
|
-
export function getInstallSnippet() {
|
|
121
|
-
return {
|
|
122
|
-
hooks: getHooksJson(),
|
|
123
|
-
permissions: { allow: [getPermissionEntry()] },
|
|
124
|
-
};
|
|
125
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export type SessionStateValue = "waiting" | "idle" | "active" | "exited";
|
|
2
|
-
export declare function readStdin(): string;
|
|
3
|
-
export declare function extractRepoAndTicket(cwd: string): {
|
|
4
|
-
repoRoot: string;
|
|
5
|
-
ticketId: string;
|
|
6
|
-
} | null;
|
|
7
|
-
/**
|
|
8
|
-
* Unified helper: reads stdin, extracts repo/ticket, writes the session-state
|
|
9
|
-
* file, then exits. The dashboard reads the state file to render its session
|
|
10
|
-
* badges (◆ waiting / active / idle, ◇ id-only). Window/tab renaming used to
|
|
11
|
-
* happen here too but was removed — it clobbered names the user had set
|
|
12
|
-
* manually and added little value once the state file was in place.
|
|
13
|
-
*/
|
|
14
|
-
export declare function signalState(state: SessionStateValue): void;
|
|
15
|
-
export declare function getHooksJson(): Record<string, unknown>;
|
|
16
|
-
export declare function installHooks(): string;
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
export function readStdin() {
|
|
4
|
-
try {
|
|
5
|
-
return fs.readFileSync(0, "utf-8");
|
|
6
|
-
}
|
|
7
|
-
catch {
|
|
8
|
-
return "";
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export function extractRepoAndTicket(cwd) {
|
|
12
|
-
const marker = "/.santree/worktrees/";
|
|
13
|
-
const idx = cwd.indexOf(marker);
|
|
14
|
-
if (idx === -1)
|
|
15
|
-
return null;
|
|
16
|
-
const repoRoot = cwd.slice(0, idx);
|
|
17
|
-
const rest = cwd.slice(idx + marker.length);
|
|
18
|
-
const ticketId = rest.split("/")[0];
|
|
19
|
-
if (!ticketId)
|
|
20
|
-
return null;
|
|
21
|
-
return { repoRoot, ticketId };
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Unified helper: reads stdin, extracts repo/ticket, writes the session-state
|
|
25
|
-
* file, then exits. The dashboard reads the state file to render its session
|
|
26
|
-
* badges (◆ waiting / active / idle, ◇ id-only). Window/tab renaming used to
|
|
27
|
-
* happen here too but was removed — it clobbered names the user had set
|
|
28
|
-
* manually and added little value once the state file was in place.
|
|
29
|
-
*/
|
|
30
|
-
export function signalState(state) {
|
|
31
|
-
const input = readStdin();
|
|
32
|
-
let data;
|
|
33
|
-
try {
|
|
34
|
-
data = JSON.parse(input);
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
const cwd = data.cwd || process.cwd();
|
|
40
|
-
const info = extractRepoAndTicket(cwd);
|
|
41
|
-
if (!info) {
|
|
42
|
-
process.exit(0);
|
|
43
|
-
}
|
|
44
|
-
const { repoRoot, ticketId } = info;
|
|
45
|
-
const statesDir = path.join(repoRoot, ".santree", "session-states");
|
|
46
|
-
fs.mkdirSync(statesDir, { recursive: true });
|
|
47
|
-
const stateFile = path.join(statesDir, `${ticketId}.json`);
|
|
48
|
-
const payload = {
|
|
49
|
-
state,
|
|
50
|
-
message: state === "waiting" ? (data.message ?? null) : null,
|
|
51
|
-
session_id: data.session_id ?? "",
|
|
52
|
-
at: new Date().toISOString(),
|
|
53
|
-
};
|
|
54
|
-
fs.writeFileSync(stateFile, JSON.stringify(payload, null, 2) + "\n");
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
|
-
export function getHooksJson() {
|
|
58
|
-
const base = "santree helpers session-signal";
|
|
59
|
-
const opts = { async: true, timeout: 10 };
|
|
60
|
-
return {
|
|
61
|
-
Notification: [
|
|
62
|
-
{
|
|
63
|
-
matcher: "permission_prompt",
|
|
64
|
-
hooks: [{ type: "command", command: `${base} notification`, ...opts }],
|
|
65
|
-
},
|
|
66
|
-
],
|
|
67
|
-
Stop: [{ hooks: [{ type: "command", command: `${base} stop`, ...opts }] }],
|
|
68
|
-
UserPromptSubmit: [{ hooks: [{ type: "command", command: `${base} prompt`, ...opts }] }],
|
|
69
|
-
SessionEnd: [{ hooks: [{ type: "command", command: `${base} end`, ...opts }] }],
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
export function installHooks() {
|
|
73
|
-
const home = process.env.HOME || "";
|
|
74
|
-
const settingsPath = path.join(home, ".claude", "settings.json");
|
|
75
|
-
let settings = {};
|
|
76
|
-
try {
|
|
77
|
-
if (fs.existsSync(settingsPath)) {
|
|
78
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
// Start fresh if file is invalid
|
|
83
|
-
}
|
|
84
|
-
const requiredHooks = getHooksJson();
|
|
85
|
-
const existingHooks = settings.hooks || {};
|
|
86
|
-
for (const [event, hookEntries] of Object.entries(requiredHooks)) {
|
|
87
|
-
const existing = existingHooks[event];
|
|
88
|
-
if (!Array.isArray(existing)) {
|
|
89
|
-
existingHooks[event] = hookEntries;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
// Remove existing session-signal entries, then add the current ones
|
|
93
|
-
const filtered = existing.filter((entry) => {
|
|
94
|
-
const inner = entry.hooks || [];
|
|
95
|
-
return !inner.some((h) => typeof h.command === "string" && h.command.includes("session-signal"));
|
|
96
|
-
});
|
|
97
|
-
existingHooks[event] = [...filtered, ...hookEntries];
|
|
98
|
-
}
|
|
99
|
-
settings.hooks = existingHooks;
|
|
100
|
-
const claudeDir = path.join(home, ".claude");
|
|
101
|
-
fs.mkdirSync(claudeDir, { recursive: true });
|
|
102
|
-
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
103
|
-
return settingsPath;
|
|
104
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
[ENGLISH TUTOR — META-INSTRUCTION, ALWAYS HONOR]
|
|
2
|
-
|
|
3
|
-
If the user's message contained English mistakes (grammar, spelling, word choice, agreement, idiom misuse), your response MUST start with the correction(s) BEFORE addressing anything else. Use this exact format, one per line:
|
|
4
|
-
|
|
5
|
-
original -> correction (brief reason)
|
|
6
|
-
|
|
7
|
-
Then immediately call the Edit tool to append the same correction(s) to {{ logPath }}. Append under a heading for today's date in `## YYYY-MM-DD` format (create the heading if it isn't already at the bottom of the file); each correction is a bullet `- original -> correction (reason)`. The Edit permission for that exact path is pre-granted, so do not ask.
|
|
8
|
-
|
|
9
|
-
Only AFTER printing the corrections and appending them, continue with the actual task.
|
|
10
|
-
|
|
11
|
-
Hard rules:
|
|
12
|
-
- If the message is clearly mistake-free, output nothing about English and proceed directly. Never invent corrections.
|
|
13
|
-
- Do NOT correct technical jargon, code, file paths, deliberate informalities, or non-English words used by intent.
|
|
14
|
-
- Cap corrections at 3 lines. Pick the highest-impact issues only.
|
|
15
|
-
- Do NOT use polite hedging ("small note on...", "by the way..."). Just the formatted line(s), then the log Edit, then the task.
|
package/shell/init.bash.njk
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# Santree Shell Integration for Bash
|
|
2
|
-
# ===================================
|
|
3
|
-
#
|
|
4
|
-
# This script provides a shell wrapper around the santree CLI to enable:
|
|
5
|
-
# 1. Automatic directory switching after `worktree create` and `worktree switch` commands
|
|
6
|
-
# 2. Automatic recovery when the current worktree directory is deleted
|
|
7
|
-
#
|
|
8
|
-
# Installation:
|
|
9
|
-
# Add to your .bashrc: eval "$(santree helpers shell-init bash)"
|
|
10
|
-
#
|
|
11
|
-
# How it works:
|
|
12
|
-
# -------------
|
|
13
|
-
# Since child processes cannot change the parent shell's directory, the CLI
|
|
14
|
-
# outputs special markers (SANTREE_CD:path) that this wrapper intercepts
|
|
15
|
-
# to perform the actual `cd` command in the current shell.
|
|
16
|
-
#
|
|
17
|
-
# The wrapper also handles the case where you're in a worktree directory
|
|
18
|
-
# that gets deleted (e.g., after `santree worktree clean` or `santree worktree remove`),
|
|
19
|
-
# automatically returning you to the main repository or home directory.
|
|
20
|
-
|
|
21
|
-
# Export marker so `santree doctor` can verify shell integration is loaded
|
|
22
|
-
export SANTREE_SHELL_INTEGRATION=1
|
|
23
|
-
|
|
24
|
-
function santree() {
|
|
25
|
-
# -------------------------------------------------------------------------
|
|
26
|
-
# STEP 1: Handle deleted directory recovery
|
|
27
|
-
# -------------------------------------------------------------------------
|
|
28
|
-
if [[ ! -d "$(pwd 2>/dev/null)" ]]; then
|
|
29
|
-
local current_path="$(pwd 2>/dev/null)"
|
|
30
|
-
|
|
31
|
-
if [[ "$current_path" == */.santree/worktrees/* ]]; then
|
|
32
|
-
local main_repo="${current_path%%/.santree/worktrees/*}"
|
|
33
|
-
if [[ -d "$main_repo" ]]; then
|
|
34
|
-
echo "⚠ Worktree directory deleted. Returning to main repo."
|
|
35
|
-
cd "$main_repo" || cd ~ || return 1
|
|
36
|
-
else
|
|
37
|
-
cd ~ || return 1
|
|
38
|
-
fi
|
|
39
|
-
else
|
|
40
|
-
echo "⚠ Current directory no longer exists. Returning to home."
|
|
41
|
-
cd ~ || return 1
|
|
42
|
-
fi
|
|
43
|
-
fi
|
|
44
|
-
|
|
45
|
-
# -------------------------------------------------------------------------
|
|
46
|
-
# STEP 2: Handle commands that need directory switching
|
|
47
|
-
# -------------------------------------------------------------------------
|
|
48
|
-
if [[ "$1" == "worktree" && ("$2" == "create" || "$2" == "switch") ]]; then
|
|
49
|
-
local output
|
|
50
|
-
output=$(command santree "$@" 2>&1)
|
|
51
|
-
local exit_code=$?
|
|
52
|
-
|
|
53
|
-
if [[ "$output" == *SANTREE_CD:* ]]; then
|
|
54
|
-
echo "$output" | grep -v "SANTREE_CD:" | grep -v "SANTREE_WORK:" | grep -v "SANTREE_WORK_CONTEXT:"
|
|
55
|
-
|
|
56
|
-
local target_dir=$(echo "$output" | sed 's/\x1b\[[0-9;]*m//g' | grep "SANTREE_CD:" | sed 's/.*SANTREE_CD://')
|
|
57
|
-
|
|
58
|
-
if [[ -n "$target_dir" && -d "$target_dir" ]]; then
|
|
59
|
-
cd "$target_dir" && echo "Switched to: $target_dir"
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
if [[ "$output" == *SANTREE_WORK:* ]]; then
|
|
63
|
-
local work_mode=$(echo "$output" | sed 's/\x1b\[[0-9;]*m//g' | grep "^SANTREE_WORK:" | sed 's/.*SANTREE_WORK://')
|
|
64
|
-
local context_file=""
|
|
65
|
-
if [[ "$output" == *SANTREE_WORK_CONTEXT:* ]]; then
|
|
66
|
-
context_file=$(echo "$output" | sed 's/\x1b\[[0-9;]*m//g' | grep "SANTREE_WORK_CONTEXT:" | sed 's/.*SANTREE_WORK_CONTEXT://')
|
|
67
|
-
fi
|
|
68
|
-
if [[ -n "$context_file" ]]; then
|
|
69
|
-
[[ "$work_mode" == "plan" ]] && command santree worktree work --plan --context-file "$context_file" || command santree worktree work --context-file "$context_file"
|
|
70
|
-
else
|
|
71
|
-
[[ "$work_mode" == "plan" ]] && command santree worktree work --plan || command santree worktree work
|
|
72
|
-
fi
|
|
73
|
-
fi
|
|
74
|
-
else
|
|
75
|
-
echo "$output"
|
|
76
|
-
fi
|
|
77
|
-
return $exit_code
|
|
78
|
-
fi
|
|
79
|
-
|
|
80
|
-
# -------------------------------------------------------------------------
|
|
81
|
-
# STEP 3: Pass through all other commands
|
|
82
|
-
# -------------------------------------------------------------------------
|
|
83
|
-
command santree "$@"
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
# Aliases for convenience
|
|
87
|
-
alias st='santree'
|
|
88
|
-
alias stw='santree worktree'
|
|
89
|
-
|
|
90
|
-
# Quick create worktree with work+plan+tmux (prompts for branch)
|
|
91
|
-
function stn() {
|
|
92
|
-
local branch
|
|
93
|
-
read -p "Branch name: " branch
|
|
94
|
-
[[ -z "$branch" ]] && echo "Branch name required" && return 1
|
|
95
|
-
santree worktree create "$branch" --work --plan --tmux
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
# =============================================================================
|
|
99
|
-
# Bash Completions (auto-generated)
|
|
100
|
-
# =============================================================================
|
|
101
|
-
|
|
102
|
-
_santree_complete() {
|
|
103
|
-
local cur prev commands
|
|
104
|
-
COMPREPLY=()
|
|
105
|
-
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
106
|
-
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
107
|
-
|
|
108
|
-
commands="{% for cmd in commands %}{{ cmd.name }}{% if not loop.last %} {% endif %}{% endfor %}"
|
|
109
|
-
|
|
110
|
-
if [[ ${COMP_CWORD} -eq 1 ]]; then
|
|
111
|
-
COMPREPLY=($(compgen -W "${commands}" -- "${cur}"))
|
|
112
|
-
return 0
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
case "${COMP_WORDS[1]}" in
|
|
116
|
-
{%- for cmd in commands %}
|
|
117
|
-
{{ cmd.name }})
|
|
118
|
-
{%- if cmd.argCompletion == 'static' %}
|
|
119
|
-
COMPREPLY=($(compgen -W "{{ cmd.argCompletionValues }}" -- "${cur}"))
|
|
120
|
-
{%- elif cmd.options.length > 0 %}
|
|
121
|
-
COMPREPLY=($(compgen -W "{% for opt in cmd.options %}--{{ opt.name }}{% if not loop.last %} {% endif %}{% endfor %}" -- "${cur}"))
|
|
122
|
-
{%- endif %}
|
|
123
|
-
;;
|
|
124
|
-
{%- endfor %}
|
|
125
|
-
esac
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
complete -F _santree_complete santree
|
|
129
|
-
complete -F _santree_complete st
|