@pugi/cli 0.1.0-beta.8 → 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 +4151 -489
  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,6 +1,45 @@
1
+ /**
2
+ * Per-session file-read cache + stale-read gate.
3
+ *
4
+ * ( upstream `FileEditTool.ts`, gap analysis
5
+ * §5.1): every FileEdit must validate the operator's last-known view of
6
+ * the file before mutating disk. The gate compares BOTH `mtimeMs` and
7
+ * `sha256(content)` of the file on disk against the record captured at
8
+ * read time:
9
+ *
10
+ * - mtimeMs is a cheap fast-path. If the inode mtime hasn't moved
11
+ * since the read, the content hash cannot have changed (barring a
12
+ * filesystem with hash-on-mtime-skew bugs) and we can short-circuit.
13
+ * - sha256 is the authoritative gate. A user editor that writes back
14
+ * identical content can leave mtime untouched on some filesystems
15
+ * (atomic-rename with preserved metadata), and conversely `touch`
16
+ * bumps mtime without changing content. Hash is the truth.
17
+ *
18
+ * Both signals must agree for the gate to PASS. Any divergence => STALE
19
+ * => refuse the edit, force the model to re-read.
20
+ *
21
+ * Cache lifetime: per-session. `FileReadCache.clear()` is called at
22
+ * session.end (see `core/session.ts`). The cache is intentionally NOT
23
+ * durable across sessions — a re-read after restart is cheap and stale
24
+ * cross-session entries would themselves be a soundness hazard.
25
+ *
26
+ * Exception: writeTool for create-new (path doesn't exist on disk) does
27
+ * not consult the cache. Creating a brand new file has no "last-known
28
+ * view" to invalidate.
29
+ */
1
30
  import { createHash } from 'node:crypto';
2
- import { statSync } from 'node:fs';
31
+ import { existsSync, statSync } from 'node:fs';
3
32
  import { resolve } from 'node:path';
33
+ export class StaleReadError extends Error {
34
+ reason;
35
+ path;
36
+ constructor(path, reason, detail) {
37
+ super(`stale_read: ${path} — ${detail}. Re-read the file before editing.`);
38
+ this.name = 'StaleReadError';
39
+ this.reason = reason;
40
+ this.path = path;
41
+ }
42
+ }
4
43
  export class FileReadCache {
5
44
  records = new Map();
6
45
  set(record) {
@@ -9,6 +48,70 @@ export class FileReadCache {
9
48
  get(root, path) {
10
49
  return this.records.get(resolve(root, path));
11
50
  }
51
+ /**
52
+ * Validate a candidate edit against the cached read record. Returns
53
+ * a tagged-union: `{ stale: false }` when the edit may proceed, or
54
+ * `{ stale: true, reason, detail }` when the gate must refuse.
55
+ *
56
+ * Pure function over the cache + supplied `currentMtimeMs` /
57
+ * `currentContent` — does NOT touch disk. Callers (editTool /
58
+ * writeTool) do their own `statSync` + `readFileSync` because they
59
+ * also need the content for the diff/edit itself.
60
+ *
61
+ * @param root workspace root (used to resolve relative path)
62
+ * @param path workspace-relative file path
63
+ * @param currentMtimeMs `fs.statSync().mtimeMs` of the on-disk file
64
+ * @param currentContent UTF-8 contents of the on-disk file
65
+ */
66
+ validate(root, path, currentMtimeMs, currentContent) {
67
+ const record = this.get(root, path);
68
+ if (!record) {
69
+ return {
70
+ stale: true,
71
+ reason: 'no_prior_read',
72
+ detail: 'file must be read first',
73
+ };
74
+ }
75
+ // Fast-path: mtime hasn't moved. Hash check is redundant in the
76
+ // common case but cheap, so we still verify below. Skipping hash
77
+ // when mtime matches would allow a subtle bug class (in-place
78
+ // writers that preserve mtime) to slip through.
79
+ if (currentMtimeMs > record.mtimeMs) {
80
+ // mtime advanced — confirm with hash before flagging. A bump
81
+ // without a content change (e.g. `touch`) shouldn't fire stale.
82
+ const currentHash = hashContent(currentContent);
83
+ if (currentHash !== record.sha256) {
84
+ return {
85
+ stale: true,
86
+ reason: 'mtime_drift',
87
+ detail: `mtime advanced (${record.mtimeMs} → ${currentMtimeMs}) and content hash diverged`,
88
+ };
89
+ }
90
+ // mtime bumped but content identical — treat as fresh. The cache
91
+ // entry's mtime is intentionally NOT refreshed here; the next
92
+ // edit will hit the same path and the gate will keep agreeing.
93
+ return { stale: false };
94
+ }
95
+ // mtime hasn't moved — hash MUST still match the record. A
96
+ // mismatch is a filesystem-level inconsistency or an in-place
97
+ // editor that preserves mtime; either way, refuse.
98
+ const currentHash = hashContent(currentContent);
99
+ if (currentHash !== record.sha256) {
100
+ return {
101
+ stale: true,
102
+ reason: 'hash_drift',
103
+ detail: 'content hash diverged from last read (mtime unchanged)',
104
+ };
105
+ }
106
+ return { stale: false };
107
+ }
108
+ /**
109
+ * Drop every cached record. Called by session.end so a fresh REPL
110
+ * session never inherits stale cross-session entries.
111
+ */
112
+ clear() {
113
+ this.records.clear();
114
+ }
12
115
  }
13
116
  export function hashContent(content) {
14
117
  return createHash('sha256').update(content).digest('hex');
@@ -26,4 +129,13 @@ export function createReadRecord(root, path, content, source) {
26
129
  source,
27
130
  };
28
131
  }
132
+ /**
133
+ * Convenience helper: does this absolute path exist on disk? Wraps the
134
+ * existsSync import so file-tools.ts can decide between create-new
135
+ * (skip stale gate) and update-existing (apply stale gate) without
136
+ * pulling in another fs import.
137
+ */
138
+ export function pathExists(absolutePath) {
139
+ return existsSync(absolutePath);
140
+ }
29
141
  //# sourceMappingURL=file-cache.js.map
@@ -0,0 +1,439 @@
1
+ /**
2
+ * `pugi flatten` — single-page repository renderer (backlog).
3
+ *
4
+ * Ported from karpathy/rendergit (BSD-0). License is compatible with
5
+ * Pugi's open-source posture so the port is permitted with any degree
6
+ * of source copying. The TypeScript adaptation below is structural —
7
+ * the data model, ignore semantics, and HTML envelope mirror the
8
+ * upstream Python single-file tool but the implementation is written
9
+ * from scratch in TypeScript-strict so it composes with our existing
10
+ * `loadPugiIgnore` chain, the CLI writeOutput sink, and node:test.
11
+ *
12
+ * Two output modes:
13
+ * - `human` — Prism.js CDN syntax highlight + nav tree + anchor
14
+ * links. Ctrl+F across the whole codebase in a single browser tab.
15
+ * - `llm` — `<repo><file path="..."><content>...</content></file>
16
+ * </repo>` envelope for paste into other LLMs.
17
+ *
18
+ * Privacy: output files are chmod 600. The .pugi/flat/ directory is
19
+ * created with chmod 700 so an operator can drop a workspace flatten
20
+ * without leaking it to other UNIX accounts.
21
+ *
22
+ * Self-contained: depends only on `node:fs` / `node:path` /
23
+ * `node:child_process` + the workspace-local `loadPugiIgnore` matcher.
24
+ * No new npm deps.
25
+ */
26
+ import { execFileSync } from 'node:child_process';
27
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, chmodSync, } from 'node:fs';
28
+ import { basename, extname, relative, resolve, sep } from 'node:path';
29
+ import { loadPugiIgnore, } from '../context/pugiignore.js';
30
+ /** Default per-file byte ceiling. 200 KiB matches Karpathy's tool. */
31
+ export const DEFAULT_MAX_FILE_SIZE = 200 * 1024;
32
+ /**
33
+ * Entry point. Walks the repo, filters, renders, and (unless `stdout`)
34
+ * writes the result to `.pugi/flat/<basename>-<sha>.html`.
35
+ */
36
+ export async function flattenRepo(options = {}) {
37
+ const cwd = resolve(options.cwd ?? process.cwd());
38
+ const mode = options.mode ?? 'human';
39
+ const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
40
+ const include = options.include ?? [];
41
+ const exclude = options.exclude ?? [];
42
+ const ignore = loadPugiIgnore(cwd);
43
+ const files = [];
44
+ const skipped = [];
45
+ for await (const entry of walkFiles(cwd, ignore, { maxFileSize, include, exclude })) {
46
+ if ('reason' in entry) {
47
+ skipped.push(entry);
48
+ }
49
+ else {
50
+ files.push(entry);
51
+ }
52
+ }
53
+ // Stable order: lexicographic by repo-relative path so the HTML nav
54
+ // tree and the LLM envelope reproduce 1:1 across runs.
55
+ files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
56
+ skipped.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
57
+ const html = mode === 'human'
58
+ ? renderHumanHTML(files, skipped, basename(cwd))
59
+ : renderLLMXML(files, skipped, basename(cwd));
60
+ if (options.stdout) {
61
+ return { html, files, skipped };
62
+ }
63
+ const outDir = options.outDir ?? resolve(cwd, '.pugi', 'flat');
64
+ ensureDir(outDir);
65
+ const sha = options.shaOverride ?? resolveShortSha(cwd);
66
+ const ext = mode === 'human' ? 'html' : 'xml';
67
+ const fileName = `${basename(cwd)}-${sha}.${ext}`;
68
+ const path = resolve(outDir, fileName);
69
+ writeFileSync(path, html, { encoding: 'utf8', mode: 0o600 });
70
+ try {
71
+ chmodSync(path, 0o600);
72
+ }
73
+ catch {
74
+ // best-effort; some filesystems (mounted shares) reject chmod.
75
+ }
76
+ if (options.open) {
77
+ tryOpenBrowser(path);
78
+ }
79
+ return { html, path, files, skipped };
80
+ }
81
+ /**
82
+ * Async generator that walks the workspace, applying the pugi-ignore
83
+ * chain + include/exclude filters + binary/size gate. Each yielded
84
+ * item is either a `FlattenedFile` (kept) or a `SkippedFile` (dropped
85
+ * with a reason).
86
+ */
87
+ export async function* walkFiles(rootDir, ignore, filters) {
88
+ const stack = [rootDir];
89
+ while (stack.length > 0) {
90
+ const dir = stack.pop();
91
+ if (!dir)
92
+ continue;
93
+ let entries;
94
+ try {
95
+ entries = readdirSync(dir, { withFileTypes: true, encoding: 'utf8' });
96
+ }
97
+ catch {
98
+ continue;
99
+ }
100
+ // Sort directory listing so the walk is deterministic regardless of
101
+ // the underlying filesystem's readdir order.
102
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
103
+ for (const ent of entries) {
104
+ const abs = resolve(dir, ent.name);
105
+ if (ignore.isIgnored(abs, ent.isDirectory()))
106
+ continue;
107
+ if (ent.isDirectory()) {
108
+ stack.push(abs);
109
+ continue;
110
+ }
111
+ if (!ent.isFile())
112
+ continue;
113
+ const relPath = toPosix(relative(rootDir, abs));
114
+ if (!matchesFilters(relPath, filters.include, filters.exclude))
115
+ continue;
116
+ let size;
117
+ try {
118
+ size = statSync(abs).size;
119
+ }
120
+ catch {
121
+ yield { path: relPath, reason: 'unreadable', size: 0 };
122
+ continue;
123
+ }
124
+ if (size > filters.maxFileSize) {
125
+ yield { path: relPath, reason: 'too_large', size };
126
+ continue;
127
+ }
128
+ let buf;
129
+ try {
130
+ buf = readFileSync(abs);
131
+ }
132
+ catch {
133
+ yield { path: relPath, reason: 'unreadable', size };
134
+ continue;
135
+ }
136
+ if (isBinary(buf)) {
137
+ yield { path: relPath, reason: 'binary', size };
138
+ continue;
139
+ }
140
+ yield { path: relPath, content: buf.toString('utf8'), size };
141
+ }
142
+ }
143
+ }
144
+ /**
145
+ * Binary-file heuristic. Mirrors Git's "first 8000 bytes contain a
146
+ * NUL" detection — simple, robust, and matches what rendergit ships.
147
+ */
148
+ export function isBinary(content) {
149
+ const len = Math.min(content.length, 8000);
150
+ for (let i = 0; i < len; i += 1) {
151
+ if (content[i] === 0)
152
+ return true;
153
+ }
154
+ return false;
155
+ }
156
+ /**
157
+ * Parse a `.gitignore`-shaped file into a deduped pattern list. Thin
158
+ * wrapper around `readFileSync` + the gitignore comment / blank-line
159
+ * rules. Re-exports `core/context/pugiignore`'s parser when callers
160
+ * want a non-layered raw read (test fixtures, CI shims).
161
+ */
162
+ export function parseIgnoreFile(path) {
163
+ if (!existsSync(path))
164
+ return [];
165
+ try {
166
+ const raw = readFileSync(path, 'utf8');
167
+ const out = [];
168
+ for (const line of raw.split(/\r?\n/)) {
169
+ const trimmed = line.trim();
170
+ if (trimmed.length === 0)
171
+ continue;
172
+ if (trimmed.startsWith('#'))
173
+ continue;
174
+ out.push(trimmed);
175
+ }
176
+ return out;
177
+ }
178
+ catch {
179
+ return [];
180
+ }
181
+ }
182
+ /**
183
+ * Render the human-facing HTML page. Single document, Prism.js loaded
184
+ * from CDN, nav tree on the left, file sections anchored on the right.
185
+ *
186
+ * Generated structure mirrors karpathy/rendergit (BSD-0):
187
+ * - <head> Prism CSS + a few inline rules for the layout
188
+ * - <aside> nav tree of file paths with anchor links
189
+ * - <main> one <section> per file with `<pre><code class="language-X">`
190
+ * - <footer> credits the upstream tool + license
191
+ */
192
+ export function renderHumanHTML(files, skipped, repoName) {
193
+ const title = escapeHtml(`${repoName} — pugi flatten`);
194
+ const nav = files
195
+ .map((f) => {
196
+ const anchor = anchorFor(f.path);
197
+ return ` <li><a href="#${anchor}">${escapeHtml(f.path)}</a></li>`;
198
+ })
199
+ .join('\n');
200
+ const skippedList = skipped.length === 0
201
+ ? ''
202
+ : `
203
+ <section class="skipped">
204
+ <h2>Skipped (${skipped.length})</h2>
205
+ <ul>
206
+ ${skipped
207
+ .map((s) => ` <li><code>${escapeHtml(s.path)}</code> — ${s.reason} (${s.size}B)</li>`)
208
+ .join('\n')}
209
+ </ul>
210
+ </section>`;
211
+ const sections = files
212
+ .map((f) => {
213
+ const lang = languageFor(f.path);
214
+ const anchor = anchorFor(f.path);
215
+ return ` <section id="${anchor}" class="file">
216
+ <h2><a href="#${anchor}">${escapeHtml(f.path)}</a></h2>
217
+ <pre><code class="language-${lang}">${escapeHtml(f.content)}</code></pre>
218
+ </section>`;
219
+ })
220
+ .join('\n');
221
+ return `<!DOCTYPE html>
222
+ <html lang="en">
223
+ <head>
224
+ <meta charset="utf-8" />
225
+ <title>${title}</title>
226
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" integrity="sha512-tN7Ec6zAFaVSG3TpNAKtk4DOHNpSwKHxxrsiw4GHKESGPs5njn/0sMCUMl2svV4wo4BK/rCP7juYz+zx+l6oeQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
227
+ <style>
228
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
229
+ margin: 0; display: grid; grid-template-columns: 280px 1fr; min-height: 100vh; }
230
+ aside { background: #f7f7f7; border-right: 1px solid #ddd; padding: 1rem;
231
+ position: sticky; top: 0; height: 100vh; overflow-y: auto; }
232
+ aside h1 { font-size: 1rem; margin: 0 0 1rem; }
233
+ aside ul { list-style: none; padding: 0; margin: 0; font-family: monospace; font-size: 0.85rem; }
234
+ aside li { margin: 0.15rem 0; word-break: break-all; }
235
+ aside a { color:; text-decoration: none; }
236
+ aside a:hover { text-decoration: underline; }
237
+ main { padding: 1.5rem; max-width: 100%; overflow-x: auto; }
238
+ section.file { margin-bottom: 2rem; border-bottom: 1px solid #eee; padding-bottom: 1rem; }
239
+ section.file h2 { font-family: monospace; font-size: 0.95rem; }
240
+ section.file h2 a { color:; text-decoration: none; }
241
+ pre { background: #fafafa; padding: 1rem; border: 1px solid #eee; overflow-x: auto; }
242
+ code { font-family: "SF Mono", Menlo, Consolas, monospace; font-size: 0.85rem; }
243
+ section.skipped { color:; font-size: 0.85rem; border-top: 2px solid #ddd; padding-top: 1rem; }
244
+ footer { grid-column: 1 / -1; padding: 1rem 1.5rem; color:; font-size: 0.8rem;
245
+ border-top: 1px solid #eee; }
246
+ </style>
247
+ </head>
248
+ <body>
249
+ <aside>
250
+ <h1>${title}</h1>
251
+ <ul>
252
+ ${nav}
253
+ </ul>
254
+ </aside>
255
+ <main>
256
+ ${sections}
257
+ ${skippedList}
258
+ </main>
259
+ <footer>
260
+ Generated by <code>pugi flatten</code>. Ported from
261
+ <a href="https://github.com/karpathy/rendergit">karpathy/rendergit</a> (BSD-0).
262
+ </footer>
263
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js" integrity="sha512-7Z9J3l1+EYfeaPKcGXu3MS/7T+w19WtKQY/n+xzmw4hZhJ9tyYmcUS+4QqAlzhicE5LAfMQSF3iFTK9bQdTxXg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
264
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-typescript.min.js" integrity="sha512-uOw7XYETzS/DPmmirpP5UCMihSDNMeyTS965J0/456OSPfxn9xEtHHjj5Q/5WefVdqyMfN/afmQnNpZd/tpkcA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
265
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-tsx.min.js" integrity="sha512-xjGCJ9YxyZBfYTCHsEjkOZMoOse1W3cKMXv1szXrxs68myuXt0YTj3/xKPar6iDMlXzTUSEqwUxprWcyp+plaw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
266
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-jsx.min.js" integrity="sha512-m3JYEI6gx5fh9jF10FjGoMzVKcV2N6nchcDcqPCdI1L3R2WQV7br2XVNR8iTLb2daOMRl3zldbcfT40xU2ntVw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
267
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js" integrity="sha512-whYhDwtTmlC/NpZlCr6PSsAaLOrfjVg/iXAnC4H/dtiHawpShhT2SlIMbpIhT/IL/NrpdMm+Hq2C13+VKpHTYw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
268
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js" integrity="sha512-QXFMVAusM85vUYDaNgcYeU3rzSlc+bTV4JvkfJhjxSHlQEo+ig53BtnGkvFTiNJh8D+wv6uWAQ2vJaVmxe8d3w==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
269
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js" integrity="sha512-6O/PZimM3TD1NN3yrazePA4AbZrPcwt1QCGJrVY7WoHDJROZFc9TlBvIKMe+QfqgcslW4lQeBzNJEJvIMC8WhA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
270
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js" integrity="sha512-AKaNmg8COK0zEbjTdMHJAPJ0z6VeNqvRvH4/d5M4sHJbQQUToMBtodq4HaV4fa+WV2UTfoperElm66c9/8cKmQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
271
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-rust.min.js" integrity="sha512-R/HpMlNfZZV6DxMhNz1dYJbBaPBVSOCqX8imo3uGUurhPDHRcXrBxkEVOjawwUwTMfOqT6t1eLYYQXIjFWCX/A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
272
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-go.min.js" integrity="sha512-w200Nz1i9KgDNi+IpPMgpZBVRIvfVK/V5vskyHjkz7XJkVnRJcb1uNmpiHhDv0/Ln+GG2VqScKKz/1izBfg64Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
273
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-markdown.min.js" integrity="sha512-IHQR8J+JbQpZ1tjkHkq8Ivsgo6ovfnYbQnYzmoKCjTCQG90YVs9l+2P14DRIZ94VBrB+F86Ju4wSGOMOjfVCQQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
274
+ </body>
275
+ </html>
276
+ `;
277
+ }
278
+ /**
279
+ * Render the LLM-CXML envelope. Each file is wrapped in a
280
+ * `<file path="...">` block with the content escaped for XML.
281
+ */
282
+ export function renderLLMXML(files, skipped, repoName) {
283
+ const blocks = files
284
+ .map((f) => ` <file path="${escapeXmlAttr(f.path)}" size="${f.size}">
285
+ <content><![CDATA[${escapeCdata(f.content)}]]></content>
286
+ </file>`)
287
+ .join('\n');
288
+ const skippedBlocks = skipped
289
+ .map((s) => ` <skipped path="${escapeXmlAttr(s.path)}" reason="${s.reason}" size="${s.size}" />`)
290
+ .join('\n');
291
+ return `<?xml version="1.0" encoding="UTF-8"?>
292
+ <repo name="${escapeXmlAttr(repoName)}" generator="pugi-flatten">
293
+ ${blocks}
294
+ ${skippedBlocks}
295
+ </repo>
296
+ `;
297
+ }
298
+ // --- helpers --------------------------------------------------------------
299
+ function matchesFilters(path, include, exclude) {
300
+ if (include.length > 0) {
301
+ let any = false;
302
+ for (const pat of include) {
303
+ if (substringOrGlob(path, pat)) {
304
+ any = true;
305
+ break;
306
+ }
307
+ }
308
+ if (!any)
309
+ return false;
310
+ }
311
+ for (const pat of exclude) {
312
+ if (substringOrGlob(path, pat))
313
+ return false;
314
+ }
315
+ return true;
316
+ }
317
+ /**
318
+ * Lightweight matcher: `*` glob OR plain substring. We deliberately
319
+ * avoid a full glob library here — the use case is operator-driven
320
+ * `--include=src/` and `--exclude=test/` overrides, not arbitrary
321
+ * gitignore semantics (which already run upstream in `loadPugiIgnore`).
322
+ */
323
+ function substringOrGlob(path, pattern) {
324
+ if (pattern.length === 0)
325
+ return false;
326
+ if (!pattern.includes('*'))
327
+ return path.includes(pattern);
328
+ const re = new RegExp('^' +
329
+ pattern
330
+ .split('*')
331
+ .map((part) => escapeRegex(part))
332
+ .join('.*') +
333
+ '$');
334
+ return re.test(path);
335
+ }
336
+ function escapeRegex(s) {
337
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
338
+ }
339
+ function toPosix(p) {
340
+ return sep === '/' ? p : p.split(sep).join('/');
341
+ }
342
+ function anchorFor(path) {
343
+ return 'f-' + path.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-+|-+$/g, '');
344
+ }
345
+ function escapeHtml(s) {
346
+ return s
347
+ .replace(/&/g, '&amp;')
348
+ .replace(/</g, '&lt;')
349
+ .replace(/>/g, '&gt;')
350
+ .replace(/"/g, '&quot;')
351
+ .replace(/'/g, '&#39;');
352
+ }
353
+ function escapeXmlAttr(s) {
354
+ return s
355
+ .replace(/&/g, '&amp;')
356
+ .replace(/</g, '&lt;')
357
+ .replace(/>/g, '&gt;')
358
+ .replace(/"/g, '&quot;');
359
+ }
360
+ /**
361
+ * Escape a payload for inclusion inside a `<![CDATA[ ... ]]>` block.
362
+ * CDATA cannot contain the literal `]]>`; the workaround is to split
363
+ * the sequence across two CDATA sections.
364
+ */
365
+ function escapeCdata(s) {
366
+ return s.replace(/]]>/g, ']]]]><![CDATA[>');
367
+ }
368
+ const LANG_BY_EXT = {
369
+ '.ts': 'typescript',
370
+ '.tsx': 'tsx',
371
+ '.js': 'javascript',
372
+ '.jsx': 'jsx',
373
+ '.mjs': 'javascript',
374
+ '.cjs': 'javascript',
375
+ '.py': 'python',
376
+ '.rs': 'rust',
377
+ '.go': 'go',
378
+ '.json': 'json',
379
+ '.yml': 'yaml',
380
+ '.yaml': 'yaml',
381
+ '.md': 'markdown',
382
+ '.sh': 'bash',
383
+ '.bash': 'bash',
384
+ '.zsh': 'bash',
385
+ '.html': 'markup',
386
+ '.css': 'css',
387
+ '.sql': 'sql',
388
+ '.toml': 'toml',
389
+ };
390
+ function languageFor(path) {
391
+ const ext = extname(path).toLowerCase();
392
+ return LANG_BY_EXT[ext] ?? 'plaintext';
393
+ }
394
+ function ensureDir(dir) {
395
+ if (!existsSync(dir)) {
396
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
397
+ return;
398
+ }
399
+ try {
400
+ chmodSync(dir, 0o700);
401
+ }
402
+ catch {
403
+ // ignore — best-effort tightening of an existing dir.
404
+ }
405
+ }
406
+ function resolveShortSha(cwd) {
407
+ try {
408
+ const out = execFileSync('git', ['rev-parse', '--short', 'HEAD'], {
409
+ cwd,
410
+ stdio: ['ignore', 'pipe', 'ignore'],
411
+ encoding: 'utf8',
412
+ });
413
+ const trimmed = out.trim();
414
+ if (/^[0-9a-f]{4,40}$/i.test(trimmed))
415
+ return trimmed;
416
+ }
417
+ catch {
418
+ // not a git repo, or git missing — fall through.
419
+ }
420
+ // Fall back to a sortable timestamp so the filename stays unique.
421
+ const now = new Date();
422
+ return [
423
+ now.getUTCFullYear().toString().slice(2),
424
+ String(now.getUTCMonth() + 1).padStart(2, '0'),
425
+ String(now.getUTCDate()).padStart(2, '0'),
426
+ String(now.getUTCHours()).padStart(2, '0'),
427
+ String(now.getUTCMinutes()).padStart(2, '0'),
428
+ ].join('');
429
+ }
430
+ function tryOpenBrowser(path) {
431
+ try {
432
+ const bin = process.platform === 'darwin' ? 'open' : 'xdg-open';
433
+ execFileSync(bin, [path], { stdio: 'ignore' });
434
+ }
435
+ catch {
436
+ // best-effort; absence of a browser opener is non-fatal.
437
+ }
438
+ }
439
+ //# sourceMappingURL=flatten-repo.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * OSC 8 hyperlink helpers — turns workspace-relative file paths into
3
+ * clickable links in modern terminals (iTerm2, kitty, Windows Terminal,
4
+ * VS Code integrated terminal, Alacritty, WezTerm). Dumb terminals and
5
+ * pipes ignore the escape sequence and render only the visible label.
6
+ *
7
+ * CEO P2 #38 — artifact linking. Triggers ONLY when:
8
+ * - stdout is a TTY
9
+ * - PUGI_ARTIFACT_LINKS_DISABLE !== '1'
10
+ * - NO_COLOR is not set
11
+ */
12
+ import { resolve, isAbsolute } from 'node:path';
13
+ import { pathToFileURL } from 'node:url';
14
+ const ESC = '';
15
+ export function linkArtifact(pathRel, opts) {
16
+ const env = opts.env ?? process.env;
17
+ const tty = opts.isTty ?? process.stdout.isTTY ?? false;
18
+ if (!tty)
19
+ return pathRel;
20
+ if (env['PUGI_ARTIFACT_LINKS_DISABLE'] === '1')
21
+ return pathRel;
22
+ if (env['NO_COLOR'] && env['NO_COLOR'].length > 0)
23
+ return pathRel;
24
+ const abs = isAbsolute(pathRel) ? pathRel : resolve(opts.workspaceRoot, pathRel);
25
+ const url = pathToFileURL(abs).href;
26
+ return `${ESC}]8;;${url}${ESC}\\${pathRel}${ESC}]8;;${ESC}\\`;
27
+ }
28
+ //# sourceMappingURL=osc8-link.js.map