@compilr-dev/cli 0.4.0 → 0.5.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 (315) hide show
  1. package/README.md +30 -12
  2. package/dist/agent.d.ts +74 -1
  3. package/dist/agent.js +259 -76
  4. package/dist/anchors/index.d.ts +9 -0
  5. package/dist/anchors/index.js +9 -0
  6. package/dist/anchors/project-anchors.d.ts +79 -0
  7. package/dist/anchors/project-anchors.js +202 -0
  8. package/dist/commands/handler-types.d.ts +68 -0
  9. package/dist/commands/handler-types.js +8 -0
  10. package/dist/commands/handlers/agent-commands.d.ts +13 -0
  11. package/dist/commands/handlers/agent-commands.js +305 -0
  12. package/dist/commands/handlers/design-commands.d.ts +15 -0
  13. package/dist/commands/handlers/design-commands.js +334 -0
  14. package/dist/commands/handlers/index.d.ts +20 -0
  15. package/dist/commands/handlers/index.js +43 -0
  16. package/dist/commands/handlers/overlay-commands.d.ts +21 -0
  17. package/dist/commands/handlers/overlay-commands.js +287 -0
  18. package/dist/commands/handlers/project-commands.d.ts +11 -0
  19. package/dist/commands/handlers/project-commands.js +167 -0
  20. package/dist/commands/handlers/simple-commands.d.ts +19 -0
  21. package/dist/commands/handlers/simple-commands.js +144 -0
  22. package/dist/commands/index.d.ts +2 -1
  23. package/dist/commands/registry.d.ts +50 -0
  24. package/dist/commands/registry.js +75 -0
  25. package/dist/commands-v2/handlers/context.d.ts +13 -0
  26. package/dist/commands-v2/handlers/context.js +348 -0
  27. package/dist/commands-v2/handlers/core.d.ts +13 -0
  28. package/dist/commands-v2/handlers/core.js +165 -0
  29. package/dist/commands-v2/handlers/debug.d.ts +11 -0
  30. package/dist/commands-v2/handlers/debug.js +159 -0
  31. package/dist/commands-v2/handlers/index.d.ts +12 -0
  32. package/dist/commands-v2/handlers/index.js +24 -0
  33. package/dist/commands-v2/handlers/project.d.ts +22 -0
  34. package/dist/commands-v2/handlers/project.js +814 -0
  35. package/dist/commands-v2/handlers/settings.d.ts +15 -0
  36. package/dist/commands-v2/handlers/settings.js +235 -0
  37. package/dist/commands-v2/index.d.ts +13 -0
  38. package/dist/commands-v2/index.js +15 -0
  39. package/dist/commands-v2/registry.d.ts +37 -0
  40. package/dist/commands-v2/registry.js +80 -0
  41. package/dist/commands-v2/types.d.ts +75 -0
  42. package/dist/commands-v2/types.js +7 -0
  43. package/dist/commands.js +110 -7
  44. package/dist/index.js +288 -29
  45. package/dist/input-handlers/index.d.ts +7 -0
  46. package/dist/input-handlers/index.js +7 -0
  47. package/dist/input-handlers/memory-handler.d.ts +26 -0
  48. package/dist/input-handlers/memory-handler.js +68 -0
  49. package/dist/repl-helpers.d.ts +63 -0
  50. package/dist/repl-helpers.js +318 -0
  51. package/dist/repl-v2.d.ts +155 -0
  52. package/dist/repl-v2.js +774 -0
  53. package/dist/repl.d.ts +32 -4
  54. package/dist/repl.js +250 -977
  55. package/dist/settings/index.d.ts +23 -0
  56. package/dist/settings/index.js +48 -0
  57. package/dist/settings/paths.d.ts +110 -0
  58. package/dist/settings/paths.js +264 -0
  59. package/dist/templates/compilr-md.js +7 -4
  60. package/dist/templates/index.js +3 -4
  61. package/dist/themes/colors.js +3 -1
  62. package/dist/themes/registry.d.ts +5 -36
  63. package/dist/themes/registry.js +11 -95
  64. package/dist/themes/types.d.ts +3 -38
  65. package/dist/themes/types.js +2 -2
  66. package/dist/tools/anchor-tools.d.ts +31 -0
  67. package/dist/tools/anchor-tools.js +255 -0
  68. package/dist/tools/backlog-wrappers.d.ts +54 -0
  69. package/dist/tools/backlog-wrappers.js +338 -0
  70. package/dist/tools/backlog.js +1 -1
  71. package/dist/tools/db-tools.d.ts +65 -0
  72. package/dist/tools/db-tools.js +19 -0
  73. package/dist/tools/document-db.d.ts +43 -0
  74. package/dist/tools/document-db.js +220 -0
  75. package/dist/tools/project-db.d.ts +102 -0
  76. package/dist/tools/project-db.js +370 -0
  77. package/dist/tools/workitem-db.d.ts +103 -0
  78. package/dist/tools/workitem-db.js +549 -0
  79. package/dist/tools.js +13 -3
  80. package/dist/ui/agents-overlay-v2.d.ts +43 -0
  81. package/dist/ui/agents-overlay-v2.js +809 -0
  82. package/dist/ui/agents-overlay.d.ts +5 -5
  83. package/dist/ui/agents-overlay.js +782 -420
  84. package/dist/ui/anchors-overlay.d.ts +12 -0
  85. package/dist/ui/anchors-overlay.js +775 -0
  86. package/dist/ui/arch-type-overlay.d.ts +1 -6
  87. package/dist/ui/arch-type-overlay.js +175 -203
  88. package/dist/ui/ask-user-overlay-v2.d.ts +26 -0
  89. package/dist/ui/ask-user-overlay-v2.js +555 -0
  90. package/dist/ui/ask-user-overlay.d.ts +2 -2
  91. package/dist/ui/ask-user-overlay.js +443 -535
  92. package/dist/ui/ask-user-simple-overlay-v2.d.ts +25 -0
  93. package/dist/ui/ask-user-simple-overlay-v2.js +215 -0
  94. package/dist/ui/ask-user-simple-overlay.d.ts +2 -2
  95. package/dist/ui/ask-user-simple-overlay.js +182 -209
  96. package/dist/ui/backlog-overlay.d.ts +16 -1
  97. package/dist/ui/backlog-overlay.js +525 -659
  98. package/dist/ui/base/index.d.ts +26 -0
  99. package/dist/ui/base/index.js +33 -0
  100. package/dist/ui/base/inline-overlay-utils.d.ts +217 -0
  101. package/dist/ui/base/inline-overlay-utils.js +320 -0
  102. package/dist/ui/base/inline-overlay.d.ts +159 -0
  103. package/dist/ui/base/inline-overlay.js +257 -0
  104. package/dist/ui/base/key-utils.d.ts +15 -0
  105. package/dist/ui/base/key-utils.js +30 -0
  106. package/dist/ui/base/overlay-base-v2.d.ts +193 -0
  107. package/dist/ui/base/overlay-base-v2.js +246 -0
  108. package/dist/ui/base/overlay-base.d.ts +156 -0
  109. package/dist/ui/base/overlay-base.js +238 -0
  110. package/dist/ui/base/overlay-lifecycle.d.ts +65 -0
  111. package/dist/ui/base/overlay-lifecycle.js +159 -0
  112. package/dist/ui/base/overlay-types.d.ts +185 -0
  113. package/dist/ui/base/overlay-types.js +7 -0
  114. package/dist/ui/base/render-utils.d.ts +8 -0
  115. package/dist/ui/base/render-utils.js +11 -0
  116. package/dist/ui/base/screen-stack.d.ts +148 -0
  117. package/dist/ui/base/screen-stack.js +184 -0
  118. package/dist/ui/base/tabbed-list-overlay-v2.d.ts +103 -0
  119. package/dist/ui/base/tabbed-list-overlay-v2.js +317 -0
  120. package/dist/ui/base/tabbed-list-overlay.d.ts +153 -0
  121. package/dist/ui/base/tabbed-list-overlay.js +369 -0
  122. package/dist/ui/commands-overlay-v2.d.ts +33 -0
  123. package/dist/ui/commands-overlay-v2.js +441 -0
  124. package/dist/ui/commands-overlay.d.ts +7 -2
  125. package/dist/ui/commands-overlay.js +384 -355
  126. package/dist/ui/config-overlay.d.ts +5 -4
  127. package/dist/ui/config-overlay.js +243 -513
  128. package/dist/ui/conversation.d.ts +75 -4
  129. package/dist/ui/conversation.js +374 -161
  130. package/dist/ui/docs-overlay.d.ts +17 -0
  131. package/dist/ui/docs-overlay.js +303 -0
  132. package/dist/ui/ephemeral.d.ts +1 -1
  133. package/dist/ui/ephemeral.js +1 -1
  134. package/dist/ui/features/index.d.ts +34 -0
  135. package/dist/ui/features/index.js +34 -0
  136. package/dist/ui/features/input-feature.d.ts +85 -0
  137. package/dist/ui/features/input-feature.js +238 -0
  138. package/dist/ui/features/list-feature.d.ts +155 -0
  139. package/dist/ui/features/list-feature.js +244 -0
  140. package/dist/ui/features/pagination-feature.d.ts +154 -0
  141. package/dist/ui/features/pagination-feature.js +238 -0
  142. package/dist/ui/features/search-feature.d.ts +148 -0
  143. package/dist/ui/features/search-feature.js +185 -0
  144. package/dist/ui/features/tab-feature.d.ts +194 -0
  145. package/dist/ui/features/tab-feature.js +307 -0
  146. package/dist/ui/footer-v2.d.ts +222 -0
  147. package/dist/ui/footer-v2.js +1349 -0
  148. package/dist/ui/footer.d.ts +107 -0
  149. package/dist/ui/footer.js +359 -67
  150. package/dist/ui/guardrail-overlay.d.ts +29 -0
  151. package/dist/ui/guardrail-overlay.js +145 -0
  152. package/dist/ui/help-overlay-v2.d.ts +34 -0
  153. package/dist/ui/help-overlay-v2.js +309 -0
  154. package/dist/ui/help-overlay.d.ts +16 -0
  155. package/dist/ui/help-overlay.js +316 -0
  156. package/dist/ui/index.d.ts +1 -1
  157. package/dist/ui/index.js +1 -3
  158. package/dist/ui/init-overlay-v2.d.ts +34 -0
  159. package/dist/ui/init-overlay-v2.js +600 -0
  160. package/dist/ui/init-overlay.d.ts +12 -2
  161. package/dist/ui/init-overlay.js +349 -270
  162. package/dist/ui/input-prompt-v2.d.ts +1 -0
  163. package/dist/ui/input-prompt-v2.js +14 -6
  164. package/dist/ui/input-prompt.d.ts +116 -33
  165. package/dist/ui/input-prompt.js +536 -337
  166. package/dist/ui/iteration-limit-overlay-v2.d.ts +21 -0
  167. package/dist/ui/iteration-limit-overlay-v2.js +114 -0
  168. package/dist/ui/iteration-limit-overlay.d.ts +2 -2
  169. package/dist/ui/iteration-limit-overlay.js +92 -128
  170. package/dist/ui/keys-overlay-v2.d.ts +41 -0
  171. package/dist/ui/keys-overlay-v2.js +248 -0
  172. package/dist/ui/keys-overlay.d.ts +1 -0
  173. package/dist/ui/keys-overlay.js +203 -141
  174. package/dist/ui/line-utils.d.ts +88 -0
  175. package/dist/ui/line-utils.js +150 -0
  176. package/dist/ui/live-region.d.ts +161 -0
  177. package/dist/ui/live-region.js +387 -0
  178. package/dist/ui/mascot/expressions.d.ts +32 -0
  179. package/dist/ui/mascot/expressions.js +213 -0
  180. package/dist/ui/mascot/index.d.ts +8 -0
  181. package/dist/ui/mascot/index.js +8 -0
  182. package/dist/ui/mascot/renderer.d.ts +19 -0
  183. package/dist/ui/mascot/renderer.js +97 -0
  184. package/dist/ui/mascot-overlay-v2.d.ts +41 -0
  185. package/dist/ui/mascot-overlay-v2.js +138 -0
  186. package/dist/ui/mascot-overlay.d.ts +21 -0
  187. package/dist/ui/mascot-overlay.js +146 -0
  188. package/dist/ui/model-overlay-v2.d.ts +49 -0
  189. package/dist/ui/model-overlay-v2.js +118 -0
  190. package/dist/ui/model-overlay.d.ts +27 -0
  191. package/dist/ui/model-overlay.js +221 -0
  192. package/dist/ui/model-warning-overlay.js +3 -5
  193. package/dist/ui/new-overlay.d.ts +34 -0
  194. package/dist/ui/new-overlay.js +604 -0
  195. package/dist/ui/overlay/impl/agents-overlay-v2.d.ts +45 -0
  196. package/dist/ui/overlay/impl/agents-overlay-v2.js +825 -0
  197. package/dist/ui/overlay/impl/anchors-overlay-v2.d.ts +47 -0
  198. package/dist/ui/overlay/impl/anchors-overlay-v2.js +783 -0
  199. package/dist/ui/overlay/impl/arch-type-overlay-v2.d.ts +37 -0
  200. package/dist/ui/overlay/impl/arch-type-overlay-v2.js +240 -0
  201. package/dist/ui/overlay/impl/ask-user-overlay-v2.d.ts +72 -0
  202. package/dist/ui/overlay/impl/ask-user-overlay-v2.js +584 -0
  203. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.d.ts +46 -0
  204. package/dist/ui/overlay/impl/ask-user-simple-overlay-v2.js +204 -0
  205. package/dist/ui/overlay/impl/backlog-overlay-v2.d.ts +49 -0
  206. package/dist/ui/overlay/impl/backlog-overlay-v2.js +642 -0
  207. package/dist/ui/overlay/impl/commands-overlay-v2.d.ts +33 -0
  208. package/dist/ui/overlay/impl/commands-overlay-v2.js +441 -0
  209. package/dist/ui/overlay/impl/config-overlay-v2.d.ts +100 -0
  210. package/dist/ui/overlay/impl/config-overlay-v2.js +654 -0
  211. package/dist/ui/overlay/impl/dashboard-overlay-v2.d.ts +55 -0
  212. package/dist/ui/overlay/impl/dashboard-overlay-v2.js +359 -0
  213. package/dist/ui/overlay/impl/docs-overlay-v2.d.ts +45 -0
  214. package/dist/ui/overlay/impl/docs-overlay-v2.js +114 -0
  215. package/dist/ui/overlay/impl/document-detail-overlay-v2.d.ts +77 -0
  216. package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1071 -0
  217. package/dist/ui/overlay/impl/guardrail-overlay-v2.d.ts +43 -0
  218. package/dist/ui/overlay/impl/guardrail-overlay-v2.js +114 -0
  219. package/dist/ui/overlay/impl/help-overlay-v2.d.ts +34 -0
  220. package/dist/ui/overlay/impl/help-overlay-v2.js +309 -0
  221. package/dist/ui/overlay/impl/init-overlay-v2.d.ts +77 -0
  222. package/dist/ui/overlay/impl/init-overlay-v2.js +593 -0
  223. package/dist/ui/overlay/impl/init-setup-overlay-v2.d.ts +25 -0
  224. package/dist/ui/overlay/impl/init-setup-overlay-v2.js +97 -0
  225. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.d.ts +35 -0
  226. package/dist/ui/overlay/impl/iteration-limit-overlay-v2.js +105 -0
  227. package/dist/ui/overlay/impl/keys-overlay-v2.d.ts +41 -0
  228. package/dist/ui/overlay/impl/keys-overlay-v2.js +248 -0
  229. package/dist/ui/overlay/impl/mascot-overlay-v2.d.ts +41 -0
  230. package/dist/ui/overlay/impl/mascot-overlay-v2.js +138 -0
  231. package/dist/ui/overlay/impl/model-overlay-v2.d.ts +49 -0
  232. package/dist/ui/overlay/impl/model-overlay-v2.js +118 -0
  233. package/dist/ui/overlay/impl/model-warning-overlay-v2.d.ts +46 -0
  234. package/dist/ui/overlay/impl/model-warning-overlay-v2.js +132 -0
  235. package/dist/ui/overlay/impl/new-overlay-v2.d.ts +77 -0
  236. package/dist/ui/overlay/impl/new-overlay-v2.js +593 -0
  237. package/dist/ui/overlay/impl/permission-overlay-v2.d.ts +36 -0
  238. package/dist/ui/overlay/impl/permission-overlay-v2.js +380 -0
  239. package/dist/ui/overlay/impl/projects-overlay-v2.d.ts +36 -0
  240. package/dist/ui/overlay/impl/projects-overlay-v2.js +499 -0
  241. package/dist/ui/overlay/impl/theme-overlay-v2.d.ts +42 -0
  242. package/dist/ui/overlay/impl/theme-overlay-v2.js +135 -0
  243. package/dist/ui/overlay/impl/tools-overlay-v2.d.ts +47 -0
  244. package/dist/ui/overlay/impl/tools-overlay-v2.js +218 -0
  245. package/dist/ui/overlay/impl/tutorial-overlay-v2.d.ts +31 -0
  246. package/dist/ui/overlay/impl/tutorial-overlay-v2.js +1035 -0
  247. package/dist/ui/overlay/impl/workflow-overlay-v2.d.ts +80 -0
  248. package/dist/ui/overlay/impl/workflow-overlay-v2.js +637 -0
  249. package/dist/ui/overlay/index.d.ts +33 -0
  250. package/dist/ui/overlay/index.js +35 -0
  251. package/dist/ui/overlay/key-utils.d.ts +6 -0
  252. package/dist/ui/overlay/key-utils.js +6 -0
  253. package/dist/ui/overlay/overlay-types.d.ts +128 -0
  254. package/dist/ui/overlay/overlay-types.js +22 -0
  255. package/dist/ui/overlay/types.d.ts +135 -0
  256. package/dist/ui/overlay/types.js +22 -0
  257. package/dist/ui/overlays/help-overlay-v2.d.ts +28 -0
  258. package/dist/ui/overlays/help-overlay-v2.js +198 -0
  259. package/dist/ui/overlays/index.d.ts +11 -0
  260. package/dist/ui/overlays/index.js +11 -0
  261. package/dist/ui/overlays.d.ts +0 -4
  262. package/dist/ui/overlays.js +0 -444
  263. package/dist/ui/permission-overlay-v2.d.ts +36 -0
  264. package/dist/ui/permission-overlay-v2.js +380 -0
  265. package/dist/ui/permission-overlay.d.ts +1 -1
  266. package/dist/ui/permission-overlay.js +186 -298
  267. package/dist/ui/projects-overlay.d.ts +19 -0
  268. package/dist/ui/projects-overlay.js +484 -0
  269. package/dist/ui/providers/types.d.ts +178 -0
  270. package/dist/ui/providers/types.js +9 -0
  271. package/dist/ui/render-modes.d.ts +36 -0
  272. package/dist/ui/render-modes.js +44 -0
  273. package/dist/ui/startup-menu.d.ts +36 -0
  274. package/dist/ui/startup-menu.js +236 -0
  275. package/dist/ui/subagent-renderer.d.ts +117 -0
  276. package/dist/ui/subagent-renderer.js +334 -0
  277. package/dist/ui/terminal-codes.d.ts +94 -0
  278. package/dist/ui/terminal-codes.js +124 -0
  279. package/dist/ui/terminal-renderer.d.ts +221 -0
  280. package/dist/ui/terminal-renderer.js +751 -0
  281. package/dist/ui/terminal-ui.d.ts +463 -0
  282. package/dist/ui/terminal-ui.js +2296 -0
  283. package/dist/ui/terminal.d.ts +20 -0
  284. package/dist/ui/terminal.js +72 -0
  285. package/dist/ui/theme-overlay-v2.d.ts +42 -0
  286. package/dist/ui/theme-overlay-v2.js +135 -0
  287. package/dist/ui/theme-overlay.d.ts +24 -0
  288. package/dist/ui/theme-overlay.js +127 -0
  289. package/dist/ui/todo-zone.js +53 -25
  290. package/dist/ui/tool-formatters.d.ts +16 -0
  291. package/dist/ui/tool-formatters.js +516 -0
  292. package/dist/ui/tools-overlay-v2.d.ts +47 -0
  293. package/dist/ui/tools-overlay-v2.js +218 -0
  294. package/dist/ui/tools-overlay.d.ts +10 -2
  295. package/dist/ui/tools-overlay.js +172 -220
  296. package/dist/ui/tutorial-overlay-v2.d.ts +31 -0
  297. package/dist/ui/tutorial-overlay-v2.js +1035 -0
  298. package/dist/ui/tutorial-overlay.d.ts +1 -0
  299. package/dist/ui/tutorial-overlay.js +400 -302
  300. package/dist/ui/workflow-overlay.d.ts +22 -0
  301. package/dist/ui/workflow-overlay.js +636 -0
  302. package/dist/utils/debug-log.d.ts +28 -0
  303. package/dist/utils/debug-log.js +57 -0
  304. package/dist/utils/model-tiers.js +1 -1
  305. package/dist/utils/path-safety.d.ts +56 -0
  306. package/dist/utils/path-safety.js +239 -0
  307. package/dist/workflow/guided-mode-injector.d.ts +42 -0
  308. package/dist/workflow/guided-mode-injector.js +191 -0
  309. package/dist/workflow/index.d.ts +8 -0
  310. package/dist/workflow/index.js +8 -0
  311. package/dist/workflow/step-criteria.d.ts +62 -0
  312. package/dist/workflow/step-criteria.js +150 -0
  313. package/dist/workflow/step-tracker.d.ts +92 -0
  314. package/dist/workflow/step-tracker.js +141 -0
  315. package/package.json +12 -5
package/dist/repl.js CHANGED
@@ -10,320 +10,34 @@
10
10
  * - Input queueing during agent execution
11
11
  * - Esc to cancel running agent
12
12
  */
13
- import { builtinSkills } from '@compilr-dev/agents';
14
13
  import { exec } from 'child_process';
15
14
  import { getToolPermissionInfo } from './agent.js';
16
15
  import * as fs from 'fs';
17
16
  import * as path from 'path';
18
17
  import pc from 'picocolors';
19
18
  import { debug, debugError } from './debug.js';
19
+ import { debugLog } from './utils/debug-log.js';
20
20
  import { getNextMode, MODE_INFO } from './ui/types.js';
21
21
  import { createInitialState, updateTodos, setAgentRunning } from './state.js';
22
22
  import { Footer } from './ui/footer.js';
23
23
  import * as conversation from './ui/conversation.js';
24
24
  import * as overlays from './ui/overlays.js';
25
25
  import * as terminal from './ui/terminal.js';
26
+ import { TerminalRenderer, RenderMode } from './ui/terminal-renderer.js';
26
27
  import { resolveCommand } from './commands.js';
27
- import { showAgentsOverlay } from './ui/agents-overlay.js';
28
- import { showCommandsOverlay } from './ui/commands-overlay.js';
29
- import { showConfigOverlay } from './ui/config-overlay.js';
30
- import { showInitOverlay } from './ui/init-overlay.js';
31
- import { showBacklogOverlay } from './ui/backlog-overlay.js';
32
- import { showKeysOverlay } from './ui/keys-overlay.js';
33
- import { showTutorialOverlay } from './ui/tutorial-overlay.js';
28
+ // Note: All overlay imports (showConfigOverlay, showModelOverlay, showThemeOverlay, showMascotOverlay, etc.)
29
+ // are now handled by command handlers in ./commands/handlers/
34
30
  import { showToolsOverlay } from './ui/tools-overlay.js';
35
- import { showModelWarningOverlay } from './ui/model-warning-overlay.js';
36
- import { showArchTypeOverlay } from './ui/arch-type-overlay.js';
37
31
  import { registerFooterCallbacks } from './ui/overlay-controller.js';
38
- import { getCustomCommandRegistry } from './commands/index.js';
32
+ import { getCustomCommandRegistry, commandRegistry, registerAllHandlers } from './commands/index.js';
39
33
  import { getPermissionMode } from './settings/index.js';
40
- import { getModelTier, modelMeetsTier } from './utils/model-tiers.js';
41
- import { findBacklogPath, parseBacklogItems } from './tools/backlog.js';
34
+ // Note: findBacklogPath, parseBacklogItems moved to command handlers
42
35
  import { isOverlayActive } from './shared-handlers.js';
43
- import { loadCompilrConfig } from './utils/project-status.js';
44
- // =============================================================================
45
- // Tool Intent Detection
46
- // =============================================================================
47
- // Keyword patterns for tool selection
48
- const TOOL_KEYWORDS = {
49
- read_file: ['read', 'show', 'display', 'look at', 'view', 'what is in', 'content of'],
50
- write_file: ['write', 'create', 'save', 'make a file'],
51
- edit: ['edit', 'modify', 'change', 'update', 'fix', 'replace'],
52
- bash: ['run', 'execute', 'command', 'shell', 'npm', 'git', 'pip', 'ls', 'pwd', 'mkdir', 'build', 'install'],
53
- grep: ['search', 'find', 'grep', 'look for', 'where is', 'occurrences'],
54
- glob: ['list files', 'find files', 'what files', 'show files', 'directory', 'pattern'],
55
- todo_write: ['todo', 'task', 'plan', 'steps'],
56
- todo_read: ['todo', 'task', 'plan', 'steps'],
57
- backlog_read: ['backlog', 'requirements', 'features', 'bugs', 'items'],
58
- backlog_write: ['backlog', 'requirements', 'features', 'bugs', 'add item', 'add feature'],
59
- git_status: ['git status', 'changes', 'modified', 'staged'],
60
- git_diff: ['git diff', 'differences', 'what changed'],
61
- git_log: ['git log', 'history', 'commits'],
62
- git_commit: ['commit', 'save changes'],
63
- git_branch: ['branch', 'branches'],
64
- detect_project: ['project type', 'what kind of project', 'framework'],
65
- find_project_root: ['project root', 'root directory'],
66
- run_tests: ['test', 'tests', 'run tests', 'check tests'],
67
- run_lint: ['lint', 'linting', 'check code', 'style'],
68
- };
69
- /**
70
- * Select relevant tool names based on user input intent
71
- */
72
- function selectToolNamesByIntent(input, allToolNames) {
73
- const lower = input.toLowerCase();
74
- // If input is very short or a question, use all tools
75
- if (input.length < 10 || input.includes('?')) {
76
- return allToolNames;
77
- }
78
- const selected = new Set();
79
- // Add tools based on keyword matches
80
- for (const [tool, keywords] of Object.entries(TOOL_KEYWORDS)) {
81
- if (keywords.some((kw) => lower.includes(kw)) && allToolNames.includes(tool)) {
82
- selected.add(tool);
83
- }
84
- }
85
- // Always include todo tools (they're used implicitly)
86
- if (allToolNames.includes('todo_write'))
87
- selected.add('todo_write');
88
- if (allToolNames.includes('todo_read'))
89
- selected.add('todo_read');
90
- // If nothing matched, use all tools
91
- if (selected.size === 0) {
92
- return allToolNames;
93
- }
94
- return Array.from(selected);
95
- }
96
- // =============================================================================
97
- // Build Command Types and Constants
98
- // =============================================================================
99
- const PRIORITY_ORDER = ['critical', 'high', 'medium', 'low'];
100
- // =============================================================================
101
- // Project Detection Helpers
102
- // =============================================================================
103
- /**
104
- * Detect if we're in a compilr project (has .compilr folder or -docs sibling)
105
- * Returns project path from config.json if available
106
- */
107
- function detectCompilrProject() {
108
- const cwd = process.cwd();
109
- // First try to load config and get paths from there
110
- const config = loadCompilrConfig(cwd);
111
- if (config) {
112
- return {
113
- found: true,
114
- backlogPath: findBacklogPath(cwd),
115
- projectPath: config.paths.project || null,
116
- docsPath: config.paths.docs,
117
- };
118
- }
119
- // Check for .compilr folder in current directory
120
- const compilrDir = path.join(cwd, '.compilr');
121
- if (fs.existsSync(compilrDir)) {
122
- const backlogPath = path.join(compilrDir, 'backlog.md');
123
- return {
124
- found: true,
125
- backlogPath: fs.existsSync(backlogPath) ? backlogPath : null,
126
- projectPath: cwd,
127
- docsPath: null,
128
- };
129
- }
130
- // Check for -docs sibling folder (two-repo pattern)
131
- const parentDir = path.dirname(cwd);
132
- const projectName = path.basename(cwd);
133
- const docsDir = path.join(parentDir, `${projectName}-docs`);
134
- const docsBacklogPath = path.join(docsDir, '01-planning', 'backlog.md');
135
- if (fs.existsSync(docsBacklogPath)) {
136
- return {
137
- found: true,
138
- backlogPath: docsBacklogPath,
139
- projectPath: cwd,
140
- docsPath: docsDir,
141
- };
142
- }
143
- // Check if we're in the docs repo itself
144
- const inDocsBacklog = path.join(cwd, '01-planning', 'backlog.md');
145
- if (fs.existsSync(inDocsBacklog)) {
146
- // We're in -docs folder, try to find sibling code folder
147
- const baseName = projectName.replace(/-docs$/, '');
148
- const codeDir = path.join(parentDir, baseName);
149
- return {
150
- found: true,
151
- backlogPath: inDocsBacklog,
152
- projectPath: fs.existsSync(codeDir) ? codeDir : null,
153
- docsPath: cwd,
154
- };
155
- }
156
- // Check for project subfolders (running from parent folder after /init)
157
- try {
158
- const entries = fs.readdirSync(cwd, { withFileTypes: true });
159
- for (const entry of entries) {
160
- if (!entry.isDirectory())
161
- continue;
162
- // Check for .compilr in subfolder (single-repo pattern)
163
- const subCompilrDir = path.join(cwd, entry.name, '.compilr');
164
- if (fs.existsSync(subCompilrDir)) {
165
- const backlogPath = path.join(subCompilrDir, 'backlog.md');
166
- return {
167
- found: true,
168
- backlogPath: fs.existsSync(backlogPath) ? backlogPath : null,
169
- projectPath: path.join(cwd, entry.name),
170
- docsPath: null,
171
- };
172
- }
173
- // Check for -docs subfolder (two-repo pattern)
174
- if (entry.name.endsWith('-docs')) {
175
- const docsBacklog = path.join(cwd, entry.name, '01-planning', 'backlog.md');
176
- if (fs.existsSync(docsBacklog)) {
177
- const baseName = entry.name.replace(/-docs$/, '');
178
- const codeDir = path.join(cwd, baseName);
179
- return {
180
- found: true,
181
- backlogPath: docsBacklog,
182
- projectPath: fs.existsSync(codeDir) ? codeDir : null,
183
- docsPath: path.join(cwd, entry.name),
184
- };
185
- }
186
- }
187
- }
188
- }
189
- catch {
190
- // Ignore read errors
191
- }
192
- return { found: false, backlogPath: null, projectPath: null, docsPath: null };
193
- }
194
- /**
195
- * Check if backlog has any items using the shared parser
196
- */
197
- function hasBacklogItems(backlogPath) {
198
- if (!backlogPath || !fs.existsSync(backlogPath)) {
199
- return false;
200
- }
201
- try {
202
- const content = fs.readFileSync(backlogPath, 'utf-8');
203
- const items = parseBacklogItems(content);
204
- return items.length > 0;
205
- }
206
- catch {
207
- return false;
208
- }
209
- }
210
- /**
211
- * Get skill prompt by name from builtinSkills
212
- */
213
- function getSkillPrompt(name) {
214
- const skill = builtinSkills.find((s) => s.name === name);
215
- return skill?.prompt ?? null;
216
- }
217
- // =============================================================================
218
- // Build Command Helpers
219
- // =============================================================================
220
- /**
221
- * Select the best backlog item to build.
222
- * If requestedId is provided, finds that specific item.
223
- * Otherwise, auto-picks the highest priority 📋 item.
224
- */
225
- function selectBuildItem(items, requestedId) {
226
- // If specific ID requested, find it
227
- if (requestedId && requestedId.toLowerCase() !== 'scaffold') {
228
- const searchId = requestedId.toUpperCase();
229
- const item = items.find(i => i.id === searchId);
230
- if (!item)
231
- return null;
232
- if (item.status !== '📋') {
233
- // Item exists but not in planned status
234
- return null; // Handler will show appropriate message
235
- }
236
- return item;
237
- }
238
- // Filter to only 📋 (planned) items
239
- const planned = items.filter(i => i.status === '📋');
240
- if (planned.length === 0)
241
- return null;
242
- // Sort by priority, then by ID
243
- planned.sort((a, b) => {
244
- const prioA = PRIORITY_ORDER.indexOf(a.priority.toLowerCase());
245
- const prioB = PRIORITY_ORDER.indexOf(b.priority.toLowerCase());
246
- if (prioA !== prioB)
247
- return prioA - prioB;
248
- return a.id.localeCompare(b.id); // REQ-001 before REQ-002
249
- });
250
- return planned[0];
251
- }
252
- /**
253
- * Find dependency IDs mentioned in item title/description.
254
- * Looks for patterns like "depends on REQ-001", "blocked by BUG-002", etc.
255
- */
256
- function findDependencies(item) {
257
- const text = `${item.title} ${item.description}`;
258
- const patterns = [
259
- /depends\s+on\s+([A-Z]+-\d+)/gi,
260
- /blocked\s+by\s+([A-Z]+-\d+)/gi,
261
- /requires\s+([A-Z]+-\d+)/gi,
262
- /after\s+([A-Z]+-\d+)/gi,
263
- /needs\s+([A-Z]+-\d+)/gi,
264
- ];
265
- const deps = [];
266
- for (const pattern of patterns) {
267
- const matches = text.matchAll(pattern);
268
- for (const match of matches) {
269
- deps.push(match[1].toUpperCase());
270
- }
271
- }
272
- return [...new Set(deps)];
273
- }
274
- /**
275
- * Get list of dependencies that are not yet completed.
276
- */
277
- function getUnmetDependencies(item, allItems) {
278
- const depIds = findDependencies(item);
279
- return allItems.filter(i => depIds.includes(i.id) && i.status !== '✅');
280
- }
281
- /**
282
- * Check if the project has a code foundation (src/, package.json, etc.)
283
- */
284
- function hasProjectFoundation() {
285
- const cwd = process.cwd();
286
- const indicators = [
287
- 'src',
288
- 'lib',
289
- 'app',
290
- 'package.json',
291
- 'requirements.txt',
292
- 'Cargo.toml',
293
- 'go.mod',
294
- 'pom.xml',
295
- 'build.gradle',
296
- 'setup.py',
297
- 'pyproject.toml',
298
- ];
299
- for (const indicator of indicators) {
300
- const fullPath = path.join(cwd, indicator);
301
- if (fs.existsSync(fullPath)) {
302
- return true;
303
- }
304
- }
305
- // Also check in project subfolders (for two-repo pattern)
306
- try {
307
- const entries = fs.readdirSync(cwd, { withFileTypes: true });
308
- for (const entry of entries) {
309
- if (!entry.isDirectory())
310
- continue;
311
- // Skip docs folder and hidden folders
312
- if (entry.name.endsWith('-docs') || entry.name.startsWith('.'))
313
- continue;
314
- for (const indicator of indicators) {
315
- const subPath = path.join(cwd, entry.name, indicator);
316
- if (fs.existsSync(subPath)) {
317
- return true;
318
- }
319
- }
320
- }
321
- }
322
- catch {
323
- // Ignore read errors
324
- }
325
- return false;
326
- }
36
+ import { getActiveProject } from './tools/project-db.js';
37
+ import { getAnchorManager } from './anchors/index.js';
38
+ import { isMemoryInput, handleMemoryInput } from './input-handlers/index.js';
39
+ // Helper functions extracted to reduce file size
40
+ import { selectToolNamesByIntent } from './repl-helpers.js';
327
41
  // =============================================================================
328
42
  // REPL Class
329
43
  // =============================================================================
@@ -336,19 +50,25 @@ export class REPL {
336
50
  showFiltering;
337
51
  onModeChange;
338
52
  onAgentFinish;
53
+ clearSubagentTracking;
339
54
  startTime;
55
+ initialCommand;
340
56
  state;
341
57
  footer;
58
+ renderer;
342
59
  // Session stats
343
60
  sessionInputTokens = 0;
344
61
  sessionOutputTokens = 0;
345
62
  sessionRequests = 0;
346
63
  // Running state
347
64
  isRunning = false;
65
+ shouldReturnToMenu = false;
348
66
  agentRunning = false;
349
67
  abortController = null;
350
68
  // Promise for waiting on REPL exit
351
69
  exitResolve = null;
70
+ // Text buffer flush function (set during processMessage, allows permission handler to flush)
71
+ textBufferFlusher = null;
352
72
  constructor(options) {
353
73
  this.agent = options.agent;
354
74
  this.model = options.model;
@@ -358,7 +78,9 @@ export class REPL {
358
78
  this.showFiltering = options.showFiltering ?? false;
359
79
  this.onModeChange = options.onModeChange;
360
80
  this.onAgentFinish = options.onAgentFinish;
81
+ this.clearSubagentTracking = options.clearSubagentTracking;
361
82
  this.startTime = new Date();
83
+ this.initialCommand = options.initialCommand;
362
84
  this.state = createInitialState(this.model);
363
85
  // Map permission mode setting to AgentMode
364
86
  const permissionMode = getPermissionMode();
@@ -368,6 +90,19 @@ export class REPL {
368
90
  showSeparators: true,
369
91
  initialMode,
370
92
  });
93
+ // Initialize TerminalRenderer in REPL mode
94
+ // TODO: Scroll regions disabled until incremental rebuild is complete
95
+ this.renderer = new TerminalRenderer({
96
+ initialMode: RenderMode.REPL,
97
+ useScrollRegions: false, // DISABLED: Use legacy mode for now
98
+ debug: false,
99
+ });
100
+ // Connect footer to renderer (enables external renderer mode on footer)
101
+ this.renderer.setFooter(this.footer);
102
+ // Connect conversation output to renderer
103
+ conversation.setRenderer(this.renderer);
104
+ // Note: renderer.start() is called in run(), not here
105
+ // This ensures proper startup sequence: footer.start() → renderer.start() → printWelcome()
371
106
  // Notify external state handler of initial mode
372
107
  if (this.onModeChange && initialMode !== 'normal') {
373
108
  this.onModeChange(initialMode);
@@ -378,8 +113,60 @@ export class REPL {
378
113
  if (options.onFooterReady) {
379
114
  options.onFooterReady(() => { this.footer.pauseAnimation(); }, () => { this.footer.resumeAnimation(); }, (action) => { this.footer.setSuggestion(action); });
380
115
  }
116
+ // Provide text buffer flush function (for permission handler to flush before overlay)
117
+ if (options.onTextBufferReady) {
118
+ options.onTextBufferReady(() => {
119
+ if (this.textBufferFlusher) {
120
+ this.textBufferFlusher();
121
+ }
122
+ });
123
+ }
124
+ // Provide subagent tracking callbacks (for agent to report subagent activity)
125
+ if (options.onSubagentReady) {
126
+ options.onSubagentReady({
127
+ onStart: (agentId, agentType, description) => {
128
+ this.footer.onSubagentStart(agentId, agentType, description);
129
+ },
130
+ onToolUse: (agentId, toolName, summary) => {
131
+ this.footer.onSubagentToolUse(agentId, toolName, summary);
132
+ },
133
+ onEnd: (agentId, success, tokenCount, error) => {
134
+ this.footer.onSubagentEnd(agentId, success, tokenCount, error);
135
+ },
136
+ });
137
+ }
381
138
  // Register footer callbacks for overlay controller (used by tools like ask_user)
382
139
  registerFooterCallbacks(() => { this.footer.pauseAnimation(); }, () => { this.footer.resumeAnimation(); });
140
+ // Register all command handlers (idempotent - safe to call multiple times)
141
+ registerAllHandlers();
142
+ }
143
+ /**
144
+ * Reload project-specific anchors when switching projects
145
+ * Clears old project anchors and loads new project anchors
146
+ */
147
+ reloadProjectAnchors(oldProjectId, newProjectId) {
148
+ // Clear old project anchors (if any)
149
+ if (oldProjectId) {
150
+ const clearedCount = this.agent.clearAnchors({ projectId: oldProjectId });
151
+ debug('anchors', `Cleared ${String(clearedCount)} anchors for project ${oldProjectId}`);
152
+ }
153
+ // Load new project anchors from disk
154
+ const newProjectAnchors = getAnchorManager(newProjectId).getAll({ scope: 'persistent' });
155
+ debug('anchors', `Loading ${String(newProjectAnchors.length)} anchors for project ${newProjectId}`);
156
+ // Add new project anchors to the agent
157
+ for (const anchor of newProjectAnchors) {
158
+ this.agent.addAnchor({
159
+ id: anchor.id,
160
+ content: anchor.content,
161
+ priority: anchor.priority,
162
+ scope: anchor.scope,
163
+ tags: anchor.tags,
164
+ projectId: newProjectId,
165
+ });
166
+ }
167
+ if (newProjectAnchors.length > 0) {
168
+ conversation.printInfo(` Anchors: ${String(newProjectAnchors.length)} loaded for project`);
169
+ }
383
170
  }
384
171
  /**
385
172
  * Set up Footer event handlers
@@ -394,16 +181,24 @@ export class REPL {
394
181
  });
395
182
  // Handle slash commands
396
183
  this.footer.on('command', (command, args) => {
397
- // Pause animation for overlays
398
- this.footer.pauseAnimation();
184
+ debugLog('REPL:command', `received /${command}`);
185
+ // NOTE: Don't pause footer here - let overlays pause when ready to render
186
+ // This prevents blank screen flash when overlays have slow constructors
399
187
  void this.handleCommand(command, args).then((shouldContinue) => {
400
- this.footer.resumeAnimation();
188
+ debugLog('REPL:command', 'handler complete');
401
189
  if (!shouldContinue) {
190
+ // Exiting - don't restart input, just resolve
402
191
  this.isRunning = false;
403
192
  if (this.exitResolve) {
404
193
  this.exitResolve();
405
194
  }
406
195
  }
196
+ else {
197
+ // Overlays handle their own pause/resume via lifecycle
198
+ // For non-overlay commands, we need to restart the input prompt
199
+ // (finishInput() stopped it when user pressed Enter)
200
+ this.footer.restartInput();
201
+ }
407
202
  });
408
203
  });
409
204
  // Handle Esc - abort running agent
@@ -450,18 +245,37 @@ export class REPL {
450
245
  */
451
246
  async run() {
452
247
  this.isRunning = true;
453
- // Set terminal title and clear screen
248
+ // Set terminal title, clear screen, and show welcome
454
249
  terminal.setTitle('compilr');
455
250
  terminal.clearScreen();
456
- conversation.printWelcome(this.model, this.version);
251
+ conversation.printWelcome(this.model, this.version, getActiveProject()?.displayName ?? null);
457
252
  // Start footer (begins render loop and input capture)
458
253
  this.footer.start();
254
+ // Start renderer (in legacy mode, this is mostly a no-op)
255
+ this.renderer.start();
256
+ // Set project name in footer
257
+ const activeProject = getActiveProject();
258
+ if (activeProject) {
259
+ this.footer.setProjectName(activeProject.displayName);
260
+ }
261
+ // Execute initial command if provided (e.g., /design from project menu)
262
+ if (this.initialCommand) {
263
+ const cmd = this.initialCommand;
264
+ // Give footer a moment to render before running command
265
+ setTimeout(() => {
266
+ void this.handleCommand(cmd, '');
267
+ }, 100);
268
+ }
459
269
  // Wait for exit signal
460
270
  await this.waitForExit();
461
271
  // Cleanup
462
272
  this.footer.stop();
463
- terminal.writeLine('');
464
- conversation.printInfo('Goodbye!');
273
+ this.renderer.stop();
274
+ // Only print goodbye when actually exiting (not when returning to menu)
275
+ if (!this.shouldReturnToMenu) {
276
+ terminal.writeLine('');
277
+ conversation.printInfo('Goodbye!');
278
+ }
465
279
  }
466
280
  /**
467
281
  * Wait for REPL exit
@@ -511,16 +325,28 @@ export class REPL {
511
325
  stop() {
512
326
  this.isRunning = false;
513
327
  this.footer.stop();
328
+ this.renderer.stop();
514
329
  if (this.exitResolve) {
515
330
  this.exitResolve();
516
331
  }
517
332
  }
333
+ /**
334
+ * Check if the user requested to return to the startup menu
335
+ */
336
+ wantsReturnToMenu() {
337
+ return this.shouldReturnToMenu;
338
+ }
518
339
  /**
519
340
  * Handle submitted input (processes message and queued inputs)
520
341
  */
521
342
  async handleSubmit(input) {
522
343
  // Clear any previous suggestion when new input is submitted
523
344
  this.footer.setSuggestion(null);
345
+ // Handle memory prefix: "# note" creates a persistent anchor
346
+ if (isMemoryInput(input)) {
347
+ handleMemoryInput(input, { agent: this.agent, footer: this.footer });
348
+ return;
349
+ }
524
350
  await this.processMessage(input);
525
351
  // Process queued inputs (FIFO)
526
352
  while (this.footer.hasQueuedInput() && this.isRunning) {
@@ -533,6 +359,57 @@ export class REPL {
533
359
  // ===========================================================================
534
360
  // Command Handling
535
361
  // ===========================================================================
362
+ /**
363
+ * Create command context for handler execution.
364
+ */
365
+ getCommandContext() {
366
+ return {
367
+ // Agent and model
368
+ agent: this.agent,
369
+ model: this.model,
370
+ currentModel: this.currentModel,
371
+ provider: this.provider,
372
+ version: this.version,
373
+ // UI components
374
+ footer: this.footer,
375
+ state: this.state,
376
+ // Session info
377
+ startTime: this.startTime,
378
+ sessionInputTokens: this.sessionInputTokens,
379
+ sessionOutputTokens: this.sessionOutputTokens,
380
+ sessionRequests: this.sessionRequests,
381
+ // Callbacks
382
+ onModeChange: this.onModeChange,
383
+ setShouldReturnToMenu: (value) => {
384
+ this.shouldReturnToMenu = value;
385
+ },
386
+ // REPL methods (bound to this instance)
387
+ processMessage: (message, options) => this.processMessage(message, options),
388
+ handleCommand: (cmd, cmdArgs) => this.handleCommand(cmd, cmdArgs),
389
+ setModel: (model) => {
390
+ this.setModel(model);
391
+ },
392
+ // UI helper methods
393
+ showTools: () => this.showTools(),
394
+ showTokens: () => {
395
+ this.showTokens();
396
+ },
397
+ showContext: () => {
398
+ this.showContext();
399
+ },
400
+ showStatus: () => {
401
+ this.showStatus();
402
+ },
403
+ showTodos: () => {
404
+ this.showTodos();
405
+ },
406
+ handleCompact: () => this.handleCompact(),
407
+ handleExport: (exportArgs) => this.handleExport(exportArgs),
408
+ reloadProjectAnchors: (oldId, newId) => {
409
+ this.reloadProjectAnchors(oldId, newId);
410
+ },
411
+ };
412
+ }
536
413
  /**
537
414
  * Handle slash command
538
415
  * Returns false if REPL should exit
@@ -559,639 +436,15 @@ export class REPL {
559
436
  conversation.printInfo('Type /help to see available commands');
560
437
  return true;
561
438
  }
562
- switch (resolved) {
563
- case 'agents':
564
- await showAgentsOverlay();
565
- return true;
566
- case 'backlog': {
567
- const backlogResult = await showBacklogOverlay();
568
- if (backlogResult.modified) {
569
- conversation.printSuccess('Backlog updated.');
570
- }
571
- return true;
572
- }
573
- case 'commands':
574
- await showCommandsOverlay();
575
- return true;
576
- case 'exit':
577
- return false;
578
- case 'help':
579
- await overlays.showHelp();
580
- return true;
581
- case 'init': {
582
- const initResult = await showInitOverlay();
583
- if (initResult.created) {
584
- // Show success message with created files info
585
- conversation.printSuccess(`Project created: ${initResult.projectPath ?? ''}`);
586
- if (initResult.docsPath) {
587
- conversation.printSuccess(`Docs repo created: ${initResult.docsPath}`);
588
- }
589
- conversation.printInfo('');
590
- conversation.printInfo('Next steps:');
591
- conversation.printInfo(` cd ${initResult.projectPath?.split('/').pop() ?? 'project'}`);
592
- conversation.printInfo(' npm install');
593
- conversation.printInfo(' npm run dev');
594
- conversation.printInfo('');
595
- // Resume footer animation before processing message
596
- this.footer.resumeAnimation();
597
- // Ask agent to read the COMPILR.md and understand the project
598
- const compilrMdPath = initResult.docsPath
599
- ? `${initResult.docsPath}/COMPILR.md`
600
- : `${initResult.projectPath ?? '.'}/COMPILR.md`;
601
- await this.processMessage(`I just created a new project using /init. Please read ${compilrMdPath} to understand the project context and confirm you're ready to help me build it.
602
-
603
- (Note to user: When you want to define requirements and populate the backlog, type /design)`);
604
- }
605
- return true;
606
- }
607
- case 'keys': {
608
- const keysResult = await showKeysOverlay();
609
- if (keysResult.changed) {
610
- conversation.printSuccess('API keys updated.');
611
- conversation.printInfo('Restart the CLI to use the new keys.');
612
- }
613
- return true;
614
- }
615
- case 'clear':
616
- terminal.clearScreen();
617
- conversation.printWelcome(this.model, this.version);
618
- return true;
619
- case 'compact':
620
- await this.handleCompact();
621
- return true;
622
- case 'config': {
623
- // Get context stats for usage tab
624
- const contextManager = this.agent.getContextManager();
625
- const history = this.agent.getHistory();
626
- let contextUsed = 0;
627
- let contextMax = 200000;
628
- if (contextManager) {
629
- const stats = contextManager.getStats(history.length);
630
- contextUsed = stats.currentTokens;
631
- contextMax = stats.maxTokens;
632
- }
633
- const configResult = await showConfigOverlay({
634
- version: this.version,
635
- cwd: process.cwd(),
636
- model: this.currentModel,
637
- provider: this.provider,
638
- toolCount: this.agent.getToolDefinitions().length,
639
- startTime: this.startTime,
640
- // Usage stats
641
- inputTokens: this.sessionInputTokens,
642
- outputTokens: this.sessionOutputTokens,
643
- requests: this.sessionRequests,
644
- contextUsed,
645
- contextMax,
646
- messageCount: history.length,
647
- // Model change callback for hot-switch
648
- onModelChange: (model) => {
649
- this.setModel(model);
650
- },
651
- });
652
- // Handle model change result (for hot-switch)
653
- if (configResult.modelChanged) {
654
- this.currentModel = configResult.modelChanged;
655
- }
656
- // Refresh prompt in case theme changed
657
- this.footer.refreshPrompt();
658
- return true;
659
- }
660
- case 'tools':
661
- await this.showTools();
662
- return true;
663
- case 'tokens':
664
- this.showTokens();
665
- return true;
666
- case 'context':
667
- this.showContext();
668
- return true;
669
- case 'export':
670
- await this.handleExport(args);
671
- return true;
672
- case 'model': {
673
- const modelResult = await showConfigOverlay({
674
- model: this.currentModel,
675
- provider: this.provider,
676
- initialMode: 'model-selector',
677
- // Don't print in callback - overlay cleanup would clear it
678
- // We'll print after overlay closes instead
679
- });
680
- // Handle model change result
681
- if (modelResult.modelChanged) {
682
- this.currentModel = modelResult.modelChanged;
683
- // Clear footer, print message, re-render footer
684
- this.footer.clearForOutput();
685
- conversation.printSuccess(`Model switched to: ${modelResult.modelChanged}`);
686
- this.footer.forceRender();
687
- }
688
- return true;
689
- }
690
- case 'status':
691
- this.showStatus();
692
- return true;
693
- case 'todos':
694
- this.showTodos();
695
- return true;
696
- case 'plan':
697
- // Switch to plan mode
698
- this.footer.setMode('plan');
699
- if (this.onModeChange) {
700
- this.onModeChange('plan');
701
- }
702
- conversation.printInfo('📋 Switched to Plan mode');
703
- conversation.printInfo('The agent will create execution plans without performing actions.');
704
- conversation.printInfo('Use Shift+Tab to switch back to Normal or Auto-accept mode.');
705
- return true;
706
- case 'design': {
707
- // Check if project is initialized
708
- const designProject = detectCompilrProject();
709
- if (!designProject.found) {
710
- conversation.printError('No .compilr project found. Run /init first.');
711
- return true;
712
- }
713
- // Check model tier - /design works best with large models
714
- if (!modelMeetsTier(this.currentModel, '/design')) {
715
- const modelTier = getModelTier(this.currentModel);
716
- // Show warning overlay
717
- const choice = await showModelWarningOverlay({
718
- command: '/design',
719
- currentModel: this.currentModel,
720
- currentTier: modelTier.tier,
721
- suggestedModel: modelTier.suggestedUpgrade,
722
- alternativeCommand: '/sketch',
723
- });
724
- switch (choice) {
725
- case 'cancel':
726
- return true;
727
- case 'alternative':
728
- // Run /sketch instead (command name without leading slash)
729
- return this.handleCommand('sketch', '');
730
- case 'switch':
731
- // Hot-switch to the suggested model
732
- if (modelTier.suggestedUpgrade) {
733
- this.currentModel = modelTier.suggestedUpgrade;
734
- conversation.printSuccess(`Switched to ${modelTier.suggestedUpgrade}`);
735
- conversation.printInfo('');
736
- }
737
- else {
738
- conversation.printWarning('No suggested upgrade available. Continuing with current model.');
739
- conversation.printInfo('');
740
- }
741
- break;
742
- case 'continue':
743
- // Proceed with current model
744
- break;
745
- }
746
- }
747
- // Get design skill prompt
748
- const designPrompt = getSkillPrompt('design');
749
- if (!designPrompt) {
750
- conversation.printError('Design skill not found.');
751
- return true;
752
- }
753
- // Note: Plan mode is not yet implemented, so we run in normal mode
754
- // The skill prompt guides the agent's behavior
755
- conversation.printInfo('📋 Starting project design...');
756
- conversation.printInfo('');
757
- // Resume footer animation before processing message
758
- this.footer.resumeAnimation();
759
- // Inject design skill and send initial message
760
- // Show short message to user, but send full skill prompt to agent
761
- await this.processMessage(`I want to design my project and create the backlog.
762
-
763
- ${designPrompt}
764
-
765
- Please start by using todo_write to track the design phases, then begin with Phase 1: Vision. Use the ask_user tool to gather information efficiently.`, {
766
- displayMessage: 'Start the design process for my project.',
767
- });
768
- return true;
769
- }
770
- case 'sketch': {
771
- // Check if project is initialized
772
- const sketchProject = detectCompilrProject();
773
- if (!sketchProject.found) {
774
- conversation.printError('No .compilr project found. Run /init first.');
775
- return true;
776
- }
777
- // Get sketch skill prompt
778
- const sketchPrompt = getSkillPrompt('sketch');
779
- if (!sketchPrompt) {
780
- conversation.printError('Sketch skill not found.');
781
- return true;
782
- }
783
- conversation.printInfo('✏️ Starting quick project sketch...');
784
- conversation.printInfo('');
785
- // Resume footer animation before processing message
786
- this.footer.resumeAnimation();
787
- // Inject sketch skill and send initial message
788
- // Uses ask_user_simple for simpler questions (one at a time)
789
- await this.processMessage(`I want to quickly outline my project.
790
-
791
- ${sketchPrompt}
792
-
793
- Please start by asking me about the type of application I'm building using the ask_user_simple tool.`, {
794
- displayMessage: 'Quick project outline.',
795
- });
796
- return true;
797
- }
798
- case 'refine': {
799
- // Check if project is initialized - use same path detection as /backlog
800
- const refineBacklogPath = findBacklogPath();
801
- if (!refineBacklogPath) {
802
- conversation.printError('No backlog found. Run /init first to create a project.');
803
- return true;
804
- }
805
- // Check if backlog has items
806
- if (!hasBacklogItems(refineBacklogPath)) {
807
- conversation.printError('No backlog items found. Run /design first to create initial requirements.');
808
- return true;
809
- }
810
- // Check if a specific item ID was provided (e.g., /refine REQ-001)
811
- const itemId = args.trim().toUpperCase();
812
- const isFocusedRefine = itemId && /^[A-Z]+-\d{3}$/.test(itemId);
813
- // For full refine mode (no item ID), check model tier and warn
814
- if (!isFocusedRefine && !modelMeetsTier(this.currentModel, '/refine')) {
815
- const modelTier = getModelTier(this.currentModel);
816
- const choice = await showModelWarningOverlay({
817
- command: '/refine',
818
- currentModel: this.currentModel,
819
- currentTier: modelTier.tier,
820
- suggestedModel: modelTier.suggestedUpgrade,
821
- alternativeCommand: '/refine <ITEM-ID>',
822
- });
823
- switch (choice) {
824
- case 'cancel':
825
- return true;
826
- case 'alternative':
827
- // Show hint about using specific item ID
828
- conversation.printInfo('💡 Tip: Use /refine <ITEM-ID> for focused refinement with smaller models.');
829
- conversation.printInfo(' Example: /refine REQ-001');
830
- conversation.printInfo('');
831
- return true;
832
- case 'switch':
833
- if (modelTier.suggestedUpgrade) {
834
- this.currentModel = modelTier.suggestedUpgrade;
835
- conversation.printSuccess(`Switched to ${this.currentModel}`);
836
- }
837
- break;
838
- case 'continue':
839
- // Continue with current model
840
- break;
841
- }
842
- }
843
- // Get appropriate skill prompt
844
- const skillName = isFocusedRefine ? 'refine-item' : 'refine';
845
- const refinePrompt = getSkillPrompt(skillName);
846
- if (!refinePrompt) {
847
- conversation.printError(`${skillName} skill not found.`);
848
- return true;
849
- }
850
- if (isFocusedRefine) {
851
- conversation.printInfo(`🔄 Refining item ${itemId}...`);
852
- }
853
- else {
854
- conversation.printInfo('🔄 Starting requirements refinement...');
855
- }
856
- conversation.printInfo('');
857
- // Resume footer animation before processing message
858
- this.footer.resumeAnimation();
859
- // Build message based on mode
860
- const userIntent = isFocusedRefine
861
- ? `I want to refine backlog item ${itemId}.`
862
- : 'I want to refine my project requirements.';
863
- const agentInstructions = isFocusedRefine
864
- ? `Please use backlog_read with id:"${itemId}" to get the item details, then guide me through refining it.`
865
- : 'Please start by using backlog_read with limit:10 to get an overview, then ask me what I\'d like to focus on using ask_user_simple.';
866
- await this.processMessage(`${userIntent}
867
-
868
- ${refinePrompt}
869
-
870
- ${agentInstructions}`, {
871
- displayMessage: userIntent,
872
- });
873
- return true;
874
- }
875
- case 'arch': {
876
- // Check if project is initialized
877
- const archProject = detectCompilrProject();
878
- if (!archProject.found) {
879
- conversation.printError('No .compilr project found. Run /init first.');
880
- return true;
881
- }
882
- // Check model tier - /arch requires large models
883
- if (!modelMeetsTier(this.currentModel, '/arch')) {
884
- const modelTier = getModelTier(this.currentModel);
885
- const choice = await showModelWarningOverlay({
886
- command: '/arch',
887
- currentModel: this.currentModel,
888
- currentTier: modelTier.tier,
889
- suggestedModel: modelTier.suggestedUpgrade,
890
- // No alternative command for /arch - it's inherently complex
891
- });
892
- switch (choice) {
893
- case 'cancel':
894
- return true;
895
- case 'switch':
896
- if (modelTier.suggestedUpgrade) {
897
- this.currentModel = modelTier.suggestedUpgrade;
898
- conversation.printSuccess(`Switched to ${modelTier.suggestedUpgrade}`);
899
- conversation.printInfo('');
900
- }
901
- else {
902
- conversation.printWarning('No suggested upgrade available. Continuing with current model.');
903
- conversation.printInfo('');
904
- }
905
- break;
906
- case 'continue':
907
- // Proceed with current model
908
- break;
909
- }
910
- }
911
- // Show type selection overlay
912
- const archChoice = await showArchTypeOverlay();
913
- if (!archChoice) {
914
- // User cancelled
915
- return true;
916
- }
917
- // Get architecture skill prompt
918
- const archPrompt = getSkillPrompt('architecture');
919
- if (!archPrompt) {
920
- conversation.printError('Architecture skill not found.');
921
- return true;
922
- }
923
- // Build the document type description for display
924
- const docTypeLabels = {
925
- 'adr': 'Architecture Decision Record',
926
- 'diagram': 'System Diagram',
927
- 'data-model': 'Data Model',
928
- 'api': 'API Design',
929
- 'custom': 'Custom Documentation',
930
- };
931
- const displayType = archChoice.customTopic
932
- ? `Custom: ${archChoice.customTopic}`
933
- : docTypeLabels[archChoice.type];
934
- conversation.printInfo(`📐 Creating ${displayType}...`);
935
- conversation.printInfo('');
936
- // Resume footer animation before processing message
937
- this.footer.resumeAnimation();
938
- // Replace placeholders in skill prompt
939
- const finalPrompt = archPrompt
940
- .replace('{{doc_type}}', archChoice.type)
941
- .replace('{{#if custom_topic}}Custom Topic: {{custom_topic}}{{/if}}', archChoice.customTopic ? `Custom Topic: ${archChoice.customTopic}` : '');
942
- await this.processMessage(`I want to create architecture documentation.
943
-
944
- ${finalPrompt}
945
-
946
- Please start by reading any existing PRD.md and using backlog_read to understand the project context. Then use ask_user to gather the information needed for this ${archChoice.type} document.`, {
947
- displayMessage: `Create ${displayType} documentation.`,
948
- });
949
- return true;
950
- }
951
- case 'note': {
952
- // Check if project is initialized
953
- const noteProject = detectCompilrProject();
954
- if (!noteProject.found) {
955
- conversation.printError('No .compilr project found. Run /init first.');
956
- return true;
957
- }
958
- // Get session-notes skill prompt
959
- const notePrompt = getSkillPrompt('session-notes');
960
- if (!notePrompt) {
961
- conversation.printError('Session-notes skill not found.');
962
- return true;
963
- }
964
- // Check for optional title argument
965
- const noteTitle = args.trim();
966
- conversation.printInfo('📝 Creating session note...');
967
- conversation.printInfo('');
968
- // Resume footer animation before processing message
969
- this.footer.resumeAnimation();
970
- // Build message based on whether title was provided
971
- const titleInstruction = noteTitle
972
- ? `The session title is: "${noteTitle}"`
973
- : 'Please ask me for a title using ask_user_simple, or generate one from the session summary.';
974
- await this.processMessage(`I want to create a session note capturing what we've done.
975
-
976
- ${notePrompt}
977
-
978
- ${titleInstruction}
979
-
980
- Review the conversation context to understand what was accomplished, then create the session note file.`, {
981
- displayMessage: noteTitle
982
- ? `Create session note: "${noteTitle}"`
983
- : 'Create a session note for this session.',
984
- });
985
- return true;
986
- }
987
- case 'prd': {
988
- // Check if project is initialized
989
- const prdProject = detectCompilrProject();
990
- if (!prdProject.found) {
991
- conversation.printError('No .compilr project found. Run /init first.');
992
- return true;
993
- }
994
- // Get prd skill prompt
995
- const prdPrompt = getSkillPrompt('prd');
996
- if (!prdPrompt) {
997
- conversation.printError('PRD skill not found.');
998
- return true;
999
- }
1000
- // Check for optional section argument
1001
- const prdSection = args.trim().toLowerCase();
1002
- const validSections = ['vision', 'scope', 'technical', 'success'];
1003
- const sectionInstruction = validSections.includes(prdSection)
1004
- ? `The user wants to update the "${prdSection}" section specifically.`
1005
- : 'Ask the user which section they want to update using ask_user_simple.';
1006
- conversation.printInfo('📄 Opening PRD for updates...');
1007
- conversation.printInfo('');
1008
- // Resume footer animation before processing message
1009
- this.footer.resumeAnimation();
1010
- await this.processMessage(`I want to update the Product Requirements Document.
1011
-
1012
- ${prdPrompt}
1013
-
1014
- ${sectionInstruction}
1015
-
1016
- Start by reading the existing PRD.md file to understand current state.`, {
1017
- displayMessage: prdSection
1018
- ? `Update PRD: ${prdSection} section`
1019
- : 'Update the Product Requirements Document',
1020
- });
1021
- return true;
1022
- }
1023
- case 'build': {
1024
- const itemId = args.trim() || undefined;
1025
- // Handle /build scaffold - redirect to scaffold handler
1026
- if (itemId?.toLowerCase() === 'scaffold') {
1027
- return this.handleScaffoldCommand();
1028
- }
1029
- // Check if project is initialized
1030
- const buildProject = detectCompilrProject();
1031
- if (!buildProject.found) {
1032
- conversation.printError('No .compilr project found. Run /init first.');
1033
- return true;
1034
- }
1035
- // Read backlog using the shared path detection
1036
- const buildBacklogPath = findBacklogPath();
1037
- if (!buildBacklogPath) {
1038
- conversation.printError('No backlog found. Run /design or /sketch first.');
1039
- return true;
1040
- }
1041
- // Parse backlog items
1042
- let buildItems = [];
1043
- try {
1044
- const backlogContent = fs.readFileSync(buildBacklogPath, 'utf-8');
1045
- buildItems = parseBacklogItems(backlogContent);
1046
- }
1047
- catch {
1048
- conversation.printError('Failed to read backlog file.');
1049
- return true;
1050
- }
1051
- if (buildItems.length === 0) {
1052
- conversation.printError('No backlog items found. Run /design or /sketch first.');
1053
- return true;
1054
- }
1055
- // Get project path for context
1056
- const projectPathForBuild = buildProject.projectPath || process.cwd();
1057
- const projectPathContext = `**IMPORTANT: Project Directory**
1058
- All code files MUST be created in: ${projectPathForBuild}
1059
- Do NOT create files in the current working directory if it differs from the project path.
1060
- `;
1061
- // Check foundation
1062
- const foundationExists = hasProjectFoundation();
1063
- if (!foundationExists) {
1064
- // Let agent handle foundation check via skill
1065
- conversation.printInfo('🔍 Checking project foundation...');
1066
- conversation.printInfo('');
1067
- this.footer.resumeAnimation();
1068
- await this.processMessage(`I want to build a feature from the backlog, but first check if the project has a foundation.
1069
-
1070
- ${projectPathContext}
1071
-
1072
- Use detect_project to check the current state. If there's no code foundation (no src/, no package.json or equivalent), use ask_user_simple to ask:
1073
- "No project foundation detected. Would you like to:"
1074
- - "Create scaffold first (recommended)"
1075
- - "Proceed with feature anyway"
1076
- - "Cancel"
1077
-
1078
- If user chooses scaffold, read COMPILR.md and PRD.md for tech stack info, then create the project scaffold following these guidelines:
1079
-
1080
- ${getSkillPrompt('scaffold') ?? ''}
1081
-
1082
- After scaffold is done (or if user chose to proceed anyway), continue with building the feature.`, { displayMessage: 'Check project foundation before building.' });
1083
- return true;
1084
- }
1085
- // Select item
1086
- const item = selectBuildItem(buildItems, itemId);
1087
- if (!item) {
1088
- if (itemId) {
1089
- const existingItem = buildItems.find(i => i.id.toUpperCase() === itemId.toUpperCase());
1090
- if (existingItem) {
1091
- conversation.printError(`Item ${itemId} is already ${existingItem.status === '✅' ? 'completed' : 'in progress'}.`);
1092
- }
1093
- else {
1094
- conversation.printError(`Item ${itemId} not found in backlog.`);
1095
- }
1096
- }
1097
- else {
1098
- conversation.printInfo('No pending items in backlog. All done! 🎉');
1099
- }
1100
- return true;
1101
- }
1102
- // Check dependencies
1103
- const unmetDeps = getUnmetDependencies(item, buildItems);
1104
- const depsWarning = unmetDeps.length > 0
1105
- ? `\n\n**⚠️ UNMET DEPENDENCIES:**\n${unmetDeps.map(d => `- ${d.id}: ${d.title} (${d.status})`).join('\n')}\n\nThese items are not yet completed. Ask user to confirm before proceeding.`
1106
- : '';
1107
- // Get build skill prompt and replace placeholders
1108
- const buildPromptTemplate = getSkillPrompt('build');
1109
- if (!buildPromptTemplate) {
1110
- conversation.printError('Build skill not found.');
1111
- return true;
1112
- }
1113
- const buildPrompt = buildPromptTemplate
1114
- .replace(/\{\{item_id\}\}/g, item.id)
1115
- .replace(/\{\{item_title\}\}/g, item.title)
1116
- .replace(/\{\{item_description\}\}/g, item.description)
1117
- .replace(/\{\{item_type\}\}/g, item.type)
1118
- .replace(/\{\{item_priority\}\}/g, item.priority);
1119
- conversation.printInfo(`🔨 Building ${item.id}: ${item.title}`);
1120
- conversation.printInfo('');
1121
- this.footer.resumeAnimation();
1122
- await this.processMessage(`I want to implement backlog item ${item.id}.
1123
-
1124
- ${projectPathContext}
1125
-
1126
- ${buildPrompt}${depsWarning}`, { displayMessage: `Build ${item.id}: ${item.title}` });
1127
- return true;
1128
- }
1129
- case 'scaffold': {
1130
- return this.handleScaffoldCommand();
1131
- }
1132
- case 'tutorial': {
1133
- await showTutorialOverlay();
1134
- return true;
1135
- }
1136
- default:
1137
- // This shouldn't happen if resolveCommand works correctly
1138
- conversation.printWarning(`Unknown command: /${command}`);
1139
- return true;
1140
- }
1141
- }
1142
- /**
1143
- * Handle /scaffold command (shared between /scaffold and /build scaffold)
1144
- */
1145
- async handleScaffoldCommand() {
1146
- // Check if project is initialized
1147
- const scaffoldProject = detectCompilrProject();
1148
- if (!scaffoldProject.found) {
1149
- conversation.printError('No .compilr project found. Run /init first.');
1150
- return true;
439
+ // Try command registry first (for extracted handlers)
440
+ const ctx = this.getCommandContext();
441
+ const registryResult = await commandRegistry.execute(resolved, args, ctx);
442
+ if (registryResult !== null) {
443
+ return registryResult;
1151
444
  }
1152
- // Get project path for context
1153
- const projectPathForScaffold = scaffoldProject.projectPath || process.cwd();
1154
- const scaffoldPathContext = `**IMPORTANT: Project Directory**
1155
- All code files MUST be created in: ${projectPathForScaffold}
1156
- Do NOT create files in the current working directory if it differs from the project path.
1157
- `;
1158
- // Check if foundation already exists
1159
- const foundationExists = hasProjectFoundation();
1160
- if (foundationExists) {
1161
- // Let agent confirm with user
1162
- conversation.printInfo('🔍 Project files detected...');
1163
- conversation.printInfo('');
1164
- this.footer.resumeAnimation();
1165
- await this.processMessage(`User wants to create a project scaffold, but a foundation may already exist.
1166
-
1167
- ${scaffoldPathContext}
1168
-
1169
- Use detect_project to analyze the current state. Then use ask_user_simple:
1170
- "Project files already exist. What would you like to do?"
1171
- - "Continue anyway (may overwrite)"
1172
- - "Cancel"
1173
-
1174
- If continuing, read COMPILR.md and PRD.md for tech stack info, then create the scaffold:
1175
-
1176
- ${getSkillPrompt('scaffold') ?? ''}`, { displayMessage: 'Create project scaffold (foundation exists).' });
1177
- return true;
1178
- }
1179
- // Get scaffold skill prompt
1180
- const scaffoldPrompt = getSkillPrompt('scaffold');
1181
- if (!scaffoldPrompt) {
1182
- conversation.printError('Scaffold skill not found.');
1183
- return true;
1184
- }
1185
- conversation.printInfo('🏗️ Creating project scaffold...');
1186
- conversation.printInfo('');
1187
- this.footer.resumeAnimation();
1188
- await this.processMessage(`I want to create the project scaffold (foundation).
1189
-
1190
- ${scaffoldPathContext}
1191
-
1192
- ${scaffoldPrompt}
1193
-
1194
- Read COMPILR.md and PRD.md first to understand the tech stack, then create the appropriate scaffold.`, { displayMessage: 'Create project scaffold.' });
445
+ // All commands are now handled by the registry
446
+ // This shouldn't happen if resolveCommand works correctly
447
+ conversation.printWarning(`Unknown command: /${command}`);
1195
448
  return true;
1196
449
  }
1197
450
  // ===========================================================================
@@ -1249,6 +502,10 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1249
502
  this.agentRunning = true;
1250
503
  setAgentRunning(this.state, true);
1251
504
  this.footer.setAgentRunning(true);
505
+ // Clear subagent tracking to prevent stale data from previous runs
506
+ // (both in agent.ts and in footer's SubagentRenderer)
507
+ this.clearSubagentTracking?.();
508
+ this.footer.clearSubagents();
1252
509
  this.abortController = new AbortController();
1253
510
  let totalInputTokens = 0;
1254
511
  let totalOutputTokens = 0;
@@ -1258,6 +515,16 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1258
515
  let lastToolInput = null;
1259
516
  // Event-based rendering: accumulate text, render complete blocks
1260
517
  let textAccumulator = '';
518
+ // Set up text buffer flusher so permission handler can flush before showing overlay
519
+ this.textBufferFlusher = () => {
520
+ if (textAccumulator.trim()) {
521
+ this.footer.clearForOutput();
522
+ conversation.printAssistantResponse(textAccumulator.trim());
523
+ terminal.writeLine('');
524
+ this.footer.forceRender();
525
+ textAccumulator = '';
526
+ }
527
+ };
1261
528
  debug('processMessage', 'Starting agent stream', {
1262
529
  inputLength: cleanedInput.length,
1263
530
  toolCount: selectedNames.length,
@@ -1370,48 +637,32 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1370
637
  this.footer.forceRender();
1371
638
  continue;
1372
639
  }
1373
- // Clear footer for output
1374
- this.footer.clearForOutput();
1375
- // Handle edit tool - diff was already shown on tool_start
640
+ // Handle edit tool - diff was already shown on tool_start (no output needed)
1376
641
  if (toolName === 'edit') {
1377
642
  // Just clear the tool indicator, diff was shown before permission prompt
1378
643
  this.footer.setCurrentTool(null);
1379
644
  this.footer.forceRender();
1380
645
  continue;
1381
646
  }
1382
- // Handle task tool (subagent) - show subagent info
1383
- if (toolName === 'task' && toolInput) {
1384
- const subagentType = typeof toolInput.subagent_type === 'string' ? toolInput.subagent_type : 'general';
1385
- const description = typeof toolInput.description === 'string' ? toolInput.description : '';
1386
- // Format result summary for subagent
1387
- let resultSummary = '';
1388
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
1389
- if (result && typeof result === 'object') {
1390
- const resultObj = result;
1391
- if (resultObj.error) {
1392
- const errorObj = resultObj.error;
1393
- const errorMsg = typeof errorObj.message === 'string' ? errorObj.message : JSON.stringify(resultObj.error);
1394
- resultSummary = pc.red(`Error: ${errorMsg.slice(0, 60)}`);
1395
- }
1396
- else if (resultObj.result && typeof resultObj.result === 'object') {
1397
- const r = resultObj.result;
1398
- const iterations = typeof r.iterations === 'number' ? r.iterations : 1;
1399
- // Try to get a summary from the result
1400
- if (typeof r.result === 'string') {
1401
- const preview = r.result.slice(0, 60);
1402
- resultSummary = pc.dim(`${String(iterations)} iteration(s) - ${preview}${r.result.length > 60 ? '...' : ''}`);
1403
- }
1404
- else {
1405
- resultSummary = pc.dim(`${String(iterations)} iteration(s)`);
1406
- }
1407
- }
1408
- }
1409
- // Print as "task(explore): Search for files" format
1410
- conversation.printToolExecution(`task(${pc.cyan(subagentType)})`, description, resultSummary);
647
+ // Handle task tool - print sub-agent completion summary to scrolling zone
648
+ // Format: Explore(description) \n ⎿ Done (stats)
649
+ if (toolName === 'task') {
1411
650
  this.footer.setCurrentTool(null);
651
+ // Get stats BEFORE clearing (so we have the data to print)
652
+ const stats = this.footer.getLastSubagentStats();
653
+ // Clear footer BEFORE clearing subagent data (so lastRenderHeight is still accurate)
654
+ this.footer.clearForOutput();
655
+ // Print the completion summary to scrolling zone
656
+ if (stats) {
657
+ conversation.printSubagentResponse(stats);
658
+ }
659
+ // Now clear the subagent data
660
+ this.footer.clearSubagents();
1412
661
  this.footer.forceRender();
1413
662
  continue;
1414
663
  }
664
+ // Clear footer for output (for tools that actually print output)
665
+ this.footer.clearForOutput();
1415
666
  // Build argument summary for other tools
1416
667
  let argSummary = '';
1417
668
  if (toolInput) {
@@ -1448,6 +699,23 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1448
699
  this.footer.setCurrentTool(null);
1449
700
  this.footer.forceRender();
1450
701
  }
702
+ else if (event.type === 'guardrail_warning') {
703
+ // Show guardrail warning to user (non-blocking)
704
+ const result = event.result;
705
+ const name = result.guardrail?.name ?? 'Unknown';
706
+ const message = event.message || result.guardrail?.message || 'Risky operation detected';
707
+ const category = result.guardrail?.tags?.[0] ?? 'general';
708
+ this.footer.clearForOutput();
709
+ conversation.printWarning(`[${category}] ${name}: ${message}`);
710
+ this.footer.forceRender();
711
+ }
712
+ else if (event.type === 'guardrail_blocked') {
713
+ // Show guardrail block message (operation was prevented)
714
+ const message = event.message || 'Operation blocked by guardrail';
715
+ this.footer.clearForOutput();
716
+ conversation.printError(`Blocked: ${message}`);
717
+ this.footer.forceRender();
718
+ }
1451
719
  }
1452
720
  debug('processMessage', 'Stream completed', {
1453
721
  hasTextOutput,
@@ -1481,6 +749,8 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1481
749
  this.sessionInputTokens += totalInputTokens;
1482
750
  this.sessionOutputTokens += totalOutputTokens;
1483
751
  this.sessionRequests++;
752
+ // Clear subagents BEFORE printing stats so footer won't show them afterward
753
+ this.footer.clearSubagents();
1484
754
  // Show token usage
1485
755
  if (totalInputTokens > 0 || totalOutputTokens > 0) {
1486
756
  this.footer.clearForOutput();
@@ -1489,6 +759,7 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1489
759
  conversation.printInfo(`[Tokens: ${total.toLocaleString()} (${totalInputTokens.toLocaleString()} in / ${totalOutputTokens.toLocaleString()} out) - ${String(llmCalls)} LLM call(s)]`);
1490
760
  conversation.printInfo(`[Session: ${sessionTotal.toLocaleString()} total (${String(this.sessionRequests)} requests)]`);
1491
761
  terminal.writeLine('');
762
+ this.footer.forceRender();
1492
763
  }
1493
764
  // Stop spinner and re-render footer (AFTER all output is printed)
1494
765
  this.agentRunning = false;
@@ -1509,6 +780,11 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1509
780
  setAgentRunning(this.state, false);
1510
781
  this.footer.setAgentRunning(false);
1511
782
  this.abortController = null;
783
+ // Clear text buffer flusher
784
+ this.textBufferFlusher = null;
785
+ // Ensure footer is rendered and isPaused is reset
786
+ // (clearForOutput sets isPaused=true, forceRender resets it)
787
+ this.footer.forceRender();
1512
788
  // Notify that agent has finished (for applying deferred suggestions)
1513
789
  this.onAgentFinish?.();
1514
790
  }
@@ -1519,12 +795,16 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1519
795
  async handleCompact() {
1520
796
  const contextManager = this.agent.getContextManager();
1521
797
  if (!contextManager) {
798
+ this.footer.clearForOutput();
1522
799
  conversation.printWarning('Context manager not available');
800
+ this.footer.forceRender();
1523
801
  return;
1524
802
  }
1525
803
  const history = this.agent.getHistory();
1526
804
  if (history.length === 0) {
805
+ this.footer.clearForOutput();
1527
806
  conversation.printInfo('No conversation history to compact');
807
+ this.footer.forceRender();
1528
808
  return;
1529
809
  }
1530
810
  const stats = contextManager.getStats(history.length);
@@ -1630,15 +910,8 @@ Read COMPILR.md and PRD.md first to understand the tech stack, then create the a
1630
910
  permission,
1631
911
  };
1632
912
  });
1633
- // Pause footer during overlay
1634
- this.footer.pauseAnimation();
1635
- try {
1636
- await showToolsOverlay(tools);
1637
- }
1638
- finally {
1639
- this.footer.resumeAnimation();
1640
- this.footer.forceRender();
1641
- }
913
+ // Overlay lifecycle handles footer pause/resume
914
+ await showToolsOverlay(tools);
1642
915
  }
1643
916
  showTokens() {
1644
917
  this.footer.clearForOutput();