@selesai/code 0.9.15 → 0.10.1

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 (269) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +6 -9
  3. package/dist/defaults/models.json +2 -2
  4. package/dist/defaults/settings.json +3 -7
  5. package/dist/extensions/package.json +2 -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/modes/interactive/components/settings-selector.d.ts +1 -1
  118. package/dist/modes/interactive/interactive-mode.js +1 -1
  119. package/dist/skills/design-references/SKILL.md +31 -0
  120. package/dist/skills/design-references/references/clickhouse/DESIGN.md +544 -0
  121. package/dist/themes/adventure.json +84 -0
  122. package/dist/themes/adwaita-dark.json +84 -0
  123. package/dist/themes/arcoiris.json +84 -0
  124. package/dist/themes/arthur.json +84 -0
  125. package/dist/themes/atom.json +84 -0
  126. package/dist/themes/aura.json +84 -0
  127. package/dist/themes/black-metal-bathory.json +84 -0
  128. package/dist/themes/black-metal-burzum.json +84 -0
  129. package/dist/themes/black-metal-khold.json +84 -0
  130. package/dist/themes/box.json +84 -0
  131. package/dist/themes/brogrammer.json +84 -0
  132. package/dist/themes/carbonfox.json +84 -0
  133. package/dist/themes/catppuccin-mocha.json +84 -0
  134. package/dist/themes/citruszest.json +84 -0
  135. package/dist/themes/cursor-dark.json +84 -0
  136. package/dist/themes/cutie-pro.json +84 -0
  137. package/dist/themes/dark-modern.json +84 -0
  138. package/dist/themes/dark-pastel.json +84 -0
  139. package/dist/themes/dimmed-monokai.json +84 -0
  140. package/dist/themes/doom-peacock.json +84 -0
  141. package/dist/themes/dracula-plus.json +84 -0
  142. package/dist/themes/dracula.json +80 -0
  143. package/dist/themes/earthsong.json +84 -0
  144. package/dist/themes/everforest-dark-hard.json +84 -0
  145. package/dist/themes/everforest-dark.json +80 -0
  146. package/dist/themes/fahrenheit.json +84 -0
  147. package/dist/themes/flatland.json +84 -0
  148. package/dist/themes/flexoki-dark.json +84 -0
  149. package/dist/themes/front-end-delight.json +84 -0
  150. package/dist/themes/fun-forrest.json +84 -0
  151. package/dist/themes/galizur.json +84 -0
  152. package/dist/themes/github-dark-colorblind.json +84 -0
  153. package/dist/themes/github-dark-high-contrast.json +84 -0
  154. package/dist/themes/glacier.json +84 -0
  155. package/dist/themes/gruber-darker.json +84 -0
  156. package/dist/themes/gruvbox-dark-hard.json +84 -0
  157. package/dist/themes/gruvbox-dark.json +84 -0
  158. package/dist/themes/gruvbox-material.json +84 -0
  159. package/dist/themes/guezwhoz.json +84 -0
  160. package/dist/themes/hacktober.json +84 -0
  161. package/dist/themes/hardcore.json +84 -0
  162. package/dist/themes/havn-skumring.json +84 -0
  163. package/dist/themes/ic-orange-ppl.json +84 -0
  164. package/dist/themes/iterm2-smoooooth.json +84 -0
  165. package/dist/themes/iterm2-tango-dark.json +84 -0
  166. package/dist/themes/japanesque.json +84 -0
  167. package/dist/themes/jellybeans.json +84 -0
  168. package/dist/themes/kanagawa-wave.json +84 -0
  169. package/dist/themes/kurokula.json +84 -0
  170. package/dist/themes/later-this-evening.json +84 -0
  171. package/dist/themes/lovelace.json +84 -0
  172. package/dist/themes/material-darker.json +84 -0
  173. package/dist/themes/matte-black.json +84 -0
  174. package/dist/themes/mellow.json +84 -0
  175. package/dist/themes/miasma.json +84 -0
  176. package/dist/themes/nord.json +80 -0
  177. package/dist/themes/nvim-dark.json +84 -0
  178. package/dist/themes/one-dark.json +80 -0
  179. package/dist/themes/popping-and-locking.json +84 -0
  180. package/dist/themes/rose-pine.json +80 -0
  181. package/dist/themes/sea-shells.json +84 -0
  182. package/dist/themes/sleepy-hollow.json +84 -0
  183. package/dist/themes/smyck.json +84 -0
  184. package/dist/themes/solarized-dark.json +80 -0
  185. package/dist/themes/tokyo-night.json +80 -0
  186. package/dist/themes/tomorrow-night-bright.json +84 -0
  187. package/dist/themes/tomorrow-night-burns.json +84 -0
  188. package/dist/themes/tomorrow-night.json +84 -0
  189. package/dist/themes/twilight.json +84 -0
  190. package/dist/themes/vague.json +84 -0
  191. package/dist/themes/vesper.json +84 -0
  192. package/dist/themes/xcode-dark-hc.json +84 -0
  193. package/docs/custom-provider.md +0 -1
  194. package/package.json +3 -3
  195. package/dist/extensions/auto-model/auto-model.test.ts +0 -18
  196. package/dist/extensions/auto-model/classifier.ts +0 -23
  197. package/dist/extensions/auto-model/config.ts +0 -51
  198. package/dist/extensions/auto-model/index.ts +0 -22
  199. package/dist/extensions/auto-model/lifecycle.test.ts +0 -24
  200. package/dist/extensions/pi-powerline-footer/.github/workflows/test.yml +0 -27
  201. package/dist/extensions/pi-powerline-footer/CHANGELOG.md +0 -687
  202. package/dist/extensions/pi-powerline-footer/CONTRIBUTING.md +0 -16
  203. package/dist/extensions/pi-powerline-footer/README.md +0 -513
  204. package/dist/extensions/pi-powerline-footer/banner.png +0 -0
  205. package/dist/extensions/pi-powerline-footer/bash-mode/completion.ts +0 -586
  206. package/dist/extensions/pi-powerline-footer/bash-mode/editor.ts +0 -583
  207. package/dist/extensions/pi-powerline-footer/bash-mode/history.ts +0 -204
  208. package/dist/extensions/pi-powerline-footer/bash-mode/shell-session.ts +0 -286
  209. package/dist/extensions/pi-powerline-footer/bash-mode/transcript.ts +0 -108
  210. package/dist/extensions/pi-powerline-footer/bash-mode/types.ts +0 -60
  211. package/dist/extensions/pi-powerline-footer/cd-command.ts +0 -190
  212. package/dist/extensions/pi-powerline-footer/colors.ts +0 -70
  213. package/dist/extensions/pi-powerline-footer/context-usage.ts +0 -156
  214. package/dist/extensions/pi-powerline-footer/currency-rates.ts +0 -142
  215. package/dist/extensions/pi-powerline-footer/editor-composition.ts +0 -32
  216. package/dist/extensions/pi-powerline-footer/editor-performance.ts +0 -99
  217. package/dist/extensions/pi-powerline-footer/git-status.ts +0 -329
  218. package/dist/extensions/pi-powerline-footer/guide.ts +0 -220
  219. package/dist/extensions/pi-powerline-footer/icons.ts +0 -190
  220. package/dist/extensions/pi-powerline-footer/index.ts +0 -3428
  221. package/dist/extensions/pi-powerline-footer/lifecycle.ts +0 -10
  222. package/dist/extensions/pi-powerline-footer/package.json +0 -49
  223. package/dist/extensions/pi-powerline-footer/paths.ts +0 -22
  224. package/dist/extensions/pi-powerline-footer/powerline-config.ts +0 -446
  225. package/dist/extensions/pi-powerline-footer/presets.ts +0 -102
  226. package/dist/extensions/pi-powerline-footer/queue/store.ts +0 -313
  227. package/dist/extensions/pi-powerline-footer/queue/types.ts +0 -53
  228. package/dist/extensions/pi-powerline-footer/render-scheduler.ts +0 -29
  229. package/dist/extensions/pi-powerline-footer/segments.ts +0 -570
  230. package/dist/extensions/pi-powerline-footer/separators.ts +0 -57
  231. package/dist/extensions/pi-powerline-footer/session-usage.ts +0 -44
  232. package/dist/extensions/pi-powerline-footer/shortcuts.ts +0 -61
  233. package/dist/extensions/pi-powerline-footer/tests/bash-mode.test.ts +0 -1905
  234. package/dist/extensions/pi-powerline-footer/tests/cd-command.test.ts +0 -144
  235. package/dist/extensions/pi-powerline-footer/tests/context-usage.test.ts +0 -156
  236. package/dist/extensions/pi-powerline-footer/tests/custom-items.test.ts +0 -344
  237. package/dist/extensions/pi-powerline-footer/tests/editor-composition.test.ts +0 -48
  238. package/dist/extensions/pi-powerline-footer/tests/editor-performance.test.ts +0 -43
  239. package/dist/extensions/pi-powerline-footer/tests/editor-responsiveness.test.ts +0 -136
  240. package/dist/extensions/pi-powerline-footer/tests/fixed-editor-border.test.ts +0 -39
  241. package/dist/extensions/pi-powerline-footer/tests/git-optional-locks.test.ts +0 -64
  242. package/dist/extensions/pi-powerline-footer/tests/git-status.test.ts +0 -92
  243. package/dist/extensions/pi-powerline-footer/tests/guide.test.ts +0 -129
  244. package/dist/extensions/pi-powerline-footer/tests/jump-shortcuts.test.ts +0 -116
  245. package/dist/extensions/pi-powerline-footer/tests/mode-aware-trigger.test.ts +0 -79
  246. package/dist/extensions/pi-powerline-footer/tests/paths.test.ts +0 -63
  247. package/dist/extensions/pi-powerline-footer/tests/queue-store.test.ts +0 -65
  248. package/dist/extensions/pi-powerline-footer/tests/quit-cursor.test.ts +0 -14
  249. package/dist/extensions/pi-powerline-footer/tests/remaining-regressions.test.ts +0 -173
  250. package/dist/extensions/pi-powerline-footer/tests/session-usage.test.ts +0 -47
  251. package/dist/extensions/pi-powerline-footer/tests/stash-shortcut.test.ts +0 -37
  252. package/dist/extensions/pi-powerline-footer/tests/thinking-segment.test.ts +0 -76
  253. package/dist/extensions/pi-powerline-footer/tests/token-stats.test.ts +0 -249
  254. package/dist/extensions/pi-powerline-footer/tests/tps.test.ts +0 -39
  255. package/dist/extensions/pi-powerline-footer/tests/usage-display.test.ts +0 -237
  256. package/dist/extensions/pi-powerline-footer/tests/welcome.test.ts +0 -199
  257. package/dist/extensions/pi-powerline-footer/tests/working-vibes.test.ts +0 -317
  258. package/dist/extensions/pi-powerline-footer/theme.example.json +0 -24
  259. package/dist/extensions/pi-powerline-footer/theme.json +0 -12
  260. package/dist/extensions/pi-powerline-footer/theme.ts +0 -241
  261. package/dist/extensions/pi-powerline-footer/token-stats.ts +0 -304
  262. package/dist/extensions/pi-powerline-footer/tsconfig.json +0 -13
  263. package/dist/extensions/pi-powerline-footer/types.ts +0 -235
  264. package/dist/extensions/pi-powerline-footer/welcome-dismiss.ts +0 -34
  265. package/dist/extensions/pi-powerline-footer/welcome.ts +0 -709
  266. package/dist/extensions/pi-powerline-footer/working-vibes.ts +0 -721
  267. package/dist/extensions/preview-tools-disabled.test.ts +0 -37
  268. package/dist/extensions/preview-tools-disabled.ts +0 -11
  269. 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
- }