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.
Files changed (114) hide show
  1. package/README.md +2 -3
  2. package/dist/commands/config.d.ts +12 -0
  3. package/dist/commands/config.js +444 -0
  4. package/dist/commands/dashboard.js +187 -80
  5. package/dist/commands/helpers/statusline.js +0 -30
  6. package/dist/commands/issue/index.d.ts +1 -1
  7. package/dist/commands/issue/index.js +1 -1
  8. package/dist/commands/pr/context.d.ts +7 -0
  9. package/dist/commands/pr/context.js +36 -0
  10. package/dist/commands/pr/fix.d.ts +11 -2
  11. package/dist/commands/pr/fix.js +64 -12
  12. package/dist/commands/worktree/create.js +17 -11
  13. package/dist/commands/worktree/open.js +3 -2
  14. package/dist/commands/worktree/switch.js +3 -11
  15. package/dist/lib/ai.d.ts +11 -0
  16. package/dist/lib/ai.js +71 -2
  17. package/dist/lib/cd-hint.d.ts +18 -0
  18. package/dist/lib/cd-hint.js +27 -0
  19. package/dist/lib/claude-config.d.ts +26 -0
  20. package/dist/lib/claude-config.js +121 -0
  21. package/dist/lib/config/TrackerPicker.d.ts +10 -0
  22. package/dist/lib/config/TrackerPicker.js +113 -0
  23. package/dist/lib/config/diagnostics.d.ts +37 -0
  24. package/dist/lib/config/diagnostics.js +190 -0
  25. package/dist/lib/config-store.d.ts +23 -0
  26. package/dist/lib/config-store.js +68 -0
  27. package/dist/lib/dashboard/DetailPanel.d.ts +7 -2
  28. package/dist/lib/dashboard/DetailPanel.js +54 -37
  29. package/dist/lib/dashboard/IssueList.d.ts +5 -1
  30. package/dist/lib/dashboard/IssueList.js +24 -5
  31. package/dist/lib/dashboard/MultilineTextArea.js +1 -1
  32. package/dist/lib/dashboard/Overlays.js +3 -4
  33. package/dist/lib/dashboard/ReviewDetailPanel.js +11 -2
  34. package/dist/lib/dashboard/ReviewList.js +6 -3
  35. package/dist/lib/dashboard/data.js +7 -34
  36. package/dist/lib/dashboard/external-editor.d.ts +1 -1
  37. package/dist/lib/dashboard/external-editor.js +3 -2
  38. package/dist/lib/dashboard/types.d.ts +19 -2
  39. package/dist/lib/dashboard/types.js +68 -2
  40. package/dist/lib/exec.d.ts +13 -0
  41. package/dist/lib/exec.js +11 -1
  42. package/dist/lib/fix-loop.d.ts +45 -0
  43. package/dist/lib/fix-loop.js +83 -0
  44. package/dist/lib/git.d.ts +3 -29
  45. package/dist/lib/git.js +6 -62
  46. package/dist/lib/github.d.ts +70 -0
  47. package/dist/lib/github.js +128 -0
  48. package/dist/lib/multiplexer/cmux.js +143 -17
  49. package/dist/lib/multiplexer/none.js +3 -3
  50. package/dist/lib/multiplexer/tmux.js +5 -29
  51. package/dist/lib/multiplexer/types.d.ts +34 -1
  52. package/dist/lib/open-url.d.ts +3 -2
  53. package/dist/lib/open-url.js +5 -4
  54. package/dist/lib/prompts.d.ts +35 -1
  55. package/dist/lib/prompts.js +7 -0
  56. package/dist/lib/setup/apply.d.ts +16 -0
  57. package/dist/lib/setup/apply.js +35 -0
  58. package/dist/lib/setup/gitignore.d.ts +24 -0
  59. package/dist/lib/setup/gitignore.js +95 -0
  60. package/dist/lib/setup/steps.d.ts +39 -0
  61. package/dist/lib/setup/steps.js +333 -0
  62. package/dist/lib/setup/tools.d.ts +23 -0
  63. package/dist/lib/setup/tools.js +47 -0
  64. package/dist/lib/squirrel-loader.d.ts +3 -1
  65. package/dist/lib/squirrel-loader.js +2 -2
  66. package/dist/lib/trackers/github/index.js +2 -2
  67. package/dist/lib/trackers/local/store.js +1 -1
  68. package/package.json +1 -2
  69. package/prompts/fix-context.njk +79 -0
  70. package/prompts/fix-loop.njk +18 -0
  71. package/prompts/pr.njk +1 -1
  72. package/dist/commands/doctor.d.ts +0 -2
  73. package/dist/commands/doctor.js +0 -618
  74. package/dist/commands/github/auth.d.ts +0 -2
  75. package/dist/commands/github/auth.js +0 -56
  76. package/dist/commands/github/index.d.ts +0 -1
  77. package/dist/commands/github/index.js +0 -1
  78. package/dist/commands/helpers/english-tutor/index.d.ts +0 -1
  79. package/dist/commands/helpers/english-tutor/index.js +0 -1
  80. package/dist/commands/helpers/english-tutor/install.d.ts +0 -8
  81. package/dist/commands/helpers/english-tutor/install.js +0 -24
  82. package/dist/commands/helpers/english-tutor/prompt.d.ts +0 -2
  83. package/dist/commands/helpers/english-tutor/prompt.js +0 -16
  84. package/dist/commands/helpers/english-tutor/session-start.d.ts +0 -2
  85. package/dist/commands/helpers/english-tutor/session-start.js +0 -34
  86. package/dist/commands/helpers/english-tutor/uninstall.d.ts +0 -2
  87. package/dist/commands/helpers/english-tutor/uninstall.js +0 -15
  88. package/dist/commands/helpers/session-signal/end.d.ts +0 -2
  89. package/dist/commands/helpers/session-signal/end.js +0 -13
  90. package/dist/commands/helpers/session-signal/index.d.ts +0 -1
  91. package/dist/commands/helpers/session-signal/index.js +0 -1
  92. package/dist/commands/helpers/session-signal/install.d.ts +0 -8
  93. package/dist/commands/helpers/session-signal/install.js +0 -24
  94. package/dist/commands/helpers/session-signal/notification.d.ts +0 -2
  95. package/dist/commands/helpers/session-signal/notification.js +0 -13
  96. package/dist/commands/helpers/session-signal/prompt.d.ts +0 -2
  97. package/dist/commands/helpers/session-signal/prompt.js +0 -13
  98. package/dist/commands/helpers/session-signal/stop.d.ts +0 -2
  99. package/dist/commands/helpers/session-signal/stop.js +0 -13
  100. package/dist/commands/helpers/shell-init.d.ts +0 -11
  101. package/dist/commands/helpers/shell-init.js +0 -122
  102. package/dist/commands/issue/setup.d.ts +0 -2
  103. package/dist/commands/issue/setup.js +0 -108
  104. package/dist/commands/issue/switch.d.ts +0 -12
  105. package/dist/commands/issue/switch.js +0 -38
  106. package/dist/commands/linear/switch.d.ts +0 -2
  107. package/dist/commands/linear/switch.js +0 -75
  108. package/dist/lib/english-tutor.d.ts +0 -13
  109. package/dist/lib/english-tutor.js +0 -125
  110. package/dist/lib/session-signal.d.ts +0 -16
  111. package/dist/lib/session-signal.js +0 -104
  112. package/prompts/english-tutor-prompt.njk +0 -15
  113. package/shell/init.bash.njk +0 -129
  114. package/shell/init.zsh.njk +0 -204
@@ -0,0 +1,47 @@
1
+ import * as os from "os";
2
+ import { run } from "../exec.js";
3
+ /**
4
+ * Tool detection + installation for `santree config`.
5
+ *
6
+ * macOS (Homebrew) is the only auto-install path today. The `PlatformInstaller`
7
+ * seam keeps the door open for apt/dnf/etc. without touching call sites — add a
8
+ * resolver branch in `getInstaller()` and the steps keep working.
9
+ */
10
+ export function isMacOS() {
11
+ return os.platform() === "darwin";
12
+ }
13
+ /** Resolve a command on PATH, or null if absent. */
14
+ export function which(cmd) {
15
+ return run(`which ${cmd}`);
16
+ }
17
+ /** Known unified-diff pagers, best first. The first one on PATH wins. */
18
+ const DIFF_PAGERS = ["delta", "diff-so-fancy", "diff-highlight", "ydiff", "riff"];
19
+ export function detectDiffPagers() {
20
+ const found = [];
21
+ for (const cmd of DIFF_PAGERS) {
22
+ const p = which(cmd);
23
+ if (p)
24
+ found.push({ command: cmd, path: p });
25
+ }
26
+ return found;
27
+ }
28
+ /** Editors we know how to launch, in rough order of popularity. */
29
+ const KNOWN_EDITORS = ["code", "cursor", "zed", "subl", "windsurf", "nvim", "vim", "nano", "emacs"];
30
+ export function detectEditors() {
31
+ const found = [];
32
+ for (const cmd of KNOWN_EDITORS) {
33
+ const p = which(cmd);
34
+ if (p)
35
+ found.push({ command: cmd, path: p });
36
+ }
37
+ return found;
38
+ }
39
+ export function getInstaller() {
40
+ if (isMacOS() && which("brew")) {
41
+ return {
42
+ name: "Homebrew",
43
+ installArgv: (pkg) => ["brew", "install", pkg],
44
+ };
45
+ }
46
+ return null;
47
+ }
@@ -1,9 +1,11 @@
1
1
  interface Props {
2
2
  text?: string;
3
+ /** Optional version line (e.g. santree's running version), shown under `text`. */
4
+ version?: string;
3
5
  }
4
6
  /** Animated 3D squirrel for loading states. SDF ray-marched at module
5
7
  * load with surface-normal lighting, soft shadows, AO, and a rainbow
6
8
  * Y-gradient. Body spin + tail wag + breathing/sniff/ear-flick layers
7
9
  * combine over independent phases. */
8
- export default function SquirrelLoader({ text }: Props): import("react/jsx-runtime").JSX.Element;
10
+ export default function SquirrelLoader({ text, version }: Props): import("react/jsx-runtime").JSX.Element;
9
11
  export {};
@@ -292,7 +292,7 @@ function colorForCell(y, lit) {
292
292
  * load with surface-normal lighting, soft shadows, AO, and a rainbow
293
293
  * Y-gradient. Body spin + tail wag + breathing/sniff/ear-flick layers
294
294
  * combine over independent phases. */
295
- export default function SquirrelLoader({ text }) {
295
+ export default function SquirrelLoader({ text, version }) {
296
296
  const [frame, setFrame] = useState(0);
297
297
  useEffect(() => {
298
298
  const id = setInterval(() => setFrame((f) => (f + 1) % FRAMES.length), FRAME_MS);
@@ -318,5 +318,5 @@ export default function SquirrelLoader({ text }) {
318
318
  return spans;
319
319
  });
320
320
  }, [frame]);
321
- return (_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [rows.map((spans, i) => (_jsx(Box, { children: _jsx(Text, { children: spans.map((s, j) => (_jsx(Text, { color: s.color, children: s.text }, j))) }) }, i))), text && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: text }) }))] }));
321
+ return (_jsxs(Box, { flexDirection: "column", alignItems: "center", children: [rows.map((spans, i) => (_jsx(Box, { children: _jsx(Text, { children: spans.map((s, j) => (_jsx(Text, { color: s.color, children: s.text }, j))) }) }, i))), text && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: text }) })), version && (_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: ["v", version] }) }))] }));
322
322
  }
@@ -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: "Run: santree github auth (or: gh auth login)" };
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: "Run: santree github auth (or: gh auth login)",
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/session-states),
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "santree",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "Git worktree manager",
5
5
  "license": "MIT",
6
6
  "author": "Santiago Toscanini",
@@ -40,7 +40,6 @@
40
40
  },
41
41
  "files": [
42
42
  "dist",
43
- "shell",
44
43
  "prompts"
45
44
  ],
46
45
  "dependencies": {
@@ -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 %}
@@ -1,2 +0,0 @@
1
- export declare const description = "Check system requirements and integrations";
2
- export default function Doctor(): import("react/jsx-runtime").JSX.Element;