@pugi/cli 0.1.0-beta.10 → 0.1.0-beta.100

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 (445) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/README.md +53 -11
  4. package/assets/pugi-prozr2-mascot.ansi +9 -0
  5. package/bin/run.js +33 -1
  6. package/dist/commands/deploy.js +40 -40
  7. package/dist/commands/flatten.js +191 -0
  8. package/dist/commands/jobs-watch.js +201 -0
  9. package/dist/commands/jobs.js +42 -27
  10. package/dist/commands/retro.js +210 -0
  11. package/dist/commands/smoke.js +133 -0
  12. package/dist/core/agent-progress/cleanup.js +134 -0
  13. package/dist/core/agent-progress/schema.js +144 -0
  14. package/dist/core/agent-progress/writer.js +101 -0
  15. package/dist/core/agents/adaptive-router.js +330 -0
  16. package/dist/core/agents/query-decomposer.js +297 -0
  17. package/dist/core/agents/registry.js +3 -3
  18. package/dist/core/approvals/shortcut-resolver.js +98 -0
  19. package/dist/core/artifact-chain/dispatcher.js +148 -0
  20. package/dist/core/artifact-chain/exporter.js +164 -0
  21. package/dist/core/artifact-chain/state.js +243 -0
  22. package/dist/core/artifact-chain/steps.js +169 -0
  23. package/dist/core/ask-user/question.js +92 -0
  24. package/dist/core/audit/audit-trail.js +275 -0
  25. package/dist/core/auth/ensure-authenticated.js +129 -0
  26. package/dist/core/auth/env-provider.js +238 -0
  27. package/dist/core/auto-open-browser.js +4 -4
  28. package/dist/core/auto-update/channels.js +122 -0
  29. package/dist/core/auto-update/checker.js +241 -0
  30. package/dist/core/auto-update/state.js +235 -0
  31. package/dist/core/bare-mode/index.js +107 -0
  32. package/dist/core/bash/redirect.js +281 -0
  33. package/dist/core/bash-classifier.js +436 -40
  34. package/dist/core/checkpoint/resumer.js +149 -0
  35. package/dist/core/checkpoint/rewinder.js +291 -0
  36. package/dist/core/checkpoints/shadow-git.js +670 -0
  37. package/dist/core/citations/parser.js +109 -0
  38. package/dist/core/classifier/yolo-classifier.js +88 -0
  39. package/dist/core/codegraph/db.js +506 -0
  40. package/dist/core/codegraph/decision-store.js +248 -0
  41. package/dist/core/codegraph/detect-repo.js +459 -0
  42. package/dist/core/codegraph/install.js +134 -0
  43. package/dist/core/codegraph/offer-hook.js +220 -0
  44. package/dist/core/codegraph/parser.js +71 -0
  45. package/dist/core/codegraph/types.js +34 -0
  46. package/dist/core/compact/auto-trigger.js +96 -0
  47. package/dist/core/compact/buffer-rewriter.js +115 -0
  48. package/dist/core/compact/summarizer.js +208 -0
  49. package/dist/core/compact/token-counter.js +108 -0
  50. package/dist/core/consensus/anvil-fanout.js +25 -25
  51. package/dist/core/consensus/diff-capture.js +121 -12
  52. package/dist/core/consensus/rubric.js +21 -21
  53. package/dist/core/context/builder.js +6 -6
  54. package/dist/core/context/compaction-events.js +8 -8
  55. package/dist/core/context/compaction.js +31 -31
  56. package/dist/core/context/index.js +15 -8
  57. package/dist/core/context/invariants.js +51 -51
  58. package/dist/core/context/markdown-loader.js +28 -10
  59. package/dist/core/context/markdown-traverse.js +255 -0
  60. package/dist/core/context/pugiignore.js +41 -41
  61. package/dist/core/context/repo-skeleton.js +37 -37
  62. package/dist/core/context/tool-eviction.js +55 -0
  63. package/dist/core/context/watcher.js +32 -32
  64. package/dist/core/context/working-set.js +23 -23
  65. package/dist/core/coordinator/agent-tools.js +77 -0
  66. package/dist/core/coordinator/agent-toolset.js +65 -0
  67. package/dist/core/coordinator/fsm.js +73 -0
  68. package/dist/core/coordinator/mode-fsm.js +70 -0
  69. package/dist/core/cost/rate-card.js +129 -0
  70. package/dist/core/cost/tracker.js +221 -0
  71. package/dist/core/credentials.js +13 -13
  72. package/dist/core/cron/scheduler.js +138 -0
  73. package/dist/core/denial-tracking/index.js +8 -0
  74. package/dist/core/denial-tracking/state.js +264 -0
  75. package/dist/core/diagnostics/probe-runner.js +93 -0
  76. package/dist/core/diagnostics/probes/api.js +46 -0
  77. package/dist/core/diagnostics/probes/auth.js +93 -0
  78. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  79. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  80. package/dist/core/diagnostics/probes/config.js +72 -0
  81. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  82. package/dist/core/diagnostics/probes/disk.js +81 -0
  83. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  84. package/dist/core/diagnostics/probes/git.js +65 -0
  85. package/dist/core/diagnostics/probes/hooks.js +118 -0
  86. package/dist/core/diagnostics/probes/mcp.js +75 -0
  87. package/dist/core/diagnostics/probes/node.js +59 -0
  88. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  89. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  90. package/dist/core/diagnostics/probes/sandbox.js +72 -0
  91. package/dist/core/diagnostics/probes/session.js +74 -0
  92. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  93. package/dist/core/diagnostics/probes/workspace.js +63 -0
  94. package/dist/core/diagnostics/types.js +70 -0
  95. package/dist/core/dispatch/cache-cleanup.js +197 -0
  96. package/dist/core/dispatch/cache-handoff.js +295 -0
  97. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  98. package/dist/core/edits/dispatch.js +333 -7
  99. package/dist/core/edits/format-detector.js +260 -0
  100. package/dist/core/edits/format-matrix.js +26 -0
  101. package/dist/core/edits/fuzzy-ladder.js +650 -0
  102. package/dist/core/edits/index.js +5 -1
  103. package/dist/core/edits/journal.js +199 -0
  104. package/dist/core/edits/layer-a-apply.js +15 -15
  105. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  106. package/dist/core/edits/layer-b-apply.js +9 -9
  107. package/dist/core/edits/layer-c-apply.js +6 -6
  108. package/dist/core/edits/layer-d-ast.js +557 -14
  109. package/dist/core/edits/marker-parser.js +12 -12
  110. package/dist/core/edits/security-gate.js +27 -27
  111. package/dist/core/edits/verify-hook.js +273 -0
  112. package/dist/core/edits/worktree.js +29 -29
  113. package/dist/core/engine/anvil-client.js +214 -26
  114. package/dist/core/engine/auto-compact.js +247 -0
  115. package/dist/core/engine/budgets.js +220 -0
  116. package/dist/core/engine/compact-llm-summarizer.js +124 -0
  117. package/dist/core/engine/context-prefix.js +155 -0
  118. package/dist/core/engine/index.js +1 -1
  119. package/dist/core/engine/intensity.js +163 -0
  120. package/dist/core/engine/intent.js +260 -0
  121. package/dist/core/engine/native-pugi.js +1559 -227
  122. package/dist/core/engine/prompts.js +187 -19
  123. package/dist/core/engine/strip-internal-fields.js +124 -0
  124. package/dist/core/engine/tool-bridge.js +1887 -59
  125. package/dist/core/engine/verification-patterns.js +195 -0
  126. package/dist/core/evaluation/golden-dataset.js +293 -0
  127. package/dist/core/feedback/queue.js +177 -0
  128. package/dist/core/feedback/submitter.js +145 -0
  129. package/dist/core/file-cache.js +113 -1
  130. package/dist/core/flatten/flatten-repo.js +439 -0
  131. package/dist/core/format/osc8-link.js +28 -0
  132. package/dist/core/hook-chains.js +392 -0
  133. package/dist/core/hooks/citation-verify-hook.js +138 -0
  134. package/dist/core/hooks/citation-verify.js +112 -0
  135. package/dist/core/hooks/events.js +46 -0
  136. package/dist/core/hooks/index.js +15 -0
  137. package/dist/core/hooks/registry.js +216 -0
  138. package/dist/core/hooks/runner.js +236 -0
  139. package/dist/core/hooks/v2/event-emitter.js +115 -0
  140. package/dist/core/hooks/v2/executor.js +282 -0
  141. package/dist/core/hooks/v2/index.js +25 -0
  142. package/dist/core/hooks/v2/lifecycle.js +104 -0
  143. package/dist/core/hooks/v2/loader.js +216 -0
  144. package/dist/core/hooks/v2/matcher.js +125 -0
  145. package/dist/core/hooks/v2/trust.js +143 -0
  146. package/dist/core/hooks/v2/types.js +86 -0
  147. package/dist/core/hooks/worktree-events.js +158 -0
  148. package/dist/core/image/renderer.js +71 -0
  149. package/dist/core/init/detector.js +582 -0
  150. package/dist/core/init/template-renderer.js +242 -0
  151. package/dist/core/jobs/registry.js +18 -18
  152. package/dist/core/ledger/results-tsv.js +142 -0
  153. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  154. package/dist/core/lsp/cache.js +105 -0
  155. package/dist/core/lsp/client.js +551 -41
  156. package/dist/core/lsp/language-detect.js +66 -0
  157. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  158. package/dist/core/lsp/server-detect.js +173 -0
  159. package/dist/core/lsp/symbol-cache.js +162 -0
  160. package/dist/core/lsp/symbol-tools.js +664 -0
  161. package/dist/core/mcp/client.js +97 -28
  162. package/dist/core/mcp/http-server.js +553 -0
  163. package/dist/core/mcp/orchestrator-config.js +192 -0
  164. package/dist/core/mcp/orchestrator-tools.js +806 -0
  165. package/dist/core/mcp/permission.js +190 -0
  166. package/dist/core/mcp/registry.js +39 -17
  167. package/dist/core/mcp/server-tools.js +219 -0
  168. package/dist/core/mcp/server.js +397 -0
  169. package/dist/core/mcp/trust.js +10 -10
  170. package/dist/core/memory/dual-write.js +416 -0
  171. package/dist/core/memory/passive-extract.js +130 -0
  172. package/dist/core/memory/phase1-kinds.js +20 -0
  173. package/dist/core/memory/secret-scanner.js +304 -0
  174. package/dist/core/memory-sync/queue.js +170 -0
  175. package/dist/core/metrics/extract.js +113 -0
  176. package/dist/core/modes/roo-modes.js +68 -0
  177. package/dist/core/notes/notes-paths.js +113 -0
  178. package/dist/core/notes/notes-recorder.js +140 -0
  179. package/dist/core/notes/notes-writer.js +53 -0
  180. package/dist/core/notes/renderers.js +0 -0
  181. package/dist/core/notes/slug.js +105 -0
  182. package/dist/core/onboarding/ensure-initialized.js +133 -0
  183. package/dist/core/onboarding/marker.js +111 -0
  184. package/dist/core/onboarding/telemetry-state.js +108 -0
  185. package/dist/core/output-style/presets.js +176 -0
  186. package/dist/core/output-style/state.js +185 -0
  187. package/dist/core/path-security.js +287 -5
  188. package/dist/core/permission.js +82 -22
  189. package/dist/core/permissions/auto-classifier.js +124 -0
  190. package/dist/core/permissions/bash-parser.js +371 -0
  191. package/dist/core/permissions/circuit-breaker.js +83 -0
  192. package/dist/core/permissions/constrained-edit.js +91 -0
  193. package/dist/core/permissions/gate.js +278 -0
  194. package/dist/core/permissions/index.js +20 -0
  195. package/dist/core/permissions/mode.js +174 -0
  196. package/dist/core/permissions/network-egress.js +137 -0
  197. package/dist/core/permissions/state.js +241 -0
  198. package/dist/core/permissions/tool-class.js +107 -0
  199. package/dist/core/plan-mode/ui-state.js +51 -0
  200. package/dist/core/plans/plan-artifact.js +721 -0
  201. package/dist/core/policy-limits/etag-store.js +122 -0
  202. package/dist/core/prd-check/parser.js +215 -0
  203. package/dist/core/prd-check/reporter.js +127 -0
  204. package/dist/core/prd-check/session-review.js +557 -0
  205. package/dist/core/prd-check/verifiers.js +223 -0
  206. package/dist/core/prompt-cache/client-cache.js +99 -0
  207. package/dist/core/prompts/assembly.js +29 -0
  208. package/dist/core/prompts/registry.js +364 -0
  209. package/dist/core/pugi-gitignore.js +52 -0
  210. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  211. package/dist/core/pugi-md/context-injector.js +76 -0
  212. package/dist/core/pugi-md/walk-up.js +207 -0
  213. package/dist/core/python/uv-installer.js +270 -0
  214. package/dist/core/python/uv-resolver.js +83 -0
  215. package/dist/core/rate-limit/narrator.js +146 -0
  216. package/dist/core/recipes/cli-types.js +20 -0
  217. package/dist/core/recipes/loader.js +103 -0
  218. package/dist/core/recipes/runner.js +345 -0
  219. package/dist/core/recipes/schema.js +587 -0
  220. package/dist/core/release-notes/parser.js +241 -0
  221. package/dist/core/release-notes/state.js +116 -0
  222. package/dist/core/repl/ask.js +37 -37
  223. package/dist/core/repl/cancellation.js +26 -26
  224. package/dist/core/repl/cap-warning.js +4 -4
  225. package/dist/core/repl/clipboard-read.js +11 -11
  226. package/dist/core/repl/dispatch-fsm.js +12 -12
  227. package/dist/core/repl/engine-bridge.js +303 -0
  228. package/dist/core/repl/history-search.js +15 -15
  229. package/dist/core/repl/history.js +28 -18
  230. package/dist/core/repl/kill-ring.js +5 -5
  231. package/dist/core/repl/model-pricing.js +135 -0
  232. package/dist/core/repl/privacy-banner.js +22 -22
  233. package/dist/core/repl/session.js +2690 -229
  234. package/dist/core/repl/slash-commands.js +540 -41
  235. package/dist/core/repl/store/index.js +1 -1
  236. package/dist/core/repl/store/jsonl-log.js +22 -22
  237. package/dist/core/repl/store/lockfile.js +10 -10
  238. package/dist/core/repl/store/session-store.js +136 -107
  239. package/dist/core/repl/store/types.js +15 -15
  240. package/dist/core/repl/store/uuid-v7.js +12 -12
  241. package/dist/core/repl/tool-route.js +382 -0
  242. package/dist/core/repl/workspace-context.js +43 -21
  243. package/dist/core/repo-map/build.js +125 -0
  244. package/dist/core/repo-map/cache.js +185 -0
  245. package/dist/core/repo-map/extractor.js +254 -0
  246. package/dist/core/repo-map/formatter.js +145 -0
  247. package/dist/core/repo-map/page-rank.js +105 -0
  248. package/dist/core/repo-map/scanner.js +211 -0
  249. package/dist/core/retro/git-collector.js +251 -0
  250. package/dist/core/retro/health-card.js +25 -0
  251. package/dist/core/retro/metrics.js +342 -0
  252. package/dist/core/retro/narrative.js +249 -0
  253. package/dist/core/retro/plane-collector.js +274 -0
  254. package/dist/core/retro/pr-issue-link.js +65 -0
  255. package/dist/core/retro/types.js +16 -0
  256. package/dist/core/retry-budget/budget.js +284 -0
  257. package/dist/core/retry-budget/index.js +5 -0
  258. package/dist/core/retry-budget/retry-cap.js +74 -0
  259. package/dist/core/routing/lead-worker.js +43 -0
  260. package/dist/core/routing/pre-flight-estimator.js +108 -0
  261. package/dist/core/runs/run-tree.js +103 -0
  262. package/dist/core/sandboxing/adapter.js +29 -0
  263. package/dist/core/sandboxing/index.js +49 -0
  264. package/dist/core/sandboxing/none.js +19 -0
  265. package/dist/core/sandboxing/seatbelt.js +183 -0
  266. package/dist/core/security/injection-scanner.js +367 -0
  267. package/dist/core/security/output-filter.js +418 -0
  268. package/dist/core/session/env-file.js +105 -0
  269. package/dist/core/session/section-budgets.js +140 -0
  270. package/dist/core/session.js +119 -0
  271. package/dist/core/settings.js +378 -5
  272. package/dist/core/share/formatter.js +271 -0
  273. package/dist/core/share/redactor.js +221 -0
  274. package/dist/core/share/uploader.js +267 -0
  275. package/dist/core/skills/defaults.js +30 -30
  276. package/dist/core/skills/loader.js +22 -22
  277. package/dist/core/skills/sources.js +27 -27
  278. package/dist/core/smoke/headless-driver.js +174 -0
  279. package/dist/core/smoke/orchestrator.js +194 -0
  280. package/dist/core/smoke/runner.js +238 -0
  281. package/dist/core/smoke/scenario-parser.js +316 -0
  282. package/dist/core/statusline.js +99 -0
  283. package/dist/core/subagents/dispatcher-real.js +600 -0
  284. package/dist/core/subagents/dispatcher.js +146 -52
  285. package/dist/core/subagents/index.js +19 -6
  286. package/dist/core/subagents/isolation-matrix.js +213 -0
  287. package/dist/core/subagents/spawn.js +19 -4
  288. package/dist/core/telemetry/emitter.js +229 -0
  289. package/dist/core/telemetry/queue.js +251 -0
  290. package/dist/core/theme/context.js +91 -0
  291. package/dist/core/theme/presets.js +228 -0
  292. package/dist/core/theme/state.js +181 -0
  293. package/dist/core/todos/invariant.js +10 -0
  294. package/dist/core/todos/state.js +177 -0
  295. package/dist/core/tool-schema/compressor.js +89 -0
  296. package/dist/core/transport/version-interceptor.js +166 -0
  297. package/dist/core/trust.js +2 -2
  298. package/dist/core/tui/thinking-block.js +64 -0
  299. package/dist/core/vim/keymap.js +288 -0
  300. package/dist/core/vim/state.js +92 -0
  301. package/dist/core/watch-markers/marker-watcher.js +133 -0
  302. package/dist/core/worktree/include-parser.js +249 -0
  303. package/dist/core/worktree-manager/cleanup.js +123 -0
  304. package/dist/core/worktree-manager/manager.js +303 -0
  305. package/dist/index.js +36 -0
  306. package/dist/runtime/bootstrap.js +190 -0
  307. package/dist/runtime/cli.js +4345 -561
  308. package/dist/runtime/commands/agents.js +31 -31
  309. package/dist/runtime/commands/budget.js +5 -5
  310. package/dist/runtime/commands/cancel.js +231 -0
  311. package/dist/runtime/commands/chain.js +489 -0
  312. package/dist/runtime/commands/codegraph-status.js +227 -0
  313. package/dist/runtime/commands/compact.js +297 -0
  314. package/dist/runtime/commands/config.js +74 -40
  315. package/dist/runtime/commands/cost.js +199 -0
  316. package/dist/runtime/commands/delegate.js +27 -4
  317. package/dist/runtime/commands/dispatch.js +126 -0
  318. package/dist/runtime/commands/doctor.js +579 -0
  319. package/dist/runtime/commands/feedback.js +184 -0
  320. package/dist/runtime/commands/hooks.js +187 -0
  321. package/dist/runtime/commands/index-cmd.js +353 -0
  322. package/dist/runtime/commands/init.js +254 -0
  323. package/dist/runtime/commands/lsp.js +200 -38
  324. package/dist/runtime/commands/mcp.js +935 -0
  325. package/dist/runtime/commands/memory.js +582 -0
  326. package/dist/runtime/commands/model.js +237 -0
  327. package/dist/runtime/commands/onboarding.js +275 -0
  328. package/dist/runtime/commands/patch.js +12 -12
  329. package/dist/runtime/commands/permissions.js +112 -0
  330. package/dist/runtime/commands/plan.js +143 -0
  331. package/dist/runtime/commands/prd-check.js +285 -0
  332. package/dist/runtime/commands/privacy.js +17 -17
  333. package/dist/runtime/commands/recipe.js +325 -0
  334. package/dist/runtime/commands/redo-blob-store.js +92 -0
  335. package/dist/runtime/commands/redo.js +361 -0
  336. package/dist/runtime/commands/release-notes.js +229 -0
  337. package/dist/runtime/commands/repo-map.js +95 -0
  338. package/dist/runtime/commands/report.js +299 -0
  339. package/dist/runtime/commands/resume.js +118 -0
  340. package/dist/runtime/commands/review-consensus.js +68 -53
  341. package/dist/runtime/commands/rewind.js +333 -0
  342. package/dist/runtime/commands/roster.js +14 -14
  343. package/dist/runtime/commands/servers.js +236 -0
  344. package/dist/runtime/commands/sessions.js +163 -0
  345. package/dist/runtime/commands/share.js +316 -0
  346. package/dist/runtime/commands/skills.js +31 -31
  347. package/dist/runtime/commands/status.js +186 -0
  348. package/dist/runtime/commands/stickers.js +82 -0
  349. package/dist/runtime/commands/style.js +194 -0
  350. package/dist/runtime/commands/theme.js +196 -0
  351. package/dist/runtime/commands/undo.js +54 -22
  352. package/dist/runtime/commands/update.js +289 -0
  353. package/dist/runtime/commands/vim.js +140 -0
  354. package/dist/runtime/commands/worktree.js +8 -8
  355. package/dist/runtime/commands/worktrees.js +155 -0
  356. package/dist/runtime/deprecation-warning.js +69 -0
  357. package/dist/runtime/engine-exit-code.js +50 -0
  358. package/dist/runtime/headless-repl.js +195 -0
  359. package/dist/runtime/headless.js +548 -0
  360. package/dist/runtime/load-hooks-or-exit.js +71 -0
  361. package/dist/runtime/plan-decompose.js +22 -22
  362. package/dist/runtime/sigint-guard.js +272 -0
  363. package/dist/runtime/stream-renderer.js +195 -0
  364. package/dist/runtime/update-check.js +28 -28
  365. package/dist/runtime/version.js +65 -0
  366. package/dist/runtime/worktree-bootstrap.js +579 -0
  367. package/dist/skills/bundled/batch.js +617 -0
  368. package/dist/skills/bundled/index.js +45 -0
  369. package/dist/skills/bundled/loop.js +358 -0
  370. package/dist/skills/bundled/remember.js +383 -0
  371. package/dist/skills/bundled/simplify.js +289 -0
  372. package/dist/skills/bundled/skillify.js +373 -0
  373. package/dist/skills/bundled/stuck.js +558 -0
  374. package/dist/skills/bundled/verify.js +439 -0
  375. package/dist/testing/vcr.js +486 -0
  376. package/dist/tools/agent-tool.js +229 -0
  377. package/dist/tools/apply-patch.js +89 -28
  378. package/dist/tools/ask-user-question.js +337 -0
  379. package/dist/tools/ask-user.js +115 -0
  380. package/dist/tools/bash.js +624 -46
  381. package/dist/tools/brief.js +224 -0
  382. package/dist/tools/cron.js +433 -0
  383. package/dist/tools/enter-worktree.js +250 -0
  384. package/dist/tools/exit-worktree.js +147 -0
  385. package/dist/tools/file-tools.js +161 -44
  386. package/dist/tools/http-request.js +336 -0
  387. package/dist/tools/lsp-tools.js +377 -1
  388. package/dist/tools/mcp-tool.js +260 -0
  389. package/dist/tools/multi-edit.js +361 -0
  390. package/dist/tools/powershell.js +268 -0
  391. package/dist/tools/registry.js +120 -5
  392. package/dist/tools/server-tools.js +892 -0
  393. package/dist/tools/skill-tool.js +96 -0
  394. package/dist/tools/sleep.js +99 -0
  395. package/dist/tools/synthetic-output.js +133 -0
  396. package/dist/tools/tasks.js +208 -0
  397. package/dist/tools/todo-write.js +184 -0
  398. package/dist/tools/verify-plan-execution.js +295 -0
  399. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  400. package/dist/tools/web-fetch.js +195 -10
  401. package/dist/tools/web-search.js +458 -0
  402. package/dist/tui/agent-progress-card.js +111 -0
  403. package/dist/tui/agent-tree.js +22 -1
  404. package/dist/tui/ask-modal.js +14 -14
  405. package/dist/tui/ask-user-question-chips.js +315 -0
  406. package/dist/tui/ask-user-question-prompt.js +203 -0
  407. package/dist/tui/compact-banner.js +81 -0
  408. package/dist/tui/conversation-pane.js +85 -11
  409. package/dist/tui/cost-table.js +111 -0
  410. package/dist/tui/device-flow.js +2 -2
  411. package/dist/tui/doctor-table.js +46 -0
  412. package/dist/tui/feedback-prompt.js +156 -0
  413. package/dist/tui/input-box.js +247 -32
  414. package/dist/tui/login-picker.js +3 -3
  415. package/dist/tui/markdown-render.js +6 -6
  416. package/dist/tui/multi-file-diff-approval.js +375 -0
  417. package/dist/tui/onboarding-wizard.js +240 -0
  418. package/dist/tui/permissions-picker.js +86 -0
  419. package/dist/tui/render.js +36 -1
  420. package/dist/tui/repl-render.js +239 -25
  421. package/dist/tui/repl-splash-art.js +16 -16
  422. package/dist/tui/repl-splash-mascot.js +48 -24
  423. package/dist/tui/repl-splash.js +22 -22
  424. package/dist/tui/repl.js +125 -45
  425. package/dist/tui/slash-palette.js +6 -6
  426. package/dist/tui/splash.js +2 -2
  427. package/dist/tui/status-bar.js +109 -31
  428. package/dist/tui/status-table.js +7 -0
  429. package/dist/tui/stickers-art.js +136 -0
  430. package/dist/tui/style-table.js +28 -0
  431. package/dist/tui/theme-table.js +29 -0
  432. package/dist/tui/thinking-spinner.js +123 -0
  433. package/dist/tui/tool-stream-pane.js +53 -4
  434. package/dist/tui/update-banner.js +27 -2
  435. package/dist/tui/vim-input.js +267 -0
  436. package/dist/tui/welcome-banner.js +107 -0
  437. package/dist/tui/welcome-data.js +293 -0
  438. package/dist/tui/workspace-context.js +2 -2
  439. package/package.json +21 -5
  440. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  441. package/test/scenarios/compact-force.scenario.txt +12 -0
  442. package/test/scenarios/identity.scenario.txt +11 -0
  443. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  444. package/test/scenarios/walkback.scenario.txt +12 -0
  445. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,113 @@
1
+ /**
2
+ * PR F (2026-06-05): Obsidian-style notes — path resolution.
3
+ *
4
+ * Three on-disk surfaces, all rooted at `<workspaceRoot>/.pugi/`:
5
+ *
6
+ * 1. Session notes — `.pugi/notes/sessions/<session-id>.md`
7
+ * One markdown file per REPL dispatch. Frontmatter (id, persona,
8
+ * command, brief, status, files, timing) + body summary.
9
+ *
10
+ * 2. File concept notes — `.pugi/notes/files/<slug>.md`
11
+ * One markdown file per touched repo file. Append-only history
12
+ * of which sessions touched it. Cross-linked via wiki-links from
13
+ * session notes (`[[files/snake-html]]`).
14
+ *
15
+ * 3. Daily journal — `.pugi/journal/YYYY-MM-DD.md`
16
+ * One markdown file per UTC day. Append-only one-paragraph
17
+ * entry per dispatch. Provides a chronological "what shipped
18
+ * today" view operators read directly in Obsidian or `cat`.
19
+ *
20
+ * The on-disk schema is OPEN by design: operators are encouraged to
21
+ * point their Obsidian vault at `.pugi/notes` and link freely. We
22
+ * avoid hidden state files (`.json` indexes, `.db` caches) so the
23
+ * tree stays grep-able and CI-friendly.
24
+ *
25
+ * All path helpers are pure — no fs side effects. The writer module
26
+ * owns `mkdir -p` + atomic write.
27
+ */
28
+ import { join, basename, resolve, isAbsolute, sep } from 'node:path';
29
+ /**
30
+ * Resolve the per-workspace notes root. Mirrors the existing
31
+ * `.pugi/audit/`, `.pugi/sessions/`, `.pugi/runs/` conventions —
32
+ * a customer who already pins `.pugi/` in `.gitignore` (most do)
33
+ * automatically picks up notes without touching their VCS hygiene.
34
+ */
35
+ export function resolveNotesRoot(workspaceRoot) {
36
+ return join(workspaceRoot, '.pugi', 'notes');
37
+ }
38
+ /**
39
+ * Resolve the per-workspace daily-journal root.
40
+ *
41
+ * Journal lives ALONGSIDE notes/ rather than under it because the
42
+ * journal is a chronological surface (one entry per day), distinct
43
+ * from the topical surface (notes by session/file). Keeping them
44
+ * sibling dirs matches how Obsidian users typically organise
45
+ * "Daily Notes" plugin output vs concept notes.
46
+ */
47
+ export function resolveJournalRoot(workspaceRoot) {
48
+ return join(workspaceRoot, '.pugi', 'journal');
49
+ }
50
+ /**
51
+ * Resolve the session-note path for a given session id. Returns an
52
+ * absolute path under the workspace.
53
+ *
54
+ * Session ids are already filesystem-safe (the engine adapter mints
55
+ * them via crypto.randomUUID + slug normalization) so we pass them
56
+ * through verbatim. If a future caller hands a session id with path
57
+ * separators we strip them defensively to prevent path traversal
58
+ * (callers should never do this, but cheap guard).
59
+ */
60
+ export function resolveSessionNotePath(workspaceRoot, sessionId) {
61
+ const safe = sessionId.replace(/[\\/]+/g, '-');
62
+ return join(resolveNotesRoot(workspaceRoot), 'sessions', `${safe}.md`);
63
+ }
64
+ /**
65
+ * Resolve the file-concept note path for a given repo-relative file
66
+ * path. The slug is derived in `slug.ts` so the same file always
67
+ * maps to the same note across sessions.
68
+ */
69
+ export function resolveFileNotePath(workspaceRoot, fileSlug) {
70
+ return join(resolveNotesRoot(workspaceRoot), 'files', `${fileSlug}.md`);
71
+ }
72
+ /**
73
+ * Resolve the daily-journal path for a given UTC date. We use UTC
74
+ * intentionally so journal files line up across operator timezone
75
+ * shifts (laptop in SF, desktop in EU, CI in UTC — all converge on
76
+ * the same `YYYY-MM-DD.md` for a given session).
77
+ */
78
+ export function resolveJournalPath(workspaceRoot, utcDate) {
79
+ const y = utcDate.getUTCFullYear();
80
+ const m = String(utcDate.getUTCMonth() + 1).padStart(2, '0');
81
+ const d = String(utcDate.getUTCDate()).padStart(2, '0');
82
+ return join(resolveJournalRoot(workspaceRoot), `${y}-${m}-${d}.md`);
83
+ }
84
+ /**
85
+ * Resolve a file path to its repo-relative form for slug derivation.
86
+ * Returns null when the path is outside the workspace (defensive —
87
+ * the slug derivation cannot produce a meaningful note for an
88
+ * absolute path outside the tree).
89
+ *
90
+ * Symlinks are not followed; we operate on the lexical path. The
91
+ * audit-trail module uses the same convention (resolve before
92
+ * hashing) so cross-feature paths stay consistent.
93
+ */
94
+ export function toWorkspaceRelative(workspaceRoot, filePath) {
95
+ const absRoot = resolve(workspaceRoot);
96
+ const abs = isAbsolute(filePath) ? resolve(filePath) : resolve(absRoot, filePath);
97
+ if (abs === absRoot)
98
+ return '.';
99
+ const prefix = absRoot + sep;
100
+ if (!abs.startsWith(prefix))
101
+ return null;
102
+ return abs.slice(prefix.length);
103
+ }
104
+ /**
105
+ * Strip the basename to a workspace-relative hint useful when the
106
+ * caller has nothing better. Lets the journal renderer fall back
107
+ * gracefully on file paths it cannot resolve against the workspace
108
+ * (e.g. agent emitted an absolute /tmp path for a scratch file).
109
+ */
110
+ export function fallbackPathHint(filePath) {
111
+ return basename(filePath);
112
+ }
113
+ //# sourceMappingURL=notes-paths.js.map
@@ -0,0 +1,140 @@
1
+ /**
2
+ * PR F (2026-06-05): Notes recorder — orchestrates session/file/daily
3
+ * markdown writes at dispatch end.
4
+ *
5
+ * Called from `native-pugi.ts` immediately after the `dispatch_end`
6
+ * audit event fires. The recorder:
7
+ * 1. Renders the per-session note (`writeNoteAtomic`).
8
+ * 2. Renders the daily-journal entry (`appendNoteLine`).
9
+ * 3. For each touched file, renders the file-concept note header
10
+ * (atomic, only when the file is empty) and appends a history
11
+ * line.
12
+ *
13
+ * Side effects gated by `enabled` flag from settings + env var
14
+ * (`PUGI_NOTES_DISABLE=1` overrides). Every call is wrapped in a
15
+ * try/catch — notes are a convenience surface, not a correctness
16
+ * guarantee. The audit-trail JSONL remains the authoritative log.
17
+ */
18
+ import { statSync, readFileSync } from 'node:fs';
19
+ import { resolveSessionNotePath, resolveFileNotePath, resolveJournalPath, toWorkspaceRelative, fallbackPathHint, } from './notes-paths.js';
20
+ import { fileSlugFor } from './slug.js';
21
+ import { writeNoteAtomic, appendNoteLine } from './notes-writer.js';
22
+ import { renderSessionNote, renderFileNoteHeader, renderFileNoteHistoryLine, renderJournalHeader, renderJournalEntry, } from './renderers.js';
23
+ export const PUGI_NOTES_DISABLE_VAR = 'PUGI_NOTES_DISABLE';
24
+ export function isNotesDisabledByEnv(env = process.env) {
25
+ const raw = env[PUGI_NOTES_DISABLE_VAR]?.trim().toLowerCase();
26
+ if (!raw)
27
+ return false;
28
+ return raw === '1' || raw === 'true' || raw === 'yes';
29
+ }
30
+ function fileExists(path) {
31
+ try {
32
+ statSync(path);
33
+ return true;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
38
+ }
39
+ function fileEmptyOrMissing(path) {
40
+ try {
41
+ const buf = readFileSync(path, 'utf8');
42
+ return buf.trim().length === 0;
43
+ }
44
+ catch {
45
+ return true;
46
+ }
47
+ }
48
+ /**
49
+ * Compute (slug, relative path) pairs for every touched file. Paths
50
+ * outside the workspace fall back to a basename-only slug so the
51
+ * concept note still lands somewhere readable.
52
+ */
53
+ function deriveFileSlugs(workspaceRoot, filesChanged) {
54
+ const out = [];
55
+ for (const f of filesChanged) {
56
+ const rel = toWorkspaceRelative(workspaceRoot, f) ?? fallbackPathHint(f);
57
+ const slug = fileSlugFor(rel);
58
+ if (slug.length > 0) {
59
+ out.push({ slug, relativePath: rel });
60
+ }
61
+ }
62
+ return out;
63
+ }
64
+ function durationSeconds(startedAt, endedAt) {
65
+ const s = Date.parse(startedAt);
66
+ const e = Date.parse(endedAt);
67
+ if (Number.isNaN(s) || Number.isNaN(e) || e < s)
68
+ return 0;
69
+ return Math.round((e - s) / 1000);
70
+ }
71
+ /**
72
+ * Single entry point — called from `native-pugi.ts` at dispatch end.
73
+ * Never throws.
74
+ */
75
+ export function recordSessionNotes(input) {
76
+ try {
77
+ if (!input.config.enabled)
78
+ return;
79
+ if (isNotesDisabledByEnv(input.env ?? process.env))
80
+ return;
81
+ const fileSlugs = deriveFileSlugs(input.workspaceRoot, input.filesChanged);
82
+ const snap = {
83
+ sessionId: input.sessionId,
84
+ command: input.command,
85
+ persona: input.persona,
86
+ brief: input.brief,
87
+ status: input.status,
88
+ toolCallCount: input.toolCallCount,
89
+ turnsUsed: input.turnsUsed,
90
+ tokensUsed: input.tokensUsed,
91
+ filesChanged: input.filesChanged,
92
+ fileSlugs,
93
+ startedAt: input.startedAt,
94
+ endedAt: input.endedAt,
95
+ durationSeconds: durationSeconds(input.startedAt, input.endedAt),
96
+ reason: input.reason,
97
+ };
98
+ // (1) Session note — atomic write.
99
+ writeNoteAtomic(resolveSessionNotePath(input.workspaceRoot, input.sessionId), renderSessionNote(snap));
100
+ // (2) Daily journal — header on first write per day, then entry.
101
+ const endedAtDate = new Date(input.endedAt);
102
+ const journalPath = resolveJournalPath(input.workspaceRoot, endedAtDate);
103
+ if (!fileExists(journalPath) || fileEmptyOrMissing(journalPath)) {
104
+ appendNoteLine(journalPath, renderJournalHeader(endedAtDate));
105
+ }
106
+ const journalEntry = {
107
+ sessionId: input.sessionId,
108
+ endedAt: input.endedAt,
109
+ command: input.command,
110
+ persona: input.persona,
111
+ brief: input.brief,
112
+ status: input.status,
113
+ filesChanged: input.filesChanged,
114
+ };
115
+ appendNoteLine(journalPath, renderJournalEntry(journalEntry));
116
+ // (3) File-concept notes — header on first touch, history line
117
+ // every touch. Both writes are best-effort.
118
+ for (const f of fileSlugs) {
119
+ const path = resolveFileNotePath(input.workspaceRoot, f.slug);
120
+ if (!fileExists(path) || fileEmptyOrMissing(path)) {
121
+ appendNoteLine(path, renderFileNoteHeader(f.slug, f.relativePath));
122
+ }
123
+ const entry = {
124
+ sessionId: input.sessionId,
125
+ slug: f.slug,
126
+ relativePath: f.relativePath,
127
+ endedAt: input.endedAt,
128
+ status: input.status,
129
+ brief: input.brief,
130
+ };
131
+ appendNoteLine(path, renderFileNoteHistoryLine(entry));
132
+ }
133
+ }
134
+ catch {
135
+ // Notes are best-effort. Failure here MUST NOT propagate to the
136
+ // dispatch — the customer's primary work is already captured in
137
+ // the audit-trail JSONL and the per-session events mirror.
138
+ }
139
+ }
140
+ //# sourceMappingURL=notes-recorder.js.map
@@ -0,0 +1,53 @@
1
+ /**
2
+ * PR F (2026-06-05): Atomic + append-only file writers for notes.
3
+ *
4
+ * Two primitives:
5
+ * - `writeNoteAtomic` — full-file replace via tmp + rename.
6
+ * Used for session notes (one file per dispatch, complete).
7
+ * - `appendNoteLine` — `O_APPEND` append. Used for daily journal
8
+ * and file-concept history sections.
9
+ *
10
+ * Both NEVER throw — failure inside the notes layer must NOT break
11
+ * a customer dispatch. The audit-trail module follows the same
12
+ * contract (`writeAuditEvent` catches everything). Notes are
13
+ * convenience surface, not load-bearing.
14
+ *
15
+ * `mkdir -p` runs every call (cheap when dir exists). Mode `0o755`
16
+ * because operators expect to read these in Obsidian without sudo,
17
+ * unlike audit trails (`0o700`) which carry tenant boundary.
18
+ */
19
+ import { mkdirSync, writeFileSync, renameSync, appendFileSync, unlinkSync } from 'node:fs';
20
+ import { dirname } from 'node:path';
21
+ import { randomBytes } from 'node:crypto';
22
+ export function writeNoteAtomic(targetPath, content) {
23
+ try {
24
+ mkdirSync(dirname(targetPath), { recursive: true, mode: 0o755 });
25
+ const tmp = `${targetPath}.${randomBytes(6).toString('hex')}.tmp`;
26
+ try {
27
+ writeFileSync(tmp, content, { encoding: 'utf8', mode: 0o644 });
28
+ renameSync(tmp, targetPath);
29
+ }
30
+ catch {
31
+ try {
32
+ unlinkSync(tmp);
33
+ }
34
+ catch {
35
+ // tmp may not exist (write failed before fd flushed)
36
+ }
37
+ }
38
+ }
39
+ catch {
40
+ // Notes are best-effort. Swallow.
41
+ }
42
+ }
43
+ export function appendNoteLine(targetPath, line) {
44
+ try {
45
+ mkdirSync(dirname(targetPath), { recursive: true, mode: 0o755 });
46
+ const ensureNewline = line.endsWith('\n') ? line : `${line}\n`;
47
+ appendFileSync(targetPath, ensureNewline, { encoding: 'utf8', mode: 0o644 });
48
+ }
49
+ catch {
50
+ // Notes are best-effort. Swallow.
51
+ }
52
+ }
53
+ //# sourceMappingURL=notes-writer.js.map
Binary file
@@ -0,0 +1,105 @@
1
+ /**
2
+ * PR F (2026-06-05): Path-to-slug normalization for file concept notes.
3
+ *
4
+ * A repo file path like `src/components/Header.tsx` becomes the slug
5
+ * `src-components-header-tsx`, which maps к
6
+ * `.pugi/notes/files/src-components-header-tsx.md`. The mapping is:
7
+ *
8
+ * - deterministic (same path → same slug across sessions, so the
9
+ * append-only history line lands in the same note);
10
+ * - filesystem-safe (no path separators that would create subdirs
11
+ * in `.pugi/notes/files/`);
12
+ * - Obsidian-link-friendly (lowercase + hyphens, no spaces or
13
+ * other characters that need escaping in `[[wiki-links]]`);
14
+ * - reversible-enough — the original path is also written into the
15
+ * note frontmatter (`path: src/components/Header.tsx`) so tooling
16
+ * can recover the canonical form without parsing the slug.
17
+ *
18
+ * Why one flat `files/` dir instead of mirroring the repo tree under
19
+ * `.pugi/notes/files/src/components/Header.md`: Obsidian's `[[link]]`
20
+ * resolver works best when notes live at a single depth. A flat dir
21
+ * also keeps the wiki-link grammar simple — `[[files/header-tsx]]`
22
+ * instead of `[[files/src/components/Header.tsx]]` (the latter is
23
+ * also a legal Obsidian link but harder to grep + harder to rename
24
+ * file paths без breaking links).
25
+ */
26
+ /**
27
+ * Maximum slug length. Empirically the longest file paths в monorepos
28
+ * are ~120 chars (deep apps/admin-api/src/modules/.../something.ts).
29
+ * Cap at 200 to leave headroom and keep `.md` filenames reasonable
30
+ * across filesystems (Windows MAX_PATH = 260 historically, modern
31
+ * ext4 = 255 на single component).
32
+ */
33
+ const MAX_SLUG_CHARS = 200;
34
+ /**
35
+ * Convert a workspace-relative path to a filesystem-safe slug.
36
+ *
37
+ * Examples:
38
+ * src/App.jsx → src-app-jsx
39
+ * apps/admin-api/main.ts → apps-admin-api-main-ts
40
+ * README.md → readme-md
41
+ * .pugi/notes/sessions/x.md → pugi-notes-sessions-x-md (defensive)
42
+ * docs/2026-06-05-foo.md → docs-2026-06-05-foo-md
43
+ *
44
+ * Edge cases:
45
+ * empty / null / undefined → `'untitled'` fallback (never throws)
46
+ * path with unicode → unicode preserved when filesystem-safe;
47
+ * separators + control chars normalized
48
+ * trailing/leading hyphens → trimmed
49
+ * collapsed multi-hyphens → single `-`
50
+ */
51
+ export function fileSlugFor(relativePath) {
52
+ if (typeof relativePath !== 'string' || relativePath.length === 0) {
53
+ return 'untitled';
54
+ }
55
+ let s = relativePath
56
+ .toLowerCase()
57
+ // Path separators + dots → hyphen so the slug stays flat.
58
+ .replace(/[\\/.]+/g, '-')
59
+ // Strip anything that is not alphanumeric, hyphen, or non-ASCII
60
+ // letter (allow CJK / Cyrillic for international repos).
61
+ .replace(/[^\p{L}\p{N}-]+/gu, '-')
62
+ // Collapse consecutive hyphens.
63
+ .replace(/-+/g, '-')
64
+ // Trim hyphens off the ends.
65
+ .replace(/^-+|-+$/g, '');
66
+ if (s.length === 0)
67
+ return 'untitled';
68
+ if (s.length > MAX_SLUG_CHARS) {
69
+ s = s.slice(0, MAX_SLUG_CHARS).replace(/-+$/g, '');
70
+ }
71
+ return s;
72
+ }
73
+ /**
74
+ * Convert an arbitrary string (e.g. a brief like "сделай snake.html")
75
+ * to a concept slug for the wiki-link grammar. Currently the recorder
76
+ * doesn't mint these (file slugs cover the v1 surface) but keeping
77
+ * the helper here keeps the slug rules centralized for PR G's
78
+ * graph-extraction work.
79
+ *
80
+ * Returns `null` rather than `'untitled'` when there is nothing to
81
+ * slug — callers should suppress the link instead of emitting a
82
+ * placeholder. Different fallback semantics than `fileSlugFor`
83
+ * because file slugs ALWAYS have a path to lean on, whereas concept
84
+ * slugs degrade silently when the input is unusable.
85
+ */
86
+ export function conceptSlugFor(input) {
87
+ if (typeof input !== 'string')
88
+ return null;
89
+ const trimmed = input.trim();
90
+ if (trimmed.length === 0)
91
+ return null;
92
+ let s = trimmed
93
+ .toLowerCase()
94
+ .replace(/[\\/.]+/g, '-')
95
+ .replace(/[^\p{L}\p{N}-]+/gu, '-')
96
+ .replace(/-+/g, '-')
97
+ .replace(/^-+|-+$/g, '');
98
+ if (s.length === 0)
99
+ return null;
100
+ if (s.length > MAX_SLUG_CHARS) {
101
+ s = s.slice(0, MAX_SLUG_CHARS).replace(/-+$/g, '');
102
+ }
103
+ return s;
104
+ }
105
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1,133 @@
1
+ /**
2
+ * UX — `ensureInitialized` helper.
3
+ *
4
+ * Auto-init pre-flight for every Pugi command. Before this helper landed,
5
+ * the only entry points that exercised the init flow were:
6
+ *
7
+ * 1. The explicit `pugi init` CLI subcommand.
8
+ * 2. The REPL's `/init` slash (β1a r1).
9
+ * 3. Engine commands (`pugi code`, `pugi build`, `pugi sync`) which
10
+ * called the legacy `ensureInitialized` in `cli.ts` and threw
11
+ * `Error('Run pugi init first')` if the operator ran them in a
12
+ * directory without `.pugi/`.
13
+ *
14
+ * Read-only commands (`pugi explain`, `pugi review`, `pugi plan`,
15
+ * `pugi smoke`, `pugi chain new`, ...) silently no-op'd the `.pugi/`
16
+ * mirror inside the engine adapter, which made early dogfooding
17
+ * confusing — the operator saw a successful command but no session
18
+ * artifacts on disk and no idea why.
19
+ *
20
+ * Auto-init contract (matches CEO directive ):
21
+ *
22
+ * - `.pugi/` already exists → return `{ status: 'already' }` silently.
23
+ * - Interactive TTY + no `.pugi/` → prompt
24
+ * "No Pugi workspace found here. Initialize? (Y/n)".
25
+ * Default Y. On Y: run `scaffoldPugiWorkspace`, return `{ status:
26
+ * 'initialized' }`. On n: return `{ status: 'declined' }` so the
27
+ * caller can bail with a helpful message.
28
+ * - Non-interactive (CI / pipe / --json / --no-tty) + no `.pugi/`:
29
+ * default behaviour is conservative — return `{ status: 'declined',
30
+ * reason: 'non_interactive' }`. The caller decides how to surface
31
+ * this (engine commands bail with a clean error; read-only
32
+ * commands MAY continue with degraded semantics).
33
+ * - `--no-init` flag forces conservative posture even on interactive
34
+ * terminals (operator wants to fail fast).
35
+ *
36
+ * Session cache: a command pre-flight that already prompted for and
37
+ * scaffolded `.pugi/` MUST NOT re-prompt for the same workspace in the
38
+ * same process. The cache key is the absolute workspace root path. The
39
+ * cache is process-local (Map) — it does not persist across `pugi`
40
+ * invocations (a second `pugi code` in the same shell starts fresh and
41
+ * re-checks the filesystem).
42
+ *
43
+ * This module is intentionally framework-free: no Ink, no React, no
44
+ * readline. The prompt reader is injected via the `prompt` callback so
45
+ * the spec can drive the helper deterministically and the CLI can
46
+ * forward to its existing stdin-reader (`readSingleChoice` in cli.ts).
47
+ */
48
+ import { existsSync, statSync } from 'node:fs';
49
+ import { resolve } from 'node:path';
50
+ /**
51
+ * Process-local cache of workspaces that already passed the pre-flight
52
+ * gate. Keyed by absolute root path. The cache is intentionally
53
+ * additive-only — there is no eviction. A long-running REPL session
54
+ * stays in one workspace and we never want to re-prompt within it.
55
+ */
56
+ const initialisedCache = new Set();
57
+ /**
58
+ * Reset the cache. Exported for spec teardown — production callers
59
+ * never need this.
60
+ */
61
+ export function resetInitializedCache() {
62
+ initialisedCache.clear();
63
+ }
64
+ /**
65
+ * Detect `.pugi/` at `root`. Pure filesystem read; swallows permission
66
+ * errors (returns false). Exported so the spec can assert the same
67
+ * detection the helper uses without re-implementing the check.
68
+ */
69
+ export function hasPugiWorkspace(root) {
70
+ const path = resolve(root, '.pugi');
71
+ try {
72
+ if (!existsSync(path))
73
+ return false;
74
+ return statSync(path).isDirectory();
75
+ }
76
+ catch {
77
+ return false;
78
+ }
79
+ }
80
+ /**
81
+ * Auto-init pre-flight. Idempotent and process-cache aware — calling
82
+ * twice in the same process for the same workspace returns `already`
83
+ * the second time even if the filesystem state changed underneath.
84
+ *
85
+ * Implementation notes:
86
+ *
87
+ * - Returns `{ status: 'already' }` when `.pugi/` exists OR the cache
88
+ * remembers this workspace. The cache short-circuit means a second
89
+ * command in the same process never blocks on the prompt.
90
+ * - Interactive + missing → prompt. The default answer (empty input
91
+ * OR a leading `y` / `yes`) maps to scaffold. Anything else
92
+ * (`n`, `no`, `cancel`, whitespace + non-y) maps to declined.
93
+ * - Scaffolder failures propagate to the caller; the helper does
94
+ * NOT swallow them because a failed scaffold means the operator's
95
+ * command cannot continue anyway. Tests assert this.
96
+ */
97
+ export async function ensureInitialized(opts) {
98
+ const root = resolve(opts.cwd ?? process.cwd());
99
+ if (initialisedCache.has(root)) {
100
+ return { status: 'already', root };
101
+ }
102
+ if (hasPugiWorkspace(root)) {
103
+ initialisedCache.add(root);
104
+ return { status: 'already', root };
105
+ }
106
+ if (opts.skip) {
107
+ return { status: 'declined', root, reason: 'disabled' };
108
+ }
109
+ if (!opts.interactive) {
110
+ return { status: 'declined', root, reason: 'non_interactive' };
111
+ }
112
+ if (!opts.prompt) {
113
+ // Defensive — an interactive caller forgot к wire the prompt
114
+ // reader. Treat the same as non-interactive rather than throwing
115
+ // so the surrounding command can degrade gracefully.
116
+ return { status: 'declined', root, reason: 'non_interactive' };
117
+ }
118
+ const write = opts.write ?? ((line) => process.stderr.write(line));
119
+ write(`No Pugi workspace found at ${root}.\n`);
120
+ const answer = (await opts.prompt('Initialize a new Pugi workspace here? (Y/n) ')).trim().toLowerCase();
121
+ // Default = yes (empty input OR leading 'y'). Anything else = no.
122
+ // Mirrors the gh CLI / the upstream prompt convention where the upper-
123
+ // case option in `(Y/n)` is the default-on-Enter answer.
124
+ const acceptedShort = answer === '' || answer === 'y' || answer === 'yes';
125
+ if (!acceptedShort) {
126
+ write('Initialization declined.\n');
127
+ return { status: 'declined', root, reason: 'user_declined' };
128
+ }
129
+ await opts.scaffold({ cwd: root });
130
+ initialisedCache.add(root);
131
+ return { status: 'initialized', root };
132
+ }
133
+ //# sourceMappingURL=ensure-initialized.js.map
@@ -0,0 +1,111 @@
1
+ /**
2
+ * — Onboarding marker file.
3
+ *
4
+ * `~/.pugi/.onboarded` is a single, contentless marker. Its existence
5
+ * tells the bare-invocation hint check that the operator has already
6
+ * walked the `/onboarding` wizard at least once, so we no longer print
7
+ * the "Tip: run `pugi onboarding` to configure defaults" line on
8
+ * cold-start. The wizard re-runs cleanly — idempotency lives in the
9
+ * wizard itself, not in the marker.
10
+ *
11
+ * Why a marker file (and not just `~/.pugi/config.json`'s existence)?
12
+ *
13
+ * - The config file is touched the moment ANY surface writes a
14
+ * default — `pugi style terse --persist`, `pugi permissions ask`,
15
+ * `pugi config set …`. Using "config exists" as the proxy for
16
+ * "operator has onboarded" would silence the first-run hint for
17
+ * operators who never saw the wizard.
18
+ *
19
+ * - The marker is explicit: it is written ONLY by the wizard's exit
20
+ * step (or `pugi onboarding --mark-only` for the upgrade-path
21
+ * where we want to suppress the hint without forcing a re-walk).
22
+ *
23
+ * - Removing the marker (`rm ~/.pugi/.onboarded`) re-arms the hint
24
+ * without nuking the operator's accumulated config — useful for
25
+ * QA, support flows, and demo-machine resets.
26
+ *
27
+ * Path resolution mirrors the L6/L18 convention: `PUGI_HOME` env wins,
28
+ * else `~/.pugi`. The marker is touched as an empty file (no JSON, no
29
+ * timestamp payload) — readers MUST treat existence as the only signal
30
+ * so a future change to mtime semantics does not break us.
31
+ *
32
+ * IO contract:
33
+ * - `isOnboarded(env)` — pure read; never throws, returns false on
34
+ * any fs error so a corrupted home dir cannot hide the hint.
35
+ * - `markOnboarded(env)` — best-effort write; creates `<home>/.pugi/`
36
+ * if missing, mode 0o600 on the marker so it never lands in a
37
+ * world-readable backup.
38
+ * - `clearOnboarded(env)` — best-effort delete; absent file is a
39
+ * no-op (not an error). Used by `pugi onboarding --reset` and the
40
+ * spec teardown.
41
+ */
42
+ import { existsSync, mkdirSync, rmSync, writeFileSync, } from 'node:fs';
43
+ import { homedir } from 'node:os';
44
+ import { resolve } from 'node:path';
45
+ /**
46
+ * Env override for `~/.pugi`. Same convention as L6 / L18 — spec
47
+ * fixtures point this at a temp dir so a real developer machine never
48
+ * lands a stray marker.
49
+ */
50
+ export const PUGI_HOME_ENV = 'PUGI_HOME';
51
+ /**
52
+ * Marker basename. Hidden (leading dot) so it does not clutter `ls`
53
+ * inside `~/.pugi/` next to `config.json` / `session.json`.
54
+ */
55
+ const MARKER_BASENAME = '.onboarded';
56
+ /**
57
+ * Resolve the absolute path to the onboarding marker. Exported for the
58
+ * spec; production callers go through `isOnboarded` / `markOnboarded`.
59
+ */
60
+ export function onboardingMarkerPath(env = process.env) {
61
+ const home = env[PUGI_HOME_ENV] ?? resolve(homedir(), '.pugi');
62
+ return resolve(home, MARKER_BASENAME);
63
+ }
64
+ /**
65
+ * True when the marker exists. Pure read. Defensive: any fs error
66
+ * (race with deletion, permission flip) degrades to `false` — printing
67
+ * the hint twice is harmless, silently swallowing the wizard would
68
+ * surprise the operator.
69
+ */
70
+ export function isOnboarded(env = process.env) {
71
+ try {
72
+ return existsSync(onboardingMarkerPath(env));
73
+ }
74
+ catch {
75
+ return false;
76
+ }
77
+ }
78
+ /**
79
+ * Touch the marker. Creates `<home>/.pugi/` if missing. Idempotent —
80
+ * re-touching an existing marker is a no-op for the consumer (the file
81
+ * was already there; the hint was already suppressed).
82
+ *
83
+ * Best-effort: a write failure is swallowed because the wizard already
84
+ * completed its real work (mode + style + telemetry were persisted).
85
+ * The worst case is a redundant hint on the next `pugi` invocation —
86
+ * preferable to crashing the freshly-completed wizard with a stat EIO.
87
+ */
88
+ export function markOnboarded(env = process.env) {
89
+ const path = onboardingMarkerPath(env);
90
+ try {
91
+ mkdirSync(resolve(path, '..'), { recursive: true });
92
+ writeFileSync(path, '', { encoding: 'utf8', mode: 0o600 });
93
+ }
94
+ catch {
95
+ // intentionally swallowed — see header
96
+ }
97
+ }
98
+ /**
99
+ * Remove the marker. Used by `pugi onboarding --reset` (and the spec
100
+ * teardown). Absent file is a no-op; any other fs error is swallowed
101
+ * so a permission glitch never leaks out of the reset surface.
102
+ */
103
+ export function clearOnboarded(env = process.env) {
104
+ try {
105
+ rmSync(onboardingMarkerPath(env), { force: true });
106
+ }
107
+ catch {
108
+ // intentionally swallowed — see header
109
+ }
110
+ }
111
+ //# sourceMappingURL=marker.js.map