@pugi/cli 0.1.0-beta.8 → 0.1.0-beta.88

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 (405) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/THIRD_PARTY_NOTICES.md +40 -0
  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/smoke.js +133 -0
  11. package/dist/core/agent-progress/cleanup.js +134 -0
  12. package/dist/core/agent-progress/schema.js +144 -0
  13. package/dist/core/agent-progress/writer.js +101 -0
  14. package/dist/core/agents/adaptive-router.js +330 -0
  15. package/dist/core/agents/query-decomposer.js +297 -0
  16. package/dist/core/agents/registry.js +3 -3
  17. package/dist/core/approvals/shortcut-resolver.js +98 -0
  18. package/dist/core/artifact-chain/dispatcher.js +148 -0
  19. package/dist/core/artifact-chain/exporter.js +164 -0
  20. package/dist/core/artifact-chain/state.js +243 -0
  21. package/dist/core/artifact-chain/steps.js +169 -0
  22. package/dist/core/ask-user/question.js +92 -0
  23. package/dist/core/audit/audit-trail.js +275 -0
  24. package/dist/core/auth/ensure-authenticated.js +129 -0
  25. package/dist/core/auth/env-provider.js +238 -0
  26. package/dist/core/auto-open-browser.js +4 -4
  27. package/dist/core/auto-update/channels.js +122 -0
  28. package/dist/core/auto-update/checker.js +241 -0
  29. package/dist/core/auto-update/state.js +235 -0
  30. package/dist/core/bare-mode/index.js +107 -0
  31. package/dist/core/bash/redirect.js +281 -0
  32. package/dist/core/bash-classifier.js +436 -40
  33. package/dist/core/checkpoint/resumer.js +149 -0
  34. package/dist/core/checkpoint/rewinder.js +291 -0
  35. package/dist/core/checkpoints/shadow-git.js +670 -0
  36. package/dist/core/citations/parser.js +109 -0
  37. package/dist/core/classifier/yolo-classifier.js +88 -0
  38. package/dist/core/codegraph/decision-store.js +248 -0
  39. package/dist/core/codegraph/detect-repo.js +459 -0
  40. package/dist/core/codegraph/install.js +134 -0
  41. package/dist/core/codegraph/offer-hook.js +220 -0
  42. package/dist/core/compact/auto-trigger.js +96 -0
  43. package/dist/core/compact/buffer-rewriter.js +115 -0
  44. package/dist/core/compact/summarizer.js +208 -0
  45. package/dist/core/compact/token-counter.js +108 -0
  46. package/dist/core/consensus/anvil-fanout.js +25 -25
  47. package/dist/core/consensus/diff-capture.js +121 -12
  48. package/dist/core/consensus/rubric.js +21 -21
  49. package/dist/core/context/builder.js +6 -6
  50. package/dist/core/context/compaction-events.js +8 -8
  51. package/dist/core/context/compaction.js +31 -31
  52. package/dist/core/context/index.js +15 -8
  53. package/dist/core/context/invariants.js +51 -51
  54. package/dist/core/context/markdown-loader.js +28 -10
  55. package/dist/core/context/markdown-traverse.js +255 -0
  56. package/dist/core/context/pugiignore.js +41 -41
  57. package/dist/core/context/repo-skeleton.js +37 -37
  58. package/dist/core/context/tool-eviction.js +55 -0
  59. package/dist/core/context/watcher.js +32 -32
  60. package/dist/core/context/working-set.js +23 -23
  61. package/dist/core/coordinator/agent-tools.js +77 -0
  62. package/dist/core/coordinator/agent-toolset.js +65 -0
  63. package/dist/core/coordinator/fsm.js +73 -0
  64. package/dist/core/coordinator/mode-fsm.js +70 -0
  65. package/dist/core/cost/rate-card.js +129 -0
  66. package/dist/core/cost/tracker.js +221 -0
  67. package/dist/core/credentials.js +12 -12
  68. package/dist/core/cron/scheduler.js +138 -0
  69. package/dist/core/denial-tracking/index.js +8 -0
  70. package/dist/core/denial-tracking/state.js +264 -0
  71. package/dist/core/diagnostics/probe-runner.js +93 -0
  72. package/dist/core/diagnostics/probes/api.js +46 -0
  73. package/dist/core/diagnostics/probes/auth.js +93 -0
  74. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  75. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  76. package/dist/core/diagnostics/probes/config.js +72 -0
  77. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  78. package/dist/core/diagnostics/probes/disk.js +81 -0
  79. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  80. package/dist/core/diagnostics/probes/git.js +65 -0
  81. package/dist/core/diagnostics/probes/hooks.js +118 -0
  82. package/dist/core/diagnostics/probes/mcp.js +75 -0
  83. package/dist/core/diagnostics/probes/node.js +59 -0
  84. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  85. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  86. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  87. package/dist/core/diagnostics/probes/session.js +74 -0
  88. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  89. package/dist/core/diagnostics/probes/workspace.js +63 -0
  90. package/dist/core/diagnostics/types.js +70 -0
  91. package/dist/core/dispatch/cache-cleanup.js +197 -0
  92. package/dist/core/dispatch/cache-handoff.js +295 -0
  93. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  94. package/dist/core/edits/dispatch.js +293 -7
  95. package/dist/core/edits/format-matrix.js +26 -0
  96. package/dist/core/edits/fuzzy-ladder.js +650 -0
  97. package/dist/core/edits/index.js +3 -1
  98. package/dist/core/edits/journal.js +199 -0
  99. package/dist/core/edits/layer-a-apply.js +15 -15
  100. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  101. package/dist/core/edits/layer-b-apply.js +9 -9
  102. package/dist/core/edits/layer-c-apply.js +6 -6
  103. package/dist/core/edits/layer-d-ast.js +557 -14
  104. package/dist/core/edits/marker-parser.js +12 -12
  105. package/dist/core/edits/security-gate.js +27 -27
  106. package/dist/core/edits/verify-hook.js +273 -0
  107. package/dist/core/edits/worktree.js +322 -0
  108. package/dist/core/engine/anvil-client.js +151 -26
  109. package/dist/core/engine/auto-compact.js +179 -0
  110. package/dist/core/engine/budgets.js +186 -0
  111. package/dist/core/engine/context-prefix.js +155 -0
  112. package/dist/core/engine/index.js +1 -1
  113. package/dist/core/engine/intensity.js +158 -0
  114. package/dist/core/engine/intent.js +260 -0
  115. package/dist/core/engine/native-pugi.js +1295 -227
  116. package/dist/core/engine/prompts.js +134 -16
  117. package/dist/core/engine/strip-internal-fields.js +124 -0
  118. package/dist/core/engine/tool-bridge.js +1295 -59
  119. package/dist/core/evaluation/golden-dataset.js +293 -0
  120. package/dist/core/feedback/queue.js +177 -0
  121. package/dist/core/feedback/submitter.js +145 -0
  122. package/dist/core/file-cache.js +113 -1
  123. package/dist/core/flatten/flatten-repo.js +439 -0
  124. package/dist/core/format/osc8-link.js +28 -0
  125. package/dist/core/hook-chains.js +392 -0
  126. package/dist/core/hooks/citation-verify-hook.js +138 -0
  127. package/dist/core/hooks/citation-verify.js +112 -0
  128. package/dist/core/hooks/events.js +44 -0
  129. package/dist/core/hooks/index.js +15 -0
  130. package/dist/core/hooks/registry.js +213 -0
  131. package/dist/core/hooks/runner.js +236 -0
  132. package/dist/core/hooks/v2/event-emitter.js +115 -0
  133. package/dist/core/hooks/v2/executor.js +282 -0
  134. package/dist/core/hooks/v2/index.js +25 -0
  135. package/dist/core/hooks/v2/lifecycle.js +104 -0
  136. package/dist/core/hooks/v2/loader.js +216 -0
  137. package/dist/core/hooks/v2/matcher.js +125 -0
  138. package/dist/core/hooks/v2/trust.js +143 -0
  139. package/dist/core/hooks/v2/types.js +86 -0
  140. package/dist/core/image/renderer.js +71 -0
  141. package/dist/core/init/detector.js +582 -0
  142. package/dist/core/init/template-renderer.js +242 -0
  143. package/dist/core/jobs/registry.js +18 -18
  144. package/dist/core/ledger/results-tsv.js +142 -0
  145. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  146. package/dist/core/lsp/cache.js +105 -0
  147. package/dist/core/lsp/client.js +776 -0
  148. package/dist/core/lsp/language-detect.js +66 -0
  149. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  150. package/dist/core/lsp/symbol-tools.js +372 -0
  151. package/dist/core/mcp/client.js +97 -28
  152. package/dist/core/mcp/http-server.js +553 -0
  153. package/dist/core/mcp/orchestrator-tools.js +662 -0
  154. package/dist/core/mcp/permission.js +190 -0
  155. package/dist/core/mcp/registry.js +39 -17
  156. package/dist/core/mcp/server-tools.js +219 -0
  157. package/dist/core/mcp/server.js +397 -0
  158. package/dist/core/mcp/trust.js +10 -10
  159. package/dist/core/memory/dual-write.js +416 -0
  160. package/dist/core/memory/passive-extract.js +130 -0
  161. package/dist/core/memory/phase1-kinds.js +20 -0
  162. package/dist/core/memory/secret-scanner.js +304 -0
  163. package/dist/core/memory-sync/queue.js +170 -0
  164. package/dist/core/metrics/extract.js +113 -0
  165. package/dist/core/modes/roo-modes.js +68 -0
  166. package/dist/core/onboarding/ensure-initialized.js +133 -0
  167. package/dist/core/onboarding/marker.js +111 -0
  168. package/dist/core/onboarding/telemetry-state.js +108 -0
  169. package/dist/core/output-style/presets.js +176 -0
  170. package/dist/core/output-style/state.js +185 -0
  171. package/dist/core/path-security.js +287 -5
  172. package/dist/core/permission.js +82 -22
  173. package/dist/core/permissions/auto-classifier.js +124 -0
  174. package/dist/core/permissions/bash-parser.js +371 -0
  175. package/dist/core/permissions/circuit-breaker.js +83 -0
  176. package/dist/core/permissions/constrained-edit.js +91 -0
  177. package/dist/core/permissions/gate.js +278 -0
  178. package/dist/core/permissions/index.js +20 -0
  179. package/dist/core/permissions/mode.js +174 -0
  180. package/dist/core/permissions/network-egress.js +137 -0
  181. package/dist/core/permissions/state.js +241 -0
  182. package/dist/core/permissions/tool-class.js +93 -0
  183. package/dist/core/plan-mode/ui-state.js +51 -0
  184. package/dist/core/plans/plan-artifact.js +721 -0
  185. package/dist/core/policy-limits/etag-store.js +122 -0
  186. package/dist/core/prd-check/parser.js +215 -0
  187. package/dist/core/prd-check/reporter.js +127 -0
  188. package/dist/core/prd-check/session-review.js +557 -0
  189. package/dist/core/prd-check/verifiers.js +223 -0
  190. package/dist/core/prompt-cache/client-cache.js +99 -0
  191. package/dist/core/prompts/assembly.js +29 -0
  192. package/dist/core/prompts/registry.js +364 -0
  193. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  194. package/dist/core/pugi-md/context-injector.js +76 -0
  195. package/dist/core/pugi-md/walk-up.js +207 -0
  196. package/dist/core/python/uv-installer.js +270 -0
  197. package/dist/core/python/uv-resolver.js +83 -0
  198. package/dist/core/rate-limit/narrator.js +146 -0
  199. package/dist/core/recipes/cli-types.js +20 -0
  200. package/dist/core/recipes/loader.js +103 -0
  201. package/dist/core/recipes/runner.js +345 -0
  202. package/dist/core/recipes/schema.js +587 -0
  203. package/dist/core/release-notes/parser.js +241 -0
  204. package/dist/core/release-notes/state.js +116 -0
  205. package/dist/core/repl/ask.js +37 -37
  206. package/dist/core/repl/cancellation.js +26 -26
  207. package/dist/core/repl/cap-warning.js +4 -4
  208. package/dist/core/repl/clipboard-read.js +11 -11
  209. package/dist/core/repl/dispatch-fsm.js +12 -12
  210. package/dist/core/repl/history-search.js +15 -15
  211. package/dist/core/repl/history.js +28 -18
  212. package/dist/core/repl/kill-ring.js +5 -5
  213. package/dist/core/repl/model-pricing.js +135 -0
  214. package/dist/core/repl/privacy-banner.js +22 -22
  215. package/dist/core/repl/session.js +2157 -214
  216. package/dist/core/repl/slash-commands.js +533 -40
  217. package/dist/core/repl/store/index.js +1 -1
  218. package/dist/core/repl/store/jsonl-log.js +22 -22
  219. package/dist/core/repl/store/lockfile.js +10 -10
  220. package/dist/core/repl/store/session-store.js +136 -107
  221. package/dist/core/repl/store/types.js +15 -15
  222. package/dist/core/repl/store/uuid-v7.js +12 -12
  223. package/dist/core/repl/workspace-context.js +43 -21
  224. package/dist/core/repo-map/build.js +125 -0
  225. package/dist/core/repo-map/cache.js +185 -0
  226. package/dist/core/repo-map/extractor.js +254 -0
  227. package/dist/core/repo-map/formatter.js +145 -0
  228. package/dist/core/repo-map/page-rank.js +105 -0
  229. package/dist/core/repo-map/scanner.js +211 -0
  230. package/dist/core/retry-budget/budget.js +284 -0
  231. package/dist/core/retry-budget/index.js +5 -0
  232. package/dist/core/retry-budget/retry-cap.js +74 -0
  233. package/dist/core/routing/lead-worker.js +43 -0
  234. package/dist/core/routing/pre-flight-estimator.js +108 -0
  235. package/dist/core/runs/run-tree.js +103 -0
  236. package/dist/core/security/injection-scanner.js +367 -0
  237. package/dist/core/security/output-filter.js +418 -0
  238. package/dist/core/session/env-file.js +105 -0
  239. package/dist/core/session/section-budgets.js +140 -0
  240. package/dist/core/session.js +92 -0
  241. package/dist/core/settings.js +298 -5
  242. package/dist/core/share/formatter.js +271 -0
  243. package/dist/core/share/redactor.js +221 -0
  244. package/dist/core/share/uploader.js +267 -0
  245. package/dist/core/skills/defaults.js +457 -0
  246. package/dist/core/skills/loader.js +22 -22
  247. package/dist/core/skills/sources.js +27 -27
  248. package/dist/core/smoke/headless-driver.js +174 -0
  249. package/dist/core/smoke/orchestrator.js +194 -0
  250. package/dist/core/smoke/runner.js +238 -0
  251. package/dist/core/smoke/scenario-parser.js +316 -0
  252. package/dist/core/statusline.js +99 -0
  253. package/dist/core/subagents/dispatcher-real.js +600 -0
  254. package/dist/core/subagents/dispatcher.js +132 -43
  255. package/dist/core/subagents/index.js +19 -6
  256. package/dist/core/subagents/isolation-matrix.js +213 -0
  257. package/dist/core/subagents/spawn.js +19 -4
  258. package/dist/core/telemetry/emitter.js +229 -0
  259. package/dist/core/telemetry/queue.js +251 -0
  260. package/dist/core/theme/context.js +91 -0
  261. package/dist/core/theme/presets.js +228 -0
  262. package/dist/core/theme/state.js +181 -0
  263. package/dist/core/todos/invariant.js +10 -0
  264. package/dist/core/todos/state.js +177 -0
  265. package/dist/core/tool-schema/compressor.js +89 -0
  266. package/dist/core/transport/version-interceptor.js +166 -0
  267. package/dist/core/trust.js +2 -2
  268. package/dist/core/tui/thinking-block.js +64 -0
  269. package/dist/core/vim/keymap.js +288 -0
  270. package/dist/core/vim/state.js +92 -0
  271. package/dist/core/watch-markers/marker-watcher.js +133 -0
  272. package/dist/core/worktree-manager/cleanup.js +123 -0
  273. package/dist/core/worktree-manager/manager.js +303 -0
  274. package/dist/index.js +36 -0
  275. package/dist/runtime/bootstrap.js +190 -0
  276. package/dist/runtime/cli.js +4203 -493
  277. package/dist/runtime/commands/agents.js +30 -30
  278. package/dist/runtime/commands/budget.js +5 -5
  279. package/dist/runtime/commands/cancel.js +231 -0
  280. package/dist/runtime/commands/chain.js +489 -0
  281. package/dist/runtime/commands/codegraph-status.js +227 -0
  282. package/dist/runtime/commands/compact.js +297 -0
  283. package/dist/runtime/commands/config.js +73 -39
  284. package/dist/runtime/commands/cost.js +199 -0
  285. package/dist/runtime/commands/delegate.js +244 -13
  286. package/dist/runtime/commands/dispatch.js +126 -0
  287. package/dist/runtime/commands/doctor.js +579 -0
  288. package/dist/runtime/commands/feedback.js +184 -0
  289. package/dist/runtime/commands/hooks.js +184 -0
  290. package/dist/runtime/commands/init.js +254 -0
  291. package/dist/runtime/commands/lsp.js +368 -0
  292. package/dist/runtime/commands/mcp.js +879 -0
  293. package/dist/runtime/commands/memory.js +582 -0
  294. package/dist/runtime/commands/model.js +237 -0
  295. package/dist/runtime/commands/onboarding.js +275 -0
  296. package/dist/runtime/commands/patch.js +128 -0
  297. package/dist/runtime/commands/permissions.js +112 -0
  298. package/dist/runtime/commands/plan.js +143 -0
  299. package/dist/runtime/commands/prd-check.js +285 -0
  300. package/dist/runtime/commands/privacy.js +17 -17
  301. package/dist/runtime/commands/recipe.js +325 -0
  302. package/dist/runtime/commands/redo-blob-store.js +92 -0
  303. package/dist/runtime/commands/redo.js +361 -0
  304. package/dist/runtime/commands/release-notes.js +229 -0
  305. package/dist/runtime/commands/repo-map.js +95 -0
  306. package/dist/runtime/commands/report.js +299 -0
  307. package/dist/runtime/commands/resume.js +118 -0
  308. package/dist/runtime/commands/review-consensus.js +68 -53
  309. package/dist/runtime/commands/rewind.js +333 -0
  310. package/dist/runtime/commands/roster.js +14 -14
  311. package/dist/runtime/commands/sessions.js +163 -0
  312. package/dist/runtime/commands/share.js +316 -0
  313. package/dist/runtime/commands/skills.js +31 -31
  314. package/dist/runtime/commands/status.js +186 -0
  315. package/dist/runtime/commands/stickers.js +82 -0
  316. package/dist/runtime/commands/style.js +194 -0
  317. package/dist/runtime/commands/theme.js +196 -0
  318. package/dist/runtime/commands/undo.js +54 -22
  319. package/dist/runtime/commands/update.js +289 -0
  320. package/dist/runtime/commands/vim.js +140 -0
  321. package/dist/runtime/commands/worktree.js +177 -0
  322. package/dist/runtime/commands/worktrees.js +155 -0
  323. package/dist/runtime/headless-repl.js +195 -0
  324. package/dist/runtime/headless.js +543 -0
  325. package/dist/runtime/load-hooks-or-exit.js +71 -0
  326. package/dist/runtime/plan-decompose.js +531 -0
  327. package/dist/runtime/sigint-guard.js +272 -0
  328. package/dist/runtime/update-check.js +28 -28
  329. package/dist/runtime/version.js +65 -0
  330. package/dist/skills/bundled/batch.js +617 -0
  331. package/dist/skills/bundled/index.js +45 -0
  332. package/dist/skills/bundled/loop.js +358 -0
  333. package/dist/skills/bundled/remember.js +383 -0
  334. package/dist/skills/bundled/simplify.js +289 -0
  335. package/dist/skills/bundled/skillify.js +373 -0
  336. package/dist/skills/bundled/stuck.js +558 -0
  337. package/dist/skills/bundled/verify.js +439 -0
  338. package/dist/testing/vcr.js +486 -0
  339. package/dist/tools/agent-tool.js +229 -0
  340. package/dist/tools/apply-patch.js +556 -0
  341. package/dist/tools/ask-user-question.js +288 -0
  342. package/dist/tools/ask-user.js +115 -0
  343. package/dist/tools/bash.js +624 -46
  344. package/dist/tools/brief.js +224 -0
  345. package/dist/tools/enter-worktree.js +250 -0
  346. package/dist/tools/exit-worktree.js +147 -0
  347. package/dist/tools/file-tools.js +161 -44
  348. package/dist/tools/lsp-tools.js +189 -0
  349. package/dist/tools/mcp-tool.js +260 -0
  350. package/dist/tools/multi-edit.js +361 -0
  351. package/dist/tools/powershell.js +268 -0
  352. package/dist/tools/registry.js +85 -0
  353. package/dist/tools/skill-tool.js +96 -0
  354. package/dist/tools/sleep.js +99 -0
  355. package/dist/tools/synthetic-output.js +133 -0
  356. package/dist/tools/tasks.js +208 -0
  357. package/dist/tools/todo-write.js +184 -0
  358. package/dist/tools/verify-plan-execution.js +295 -0
  359. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  360. package/dist/tools/web-fetch.js +195 -10
  361. package/dist/tools/web-search.js +458 -0
  362. package/dist/tui/agent-progress-card.js +111 -0
  363. package/dist/tui/agent-tree.js +11 -1
  364. package/dist/tui/ask-modal.js +14 -14
  365. package/dist/tui/ask-user-question-chips.js +257 -0
  366. package/dist/tui/ask-user-question-prompt.js +203 -0
  367. package/dist/tui/compact-banner.js +81 -0
  368. package/dist/tui/conversation-pane.js +85 -11
  369. package/dist/tui/cost-table.js +111 -0
  370. package/dist/tui/device-flow.js +2 -2
  371. package/dist/tui/doctor-table.js +46 -0
  372. package/dist/tui/feedback-prompt.js +156 -0
  373. package/dist/tui/input-box.js +247 -32
  374. package/dist/tui/login-picker.js +3 -3
  375. package/dist/tui/markdown-render.js +6 -6
  376. package/dist/tui/onboarding-wizard.js +240 -0
  377. package/dist/tui/permissions-picker.js +86 -0
  378. package/dist/tui/render.js +35 -0
  379. package/dist/tui/repl-render.js +332 -54
  380. package/dist/tui/repl-splash-art.js +16 -16
  381. package/dist/tui/repl-splash-mascot.js +48 -24
  382. package/dist/tui/repl-splash.js +22 -22
  383. package/dist/tui/repl.js +124 -44
  384. package/dist/tui/slash-palette.js +6 -6
  385. package/dist/tui/splash.js +2 -2
  386. package/dist/tui/status-bar.js +109 -31
  387. package/dist/tui/status-table.js +7 -0
  388. package/dist/tui/stickers-art.js +136 -0
  389. package/dist/tui/style-table.js +28 -0
  390. package/dist/tui/theme-table.js +29 -0
  391. package/dist/tui/thinking-spinner.js +123 -0
  392. package/dist/tui/tool-stream-pane.js +53 -4
  393. package/dist/tui/update-banner.js +27 -2
  394. package/dist/tui/vim-input.js +267 -0
  395. package/dist/tui/welcome-banner.js +107 -0
  396. package/dist/tui/welcome-data.js +293 -0
  397. package/dist/tui/workspace-context.js +2 -2
  398. package/docs/examples/codegraph.mcp.json +10 -0
  399. package/package.json +25 -7
  400. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  401. package/test/scenarios/compact-force.scenario.txt +11 -0
  402. package/test/scenarios/identity.scenario.txt +11 -0
  403. package/test/scenarios/persona-handoff.scenario.txt +11 -0
  404. package/test/scenarios/walkback.scenario.txt +12 -0
  405. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,299 @@
1
+ /**
2
+ * PAVF-7 — `pugi report --from-error` field-bug capture.
3
+ *
4
+ * Operator hit a CLI failure ("pugi explain: failed [auth_missing]...")
5
+ * and wants to file a clean report без manual log-grepping. This command:
6
+ *
7
+ * 1. Locates the most-recently-modified session under .pugi/sessions/
8
+ * (the engine adapter mirrors EVERY dispatch's events to a fresh
9
+ * session dir; the latest one is always the failure that just
10
+ * surprised the operator).
11
+ * 2. Reads events.jsonl + extracts the terminal-state event +
12
+ * the last 50 frames before it (enough context to triage; small
13
+ * enough for a GH issue body or email paste).
14
+ * 3. Captures workspace metadata (CLI version, Node version, OS,
15
+ * tenant id from credentials, current dir, .pugi/PUGI.md presence).
16
+ * 4. Strips secrets — auth tokens, env values, JWT signatures —
17
+ * before the report ever touches disk OR network.
18
+ * 5. Writes the bundle к .pugi/reports/<ISO-timestamp>-<session-id>/
19
+ * with both a machine-readable report.json and a human-readable
20
+ * report.md the operator can paste into a GH issue / email.
21
+ * 6. Prints the path + the canonical share command the operator can
22
+ * run when ready to upload (the upload endpoint is deferred to a
23
+ * follow-up; v1 keeps everything LOCAL so an operator working
24
+ * offline / behind a corporate firewall can still file a clean
25
+ * report).
26
+ *
27
+ * Why not auto-upload in v1:
28
+ * The CEO HARD rule `feedback_no_fake_dispatch_promises` says we do
29
+ * not invent dispatch we cannot deliver. Without a live
30
+ * /api/pugi/report endpoint, an auto-upload would either silently
31
+ * no-op or claim shipped и lie. v1 emits the artifacts + a clear
32
+ * "upload pending" status; v2 (separate PR) wires the endpoint и
33
+ * flips the default к upload-on-success.
34
+ *
35
+ * Exit codes (match the existing PAVF-1 stage_code table):
36
+ * 0 = report written successfully
37
+ * 8 = no sessions found (operator ran in a workspace без .pugi/)
38
+ * 9 = session events.jsonl unreadable / corrupted
39
+ * 20 = output path not writable (disk full / perms)
40
+ *
41
+ * Secret-redaction posture: PII / tokens / env values are stripped at
42
+ * the report-generation layer, NOT at upload time. Even if the operator
43
+ * never uploads, the report dir on disk MUST NOT carry plaintext
44
+ * secrets — a colleague who later runs `cat .pugi/reports/.../report.md`
45
+ * over the shoulder sees the bug context but not the bearer token.
46
+ */
47
+ import { existsSync, readdirSync, readFileSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
48
+ import { join, resolve as resolvePath } from 'node:path';
49
+ import { homedir, platform, release } from 'node:os';
50
+ import { PUGI_CLI_VERSION } from '../version.js';
51
+ const MAX_TAIL_FRAMES = 50;
52
+ const MAX_DETAIL_CHARS = 400;
53
+ const TERMINAL_TYPES = new Set([
54
+ 'agent.completed',
55
+ 'agent.failed',
56
+ 'agent.blocked',
57
+ 'subagent.outcome',
58
+ 'result',
59
+ ]);
60
+ /**
61
+ * Bearer / JWT / env-secret patterns. We do NOT try to be exhaustive
62
+ * (cat-and-mouse with custom secret formats is unwinnable); we cover
63
+ * the shapes that actually appear in Pugi sessions:
64
+ *
65
+ * - `Authorization: Bearer eyJ...` (JWT header.payload.signature)
66
+ * - `apiKey: eyJ...` inside captured JSON envelopes
67
+ * - any long base64-ish token (>= 20 chars, [A-Za-z0-9_-]) following
68
+ * `token`, `password`, `secret`, or `key` field names
69
+ *
70
+ * Replacement is a length-preserving `[REDACTED:<n>]` marker so the
71
+ * operator can still verify the report at-a-glance ("yes, a 32-char
72
+ * token was here") без leaking the value.
73
+ */
74
+ function redact(text) {
75
+ if (!text)
76
+ return text;
77
+ // Bearer + JWT shape.
78
+ text = text.replace(/(Bearer\s+)([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/gi, (_m, prefix, tok) => `${prefix}[REDACTED:${tok.length}]`);
79
+ // Bare JWTs (no Bearer prefix) inside JSON / log lines.
80
+ text = text.replace(/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/g, (tok) => `[REDACTED:${tok.length}]`);
81
+ // `"token": "..."` / `"apiKey": "..."` / `"password": "..."` shapes.
82
+ text = text.replace(/("(?:apiKey|api_key|token|access_token|refresh_token|password|secret|bearer)"\s*:\s*")([^"]{10,})(")/gi, (_m, before, val, after) => `${before}[REDACTED:${val.length}]${after}`);
83
+ // Bare env-style KEY=VALUE на длинных значениях.
84
+ text = text.replace(/\b((?:PUGI_API_KEY|GITHUB_TOKEN|NPM_TOKEN|ANVIL_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY|GEMINI_API_KEY)=)([^\s"']{10,})/g, (_m, prefix, val) => `${prefix}[REDACTED:${val.length}]`);
85
+ return text;
86
+ }
87
+ function clampDetail(value) {
88
+ if (typeof value !== 'string')
89
+ return undefined;
90
+ const redacted = redact(value);
91
+ return redacted.length > MAX_DETAIL_CHARS
92
+ ? `${redacted.slice(0, MAX_DETAIL_CHARS)}…`
93
+ : redacted;
94
+ }
95
+ function findLatestSession(cwd) {
96
+ const dir = resolvePath(cwd, '.pugi/sessions');
97
+ if (!existsSync(dir))
98
+ return null;
99
+ const entries = readdirSync(dir, { withFileTypes: true })
100
+ .filter((e) => e.isDirectory())
101
+ .map((e) => {
102
+ const path = join(dir, e.name);
103
+ let mtime = 0;
104
+ try {
105
+ mtime = statSync(join(path, 'events.jsonl')).mtimeMs;
106
+ }
107
+ catch {
108
+ // Session dir without events.jsonl yet — never opened. Skip.
109
+ return null;
110
+ }
111
+ return { name: e.name, path, mtime };
112
+ })
113
+ .filter((x) => x !== null)
114
+ .sort((a, b) => b.mtime - a.mtime);
115
+ return entries[0]?.path ?? null;
116
+ }
117
+ function readTenantIdSafely() {
118
+ const credPath = resolvePath(homedir(), '.pugi/credentials.json');
119
+ if (!existsSync(credPath))
120
+ return undefined;
121
+ try {
122
+ const raw = JSON.parse(readFileSync(credPath, 'utf8'));
123
+ const first = raw.tokens?.[0]?.apiKey;
124
+ if (!first || typeof first !== 'string')
125
+ return undefined;
126
+ // JWT payload is the middle segment; base64-decode + parse for the
127
+ // `customerId` claim. Failure here returns undefined (the report
128
+ // still emits useful context without it).
129
+ const parts = first.split('.');
130
+ if (parts.length !== 3)
131
+ return undefined;
132
+ const payload = JSON.parse(Buffer.from(parts[1] ?? '', 'base64').toString('utf8'));
133
+ return typeof payload.customerId === 'string' ? payload.customerId : undefined;
134
+ }
135
+ catch {
136
+ return undefined;
137
+ }
138
+ }
139
+ function captureFrames(eventsPath) {
140
+ const lines = readFileSync(eventsPath, 'utf8')
141
+ .split('\n')
142
+ .filter((l) => l.trim().length > 0);
143
+ const parsed = lines
144
+ .map((line) => {
145
+ try {
146
+ return JSON.parse(line);
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ })
152
+ .filter((f) => f !== null);
153
+ // Keep the LAST MAX_TAIL_FRAMES frames — failures cluster at the
154
+ // end, and the tail is where the operator's context actually lives.
155
+ const tail = parsed.slice(-MAX_TAIL_FRAMES);
156
+ return tail.map((f) => {
157
+ const out = {
158
+ type: typeof f.type === 'string' ? f.type : 'unknown',
159
+ };
160
+ if (typeof f.taskId === 'string')
161
+ out.taskId = f.taskId;
162
+ if (typeof f.timestamp === 'string')
163
+ out.timestamp = f.timestamp;
164
+ if (typeof f.outcome === 'string')
165
+ out.outcome = f.outcome;
166
+ // Keep detail / error ONLY on terminal frames (full reply text on
167
+ // every agent.message would blow the report past the GH issue cap).
168
+ if (TERMINAL_TYPES.has(out.type)) {
169
+ const detail = clampDetail(f.detail) ?? clampDetail(f.error);
170
+ if (detail)
171
+ out.detail = detail;
172
+ if (typeof f.error === 'string')
173
+ out.error = clampDetail(f.error);
174
+ }
175
+ return out;
176
+ });
177
+ }
178
+ export function runReport(args, ctx) {
179
+ const fromError = args.includes('--from-error');
180
+ if (!fromError) {
181
+ ctx.writeOutput({
182
+ command: 'report',
183
+ status: 'no_sessions',
184
+ message: 'pugi report — capture a bug report from the most-recent session.\n\n' +
185
+ 'Usage:\n' +
186
+ ' pugi report --from-error Bundle the most-recent failed session as a report.\n\n' +
187
+ 'Output: writes .pugi/reports/<timestamp>-<session-id>/{report.json, report.md}.\n' +
188
+ 'Secrets (bearer tokens, JWTs, named env values) are stripped before disk write.',
189
+ }, 'pugi report — see `pugi report --help`');
190
+ return 0;
191
+ }
192
+ const sessionPath = findLatestSession(ctx.cwd);
193
+ if (!sessionPath) {
194
+ ctx.writeOutput({
195
+ command: 'report',
196
+ status: 'no_sessions',
197
+ message: 'No sessions found under .pugi/sessions/. Run a `pugi` command first.',
198
+ }, 'pugi report: no sessions found under .pugi/sessions/ — run a `pugi` command first.');
199
+ return 8;
200
+ }
201
+ const eventsPath = join(sessionPath, 'events.jsonl');
202
+ let frames;
203
+ try {
204
+ frames = captureFrames(eventsPath);
205
+ }
206
+ catch (err) {
207
+ const message = err instanceof Error ? err.message : String(err);
208
+ ctx.writeOutput({
209
+ command: 'report',
210
+ status: 'unreadable',
211
+ message: `Failed to read ${eventsPath}: ${message}`,
212
+ }, `pugi report: cannot read session events (${message})`);
213
+ return 9;
214
+ }
215
+ const sessionId = sessionPath.split('/').pop() ?? 'unknown';
216
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
217
+ const reportDir = resolvePath(ctx.cwd, '.pugi/reports', `${timestamp}-${sessionId}`);
218
+ let reportJson;
219
+ let reportMd;
220
+ try {
221
+ mkdirSync(reportDir, { recursive: true });
222
+ reportJson = join(reportDir, 'report.json');
223
+ reportMd = join(reportDir, 'report.md');
224
+ const meta = {
225
+ schema: 1,
226
+ generatedAt: new Date().toISOString(),
227
+ cliVersion: PUGI_CLI_VERSION,
228
+ nodeVersion: process.version,
229
+ os: `${platform()} ${release()}`,
230
+ cwd: ctx.cwd,
231
+ sessionId,
232
+ tenantId: readTenantIdSafely() ?? '(not resolvable)',
233
+ pugiMd: existsSync(resolvePath(ctx.cwd, '.pugi/PUGI.md')),
234
+ frames,
235
+ };
236
+ writeFileSync(reportJson, JSON.stringify(meta, null, 2), 'utf8');
237
+ const mdLines = [
238
+ `# Pugi bug report — ${sessionId}`,
239
+ '',
240
+ `Generated: \`${meta.generatedAt}\``,
241
+ `CLI version: \`${meta.cliVersion}\``,
242
+ `Node: \`${meta.nodeVersion}\` · OS: \`${meta.os}\``,
243
+ `Workspace: \`${meta.cwd}\` (PUGI.md present: ${meta.pugiMd ? 'yes' : 'no'})`,
244
+ `Tenant: \`${meta.tenantId}\``,
245
+ '',
246
+ `## Last ${frames.length} frames`,
247
+ '',
248
+ '```jsonl',
249
+ ...frames.map((f) => JSON.stringify(f)),
250
+ '```',
251
+ '',
252
+ '## How to share',
253
+ '',
254
+ '1. Review `report.md` for accidental PII or sensitive paths.',
255
+ '2. Paste the contents into a GH issue at https://github.com/pugi-io/pugi/issues',
256
+ ' OR attach the `report.json` as a file.',
257
+ '',
258
+ 'Auto-upload to api.pugi.io is planned (`pugi report --upload`) but',
259
+ 'NOT shipped in this build — v1 keeps everything local so an operator',
260
+ 'behind a firewall can still file a clean report.',
261
+ ];
262
+ writeFileSync(reportMd, mdLines.join('\n'), 'utf8');
263
+ }
264
+ catch (err) {
265
+ const message = err instanceof Error ? err.message : String(err);
266
+ ctx.writeOutput({
267
+ command: 'report',
268
+ status: 'output_not_writable',
269
+ message: `Failed to write report bundle to ${reportDir}: ${message}`,
270
+ }, `pugi report: cannot write report dir (${message})`);
271
+ return 20;
272
+ }
273
+ ctx.writeOutput({
274
+ command: 'report',
275
+ status: 'written',
276
+ reportDir,
277
+ reportJson,
278
+ reportMd,
279
+ sessionId,
280
+ message: `Report written: ${reportDir}`,
281
+ }, [
282
+ `pugi report: bundle written`,
283
+ ` Session: ${sessionId}`,
284
+ ` Frames captured: ${frames.length}`,
285
+ ` Files:`,
286
+ ` ${reportJson}`,
287
+ ` ${reportMd}`,
288
+ ``,
289
+ `Review report.md for accidental PII, then paste into a GH issue OR`,
290
+ `attach report.json. Auto-upload is planned for a follow-up build.`,
291
+ ].join('\n'));
292
+ return 0;
293
+ }
294
+ // Test seam — the redactor is the most-tested piece (false negatives
295
+ // leak secrets; false positives garble bug context). Exported so
296
+ // apps/pugi-cli/test/report.spec.ts can assert the regex behaviour
297
+ // без spinning up a full session.
298
+ export const __INTERNAL_FOR_TESTS = { redact, clampDetail };
299
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1,118 @@
1
+ /**
2
+ * `/resume` runtime — .
3
+ *
4
+ * Light runner that augments the existing `pugi resume` surface in
5
+ * `runtime/cli.ts` with rewind-aware session listings. The full
6
+ * REPL-mount path stays in cli.ts (it owns the credential resolver +
7
+ * Ink renderer); this module exposes the data-access helpers the slash
8
+ * dispatcher + the cli.ts handler share.
9
+ *
10
+ * Why a separate runner rather than inlining inside cli.ts: the in-REPL
11
+ * `/resume` slash already holds the writer lock, so it cannot use the
12
+ * read-only view path the top-level command relies on. The split lets
13
+ * the slash code call `listResumableSessionsForRepl` (which routes
14
+ * through the live store) while the shell code calls
15
+ * `listResumableSessionsReadOnly` (which uses the no-lock view).
16
+ */
17
+ import { homedir } from 'node:os';
18
+ import { slugForCwd } from '../../core/repl/history.js';
19
+ import { applyAllMasks, listResumableSessions, } from '../../core/checkpoint/resumer.js';
20
+ import { findLatestActiveRewind } from '../../core/checkpoint/rewinder.js';
21
+ /**
22
+ * Read-only `pugi resume --list` path. Uses the no-lock view so a live
23
+ * REPL writing in the same project does not block the listing.
24
+ */
25
+ export async function runResumeList(ctx) {
26
+ const slug = slugForCwd(ctx.workspaceRoot);
27
+ const baseInput = {
28
+ projectSlug: slug,
29
+ limit: ctx.limit ?? 10,
30
+ home: ctx.home ?? homedir(),
31
+ };
32
+ const sessions = await listResumableSessions(baseInput);
33
+ const rows = sessions.map(toResumeListRow);
34
+ const text = renderResumeList(rows, slug);
35
+ ctx.writeOutput({
36
+ command: 'resume',
37
+ status: rows.length === 0 ? 'empty' : 'listed',
38
+ projectSlug: slug,
39
+ sessions: rows,
40
+ }, text);
41
+ return {
42
+ command: 'resume',
43
+ status: rows.length === 0 ? 'empty' : 'listed',
44
+ projectSlug: slug,
45
+ sessions: rows,
46
+ };
47
+ }
48
+ /**
49
+ * In-REPL `/resume` slash variant. The live REPL holds the writer
50
+ * lockfile so we cannot reuse the read-only view path; this routes
51
+ * through the store the session module already opened. Returns the
52
+ * sessions list directly so the slash handler can render system lines.
53
+ */
54
+ export async function runResumeListForRepl(input) {
55
+ const slug = slugForCwd(input.workspaceRoot);
56
+ const limit = input.limit ?? 10;
57
+ const sessionRows = await input.store.listSessions({
58
+ project: slug,
59
+ limit,
60
+ status: 'active+archived',
61
+ });
62
+ const out = [];
63
+ for (const row of sessionRows) {
64
+ const events = await input.store.loadEvents(row.id);
65
+ const visible = applyAllMasks(events);
66
+ const latest = findLatestActiveRewind(events);
67
+ out.push({
68
+ id: row.id,
69
+ title: row.title,
70
+ branch: row.branch,
71
+ turnCount: row.turnCount,
72
+ eventCount: row.eventCount,
73
+ visibleEventCount: visible.length,
74
+ hasActiveRewind: latest !== null,
75
+ updatedAt: row.updatedAt,
76
+ });
77
+ }
78
+ return out;
79
+ }
80
+ function toResumeListRow(input) {
81
+ return {
82
+ id: input.row.id,
83
+ title: input.row.title,
84
+ branch: input.row.branch,
85
+ turnCount: input.row.turnCount,
86
+ eventCount: input.row.eventCount,
87
+ visibleEventCount: input.visibleEventCount,
88
+ hasActiveRewind: input.hasActiveRewind,
89
+ updatedAt: input.row.updatedAt,
90
+ };
91
+ }
92
+ /**
93
+ * Pretty-print a resume list. Adds a small "rewound" tag when the
94
+ * session carries an unfinished rewind so the operator knows the
95
+ * transcript will load with a masked range.
96
+ */
97
+ export function renderResumeList(rows, projectSlug) {
98
+ if (rows.length === 0) {
99
+ return `No stored sessions for project '${projectSlug}' yet.`;
100
+ }
101
+ const lines = [
102
+ `Recent local sessions for '${projectSlug}' (${rows.length}):`,
103
+ '',
104
+ ];
105
+ for (let i = 0; i < rows.length; i += 1) {
106
+ const row = rows[i];
107
+ const title = (row.title ?? '(untitled)').slice(0, 64);
108
+ const idShort = row.id.slice(0, 13);
109
+ const branch = (row.branch ?? 'no-branch').padEnd(16);
110
+ const turns = `${row.turnCount}t`.padStart(4);
111
+ const events = `${row.visibleEventCount}e`.padStart(5);
112
+ const tag = row.hasActiveRewind ? ' [rewound]' : '';
113
+ lines.push(` ${idShort} ${branch} ${turns} ${events}${tag} ${title}`);
114
+ }
115
+ lines.push('', 'Resume with: pugi resume <id>');
116
+ return lines.join('\n');
117
+ }
118
+ //# sourceMappingURL=resume.js.map
@@ -1,7 +1,7 @@
1
1
  /**
2
- * `pugi review --consensus` — customer-facing triple-review (α6.7).
2
+ * `pugi review --consensus` — customer-facing triple-review .
3
3
  *
4
- * The differentiator: Claude Code ships single-Claude review, Codex CLI
4
+ * The differentiator: the upstream tool ships single-Claude review, Codex CLI
5
5
  * ships single-GPT review, Gemini CLI ships single-Gemini review. Pugi
6
6
  * ships a 3-model consensus gate as a first-class command so customers
7
7
  * get the same production-readiness signal we use internally - without the
@@ -9,23 +9,23 @@
9
9
  *
10
10
  * Flow:
11
11
  *
12
- * 1. Resolve diff source from flags (`--commit` / `--pr` / `--branch`
13
- * OR default to merge-base vs `origin/main`).
14
- * 2. POST diff to Anvil's `POST /api/pugi/review-consensus`. Anvil
15
- * fans out to 3 reviewer routes server-side and streams an SSE.
16
- * 3. Render per-reviewer state inline as the SSE stream emits events.
17
- * 4. After the stream closes, recompute the rubric locally (never
18
- * trust the server's verdict - see anvil-fanout.ts) and print:
19
- * - per-reviewer summary
20
- * - rubric verdict + reasoning
21
- * - recommended next action
22
- * 5. Exit with 0 PASS / 1 WARN / 2 BLOCK.
12
+ * 1. Resolve diff source from flags (`--commit` / `--pr` / `--branch`
13
+ * OR default to merge-base vs `origin/main`).
14
+ * 2. POST diff to Anvil's `POST /api/pugi/review-consensus`. Anvil
15
+ * fans out to 3 reviewer routes server-side and streams an SSE.
16
+ * 3. Render per-reviewer state inline as the SSE stream emits events.
17
+ * 4. After the stream closes, recompute the rubric locally (never
18
+ * trust the server's verdict - see anvil-fanout.ts) and print:
19
+ * - per-reviewer summary
20
+ * - rubric verdict + reasoning
21
+ * - recommended next action
22
+ * 5. Exit with 0 PASS / 1 WARN / 2 BLOCK.
23
23
  *
24
- * Backend status: at α6.7 ship the admin-api endpoint is not yet
24
+ * Backend status: at ship the admin-api endpoint is not yet
25
25
  * deployed. The handler degrades gracefully — on `endpoint_missing`
26
26
  * the CLI prints an actionable "backend not deployed yet" notice and
27
27
  * exits 0 (the gate didn't run, but failing CI would be wrong because
28
- * the operator did nothing wrong). The α6.7.1 sprint lands the server.
28
+ * the operator did nothing wrong). The sprint lands the server.
29
29
  */
30
30
  import { captureDiff } from '../../core/consensus/diff-capture.js';
31
31
  import { dispatchConsensus, } from '../../core/consensus/anvil-fanout.js';
@@ -35,13 +35,28 @@ import { aggregate, exitCodeFor, reviewerVerdictFromRaw, } from '../../core/cons
35
35
  * arg list excludes the dispatcher's leading `review` keyword.
36
36
  *
37
37
  * Accepted forms:
38
- * `--commit <sha>` / `--commit=<sha>`
39
- * `--pr <number>` / `--pr=<number>`
40
- * `--branch <name>` / `--branch=<name>`
41
- * `--base <ref>` / `--base=<ref>` (override default origin/main)
38
+ * `--commit <sha>` / `--commit=<sha>`
39
+ * `--pr <number>` / `--pr=<number>`
40
+ * `--branch <name>` / `--branch=<name>`
41
+ * `--base <ref>` / `--base=<ref>` (override default origin/main)
42
42
  */
43
- export function parseConsensusArgs(args) {
43
+ export function parseConsensusArgs(args,
44
+ /**
45
+ * (Codex r0 P1 on PR): cli.ts now parses --commit /
46
+ * --base in the GLOBAL flag pass for the new triple-provider path.
47
+ * Those tokens are consumed BEFORE this function sees `args`, so
48
+ * `pugi review --consensus --commit X` would silently fall back to
49
+ * the default diff and review the wrong changes. Pass the global
50
+ * flags through here so consensus picks them up when present.
51
+ * Inline `--commit`/`--base` tokens in args still win — explicit
52
+ * caller intent is preserved.
53
+ */
54
+ fallback) {
44
55
  const spec = {};
56
+ if (fallback?.commit)
57
+ spec.commit = fallback.commit;
58
+ if (fallback?.base)
59
+ spec.baseRef = fallback.base;
45
60
  for (let i = 0; i < args.length; i += 1) {
46
61
  const arg = args[i] ?? '';
47
62
  const equalsIdx = arg.indexOf('=');
@@ -91,12 +106,12 @@ export function parseConsensusArgs(args) {
91
106
  *
92
107
  * Exit code contract (matches `handleFanoutFailure` + `exitCodeFor`):
93
108
  *
94
- * 0 = endpoint_missing (graceful degrade, consensus disabled on tier)
95
- * 0 = PASS (rubric clean) OR empty diff (nothing to review)
96
- * 1 = WARN (rubric: one reviewer P1, informational)
97
- * 2 = BLOCK (rubric: P0 or consensus P1) / failed / capture_failed
98
- * 5 = auth_missing (no credentials) / unauthenticated (token rejected)
99
- * 7 = rate_limited (quota exhausted, retry after backoff)
109
+ * 0 = endpoint_missing (graceful degrade, consensus disabled on tier)
110
+ * 0 = PASS (rubric clean) OR empty diff (nothing to review)
111
+ * 1 = WARN (rubric: one reviewer P1, informational)
112
+ * 2 = BLOCK (rubric: P0 or consensus P1) / failed / capture_failed
113
+ * 5 = auth_missing (no credentials) / unauthenticated (token rejected)
114
+ * 7 = rate_limited (quota exhausted, retry after backoff)
100
115
  *
101
116
  * Aligned with the legacy `describeSubmitFailure` in cli.ts so shell
102
117
  * scripts can branch on identical codes across both review surfaces.
@@ -119,7 +134,7 @@ export async function runReviewConsensus(args, ctx) {
119
134
  // exit 2 — same as BLOCK because the gate could not even run.
120
135
  let captured;
121
136
  try {
122
- const spec = parseConsensusArgs(args);
137
+ const spec = parseConsensusArgs(args, ctx.flagsFallback);
123
138
  captured = captureDiff({ ...spec, cwd: ctx.cwd });
124
139
  }
125
140
  catch (error) {
@@ -183,7 +198,7 @@ export async function runReviewConsensus(args, ctx) {
183
198
  }
184
199
  ctx.emit('\n────────────────────────────────────────\n');
185
200
  ctx.emit(`Rubric: ${result.verdict}\n`);
186
- ctx.emit(` ${result.reasoning}\n`);
201
+ ctx.emit(` ${result.reasoning}\n`);
187
202
  ctx.emit('\n');
188
203
  ctx.emit(`Recommended action: ${recommendedAction(result)}\n`);
189
204
  ctx.writeOutput({
@@ -239,12 +254,12 @@ function handleFanoutFailure(result, ctx) {
239
254
  ctx.emit(`${message}\n`);
240
255
  ctx.writeOutput({ command: 'review-consensus', status: 'rate_limited', message }, message);
241
256
  // Exit code contract (kept in sync with `runReviewConsensus`):
242
- // 0 = endpoint_missing (graceful degrade, consensus disabled on tier)
243
- // 0 = PASS / empty diff
244
- // 1 = WARN (informational, single asymmetric P1)
245
- // 2 = BLOCK / failed / capture_failed (real findings or unrecoverable)
246
- // 5 = auth_missing / unauthenticated (token rejected by Anvil)
247
- // 7 = rate_limited (quota exhausted, retry with backoff)
257
+ // 0 = endpoint_missing (graceful degrade, consensus disabled on tier)
258
+ // 0 = PASS / empty diff
259
+ // 1 = WARN (informational, single asymmetric P1)
260
+ // 2 = BLOCK / failed / capture_failed (real findings or unrecoverable)
261
+ // 5 = auth_missing / unauthenticated (token rejected by Anvil)
262
+ // 7 = rate_limited (quota exhausted, retry with backoff)
248
263
  //
249
264
  // Aligned with the legacy `describeSubmitFailure` in cli.ts so a
250
265
  // shell script branching on exit code behaves identically across
@@ -264,15 +279,15 @@ function handleFanoutFailure(result, ctx) {
264
279
  *
265
280
  * Precedence (verdict wins over error):
266
281
  *
267
- * started -> verdict => verdict (rubric processes findings)
268
- * started -> error => error (errored=true, no signal)
269
- * started -> verdict -> error => verdict (terminal verdict wins; the
270
- * trailing error is a stale retry/transport artifact and must NOT
271
- * silently downgrade a real P0 BLOCK to "all errored")
272
- * started -> error -> verdict => verdict (verdict still wins)
273
- * started (no terminal) => errored placeholder so the reviewer
274
- * appears in the output instead of being
275
- * silently dropped
282
+ * started -> verdict => verdict (rubric processes findings)
283
+ * started -> error => error (errored=true, no signal)
284
+ * started -> verdict -> error => verdict (terminal verdict wins; the
285
+ * trailing error is a stale retry/transport artifact and must NOT
286
+ * silently downgrade a real P0 BLOCK to "all errored")
287
+ * started -> error -> verdict => verdict (verdict still wins)
288
+ * started (no terminal) => errored placeholder so the reviewer
289
+ * appears in the output instead of being
290
+ * silently dropped
276
291
  *
277
292
  * The verdict-wins-over-error rule is the fix for a real BLOCK
278
293
  * downgrade: Anvil's SSE emitter can send a verdict frame followed by
@@ -335,32 +350,32 @@ function collapseVerdicts(events) {
335
350
  function formatReviewerEventLine(event) {
336
351
  const name = event.reviewer.padEnd(9);
337
352
  if (event.type === 'started') {
338
- return ` ${name} reviewing…\n`;
353
+ return ` ${name} reviewing…\n`;
339
354
  }
340
355
  if (event.type === 'error') {
341
356
  const why = event.error ?? 'unknown';
342
- const ms = event.latencyMs ? ` ${event.latencyMs}ms` : '';
343
- return ` ${name} ERROR: ${why}${ms}\n`;
357
+ const ms = event.latencyMs ? ` ${event.latencyMs}ms` : '';
358
+ return ` ${name} ERROR: ${why}${ms}\n`;
344
359
  }
345
360
  const severity = event.severity ?? 'CLEAN';
346
- const ms = event.latencyMs ? ` ${event.latencyMs}ms` : '';
347
- return ` ${name} ${severity}${ms}\n`;
361
+ const ms = event.latencyMs ? ` ${event.latencyMs}ms` : '';
362
+ return ` ${name} ${severity}${ms}\n`;
348
363
  }
349
364
  function formatReviewerSummaryLine(verdict) {
350
365
  const name = verdict.reviewer.padEnd(9);
351
366
  if (verdict.errored) {
352
- return ` ${name} ERROR (no signal)\n`;
367
+ return ` ${name} ERROR (no signal)\n`;
353
368
  }
354
369
  if (verdict.findings.length === 0) {
355
- return ` ${name} CLEAN\n`;
370
+ return ` ${name} CLEAN\n`;
356
371
  }
357
372
  // Group counts: shows operator the severity breakdown in one line.
358
373
  const counts = countSeverities(verdict);
359
374
  const summary = formatCounts(counts);
360
375
  const top = verdict.findings.slice(0, 3);
361
- const tail = verdict.findings.length > 3 ? `\n … ${verdict.findings.length - 3} more` : '';
362
- const findings = top.map((f) => `\n - [${f.severity}] ${f.summary}`).join('');
363
- return ` ${name} ${summary}${findings}${tail}\n`;
376
+ const tail = verdict.findings.length > 3 ? `\n … ${verdict.findings.length - 3} more` : '';
377
+ const findings = top.map((f) => `\n - [${f.severity}] ${f.summary}`).join('');
378
+ return ` ${name} ${summary}${findings}${tail}\n`;
364
379
  }
365
380
  function countSeverities(verdict) {
366
381
  const counts = { P0: 0, P1: 0, P2: 0, P3: 0 };