@pugi/cli 0.1.0-beta.7 → 0.1.0-beta.87

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 (402) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/THIRD_PARTY_NOTICES.md +40 -0
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/deploy.js +40 -40
  6. package/dist/commands/flatten.js +191 -0
  7. package/dist/commands/jobs-watch.js +201 -0
  8. package/dist/commands/jobs.js +42 -27
  9. package/dist/commands/smoke.js +133 -0
  10. package/dist/core/agent-progress/cleanup.js +134 -0
  11. package/dist/core/agent-progress/schema.js +144 -0
  12. package/dist/core/agent-progress/writer.js +101 -0
  13. package/dist/core/agents/adaptive-router.js +330 -0
  14. package/dist/core/agents/query-decomposer.js +297 -0
  15. package/dist/core/agents/registry.js +2 -2
  16. package/dist/core/approvals/shortcut-resolver.js +98 -0
  17. package/dist/core/artifact-chain/dispatcher.js +148 -0
  18. package/dist/core/artifact-chain/exporter.js +164 -0
  19. package/dist/core/artifact-chain/state.js +243 -0
  20. package/dist/core/artifact-chain/steps.js +169 -0
  21. package/dist/core/ask-user/question.js +92 -0
  22. package/dist/core/audit/audit-trail.js +275 -0
  23. package/dist/core/auth/ensure-authenticated.js +129 -0
  24. package/dist/core/auth/env-provider.js +238 -0
  25. package/dist/core/auto-open-browser.js +4 -4
  26. package/dist/core/auto-update/channels.js +122 -0
  27. package/dist/core/auto-update/checker.js +241 -0
  28. package/dist/core/auto-update/state.js +235 -0
  29. package/dist/core/bare-mode/index.js +107 -0
  30. package/dist/core/bash/redirect.js +281 -0
  31. package/dist/core/bash-classifier.js +436 -40
  32. package/dist/core/checkpoint/resumer.js +149 -0
  33. package/dist/core/checkpoint/rewinder.js +291 -0
  34. package/dist/core/checkpoints/shadow-git.js +670 -0
  35. package/dist/core/citations/parser.js +109 -0
  36. package/dist/core/classifier/yolo-classifier.js +88 -0
  37. package/dist/core/codegraph/decision-store.js +248 -0
  38. package/dist/core/codegraph/detect-repo.js +459 -0
  39. package/dist/core/codegraph/install.js +134 -0
  40. package/dist/core/codegraph/offer-hook.js +220 -0
  41. package/dist/core/compact/auto-trigger.js +96 -0
  42. package/dist/core/compact/buffer-rewriter.js +115 -0
  43. package/dist/core/compact/summarizer.js +208 -0
  44. package/dist/core/compact/token-counter.js +108 -0
  45. package/dist/core/consensus/anvil-fanout.js +25 -25
  46. package/dist/core/consensus/diff-capture.js +121 -12
  47. package/dist/core/consensus/rubric.js +21 -21
  48. package/dist/core/context/builder.js +6 -6
  49. package/dist/core/context/compaction-events.js +8 -8
  50. package/dist/core/context/compaction.js +31 -31
  51. package/dist/core/context/index.js +15 -8
  52. package/dist/core/context/invariants.js +51 -51
  53. package/dist/core/context/markdown-loader.js +28 -10
  54. package/dist/core/context/markdown-traverse.js +255 -0
  55. package/dist/core/context/pugiignore.js +41 -41
  56. package/dist/core/context/repo-skeleton.js +37 -37
  57. package/dist/core/context/tool-eviction.js +55 -0
  58. package/dist/core/context/watcher.js +32 -32
  59. package/dist/core/context/working-set.js +23 -23
  60. package/dist/core/coordinator/agent-tools.js +77 -0
  61. package/dist/core/coordinator/agent-toolset.js +65 -0
  62. package/dist/core/coordinator/fsm.js +73 -0
  63. package/dist/core/coordinator/mode-fsm.js +70 -0
  64. package/dist/core/cost/rate-card.js +129 -0
  65. package/dist/core/cost/tracker.js +221 -0
  66. package/dist/core/credentials.js +12 -12
  67. package/dist/core/cron/scheduler.js +138 -0
  68. package/dist/core/denial-tracking/index.js +8 -0
  69. package/dist/core/denial-tracking/state.js +264 -0
  70. package/dist/core/diagnostics/probe-runner.js +93 -0
  71. package/dist/core/diagnostics/probes/api.js +46 -0
  72. package/dist/core/diagnostics/probes/auth.js +93 -0
  73. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  74. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  75. package/dist/core/diagnostics/probes/config.js +72 -0
  76. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  77. package/dist/core/diagnostics/probes/disk.js +81 -0
  78. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  79. package/dist/core/diagnostics/probes/git.js +65 -0
  80. package/dist/core/diagnostics/probes/hooks.js +118 -0
  81. package/dist/core/diagnostics/probes/mcp.js +75 -0
  82. package/dist/core/diagnostics/probes/node.js +59 -0
  83. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  84. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  85. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  86. package/dist/core/diagnostics/probes/session.js +74 -0
  87. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  88. package/dist/core/diagnostics/probes/workspace.js +63 -0
  89. package/dist/core/diagnostics/types.js +70 -0
  90. package/dist/core/dispatch/cache-cleanup.js +197 -0
  91. package/dist/core/dispatch/cache-handoff.js +295 -0
  92. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  93. package/dist/core/edits/dispatch.js +293 -7
  94. package/dist/core/edits/format-matrix.js +26 -0
  95. package/dist/core/edits/fuzzy-ladder.js +650 -0
  96. package/dist/core/edits/index.js +3 -1
  97. package/dist/core/edits/journal.js +199 -0
  98. package/dist/core/edits/layer-a-apply.js +15 -15
  99. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  100. package/dist/core/edits/layer-b-apply.js +9 -9
  101. package/dist/core/edits/layer-c-apply.js +6 -6
  102. package/dist/core/edits/layer-d-ast.js +557 -14
  103. package/dist/core/edits/marker-parser.js +12 -12
  104. package/dist/core/edits/security-gate.js +27 -27
  105. package/dist/core/edits/verify-hook.js +273 -0
  106. package/dist/core/edits/worktree.js +322 -0
  107. package/dist/core/engine/anvil-client.js +140 -26
  108. package/dist/core/engine/auto-compact.js +179 -0
  109. package/dist/core/engine/budgets.js +186 -0
  110. package/dist/core/engine/context-prefix.js +155 -0
  111. package/dist/core/engine/index.js +1 -1
  112. package/dist/core/engine/intensity.js +158 -0
  113. package/dist/core/engine/intent.js +260 -0
  114. package/dist/core/engine/native-pugi.js +1295 -227
  115. package/dist/core/engine/prompts.js +134 -16
  116. package/dist/core/engine/strip-internal-fields.js +124 -0
  117. package/dist/core/engine/tool-bridge.js +1295 -59
  118. package/dist/core/evaluation/golden-dataset.js +293 -0
  119. package/dist/core/feedback/queue.js +177 -0
  120. package/dist/core/feedback/submitter.js +145 -0
  121. package/dist/core/file-cache.js +113 -1
  122. package/dist/core/flatten/flatten-repo.js +439 -0
  123. package/dist/core/format/osc8-link.js +28 -0
  124. package/dist/core/hook-chains.js +392 -0
  125. package/dist/core/hooks/citation-verify-hook.js +138 -0
  126. package/dist/core/hooks/citation-verify.js +112 -0
  127. package/dist/core/hooks/events.js +44 -0
  128. package/dist/core/hooks/index.js +15 -0
  129. package/dist/core/hooks/registry.js +213 -0
  130. package/dist/core/hooks/runner.js +236 -0
  131. package/dist/core/hooks/v2/event-emitter.js +115 -0
  132. package/dist/core/hooks/v2/executor.js +282 -0
  133. package/dist/core/hooks/v2/index.js +25 -0
  134. package/dist/core/hooks/v2/lifecycle.js +104 -0
  135. package/dist/core/hooks/v2/loader.js +216 -0
  136. package/dist/core/hooks/v2/matcher.js +125 -0
  137. package/dist/core/hooks/v2/trust.js +143 -0
  138. package/dist/core/hooks/v2/types.js +86 -0
  139. package/dist/core/image/renderer.js +71 -0
  140. package/dist/core/init/detector.js +582 -0
  141. package/dist/core/init/template-renderer.js +242 -0
  142. package/dist/core/jobs/registry.js +18 -18
  143. package/dist/core/ledger/results-tsv.js +142 -0
  144. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  145. package/dist/core/lsp/cache.js +105 -0
  146. package/dist/core/lsp/client.js +776 -0
  147. package/dist/core/lsp/language-detect.js +66 -0
  148. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  149. package/dist/core/lsp/symbol-tools.js +372 -0
  150. package/dist/core/mcp/client.js +97 -28
  151. package/dist/core/mcp/http-server.js +553 -0
  152. package/dist/core/mcp/orchestrator-tools.js +662 -0
  153. package/dist/core/mcp/permission.js +190 -0
  154. package/dist/core/mcp/registry.js +39 -17
  155. package/dist/core/mcp/server-tools.js +219 -0
  156. package/dist/core/mcp/server.js +397 -0
  157. package/dist/core/mcp/trust.js +10 -10
  158. package/dist/core/memory/dual-write.js +416 -0
  159. package/dist/core/memory/passive-extract.js +130 -0
  160. package/dist/core/memory/phase1-kinds.js +20 -0
  161. package/dist/core/memory/secret-scanner.js +304 -0
  162. package/dist/core/memory-sync/queue.js +170 -0
  163. package/dist/core/metrics/extract.js +113 -0
  164. package/dist/core/modes/roo-modes.js +68 -0
  165. package/dist/core/onboarding/ensure-initialized.js +133 -0
  166. package/dist/core/onboarding/marker.js +111 -0
  167. package/dist/core/onboarding/telemetry-state.js +108 -0
  168. package/dist/core/output-style/presets.js +176 -0
  169. package/dist/core/output-style/state.js +185 -0
  170. package/dist/core/path-security.js +287 -5
  171. package/dist/core/permission.js +82 -22
  172. package/dist/core/permissions/auto-classifier.js +124 -0
  173. package/dist/core/permissions/bash-parser.js +371 -0
  174. package/dist/core/permissions/circuit-breaker.js +83 -0
  175. package/dist/core/permissions/constrained-edit.js +91 -0
  176. package/dist/core/permissions/gate.js +278 -0
  177. package/dist/core/permissions/index.js +20 -0
  178. package/dist/core/permissions/mode.js +174 -0
  179. package/dist/core/permissions/network-egress.js +137 -0
  180. package/dist/core/permissions/state.js +241 -0
  181. package/dist/core/permissions/tool-class.js +93 -0
  182. package/dist/core/plan-mode/ui-state.js +51 -0
  183. package/dist/core/plans/plan-artifact.js +721 -0
  184. package/dist/core/policy-limits/etag-store.js +122 -0
  185. package/dist/core/prd-check/parser.js +215 -0
  186. package/dist/core/prd-check/reporter.js +127 -0
  187. package/dist/core/prd-check/session-review.js +557 -0
  188. package/dist/core/prd-check/verifiers.js +223 -0
  189. package/dist/core/prompt-cache/client-cache.js +99 -0
  190. package/dist/core/prompts/assembly.js +29 -0
  191. package/dist/core/prompts/registry.js +364 -0
  192. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  193. package/dist/core/pugi-md/context-injector.js +76 -0
  194. package/dist/core/pugi-md/walk-up.js +207 -0
  195. package/dist/core/python/uv-installer.js +270 -0
  196. package/dist/core/python/uv-resolver.js +83 -0
  197. package/dist/core/rate-limit/narrator.js +146 -0
  198. package/dist/core/recipes/cli-types.js +20 -0
  199. package/dist/core/recipes/loader.js +103 -0
  200. package/dist/core/recipes/runner.js +345 -0
  201. package/dist/core/recipes/schema.js +587 -0
  202. package/dist/core/release-notes/parser.js +241 -0
  203. package/dist/core/release-notes/state.js +116 -0
  204. package/dist/core/repl/ask.js +37 -37
  205. package/dist/core/repl/cancellation.js +26 -26
  206. package/dist/core/repl/cap-warning.js +4 -4
  207. package/dist/core/repl/clipboard-read.js +11 -11
  208. package/dist/core/repl/dispatch-fsm.js +12 -12
  209. package/dist/core/repl/history-search.js +15 -15
  210. package/dist/core/repl/history.js +28 -18
  211. package/dist/core/repl/kill-ring.js +5 -5
  212. package/dist/core/repl/model-pricing.js +135 -0
  213. package/dist/core/repl/privacy-banner.js +22 -22
  214. package/dist/core/repl/session.js +2157 -214
  215. package/dist/core/repl/slash-commands.js +533 -40
  216. package/dist/core/repl/store/index.js +1 -1
  217. package/dist/core/repl/store/jsonl-log.js +22 -22
  218. package/dist/core/repl/store/lockfile.js +10 -10
  219. package/dist/core/repl/store/session-store.js +136 -107
  220. package/dist/core/repl/store/types.js +15 -15
  221. package/dist/core/repl/store/uuid-v7.js +12 -12
  222. package/dist/core/repl/workspace-context.js +43 -21
  223. package/dist/core/repo-map/build.js +125 -0
  224. package/dist/core/repo-map/cache.js +185 -0
  225. package/dist/core/repo-map/extractor.js +254 -0
  226. package/dist/core/repo-map/formatter.js +145 -0
  227. package/dist/core/repo-map/page-rank.js +105 -0
  228. package/dist/core/repo-map/scanner.js +211 -0
  229. package/dist/core/retry-budget/budget.js +284 -0
  230. package/dist/core/retry-budget/index.js +5 -0
  231. package/dist/core/retry-budget/retry-cap.js +74 -0
  232. package/dist/core/routing/lead-worker.js +43 -0
  233. package/dist/core/routing/pre-flight-estimator.js +108 -0
  234. package/dist/core/runs/run-tree.js +103 -0
  235. package/dist/core/security/injection-scanner.js +367 -0
  236. package/dist/core/security/output-filter.js +418 -0
  237. package/dist/core/session/env-file.js +105 -0
  238. package/dist/core/session/section-budgets.js +140 -0
  239. package/dist/core/session.js +92 -0
  240. package/dist/core/settings.js +286 -5
  241. package/dist/core/share/formatter.js +271 -0
  242. package/dist/core/share/redactor.js +221 -0
  243. package/dist/core/share/uploader.js +267 -0
  244. package/dist/core/skills/defaults.js +457 -0
  245. package/dist/core/skills/loader.js +22 -22
  246. package/dist/core/skills/sources.js +27 -27
  247. package/dist/core/smoke/headless-driver.js +174 -0
  248. package/dist/core/smoke/orchestrator.js +194 -0
  249. package/dist/core/smoke/runner.js +238 -0
  250. package/dist/core/smoke/scenario-parser.js +316 -0
  251. package/dist/core/statusline.js +99 -0
  252. package/dist/core/subagents/dispatcher-real.js +600 -0
  253. package/dist/core/subagents/dispatcher.js +132 -43
  254. package/dist/core/subagents/index.js +19 -6
  255. package/dist/core/subagents/isolation-matrix.js +213 -0
  256. package/dist/core/subagents/spawn.js +19 -4
  257. package/dist/core/telemetry/emitter.js +229 -0
  258. package/dist/core/telemetry/queue.js +251 -0
  259. package/dist/core/theme/context.js +91 -0
  260. package/dist/core/theme/presets.js +228 -0
  261. package/dist/core/theme/state.js +181 -0
  262. package/dist/core/todos/invariant.js +10 -0
  263. package/dist/core/todos/state.js +177 -0
  264. package/dist/core/tool-schema/compressor.js +89 -0
  265. package/dist/core/transport/version-interceptor.js +166 -0
  266. package/dist/core/trust.js +2 -2
  267. package/dist/core/tui/thinking-block.js +64 -0
  268. package/dist/core/vim/keymap.js +288 -0
  269. package/dist/core/vim/state.js +92 -0
  270. package/dist/core/watch-markers/marker-watcher.js +133 -0
  271. package/dist/core/worktree-manager/cleanup.js +123 -0
  272. package/dist/core/worktree-manager/manager.js +303 -0
  273. package/dist/index.js +28 -0
  274. package/dist/runtime/bootstrap.js +190 -0
  275. package/dist/runtime/cli.js +4162 -488
  276. package/dist/runtime/commands/agents.js +30 -30
  277. package/dist/runtime/commands/budget.js +5 -5
  278. package/dist/runtime/commands/cancel.js +231 -0
  279. package/dist/runtime/commands/chain.js +489 -0
  280. package/dist/runtime/commands/codegraph-status.js +227 -0
  281. package/dist/runtime/commands/compact.js +297 -0
  282. package/dist/runtime/commands/config.js +32 -32
  283. package/dist/runtime/commands/cost.js +199 -0
  284. package/dist/runtime/commands/delegate.js +244 -13
  285. package/dist/runtime/commands/dispatch.js +126 -0
  286. package/dist/runtime/commands/doctor.js +579 -0
  287. package/dist/runtime/commands/feedback.js +184 -0
  288. package/dist/runtime/commands/hooks.js +184 -0
  289. package/dist/runtime/commands/init.js +254 -0
  290. package/dist/runtime/commands/lsp.js +368 -0
  291. package/dist/runtime/commands/mcp.js +879 -0
  292. package/dist/runtime/commands/memory.js +582 -0
  293. package/dist/runtime/commands/model.js +237 -0
  294. package/dist/runtime/commands/onboarding.js +275 -0
  295. package/dist/runtime/commands/patch.js +128 -0
  296. package/dist/runtime/commands/permissions.js +112 -0
  297. package/dist/runtime/commands/plan.js +143 -0
  298. package/dist/runtime/commands/prd-check.js +285 -0
  299. package/dist/runtime/commands/privacy.js +17 -17
  300. package/dist/runtime/commands/recipe.js +325 -0
  301. package/dist/runtime/commands/redo-blob-store.js +92 -0
  302. package/dist/runtime/commands/redo.js +361 -0
  303. package/dist/runtime/commands/release-notes.js +229 -0
  304. package/dist/runtime/commands/repo-map.js +95 -0
  305. package/dist/runtime/commands/report.js +299 -0
  306. package/dist/runtime/commands/resume.js +118 -0
  307. package/dist/runtime/commands/review-consensus.js +68 -53
  308. package/dist/runtime/commands/rewind.js +333 -0
  309. package/dist/runtime/commands/roster.js +14 -14
  310. package/dist/runtime/commands/sessions.js +163 -0
  311. package/dist/runtime/commands/share.js +316 -0
  312. package/dist/runtime/commands/skills.js +31 -31
  313. package/dist/runtime/commands/status.js +186 -0
  314. package/dist/runtime/commands/stickers.js +82 -0
  315. package/dist/runtime/commands/style.js +194 -0
  316. package/dist/runtime/commands/theme.js +196 -0
  317. package/dist/runtime/commands/undo.js +54 -22
  318. package/dist/runtime/commands/update.js +289 -0
  319. package/dist/runtime/commands/vim.js +140 -0
  320. package/dist/runtime/commands/worktree.js +177 -0
  321. package/dist/runtime/commands/worktrees.js +155 -0
  322. package/dist/runtime/headless-repl.js +195 -0
  323. package/dist/runtime/headless.js +543 -0
  324. package/dist/runtime/load-hooks-or-exit.js +71 -0
  325. package/dist/runtime/plan-decompose.js +531 -0
  326. package/dist/runtime/update-check.js +28 -28
  327. package/dist/runtime/version.js +65 -0
  328. package/dist/skills/bundled/batch.js +617 -0
  329. package/dist/skills/bundled/index.js +45 -0
  330. package/dist/skills/bundled/loop.js +358 -0
  331. package/dist/skills/bundled/remember.js +383 -0
  332. package/dist/skills/bundled/simplify.js +289 -0
  333. package/dist/skills/bundled/skillify.js +373 -0
  334. package/dist/skills/bundled/stuck.js +558 -0
  335. package/dist/skills/bundled/verify.js +439 -0
  336. package/dist/testing/vcr.js +486 -0
  337. package/dist/tools/agent-tool.js +229 -0
  338. package/dist/tools/apply-patch.js +556 -0
  339. package/dist/tools/ask-user-question.js +222 -0
  340. package/dist/tools/ask-user.js +115 -0
  341. package/dist/tools/bash.js +623 -45
  342. package/dist/tools/brief.js +224 -0
  343. package/dist/tools/enter-worktree.js +250 -0
  344. package/dist/tools/exit-worktree.js +147 -0
  345. package/dist/tools/file-tools.js +161 -44
  346. package/dist/tools/lsp-tools.js +189 -0
  347. package/dist/tools/mcp-tool.js +260 -0
  348. package/dist/tools/multi-edit.js +361 -0
  349. package/dist/tools/powershell.js +268 -0
  350. package/dist/tools/registry.js +85 -0
  351. package/dist/tools/skill-tool.js +96 -0
  352. package/dist/tools/sleep.js +99 -0
  353. package/dist/tools/synthetic-output.js +133 -0
  354. package/dist/tools/tasks.js +208 -0
  355. package/dist/tools/todo-write.js +184 -0
  356. package/dist/tools/verify-plan-execution.js +295 -0
  357. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  358. package/dist/tools/web-fetch.js +195 -10
  359. package/dist/tools/web-search.js +458 -0
  360. package/dist/tui/agent-progress-card.js +111 -0
  361. package/dist/tui/agent-tree.js +11 -1
  362. package/dist/tui/ask-modal.js +14 -14
  363. package/dist/tui/ask-user-question-prompt.js +203 -0
  364. package/dist/tui/compact-banner.js +81 -0
  365. package/dist/tui/conversation-pane.js +85 -11
  366. package/dist/tui/cost-table.js +111 -0
  367. package/dist/tui/device-flow.js +2 -2
  368. package/dist/tui/doctor-table.js +46 -0
  369. package/dist/tui/feedback-prompt.js +156 -0
  370. package/dist/tui/input-box.js +247 -32
  371. package/dist/tui/login-picker.js +3 -3
  372. package/dist/tui/markdown-render.js +6 -6
  373. package/dist/tui/onboarding-wizard.js +240 -0
  374. package/dist/tui/permissions-picker.js +86 -0
  375. package/dist/tui/render.js +35 -0
  376. package/dist/tui/repl-render.js +332 -54
  377. package/dist/tui/repl-splash-art.js +16 -16
  378. package/dist/tui/repl-splash-mascot.js +48 -24
  379. package/dist/tui/repl-splash.js +22 -22
  380. package/dist/tui/repl.js +124 -44
  381. package/dist/tui/slash-palette.js +6 -6
  382. package/dist/tui/splash.js +2 -2
  383. package/dist/tui/status-bar.js +109 -31
  384. package/dist/tui/status-table.js +7 -0
  385. package/dist/tui/stickers-art.js +136 -0
  386. package/dist/tui/style-table.js +28 -0
  387. package/dist/tui/theme-table.js +29 -0
  388. package/dist/tui/thinking-spinner.js +123 -0
  389. package/dist/tui/tool-stream-pane.js +53 -4
  390. package/dist/tui/update-banner.js +27 -2
  391. package/dist/tui/vim-input.js +267 -0
  392. package/dist/tui/welcome-banner.js +107 -0
  393. package/dist/tui/welcome-data.js +293 -0
  394. package/dist/tui/workspace-context.js +2 -2
  395. package/docs/examples/codegraph.mcp.json +10 -0
  396. package/package.json +23 -6
  397. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  398. package/test/scenarios/compact-force.scenario.txt +11 -0
  399. package/test/scenarios/identity.scenario.txt +11 -0
  400. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  401. package/test/scenarios/walkback.scenario.txt +12 -0
  402. package/dist/core/engine/compaction-hook.js +0 -154
@@ -1,5 +1,12 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
+ import { formatCostUsd, formatTokens } from '../core/repl/model-pricing.js';
4
+ /**
5
+ * Window during which the per-turn delta flash stays visible on the
6
+ * cost-meter row. CEO spec: ~2 seconds after completion. Past that, the
7
+ * flash dimms out and the row shows session totals only.
8
+ */
9
+ const TURN_DELTA_FLASH_MS = 2_000;
3
10
  /**
4
11
  * Cyan dot glyphs across the pulse cycle. Three steps keep the motion
5
12
  * subtle - a true gradient would force an Ink rerender on every
@@ -12,13 +19,78 @@ export function StatusBar(props) {
12
19
  const tokenLabel = formatTokens(props.tokensDownstreamTotal);
13
20
  const phase = clampPhase(props.pulsePhase);
14
21
  const glyph = PULSE_DOTS[Math.min(phase, PULSE_DOTS.length - 1)] ?? PULSE_DOTS[0];
15
- // α6.9: composite status label — connection problems trump dispatch
22
+ // : composite status label — connection problems trump dispatch
16
23
  // state because the operator needs to know about a dropped admin-api
17
24
  // first. When the connection is healthy (`on_watch` / `connecting`),
18
25
  // the FSM dispatch state takes over to show the dispatch lifecycle
19
26
  // (`dispatching` / `tool: read` / `aborting` / etc.).
20
- const status = composeStatusLabel(props.connection, props.dispatchState, props.dispatchToolLabel);
21
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: status.color, children: `${glyph ?? '●'} ${status.label}` }), _jsx(Text, { dimColor: true, children: ` · ${props.activeAgentCount} agents · ` }), _jsx(Text, { children: `↓ ${tokenLabel} tokens` }), _jsx(Text, { dimColor: true, children: ` · ${elapsedLabel}` })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${formatCount(props.pugiMdCount)} PUGI.md · ${formatCount(props.mcpServerCount)} MCP · ${formatCount(props.skillCount)} skills · ${formatQuota(props.quotaPct)} quota` }) })] }));
27
+ const status = composeStatusLabel(props.connection, props.dispatchState, props.dispatchToolLabel, props.lastCompletedOutcome);
28
+ // cost-meter sprint the cost row anchors above the legacy
29
+ // dispatch-state line so the operator's eye lands on the meter first
30
+ // (matches the upstream tool TUI footer rhythm). The session-elapsed slot
31
+ // uses sessionStartedAtEpochMs (REPL boot), distinct from the
32
+ // per-brief `elapsedLabel` on the row below.
33
+ const costRow = renderCostMeterRow(props.sessionTokensIn ?? 0, props.sessionTokensOut ?? 0, props.sessionCostUsd ?? 0, props.sessionStartedAtEpochMs, now);
34
+ const deltaFlash = renderTurnDeltaFlash(props.lastTurnDelta, now);
35
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: `↑ ${costRow.tokensInLabel}` }), _jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { color: "cyan", children: `↓ ${costRow.tokensOutLabel}` }), _jsx(Text, { dimColor: true, children: ` · ` }), _jsx(Text, { bold: true, children: costRow.costLabel }), _jsx(Text, { dimColor: true, children: ` · ${costRow.elapsedLabel}` }), deltaFlash ? (_jsxs(_Fragment, { children: [_jsx(Text, { dimColor: true, children: ' ' }), _jsx(Text, { color: "green", children: deltaFlash })] })) : null] }), _jsxs(Box, { children: [_jsx(Text, { color: status.color, children: `${glyph ?? '●'} ${status.label}` }), _jsx(Text, { dimColor: true, children: ` · ${props.activeAgentCount} agents · ` }), _jsx(Text, { children: `↓ ${tokenLabel} tokens` }), _jsx(Text, { dimColor: true, children: ` · ${elapsedLabel}` }), typeof props.externalDispatchCount === 'number' && props.externalDispatchCount > 0 ? (_jsx(Text, { color: "yellow", children: ` · ${props.externalDispatchCount} dispatch${props.externalDispatchCount === 1 ? '' : 'es'} active. /cancel к manage.` })) : null] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${formatCount(props.pugiMdCount)} PUGI.md · ${formatCount(props.mcpServerCount)} MCP · ${formatCount(props.skillCount)} skills · ${formatQuota(props.quotaPct)} quota` }) })] }));
36
+ }
37
+ /**
38
+ * cost-meter sprint — assemble the cost-meter row labels. Pure helper
39
+ * so the snapshot tests assert the formatted shape without standing up
40
+ * an Ink renderer.
41
+ */
42
+ export function renderCostMeterRow(tokensIn, tokensOut, costUsd, sessionStartedAtEpochMs, nowEpochMs) {
43
+ const elapsedMs = typeof sessionStartedAtEpochMs === 'number'
44
+ ? Math.max(0, nowEpochMs - sessionStartedAtEpochMs)
45
+ : 0;
46
+ return {
47
+ tokensInLabel: formatTokens(tokensIn),
48
+ tokensOutLabel: formatTokens(tokensOut),
49
+ costLabel: formatCostUsd(costUsd),
50
+ elapsedLabel: formatElapsedShort(elapsedMs),
51
+ };
52
+ }
53
+ /**
54
+ * cost-meter sprint — render the per-turn delta flash when the most
55
+ * recent turn completed within the flash window. Returns null when no
56
+ * turn has completed yet OR the flash has expired (the elapsed slot
57
+ * gets the slot back). The flash format mirrors the spec:
58
+ *
59
+ * `+200/+1.1k +$0.01`
60
+ *
61
+ * Exported for snapshot tests.
62
+ */
63
+ export function renderTurnDeltaFlash(delta, nowEpochMs) {
64
+ if (!delta)
65
+ return null;
66
+ const elapsedSinceMs = nowEpochMs - delta.completedAtEpochMs;
67
+ if (elapsedSinceMs < 0 || elapsedSinceMs > TURN_DELTA_FLASH_MS)
68
+ return null;
69
+ const inLabel = formatTokens(delta.tokensIn);
70
+ const outLabel = formatTokens(delta.tokensOut);
71
+ const costLabel = delta.costUsd > 0 ? ` +${formatCostUsd(delta.costUsd)}` : '';
72
+ return `+${inLabel}/+${outLabel}${costLabel}`;
73
+ }
74
+ /**
75
+ * cost-meter sprint — local copy of the session elapsed formatter.
76
+ * Mirrors the helper in `core/repl/session.ts` so the status bar stays
77
+ * a pure leaf component without a circular import on session.ts (the
78
+ * model-pricing module's `formatDuration` is similar but ships an
79
+ * `XhYm` ceiling that does not match the CEO spec's `2m44s` shape).
80
+ */
81
+ function formatElapsedShort(elapsedMs) {
82
+ if (!Number.isFinite(elapsedMs) || elapsedMs <= 0)
83
+ return '0s';
84
+ const totalSec = Math.floor(elapsedMs / 1000);
85
+ if (totalSec < 60)
86
+ return `${totalSec}s`;
87
+ const min = Math.floor(totalSec / 60);
88
+ const sec = totalSec % 60;
89
+ if (min < 60)
90
+ return `${min}m${sec.toString().padStart(2, '0')}s`;
91
+ const hr = Math.floor(min / 60);
92
+ const restMin = min % 60;
93
+ return `${hr}h${restMin.toString().padStart(2, '0')}m`;
22
94
  }
23
95
  /**
24
96
  * Render a count badge — number if defined, `—` placeholder otherwise.
@@ -53,29 +125,29 @@ export function connectionLabel(connection) {
53
125
  }
54
126
  }
55
127
  /**
56
- * α6.9: compose the visible status label from connection + FSM state.
128
+ * : compose the visible status label from connection + FSM state.
57
129
  *
58
130
  * Priority order:
59
131
  *
60
- * 1. `offline` / `reconnecting` — transport health wins; the
61
- * operator needs to know about a dropped stream before anything
62
- * about the dispatch.
63
- * 2. `aborting` / `aborted` / `failed` — operator-visible terminal
64
- * states the FSM reached; the colour shifts to amber/red so the
65
- * anomaly stands out vs the calm cyan baseline.
66
- * 3. `tool_running` — surfaces the tool label when available
67
- * (`tool: read`), falls back to `tool` when not.
68
- * 4. `awaiting_response` — `dispatching` (matches Codex CLI's verb
69
- * for the same state).
70
- * 5. `completed` — `shipped` (matches the agent tree status glyph
71
- * so the operator's eye links the two surfaces).
72
- * 6. `idle` / unknown — connection label (`on watch` / `connecting`).
132
+ * 1. `offline` / `reconnecting` — transport health wins; the
133
+ * operator needs to know about a dropped stream before anything
134
+ * about the dispatch.
135
+ * 2. `aborting` / `aborted` / `failed` — operator-visible terminal
136
+ * states the FSM reached; the colour shifts to amber/red so the
137
+ * anomaly stands out vs the calm cyan baseline.
138
+ * 3. `tool_running` — surfaces the tool label when available
139
+ * (`tool: read`), falls back to `tool` when not.
140
+ * 4. `awaiting_response` — `dispatching` (matches Codex CLI's verb
141
+ * for the same state).
142
+ * 5. `completed` — `shipped` (matches the agent tree status glyph
143
+ * so the operator's eye links the two surfaces).
144
+ * 6. `idle` / unknown — connection label (`on watch` / `connecting`).
73
145
  *
74
146
  * The dispatch label `dispatchToolLabel` is already shaped as
75
147
  * `tool: <kind>` upstream so we just concatenate; null falls through
76
148
  * to the bare `tool` placeholder.
77
149
  */
78
- function composeStatusLabel(connection, dispatchState, toolLabel) {
150
+ function composeStatusLabel(connection, dispatchState, toolLabel, lastCompletedOutcome) {
79
151
  // Transport health wins.
80
152
  if (connection === 'offline' || connection === 'reconnecting') {
81
153
  return connectionLabel(connection);
@@ -93,6 +165,16 @@ function composeStatusLabel(connection, dispatchState, toolLabel) {
93
165
  case 'awaiting_response':
94
166
  return { label: 'dispatching', color: 'cyan' };
95
167
  case 'completed':
168
+ // Branch on the work-done outcome so the bottom-bar tells the
169
+ // same truth as the agent-tree (2026-05-26 — memory
170
+ // feedback_no_fake_dispatch_promises). `'replied'` = text-only
171
+ // turn, render with the same neutral gray + arrow used in the
172
+ // agent-tree. `'shipped'` = real side-effect (or older server
173
+ // that omits the outcome field). Defaults to `'shipped'` so
174
+ // older callers without the prop wired stay back-compat.
175
+ if (lastCompletedOutcome === 'replied') {
176
+ return { label: 'replied', color: 'gray' };
177
+ }
96
178
  return { label: 'shipped', color: 'green' };
97
179
  case 'idle':
98
180
  case undefined:
@@ -111,18 +193,14 @@ function formatElapsed(startedAt, now) {
111
193
  const seconds = Math.floor((ms % 60_000) / 1000);
112
194
  return `${minutes}m ${seconds.toString().padStart(2, '0')}s`;
113
195
  }
114
- /**
115
- * Format the downstream token counter as 1.2k / 12.4k / 1.0m. Anvil
116
- * F1 emits totals in the tens-of-thousands range during a single
117
- * brief, so anything more than three significant figures is noise.
118
- */
119
- function formatTokens(total) {
120
- if (total < 1_000)
121
- return total.toString();
122
- if (total < 1_000_000)
123
- return `${(total / 1_000).toFixed(1)}k`;
124
- return `${(total / 1_000_000).toFixed(1)}m`;
125
- }
196
+ // `formatTokens` for the downstream-throughput slot is imported from
197
+ // `core/repl/model-pricing.ts` single source of truth for token
198
+ // formatting across the cost-meter row, `/cost` slash, and the legacy
199
+ // downstream-tokens slot. The shape is identical to the prior local
200
+ // helper (`<1000` raw, `<1m` one-decimal k, `≥1m` one-decimal m); the
201
+ // only semantic difference is non-finite / negative inputs render as
202
+ // `0` instead of throwing, matching the cost-meter row's defensive
203
+ // posture.
126
204
  function clampPhase(phase) {
127
205
  if (typeof phase !== 'number' || Number.isNaN(phase))
128
206
  return 0;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function StatusTable({ snapshot }) {
4
+ const labelWidth = Math.max('Label'.length, ...snapshot.fields.map((f) => f.label.length));
5
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Pugi status" }) }), snapshot.fields.map((field) => (_jsxs(Box, { children: [_jsxs(Text, { children: [field.label.padEnd(labelWidth, ' '), " "] }), field.available ? (_jsx(Text, { children: field.value })) : (_jsx(Text, { dimColor: true, children: field.value }))] }, field.key))), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["CLI ", snapshot.meta.cliVersion, " Node ", snapshot.meta.nodeVersion, " cwd ", snapshot.meta.cwd] }) })] }));
6
+ }
7
+ //# sourceMappingURL=status-table.js.map
@@ -0,0 +1,136 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ /**
4
+ * Curated ASCII pug corpus. Five variants — wide enough that repeat
5
+ * invocations look fresh, narrow enough that every entry stays
6
+ * hand-vetted (no procedural slop). Each art block intentionally fits
7
+ * inside an 80-column terminal so the surrounding box border does not
8
+ * wrap on narrow shells.
9
+ *
10
+ * The trailing newline at the end of each `art` string is intentional —
11
+ * keeps the renderer's join logic uniform between the boxed and the
12
+ * `--ascii-only` paths.
13
+ */
14
+ export const PUG_STICKERS = Object.freeze([
15
+ {
16
+ id: 'classic-face',
17
+ caption: 'classic pug face',
18
+ art: [
19
+ ' _._ _,-\'""`-._',
20
+ ' (,-.`._,\'( |\\`-/|',
21
+ ' `-.-\' \\ )-`( , o o)',
22
+ ' `- \\`_`"\'-',
23
+ ].join('\n'),
24
+ },
25
+ {
26
+ id: 'sit-pose',
27
+ caption: 'sit, stay, ship',
28
+ art: [
29
+ ' /\\___/\\',
30
+ ' ( o o )',
31
+ ' ( =^= )',
32
+ ' (______)',
33
+ ].join('\n'),
34
+ },
35
+ {
36
+ id: 'peek',
37
+ caption: 'peek-a-pug',
38
+ art: [
39
+ ' __',
40
+ ' ___/ \\___',
41
+ ' / o o \\',
42
+ ' | > ^ < |',
43
+ ' \\__________/',
44
+ ].join('\n'),
45
+ },
46
+ {
47
+ id: 'sleepy',
48
+ caption: 'sleepy pug, no Zzz today',
49
+ art: [
50
+ ' .--.',
51
+ ' / - -\\',
52
+ ' ( ^ ^ )',
53
+ ' \\ ^^ /',
54
+ ' `----\'',
55
+ ].join('\n'),
56
+ },
57
+ {
58
+ id: 'shipping',
59
+ caption: 'shipping pug',
60
+ art: [
61
+ ' .---. .---.',
62
+ ' |o_o| |o_o|',
63
+ ' \\_^_/ \\_^_/',
64
+ ' /| |\\ /| |\\',
65
+ ' shipped • shipped',
66
+ ].join('\n'),
67
+ },
68
+ ]);
69
+ /**
70
+ * Curated rotating-quote pool. Brand voice gate (brandbook §08):
71
+ * `brief / dispatch / stop / agents / quit / shipped` are the power
72
+ * words; quotes lean on those and на the operator-mode register.
73
+ * Adding lines: keep each ≤ 64 chars so the boxed renderer never wraps,
74
+ * stay в the operator's voice, no AI attribution, no hype.
75
+ */
76
+ export const PUG_QUOTES = Object.freeze([
77
+ 'Pugi: your engineering co-pilot.',
78
+ 'Brief it. It ships.',
79
+ 'Built for operators, not for benchmarks.',
80
+ 'Pugi: твой инженерный напарник.',
81
+ 'Dispatch agents, not promises.',
82
+ 'Small CLI. Loud workforce.',
83
+ 'Engineering at the speed of brief.',
84
+ 'Pugi: shipping is the default mode.',
85
+ ]);
86
+ /**
87
+ * Clamp a raw rng draw to a safe array index. Handles every hostile
88
+ * shape the spec exercises:
89
+ * - rng returns NaN → fall back to 0
90
+ * - rng returns 1.0 → clamp to length-1 (Math.floor would land at n)
91
+ * - rng returns -ε → clamp to 0
92
+ * The caller hands в the corpus length; the helper never touches the
93
+ * corpus itself so it stays trivially testable.
94
+ */
95
+ function safeIndex(raw, length) {
96
+ if (!Number.isFinite(raw))
97
+ return 0;
98
+ const floored = Math.floor(raw);
99
+ if (floored < 0)
100
+ return 0;
101
+ if (floored >= length)
102
+ return length - 1;
103
+ return floored;
104
+ }
105
+ /**
106
+ * Pick one art variant. Defaults to `Math.random` but the caller can
107
+ * inject a deterministic source — the spec uses a sequence-driven
108
+ * stub to assert the picker hits each entry в the corpus.
109
+ */
110
+ export function pickArtVariant(rng = Math.random) {
111
+ const raw = rng() * PUG_STICKERS.length;
112
+ return PUG_STICKERS[safeIndex(raw, PUG_STICKERS.length)];
113
+ }
114
+ /**
115
+ * Pick one rotating brand quote. Same contract as `pickArtVariant` —
116
+ * test-injectable rng so the spec can pin the chosen index.
117
+ */
118
+ export function pickQuote(rng = Math.random) {
119
+ const raw = rng() * PUG_QUOTES.length;
120
+ return PUG_QUOTES[safeIndex(raw, PUG_QUOTES.length)];
121
+ }
122
+ /**
123
+ * Plain-text renderer for the `--ascii-only` flag and the non-TTY shell
124
+ * path. Emits the art verbatim, then a blank line, then the quote.
125
+ * No box border — scripting use-case (`pugi stickers --ascii-only`
126
+ * piped to `figlet`, `lolcat`, or a regression-fixture file) gets a
127
+ * stable contract free of decorative ANSI noise.
128
+ */
129
+ export function renderPugStickersText(art, quote) {
130
+ return `${art.art}\n\n${quote}`;
131
+ }
132
+ export function PugStickersArt({ art, quote }) {
133
+ const lines = art.art.split('\n');
134
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, children: "Pugi stickers" }), _jsxs(Text, { dimColor: true, children: [" \u2014 ", art.caption] })] }), _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { children: line }, `art-${i}`))) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["\"", quote, "\""] }) })] }));
135
+ }
136
+ //# sourceMappingURL=stickers-art.js.map
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { OUTPUT_STYLES, OUTPUT_STYLE_SLUGS, } from '../core/output-style/presets.js';
4
+ import { useTheme } from '../core/theme/context.js';
5
+ /**
6
+ * Banner above the table. Plain text (not bold) so the prefix `*`
7
+ * remains the dominant active-row cue.
8
+ */
9
+ function buildBanner(active, source) {
10
+ return `Active style: ${active} (${source})`;
11
+ }
12
+ export function StyleTable({ active, source }) {
13
+ // the active-row marker color flows through
14
+ // the theme so `colorblind` operators see cyan instead of green
15
+ // (which their palette re-maps to `success`). Falls back to the
16
+ // default theme's `success` token when no provider is mounted.
17
+ const theme = useTheme();
18
+ const slugWidth = Math.max('NAME'.length, ...OUTPUT_STYLE_SLUGS.map((slug) => slug.length));
19
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Pugi output styles" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: ` ${'NAME'.padEnd(slugWidth)} GLOSS` }) }), OUTPUT_STYLE_SLUGS.map((slug) => (_jsx(StyleRow, { slug: slug, active: active, slugWidth: slugWidth, activeColor: theme.success }, slug))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: buildBanner(active, source) }) })] }));
20
+ }
21
+ function StyleRow({ slug, active, slugWidth, activeColor }) {
22
+ const isActive = slug === active;
23
+ const marker = isActive ? '*' : ' ';
24
+ const slugPart = slug.padEnd(slugWidth, ' ');
25
+ const gloss = OUTPUT_STYLES[slug].gloss;
26
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isActive ? activeColor : undefined, bold: isActive, children: `${marker} ${slugPart}` }), _jsx(Text, { children: ` ${gloss}` })] }));
27
+ }
28
+ //# sourceMappingURL=style-table.js.map
@@ -0,0 +1,29 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { compileSampleRow, THEMES, THEME_SLUGS, } from '../core/theme/presets.js';
4
+ /**
5
+ * Banner above the table. Plain text (not bold) so the prefix `*`
6
+ * remains the dominant active-row cue. Mirrors `<StyleTable>` so the
7
+ * Settings-group surfaces read identically.
8
+ */
9
+ function buildBanner(active, source) {
10
+ return `Active theme: ${active} (${source})`;
11
+ }
12
+ export function ThemeTable({ active, source }) {
13
+ const slugWidth = Math.max('NAME'.length, ...THEME_SLUGS.map((slug) => slug.length));
14
+ // The gloss column gets sized to the widest gloss + 2 padding so
15
+ // the sample column lines up. Computed once per render so the
16
+ // layout stays stable when the catalogue grows.
17
+ const glossWidth = Math.max('GLOSS'.length, ...THEME_SLUGS.map((slug) => THEMES[slug].gloss.length));
18
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, children: "Pugi themes" }) }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: ` ${'NAME'.padEnd(slugWidth)} ${'GLOSS'.padEnd(glossWidth)} SAMPLE` }) }), THEME_SLUGS.map((slug) => (_jsx(ThemeRow, { slug: slug, active: active, slugWidth: slugWidth, glossWidth: glossWidth }, slug))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { children: buildBanner(active, source) }) })] }));
19
+ }
20
+ function ThemeRow({ slug, active, slugWidth, glossWidth }) {
21
+ const isActive = slug === active;
22
+ const marker = isActive ? '*' : ' ';
23
+ const slugPart = slug.padEnd(slugWidth, ' ');
24
+ const preset = THEMES[slug];
25
+ const gloss = preset.gloss.padEnd(glossWidth, ' ');
26
+ const sample = compileSampleRow(slug);
27
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isActive ? preset.colors.accent : undefined, bold: isActive, children: `${marker} ${slugPart}` }), _jsx(Text, { children: ` ${gloss} ` }), _jsx(Text, { color: preset.colors.foreground, children: sample.foreground }), _jsx(Text, { children: ' ' }), _jsx(Text, { color: preset.colors.accent, children: sample.accent }), _jsx(Text, { children: ' ' }), _jsx(Text, { color: preset.colors.success, children: sample.success }), _jsx(Text, { children: ' ' }), _jsx(Text, { color: preset.colors.warning, children: sample.warning }), _jsx(Text, { children: ' ' }), _jsx(Text, { color: preset.colors.error, children: sample.error })] }));
28
+ }
29
+ //# sourceMappingURL=theme-table.js.map
@@ -0,0 +1,123 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Thinking spinner — animated dispatch indicator (CEO P0 #2).
4
+ *
5
+ * Replaces the static `dispatching` / `tool: <name>` strings the bottom
6
+ * status row used к paint during active brief dispatch with а Claude-
7
+ * Code-style rotating verb + glyph pair:
8
+ *
9
+ * ✽ Briefing… (awaiting_response, before the first tool call)
10
+ * ◆ Dispatching… (awaiting_response, после the first tool call)
11
+ * ◇ Reviewing… (tool_running)
12
+ * ✦ Synthesizing… (awaiting_response, late in turn)
13
+ * ❋ Shipping… (tool_running, edit/write/build family tool)
14
+ *
15
+ * The rotation cadence is ~800 ms per step so the verb feels alive but
16
+ * never strobes. The component owns its own timer (cleared on unmount)
17
+ * и takes the active dispatch state + the optional tool label so the
18
+ * "Shipping…" branch can light up specifically для write-class tools.
19
+ *
20
+ * Mount lifecycle: the REPL renders `<ThinkingSpinner />` only while
21
+ * `dispatchState` ∈ {`awaiting_response`, `tool_running`}. The
22
+ * status-bar's legacy `composeStatusLabel` row stays mounted too — they
23
+ * are not mutually exclusive — but the spinner row sits above the
24
+ * status row так the operator's eye lands on the live signal first.
25
+ *
26
+ * Brand discipline:
27
+ * - Verbs from the approved power-words list: `Briefing`,
28
+ * `Dispatching`, `Reviewing`, `Synthesizing`, `Shipping`. No
29
+ * `Thinking…` (forbidden cool word per DESIGN.md §3.2 voice gate).
30
+ * - Glyphs from the Pugi mascot brand glyph kit (`✽ ◆ ◇ ✦ ❋`). No
31
+ * emoji — the spinner must paint in а narrow Bash / WSL terminal
32
+ * где emoji fall back к `?`.
33
+ * - Cyan accent (`#3da9fc`) on the glyph; verb stays default-tone so
34
+ * the rotation does not compete with the cost-meter row above.
35
+ * - Trailing ellipsis is а single Unicode `…` (DESIGN.md §4 — three-
36
+ * dot ellipsis is the ONLY allowed truncation glyph).
37
+ *
38
+ * Test surface: `tickSpinnerFrame` is exported so the spec can drive
39
+ * the frame index without а real-clock timer.
40
+ */
41
+ import { useEffect, useState } from 'react';
42
+ import { Box, Text } from 'ink';
43
+ /* ------------------------------------------------------------------ */
44
+ /* Constants */
45
+ /* ------------------------------------------------------------------ */
46
+ const ACCENT = '#3da9fc';
47
+ const FRAME_INTERVAL_MS = 800;
48
+ const FRAMES = [
49
+ { glyph: '✽', verb: 'Briefing' },
50
+ { glyph: '◆', verb: 'Dispatching' },
51
+ { glyph: '◇', verb: 'Reviewing' },
52
+ { glyph: '✦', verb: 'Synthesizing' },
53
+ { glyph: '❋', verb: 'Shipping' },
54
+ ];
55
+ /**
56
+ * Tool labels that anchor к the `Shipping…` frame regardless of
57
+ * rotation index. These are the write-class tools — the operator sees
58
+ * "shipping" the moment а real file mutation fires так the indicator
59
+ * matches the visible filesystem effect.
60
+ */
61
+ const SHIPPING_TOOLS = new Set([
62
+ 'edit',
63
+ 'write',
64
+ 'build',
65
+ 'multi_edit',
66
+ 'apply_patch',
67
+ ]);
68
+ /* ------------------------------------------------------------------ */
69
+ /* Helpers */
70
+ /* ------------------------------------------------------------------ */
71
+ /**
72
+ * Compute the visible frame для the current tick. The base index is
73
+ * derived from а monotonically-incrementing counter (one increment per
74
+ * `FRAME_INTERVAL_MS` window), и а ship-class tool label overrides the
75
+ * base so write-tools light up `Shipping…` immediately.
76
+ *
77
+ * Exported for the spec so the frame selection is testable without
78
+ * mounting Ink.
79
+ */
80
+ export function tickSpinnerFrame(baseIndex, toolLabel) {
81
+ if (toolLabel && SHIPPING_TOOLS.has(toolLabel.toLowerCase())) {
82
+ return FRAMES[FRAMES.length - 1] ?? FRAMES[0];
83
+ }
84
+ const wrapped = ((baseIndex % FRAMES.length) + FRAMES.length) % FRAMES.length;
85
+ return FRAMES[wrapped] ?? FRAMES[0];
86
+ }
87
+ /**
88
+ * Decide whether the spinner should be visible for а dispatch state.
89
+ * `awaiting_response` и `tool_running` are the only active states; all
90
+ * other states (`idle`, `aborting`, `aborted`, `completed`, `failed`)
91
+ * suppress the spinner так the operator does not see а phantom verb
92
+ * after the dispatch settles.
93
+ */
94
+ export function isSpinnerActive(dispatchState) {
95
+ return dispatchState === 'awaiting_response' || dispatchState === 'tool_running';
96
+ }
97
+ /* ------------------------------------------------------------------ */
98
+ /* Component */
99
+ /* ------------------------------------------------------------------ */
100
+ export function ThinkingSpinner(props) {
101
+ const active = isSpinnerActive(props.dispatchState);
102
+ const [frameIndex, setFrameIndex] = useState(0);
103
+ const intervalMs = props.frameIntervalMs ?? FRAME_INTERVAL_MS;
104
+ useEffect(() => {
105
+ if (!active) {
106
+ // Reset так the next dispatch starts on `Briefing` instead of
107
+ // mid-rotation. Without this the spinner would resume one verb
108
+ // later on every turn и quickly drift to `Synthesizing` even на
109
+ // а fresh brief.
110
+ setFrameIndex(0);
111
+ return;
112
+ }
113
+ const timer = setInterval(() => {
114
+ setFrameIndex((previous) => (previous + 1) % FRAMES.length);
115
+ }, intervalMs);
116
+ return () => clearInterval(timer);
117
+ }, [active, intervalMs]);
118
+ if (!active)
119
+ return null;
120
+ const frame = tickSpinnerFrame(frameIndex, props.dispatchToolLabel ?? null);
121
+ return (_jsxs(Box, { children: [_jsx(Text, { color: ACCENT, children: `${frame.glyph} ` }), _jsx(Text, { children: `${frame.verb}…` })] }));
122
+ }
123
+ //# sourceMappingURL=thinking-spinner.js.map
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
+ import { RESULT_PREVIEW_MAX_CHARS, STREAMING_DELTA_MAX_CHARS, } from '../core/repl/session.js';
3
4
  const DEFAULT_COLLAPSE_THRESHOLD = 5;
4
5
  const DEFAULT_MAX_ROWS = 8;
5
6
  export function ToolStreamPane(props) {
@@ -20,7 +21,7 @@ export function ToolStreamPane(props) {
20
21
  }
21
22
  function PaneHeader({ count, collapsed }) {
22
23
  const verb = collapsed ? 'Ctrl+T to expand' : 'Ctrl+T to collapse';
23
- return (_jsxs(Box, { children: [_jsx(Text, { bold: true, dimColor: true, children: '─ tools ' }), _jsx(Text, { dimColor: true, children: `(${count}) ` }), _jsx(Text, { dimColor: true, children: verb })] }));
24
+ return (_jsxs(Box, { children: [_jsx(Text, { bold: true, dimColor: true, children: '─ tools ' }), _jsx(Text, { dimColor: true, children: `(${count}) ` }), _jsx(Text, { dimColor: true, children: verb })] }));
24
25
  }
25
26
  function ToolCallRow({ call, collapseThreshold, }) {
26
27
  const glyph = statusGlyph(call.status);
@@ -28,7 +29,38 @@ function ToolCallRow({ call, collapseThreshold, }) {
28
29
  const label = formatToolLabel(call.tool, call.args);
29
30
  const summary = formatSummary(call);
30
31
  const showHint = (call.resultLines ?? 0) > collapseThreshold;
31
- return (_jsxs(Box, { children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { children: ' ' }), _jsx(Text, { bold: true, children: label }), _jsx(Text, { dimColor: true, children: ` ${summary}` }), showHint ? (_jsx(Text, { dimColor: true, children: ` · ${call.resultLines} lines, Ctrl+O to expand` })) : null] }));
32
+ // small-CC-parity batch : on a `running` row,
33
+ // surface the rolling streaming delta as a dim inline preview after
34
+ // the label. On a completed row, the same slot carries the
35
+ // `resultPreview` quoted head. Either way the row stays single-line —
36
+ // both fields are clamped к their respective char ceilings upstream.
37
+ const inlineTail = call.status === 'running'
38
+ ? call.streamingDelta
39
+ : call.resultPreview;
40
+ // Error rows: render the label too in red so the eye lands on the
41
+ // failure even при peripheral attention. The other states keep the
42
+ // glyph as the only color signal.
43
+ const labelColor = call.status === 'error' ? 'red' : undefined;
44
+ return (_jsxs(Box, { children: [_jsx(Text, { color: color, children: glyph }), _jsx(Text, { children: ' ' }), _jsx(Text, { bold: true, color: labelColor, children: label }), _jsx(Text, { dimColor: true, children: ` ${summary}` }), inlineTail ? (_jsx(Text, { dimColor: true, children: ` ${formatInlineTail(call.status, inlineTail)}` })) : null, showHint ? (_jsx(Text, { dimColor: true, children: ` · ${call.resultLines} lines, Ctrl+O to expand` })) : null] }));
45
+ }
46
+ /**
47
+ * small-CC-parity batch : render the inline tail
48
+ * slot for either the live `streamingDelta` (during `running`) or the
49
+ * collapsed `resultPreview` (after completion). Pure helper so the
50
+ * spec can assert the exact shape.
51
+ *
52
+ * running → `… npm WARN deprecated…`
53
+ * ok / error → `"<preview head>"`
54
+ */
55
+ export function formatInlineTail(status, tail) {
56
+ if (status === 'running') {
57
+ return tail;
58
+ }
59
+ // Wrap the completed preview in quotes so the operator's eye groups
60
+ // the preview block visually distinct from the canonical detail
61
+ // (`OK`, `+12 -0`). Mirrors the the upstream tool TUI's quoted-preview
62
+ // pattern.
63
+ return `"${tail}"`;
32
64
  }
33
65
  function statusGlyph(status) {
34
66
  switch (status) {
@@ -52,18 +84,35 @@ function statusColor(status) {
52
84
  }
53
85
  /**
54
86
  * Render the canonical `Tool(args)` form. Tool names are capitalised
55
- * the way Claude Code shows them; args are truncated to 60 chars so
56
- * the row stays single-line even on 80-col terminals.
87
+ * the way the upstream tool shows them; args are truncated to keep the row
88
+ * single-line even on 80-col terminals. Cap = 60 chars (chosen empirically
89
+ * to leave room for the glyph + 1-space gap + summary + optional
90
+ * inline tail without overflow on narrow shells).
57
91
  */
58
92
  function formatToolLabel(tool, args) {
59
93
  const name = toolDisplayName(tool);
60
94
  const trimmedArgs = args.length > 60 ? `${args.slice(0, 57)}…` : args;
61
95
  return `${name}(${trimmedArgs})`;
62
96
  }
97
+ /**
98
+ * Re-export the upstream char caps so the operator-facing constants
99
+ * have one source of truth. The session module owns the canonical
100
+ * values (used both during ingest и during the pane's render lookup);
101
+ * tests assert against these names rather than literal numbers so a
102
+ * future tuning сtays diff-friendly.
103
+ */
104
+ export { RESULT_PREVIEW_MAX_CHARS, STREAMING_DELTA_MAX_CHARS };
63
105
  function toolDisplayName(tool) {
64
106
  switch (tool) {
65
107
  case 'read':
66
108
  return 'Read';
109
+ case 'write':
110
+ // — Write is the most operator-visible tool for the
111
+ // codegen-dispatch surface (Hiroshi writing index.html / style.css
112
+ // / script.js for a tic-tac-toe brief). Add the display name so
113
+ // the tool stream pane renders ✓ Write(index.html) instead of an
114
+ // unlabeled placeholder. Mirrors the the upstream tool Write rendering.
115
+ return 'Write';
67
116
  case 'edit':
68
117
  return 'Edit';
69
118
  case 'bash':
@@ -1,8 +1,33 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Box, Text } from 'ink';
3
- import { upgradeCommand } from '../runtime/update-check.js';
3
+ import { compareVersions, upgradeCommand, } from '../runtime/update-check.js';
4
+ import { getCachedServerRecommendation } from '../core/transport/version-interceptor.js';
5
+ /**
6
+ * Resolve the `latest` value the banner should show. Exported so the
7
+ * spec can lock the merge logic without rendering Ink.
8
+ */
9
+ export function resolveDisplayedLatest(npmLatest, serverRecommended) {
10
+ if (!serverRecommended)
11
+ return npmLatest;
12
+ return compareVersions(serverRecommended, npmLatest) > 0
13
+ ? serverRecommended
14
+ : npmLatest;
15
+ }
16
+ /**
17
+ * Claude-Code-style corner banner: single line, dim orange, right
18
+ * aligned. Renders to the bottom of the REPL frame so it never
19
+ * displaces conversation content. Only mounts when an update is
20
+ * actually available; identical to no-op when the registry poll
21
+ * reports current. Suppress with `PUGI_SKIP_UPDATE_BANNER=1`.
22
+ */
4
23
  export function UpdateBanner({ result }) {
5
24
  const command = upgradeCommand(result.method);
6
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: '─ ' }), _jsx(Text, { bold: true, color: "cyan", children: 'Pugi ' }), _jsx(Text, { children: result.installed }), _jsx(Text, { dimColor: true, children: ' (installed) → ' }), _jsx(Text, { bold: true, children: result.latest }), _jsx(Text, { dimColor: true, children: ' (latest)' })] }), _jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: ' Update: ' }), _jsx(Text, { color: "cyan", children: command })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: ' Skip with PUGI_SKIP_UPDATE_BANNER=1' }) })] }));
25
+ // Read the cache lazily inside the render so a server response that
26
+ // landed AFTER the banner was constructed still shows up on the next
27
+ // re-render. The cache lookup is a single map read — cheap enough to
28
+ // do per render.
29
+ const serverRecommended = getCachedServerRecommendation();
30
+ const displayedLatest = resolveDisplayedLatest(result.latest, serverRecommended);
31
+ return (_jsx(Box, { justifyContent: "flex-end", children: _jsxs(Text, { color: "#d97706", children: [_jsx(Text, { dimColor: true, children: 'Update available! ' }), _jsx(Text, { dimColor: true, children: `v${displayedLatest} — Run: ` }), _jsx(Text, { bold: true, children: command })] }) }));
7
32
  }
8
33
  //# sourceMappingURL=update-banner.js.map