@selesai/code 0.9.14 → 0.10.0

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 (263) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +79 -29
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +3 -4
  6. package/dist/extensions/pi-zentui/.editorconfig +12 -0
  7. package/dist/extensions/pi-zentui/.github/CODEOWNERS +1 -0
  8. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/bug_report.yml +110 -0
  9. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config.yml +8 -0
  10. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/config_question.yml +56 -0
  11. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/feature_request.yml +63 -0
  12. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/rendering_issue.yml +106 -0
  13. package/dist/extensions/pi-zentui/.github/ISSUE_TEMPLATE/runtime_detection.yml +103 -0
  14. package/dist/extensions/pi-zentui/.github/dependabot.yml +47 -0
  15. package/dist/extensions/pi-zentui/.github/pull_request_template.md +34 -0
  16. package/dist/extensions/pi-zentui/.github/release.yml +18 -0
  17. package/dist/extensions/pi-zentui/.github/workflows/ci.yml +79 -0
  18. package/dist/extensions/pi-zentui/.github/workflows/publish.yml +63 -0
  19. package/dist/extensions/pi-zentui/AGENTS.md +107 -0
  20. package/dist/extensions/pi-zentui/CONTRIBUTING.md +68 -0
  21. package/dist/extensions/pi-zentui/LICENSE +21 -0
  22. package/dist/extensions/pi-zentui/README.md +188 -0
  23. package/dist/extensions/pi-zentui/SECURITY.md +18 -0
  24. package/dist/extensions/pi-zentui/assets/main-cover.png +0 -0
  25. package/dist/extensions/pi-zentui/assets/screenshots/editor-accent-rail.png +0 -0
  26. package/dist/extensions/pi-zentui/assets/screenshots/editor-minimalist.png +0 -0
  27. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode-copy-friendly.png +0 -0
  28. package/dist/extensions/pi-zentui/assets/screenshots/editor-opencode.png +0 -0
  29. package/dist/extensions/pi-zentui/assets/screenshots/user-message-compact.png +0 -0
  30. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed-copy-friendly.png +0 -0
  31. package/dist/extensions/pi-zentui/assets/screenshots/user-message-framed.png +0 -0
  32. package/dist/extensions/pi-zentui/assets/screenshots/user-message-labeled.png +0 -0
  33. package/dist/extensions/pi-zentui/assets/zentui.png +0 -0
  34. package/dist/extensions/pi-zentui/biome.json +31 -0
  35. package/dist/extensions/pi-zentui/docs/configuration.md +509 -0
  36. package/dist/extensions/pi-zentui/docs/footer-format.md +162 -0
  37. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-editor.ts +99 -0
  38. package/dist/extensions/pi-zentui/extensions/zentui/accent-rail-layout-patch.ts +457 -0
  39. package/dist/extensions/pi-zentui/extensions/zentui/completion-menu.ts +157 -0
  40. package/dist/extensions/pi-zentui/extensions/zentui/config.ts +2100 -0
  41. package/dist/extensions/pi-zentui/extensions/zentui/editor-metadata-format.ts +321 -0
  42. package/dist/extensions/pi-zentui/extensions/zentui/editor-transfer.ts +73 -0
  43. package/dist/extensions/pi-zentui/extensions/zentui/extension-status.ts +89 -0
  44. package/dist/extensions/pi-zentui/extensions/zentui/footer-format.ts +364 -0
  45. package/dist/extensions/pi-zentui/extensions/zentui/footer-layout.ts +122 -0
  46. package/dist/extensions/pi-zentui/extensions/zentui/footer.ts +812 -0
  47. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +568 -0
  48. package/dist/extensions/pi-zentui/extensions/zentui/git.ts +384 -0
  49. package/dist/extensions/pi-zentui/extensions/zentui/icons.ts +252 -0
  50. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +1406 -0
  51. package/dist/extensions/pi-zentui/extensions/zentui/interaction-summary.ts +1049 -0
  52. package/dist/extensions/pi-zentui/extensions/zentui/live-context.ts +75 -0
  53. package/dist/extensions/pi-zentui/extensions/zentui/minimalist-editor.ts +463 -0
  54. package/dist/extensions/pi-zentui/extensions/zentui/package-version.ts +651 -0
  55. package/dist/extensions/pi-zentui/extensions/zentui/project-refresh.ts +120 -0
  56. package/dist/extensions/pi-zentui/extensions/zentui/project-state.ts +59 -0
  57. package/dist/extensions/pi-zentui/extensions/zentui/prototype-patch-registry.ts +166 -0
  58. package/dist/extensions/pi-zentui/extensions/zentui/repository-root.ts +98 -0
  59. package/dist/extensions/pi-zentui/extensions/zentui/runtime.ts +841 -0
  60. package/dist/extensions/pi-zentui/extensions/zentui/selector-border.ts +93 -0
  61. package/dist/extensions/pi-zentui/extensions/zentui/session-lifecycle.ts +60 -0
  62. package/dist/extensions/pi-zentui/extensions/zentui/settings-command.ts +1796 -0
  63. package/dist/extensions/pi-zentui/extensions/zentui/settings-previews.ts +128 -0
  64. package/dist/extensions/pi-zentui/extensions/zentui/state.ts +94 -0
  65. package/dist/extensions/pi-zentui/extensions/zentui/style.ts +424 -0
  66. package/dist/extensions/pi-zentui/extensions/zentui/telemetry.ts +82 -0
  67. package/dist/extensions/pi-zentui/extensions/zentui/ui.ts +1290 -0
  68. package/dist/extensions/pi-zentui/extensions/zentui/user-message-osc.ts +248 -0
  69. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +215 -0
  70. package/dist/extensions/pi-zentui/extensions/zentui/user-message.ts +206 -0
  71. package/dist/extensions/pi-zentui/extensions/zentui/working-line-messages.ts +24 -0
  72. package/dist/extensions/pi-zentui/extensions/zentui/working-line-spinners.ts +54 -0
  73. package/dist/extensions/pi-zentui/extensions/zentui/working-line.ts +1433 -0
  74. package/dist/extensions/pi-zentui/package-lock.json +5311 -0
  75. package/dist/extensions/pi-zentui/package.json +60 -0
  76. package/dist/extensions/pi-zentui/test/accent-rail-editor.test.ts +232 -0
  77. package/dist/extensions/pi-zentui/test/accent-rail-layout-patch.test.ts +669 -0
  78. package/dist/extensions/pi-zentui/test/completion-menu.test.ts +265 -0
  79. package/dist/extensions/pi-zentui/test/config.test.ts +3119 -0
  80. package/dist/extensions/pi-zentui/test/editor-metadata-format.test.ts +405 -0
  81. package/dist/extensions/pi-zentui/test/editor-transfer.test.ts +260 -0
  82. package/dist/extensions/pi-zentui/test/editor-viewport-indicators.test.ts +1286 -0
  83. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +7196 -0
  84. package/dist/extensions/pi-zentui/test/extension-status.test.ts +182 -0
  85. package/dist/extensions/pi-zentui/test/fixtures/starship-nerd-font-symbols.toml +172 -0
  86. package/dist/extensions/pi-zentui/test/footer-format.test.ts +431 -0
  87. package/dist/extensions/pi-zentui/test/footer-layout.test.ts +113 -0
  88. package/dist/extensions/pi-zentui/test/footer-repository-path.test.ts +123 -0
  89. package/dist/extensions/pi-zentui/test/format.test.ts +865 -0
  90. package/dist/extensions/pi-zentui/test/git.test.ts +212 -0
  91. package/dist/extensions/pi-zentui/test/icons.test.ts +69 -0
  92. package/dist/extensions/pi-zentui/test/interaction-summary.test.ts +1402 -0
  93. package/dist/extensions/pi-zentui/test/live-context-integration.test.ts +378 -0
  94. package/dist/extensions/pi-zentui/test/live-context.test.ts +139 -0
  95. package/dist/extensions/pi-zentui/test/minimalist-editor.test.ts +811 -0
  96. package/dist/extensions/pi-zentui/test/package-version.test.ts +321 -0
  97. package/dist/extensions/pi-zentui/test/project-refresh.test.ts +250 -0
  98. package/dist/extensions/pi-zentui/test/project-state.test.ts +159 -0
  99. package/dist/extensions/pi-zentui/test/repository-root.test.ts +108 -0
  100. package/dist/extensions/pi-zentui/test/responsive-dependencies.test.ts +825 -0
  101. package/dist/extensions/pi-zentui/test/runtime.test.ts +183 -0
  102. package/dist/extensions/pi-zentui/test/session-lifecycle.test.ts +40 -0
  103. package/dist/extensions/pi-zentui/test/settings-command.test.ts +1381 -0
  104. package/dist/extensions/pi-zentui/test/settings-previews.test.ts +347 -0
  105. package/dist/extensions/pi-zentui/test/state.test.ts +84 -0
  106. package/dist/extensions/pi-zentui/test/telemetry-lifecycle-integration.test.ts +276 -0
  107. package/dist/extensions/pi-zentui/test/telemetry.test.ts +105 -0
  108. package/dist/extensions/pi-zentui/test/turn-summary-lifecycle.integration.test.ts +327 -0
  109. package/dist/extensions/pi-zentui/test/user-message-osc.test.ts +164 -0
  110. package/dist/extensions/pi-zentui/test/user-message-styles.test.ts +292 -0
  111. package/dist/extensions/pi-zentui/test/working-line-lifecycle.integration.test.ts +730 -0
  112. package/dist/extensions/pi-zentui/test/working-line-messages.test.ts +34 -0
  113. package/dist/extensions/pi-zentui/test/working-line.test.ts +1990 -0
  114. package/dist/extensions/pi-zentui/tsconfig.json +16 -0
  115. package/dist/extensions/tps.test.ts +38 -0
  116. package/dist/extensions/{pi-powerline-footer/tps.ts → tps.ts} +4 -0
  117. package/dist/skills/design-references/SKILL.md +31 -0
  118. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  119. package/dist/themes/adventure.json +84 -0
  120. package/dist/themes/adwaita-dark.json +84 -0
  121. package/dist/themes/arcoiris.json +84 -0
  122. package/dist/themes/arthur.json +84 -0
  123. package/dist/themes/atom.json +84 -0
  124. package/dist/themes/aura.json +84 -0
  125. package/dist/themes/black-metal-bathory.json +84 -0
  126. package/dist/themes/black-metal-burzum.json +84 -0
  127. package/dist/themes/black-metal-khold.json +84 -0
  128. package/dist/themes/box.json +84 -0
  129. package/dist/themes/brogrammer.json +84 -0
  130. package/dist/themes/carbonfox.json +84 -0
  131. package/dist/themes/catppuccin-mocha.json +84 -0
  132. package/dist/themes/citruszest.json +84 -0
  133. package/dist/themes/cursor-dark.json +84 -0
  134. package/dist/themes/cutie-pro.json +84 -0
  135. package/dist/themes/dark-modern.json +84 -0
  136. package/dist/themes/dark-pastel.json +84 -0
  137. package/dist/themes/dimmed-monokai.json +84 -0
  138. package/dist/themes/doom-peacock.json +84 -0
  139. package/dist/themes/dracula-plus.json +84 -0
  140. package/dist/themes/dracula.json +80 -0
  141. package/dist/themes/earthsong.json +84 -0
  142. package/dist/themes/everforest-dark-hard.json +84 -0
  143. package/dist/themes/everforest-dark.json +80 -0
  144. package/dist/themes/fahrenheit.json +84 -0
  145. package/dist/themes/flatland.json +84 -0
  146. package/dist/themes/flexoki-dark.json +84 -0
  147. package/dist/themes/front-end-delight.json +84 -0
  148. package/dist/themes/fun-forrest.json +84 -0
  149. package/dist/themes/galizur.json +84 -0
  150. package/dist/themes/github-dark-colorblind.json +84 -0
  151. package/dist/themes/github-dark-high-contrast.json +84 -0
  152. package/dist/themes/glacier.json +84 -0
  153. package/dist/themes/gruber-darker.json +84 -0
  154. package/dist/themes/gruvbox-dark-hard.json +84 -0
  155. package/dist/themes/gruvbox-dark.json +84 -0
  156. package/dist/themes/gruvbox-material.json +84 -0
  157. package/dist/themes/guezwhoz.json +84 -0
  158. package/dist/themes/hacktober.json +84 -0
  159. package/dist/themes/hardcore.json +84 -0
  160. package/dist/themes/havn-skumring.json +84 -0
  161. package/dist/themes/ic-orange-ppl.json +84 -0
  162. package/dist/themes/iterm2-smoooooth.json +84 -0
  163. package/dist/themes/iterm2-tango-dark.json +84 -0
  164. package/dist/themes/japanesque.json +84 -0
  165. package/dist/themes/jellybeans.json +84 -0
  166. package/dist/themes/kanagawa-wave.json +84 -0
  167. package/dist/themes/kurokula.json +84 -0
  168. package/dist/themes/later-this-evening.json +84 -0
  169. package/dist/themes/lovelace.json +84 -0
  170. package/dist/themes/material-darker.json +84 -0
  171. package/dist/themes/matte-black.json +84 -0
  172. package/dist/themes/mellow.json +84 -0
  173. package/dist/themes/miasma.json +84 -0
  174. package/dist/themes/nord.json +80 -0
  175. package/dist/themes/nvim-dark.json +84 -0
  176. package/dist/themes/one-dark.json +80 -0
  177. package/dist/themes/popping-and-locking.json +84 -0
  178. package/dist/themes/rose-pine.json +80 -0
  179. package/dist/themes/sea-shells.json +84 -0
  180. package/dist/themes/sleepy-hollow.json +84 -0
  181. package/dist/themes/smyck.json +84 -0
  182. package/dist/themes/solarized-dark.json +80 -0
  183. package/dist/themes/tokyo-night.json +80 -0
  184. package/dist/themes/tomorrow-night-bright.json +84 -0
  185. package/dist/themes/tomorrow-night-burns.json +84 -0
  186. package/dist/themes/tomorrow-night.json +84 -0
  187. package/dist/themes/twilight.json +84 -0
  188. package/dist/themes/vague.json +84 -0
  189. package/dist/themes/vesper.json +84 -0
  190. package/dist/themes/xcode-dark-hc.json +84 -0
  191. package/docs/custom-provider.md +0 -1
  192. package/docs/plans/auto-model-routing-extension.md +490 -0
  193. package/package.json +3 -3
  194. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  195. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  196. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  197. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  198. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  199. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  200. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  201. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  202. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  203. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  204. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  205. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  206. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  207. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  208. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  209. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  210. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  211. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  212. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  213. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  214. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  215. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  216. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  217. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  218. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  219. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  220. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  221. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  222. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  223. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  224. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  225. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  226. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  227. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  228. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  229. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  230. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  231. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  232. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  233. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  234. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  235. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  236. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  237. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  238. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  239. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  240. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  241. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  242. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  243. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  244. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  245. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  246. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  247. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  248. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  249. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  250. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  251. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  252. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  253. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  254. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  255. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  256. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  257. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  258. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  259. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  260. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  261. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  262. package/dist/extensions/preview-tools-disabled.ts +0 -11
  263. package/dist/themes/powerline-footer/theme.json +0 -33
@@ -1,329 +0,0 @@
1
- import { spawn } from "node:child_process";
2
- import type { GitStatus } from "./types.ts";
3
-
4
- interface CachedGitStatus {
5
- staged: number;
6
- unstaged: number;
7
- untracked: number;
8
- timestamp: number;
9
- }
10
-
11
- interface CachedBranch {
12
- branch: string | null;
13
- timestamp: number;
14
- }
15
-
16
- export type GitPollingMode = "full" | "branch" | "off";
17
-
18
- /** Known git hosting providers we render a dedicated icon for. */
19
- export type GitHost = "github" | "gitlab" | "bitbucket" | "other";
20
-
21
- interface CachedRemoteHost {
22
- host: GitHost | null;
23
- timestamp: number;
24
- }
25
-
26
- const CACHE_TTL_MS = 1000; // 1 second for file status
27
- const BRANCH_TTL_MS = 500; // Shorter TTL so branch updates quickly after invalidation
28
- const REMOTE_TTL_MS = 60_000; // Origin remote almost never changes within a session
29
- let cachedStatus: CachedGitStatus | null = null;
30
- let cachedBranch: CachedBranch | null = null;
31
- let cachedRemoteHost: CachedRemoteHost | null = null;
32
- let pendingRemoteFetch: Promise<void> | null = null;
33
- let pendingFetch: Promise<void> | null = null;
34
- let pendingBranchFetch: Promise<void> | null = null;
35
- let invalidationCounter = 0; // Track invalidations to prevent stale updates
36
- let branchInvalidationCounter = 0;
37
- const updateListeners = new Set<() => void>();
38
-
39
- function notifyGitUpdate(): void {
40
- for (const listener of updateListeners) listener();
41
- }
42
-
43
- export function subscribeGitUpdates(listener: () => void): () => void {
44
- updateListeners.add(listener);
45
- return () => updateListeners.delete(listener);
46
- }
47
-
48
- /**
49
- * Parse git status --porcelain output
50
- *
51
- * Format: XY filename
52
- * X = index status, Y = working tree status
53
- * ?? = untracked
54
- * Other X values = staged
55
- * Other Y values = unstaged
56
- */
57
- function parseGitStatusOutput(output: string): { staged: number; unstaged: number; untracked: number } {
58
- let staged = 0;
59
- let unstaged = 0;
60
- let untracked = 0;
61
-
62
- for (const line of output.split("\n")) {
63
- if (!line) continue;
64
- const x = line[0];
65
- const y = line[1];
66
-
67
- if (x === "?" && y === "?") {
68
- untracked++;
69
- continue;
70
- }
71
-
72
- // X position (index/staged)
73
- if (x && x !== " " && x !== "?") {
74
- staged++;
75
- }
76
-
77
- // Y position (working tree/unstaged)
78
- if (y && y !== " ") {
79
- unstaged++;
80
- }
81
- }
82
-
83
- return { staged, unstaged, untracked };
84
- }
85
-
86
- /**
87
- * Environment for this module's read-only git commands.
88
- *
89
- * Polling `git status` otherwise refreshes the index as a side effect, taking
90
- * `.git/index.lock`: that races interactive git in the same repo, and orphans
91
- * the lock if we are killed mid-write. `GIT_OPTIONAL_LOCKS=0` skips the
92
- * refresh; the reported counts are unchanged. Preferred over
93
- * `--no-optional-locks` because it also reaches the child git processes
94
- * `status` spawns for submodules.
95
- */
96
- export function readOnlyGitEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
97
- return { ...env, GIT_OPTIONAL_LOCKS: "0" };
98
- }
99
-
100
- function runGit(args: string[], timeoutMs = 200): Promise<string | null> {
101
- return new Promise((resolve) => {
102
- const proc = spawn("git", args, {
103
- stdio: ["ignore", "pipe", "pipe"],
104
- env: readOnlyGitEnv(),
105
- });
106
-
107
- let stdout = "";
108
- let resolved = false;
109
-
110
- const finish = (result: string | null) => {
111
- if (resolved) return;
112
- resolved = true;
113
- clearTimeout(timeoutId);
114
- resolve(result);
115
- };
116
-
117
- proc.stdout.on("data", (data) => {
118
- stdout += data.toString();
119
- });
120
-
121
- proc.on("close", (code) => {
122
- finish(code === 0 ? stdout.trim() : null);
123
- });
124
-
125
- proc.on("error", () => {
126
- finish(null);
127
- });
128
-
129
- const timeoutId = setTimeout(() => {
130
- proc.kill();
131
- finish(null);
132
- }, timeoutMs);
133
- });
134
- }
135
-
136
- /**
137
- * Fetch current git branch asynchronously.
138
- * For detached HEAD, returns the short commit SHA (matches provider's "detached" behavior).
139
- */
140
- async function fetchGitBranch(): Promise<string | null> {
141
- const branch = await runGit(["branch", "--show-current"]);
142
- if (branch === null) return null;
143
- if (branch) return branch;
144
-
145
- const sha = await runGit(["rev-parse", "--short", "HEAD"]);
146
- return sha ? `${sha} (detached)` : "detached";
147
- }
148
-
149
- /**
150
- * Classify an origin remote URL into a known hosting provider. Handles both
151
- * SSH (`git@host:owner/repo`, `ssh://git@host/…`) and HTTP(S) forms, and
152
- * treats sub-domains (e.g. `www.github.com`) and any non-empty remote we
153
- * don't recognize as a generic git host.
154
- */
155
- export function detectGitHost(remoteUrl: string | null): GitHost | null {
156
- if (!remoteUrl) return null;
157
- const trimmed = remoteUrl.trim();
158
- if (!trimmed) return null;
159
-
160
- let host: string;
161
- const scpLike = /^[^/@]+@([^:/]+):/.exec(trimmed);
162
- if (scpLike) {
163
- host = scpLike[1]!;
164
- } else {
165
- try {
166
- host = new URL(trimmed).hostname;
167
- } catch {
168
- return "other";
169
- }
170
- }
171
-
172
- host = host.toLowerCase().replace(/^www\./, "");
173
- if (host === "github.com" || host.endsWith(".github.com")) return "github";
174
- if (host === "gitlab.com" || host.endsWith(".gitlab.com")) return "gitlab";
175
- if (host === "bitbucket.org" || host.endsWith(".bitbucket.org")) return "bitbucket";
176
- return "other";
177
- }
178
-
179
- /**
180
- * Fetch the origin remote host asynchronously and cache the result.
181
- */
182
- async function fetchRemoteHost(): Promise<GitHost | null> {
183
- const url = await runGit(["remote", "get-url", "origin"]);
184
- return detectGitHost(url);
185
- }
186
-
187
- /**
188
- * Get the origin remote's hosting provider with a long TTL cache. Returns the
189
- * cached value immediately (or null before the first fetch completes) and
190
- * refreshes in the background, matching the branch/status caching pattern.
191
- */
192
- export function getGitRemoteHost(): GitHost | null {
193
- const now = Date.now();
194
- if (cachedRemoteHost && now - cachedRemoteHost.timestamp < REMOTE_TTL_MS) {
195
- return cachedRemoteHost.host;
196
- }
197
-
198
- if (!pendingRemoteFetch) {
199
- pendingRemoteFetch = fetchRemoteHost()
200
- .then((host) => {
201
- cachedRemoteHost = { host, timestamp: Date.now() };
202
- notifyGitUpdate();
203
- })
204
- .catch(() => {
205
- cachedRemoteHost = { host: null, timestamp: Date.now() };
206
- notifyGitUpdate();
207
- })
208
- .finally(() => {
209
- pendingRemoteFetch = null;
210
- });
211
- }
212
-
213
- return cachedRemoteHost ? cachedRemoteHost.host : null;
214
- }
215
-
216
- /**
217
- * Fetch git status asynchronously
218
- */
219
- async function fetchGitStatus(): Promise<{ staged: number; unstaged: number; untracked: number } | null> {
220
- const output = await runGit(["status", "--porcelain"], 500);
221
- if (output === null) return null;
222
- return parseGitStatusOutput(output);
223
- }
224
-
225
- /**
226
- * Get the current git branch with caching.
227
- * Falls back to provider branch if our cache is empty.
228
- */
229
- export function getCurrentBranch(providerBranch: string | null): string | null {
230
- const now = Date.now();
231
-
232
- // Return cached if fresh
233
- if (cachedBranch && now - cachedBranch.timestamp < BRANCH_TTL_MS) {
234
- return cachedBranch.branch;
235
- }
236
-
237
- // Trigger background fetch if not already pending
238
- if (!pendingBranchFetch) {
239
- const fetchId = branchInvalidationCounter;
240
- pendingBranchFetch = fetchGitBranch().then((result) => {
241
- // Cache result if no invalidation happened (including null for non-git dirs)
242
- if (fetchId === branchInvalidationCounter) {
243
- cachedBranch = {
244
- branch: result,
245
- timestamp: Date.now(),
246
- };
247
- notifyGitUpdate();
248
- }
249
- pendingBranchFetch = null;
250
- });
251
- }
252
-
253
- // Return stale cache while refreshing; only use provider before first fetch
254
- return cachedBranch ? cachedBranch.branch : providerBranch;
255
- }
256
-
257
- /**
258
- * Get git status with caching.
259
- * Returns cached value if within TTL, otherwise triggers async fetch.
260
- * This is designed for synchronous render() calls - returns last known value
261
- * while refreshing in background.
262
- */
263
- export function getGitStatus(providerBranch: string | null, pollingMode: GitPollingMode = "full"): GitStatus {
264
- const now = Date.now();
265
- const branch = pollingMode === "off" ? providerBranch : getCurrentBranch(providerBranch);
266
-
267
- if (pollingMode !== "full") {
268
- return { branch, staged: 0, unstaged: 0, untracked: 0 };
269
- }
270
-
271
- // Return cached if fresh
272
- if (cachedStatus && now - cachedStatus.timestamp < CACHE_TTL_MS) {
273
- return {
274
- branch,
275
- staged: cachedStatus.staged,
276
- unstaged: cachedStatus.unstaged,
277
- untracked: cachedStatus.untracked,
278
- };
279
- }
280
-
281
- // Trigger background fetch if not already pending
282
- if (!pendingFetch) {
283
- const fetchId = invalidationCounter; // Capture current counter
284
- pendingFetch = fetchGitStatus().then((result) => {
285
- // Cache result if no invalidation happened (including null for non-git dirs)
286
- if (fetchId === invalidationCounter) {
287
- cachedStatus = result
288
- ? { staged: result.staged, unstaged: result.unstaged, untracked: result.untracked, timestamp: Date.now() }
289
- : { staged: 0, unstaged: 0, untracked: 0, timestamp: Date.now() };
290
- notifyGitUpdate();
291
- }
292
- pendingFetch = null;
293
- });
294
- }
295
-
296
- // Return last cached or empty
297
- if (cachedStatus) {
298
- return {
299
- branch,
300
- staged: cachedStatus.staged,
301
- unstaged: cachedStatus.unstaged,
302
- untracked: cachedStatus.untracked,
303
- };
304
- }
305
-
306
- return { branch, staged: 0, unstaged: 0, untracked: 0 };
307
- }
308
-
309
- /**
310
- * Force refresh git status (call when you know files changed).
311
- * Serve-stale: keep the last known counts on screen while a background
312
- * refresh runs, instead of blanking the segment to zeros (footer flicker).
313
- */
314
- export function invalidateGitStatus(): void {
315
- if (cachedStatus) cachedStatus.timestamp = 0; // expire, but keep serving the stale value
316
- invalidationCounter++; // Increment to invalidate any pending fetches
317
- }
318
-
319
- /**
320
- * Force refresh git branch (call when you know branch might have changed).
321
- * Serve-stale: keep showing the last known branch until the refresh lands.
322
- */
323
- export function invalidateGitBranch(): void {
324
- if (cachedBranch) cachedBranch.timestamp = 0; // expire, but keep serving the stale value
325
- branchInvalidationCounter++;
326
- // The origin remote is repo-scoped, so a branch/cwd change may mean a
327
- // different repo; drop the host cache so it re-detects.
328
- cachedRemoteHost = null;
329
- }
@@ -1,220 +0,0 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname } from "node:path";
3
-
4
- export type GuideMode = "full" | "compact" | "off";
5
- export type GuideDisplayMode = "full" | "compact" | "none";
6
-
7
- export interface GuideFeature {
8
- id: string;
9
- section?: string;
10
- title: string;
11
- example: string;
12
- introducedIn: string;
13
- }
14
-
15
- export interface GuidePreferences {
16
- mode: GuideMode;
17
- lastSeenVersion?: string;
18
- }
19
-
20
- export interface GuidePreferencesUpdate {
21
- mode?: GuideMode;
22
- lastSeenVersion?: string | null;
23
- }
24
-
25
- // Keep this map useful, not exhaustive: each row points at a real command, shortcut,
26
- // or prompt from the root README without reproducing the README in the overlay.
27
- export const GUIDE_FEATURES: readonly GuideFeature[] = [
28
- {
29
- id: "workflow",
30
- section: "Start here",
31
- title: "Workflow",
32
- example: '"Use $workflow to orchestrate this implementation."',
33
- introducedIn: "0.9.14",
34
- },
35
- {
36
- id: "settings",
37
- section: "Start here",
38
- title: "Settings",
39
- example: "/settings · /guide compact|off|reset · /reload",
40
- introducedIn: "0.5.13",
41
- },
42
- {
43
- id: "subagents",
44
- section: "Delegate / research",
45
- title: "Plan",
46
- example: '"Ask the architect to challenge this plan..."',
47
- introducedIn: "0.5.13",
48
- },
49
- {
50
- id: "parallel-review",
51
- section: "Delegate / research",
52
- title: "Review",
53
- example: '"Run parallel commentator agents for correctness..."',
54
- introducedIn: "0.5.13",
55
- },
56
- {
57
- id: "web-research",
58
- section: "Delegate / research",
59
- title: "Research",
60
- example: '"Ask the researcher to research <topic> and cite sources."',
61
- introducedIn: "0.5.13",
62
- },
63
- {
64
- id: "skills",
65
- section: "Delegate / research",
66
- title: "Skills",
67
- example: "/skill:*",
68
- introducedIn: "0.5.13",
69
- },
70
- {
71
- id: "recovery",
72
- section: "Recover",
73
- title: "Recover",
74
- example: "/undo · /tree · /fork · /cp <hash>",
75
- introducedIn: "0.5.13",
76
- },
77
- {
78
- id: "handoff",
79
- section: "Recover",
80
- title: "Handoff",
81
- example: "/handoff-new Next: fix <problem>; start with a coding plan.",
82
- introducedIn: "0.5.13",
83
- },
84
- ];
85
-
86
- export const GUIDE_DISMISS_HINT = "Press any key to continue";
87
-
88
- export const GUIDE_COMPACT_LINES = [
89
- "/guide full · /settings",
90
- '"Use $workflow to orchestrate this implementation."',
91
- '"Ask the researcher to research <topic>."',
92
- "/skill:* · /undo · /handoff-new <next focus>",
93
- ] as const;
94
-
95
- export type GuideLine =
96
- | { kind: "section"; text: string }
97
- | { kind: "feature"; feature: GuideFeature };
98
-
99
- export function getGuideFeatureLines(features: readonly GuideFeature[] = GUIDE_FEATURES): GuideLine[] {
100
- return features.flatMap((feature, index) => {
101
- const previous = features[index - 1];
102
- const section = feature.section && feature.section !== previous?.section
103
- ? [{ kind: "section" as const, text: feature.section }]
104
- : [];
105
- return [...section, { kind: "feature" as const, feature }];
106
- });
107
- }
108
-
109
- function isRecord(value: unknown): value is Record<string, unknown> {
110
- return typeof value === "object" && value !== null && !Array.isArray(value);
111
- }
112
-
113
- function isGuideMode(value: unknown): value is GuideMode {
114
- return value === "full" || value === "compact" || value === "off";
115
- }
116
-
117
- function versionParts(version: string): [number, number, number] {
118
- const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
119
- return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : [0, 0, 0];
120
- }
121
-
122
- export function compareGuideVersions(left: string, right: string): number {
123
- const leftParts = versionParts(left);
124
- const rightParts = versionParts(right);
125
- for (let i = 0; i < leftParts.length; i++) {
126
- if (leftParts[i] !== rightParts[i]) return leftParts[i] - rightParts[i];
127
- }
128
- return 0;
129
- }
130
-
131
- export function getGuidePreferences(settings: unknown): GuidePreferences {
132
- const raw = isRecord(settings) && isRecord(settings.selesaiGuide)
133
- ? settings.selesaiGuide
134
- : {};
135
-
136
- return {
137
- mode: isGuideMode(raw.mode) ? raw.mode : "compact",
138
- lastSeenVersion: typeof raw.lastSeenVersion === "string" ? raw.lastSeenVersion : undefined,
139
- };
140
- }
141
-
142
- export function getNewGuideFeatures(
143
- lastSeenVersion: string | undefined,
144
- currentVersion: string,
145
- features: readonly GuideFeature[] = GUIDE_FEATURES,
146
- ): GuideFeature[] {
147
- if (!lastSeenVersion || compareGuideVersions(lastSeenVersion, currentVersion) >= 0) {
148
- return [];
149
- }
150
-
151
- return features.filter((feature) => (
152
- compareGuideVersions(feature.introducedIn, lastSeenVersion) > 0
153
- && compareGuideVersions(feature.introducedIn, currentVersion) <= 0
154
- ));
155
- }
156
-
157
- export function resolveGuideDisplayMode(options: {
158
- reason: string;
159
- hasUI: boolean;
160
- guideMode: GuideMode;
161
- }): GuideDisplayMode {
162
- if (!options.hasUI || options.reason === "reload" || options.guideMode === "off") {
163
- return "none";
164
- }
165
-
166
- return options.guideMode === "compact" ? "compact" : "full";
167
- }
168
-
169
- function readSettingsFile(settingsPath: string): Record<string, unknown> | null {
170
- if (!existsSync(settingsPath)) return {};
171
-
172
- try {
173
- const parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
174
- return isRecord(parsed) ? parsed : null;
175
- } catch {
176
- return null;
177
- }
178
- }
179
-
180
- export function saveGuidePreferences(
181
- update: GuidePreferencesUpdate,
182
- settingsPath: string,
183
- ): boolean {
184
- const settings = readSettingsFile(settingsPath);
185
- if (!settings) return false;
186
-
187
- const current = getGuidePreferences(settings);
188
- const raw = isRecord(settings) && isRecord(settings.selesaiGuide)
189
- ? settings.selesaiGuide
190
- : {};
191
- const next: Record<string, unknown> = {};
192
- if (update.mode !== undefined) {
193
- next.mode = update.mode;
194
- } else if (typeof raw.mode === "string") {
195
- // Version-only writes (markGuideVersionSeen) must preserve an explicit user
196
- // mode but never persist the fallback default as a user choice.
197
- next.mode = raw.mode;
198
- }
199
- const lastSeenVersion = update.lastSeenVersion === null
200
- ? undefined
201
- : update.lastSeenVersion ?? current.lastSeenVersion;
202
- if (lastSeenVersion) next.lastSeenVersion = lastSeenVersion;
203
-
204
- settings.selesaiGuide = next;
205
-
206
- try {
207
- mkdirSync(dirname(settingsPath), { recursive: true });
208
- writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
209
- return true;
210
- } catch {
211
- return false;
212
- }
213
- }
214
-
215
- export function markGuideVersionSeen(
216
- version: string,
217
- settingsPath: string,
218
- ): boolean {
219
- return saveGuidePreferences({ lastSeenVersion: version }, settingsPath);
220
- }