@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.90
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.
- package/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/assets/pugi-prozr2-mascot.ansi +9 -0
- package/bin/run.js +33 -1
- package/dist/commands/deploy.js +40 -40
- package/dist/commands/flatten.js +191 -0
- package/dist/commands/jobs-watch.js +201 -0
- package/dist/commands/jobs.js +42 -27
- package/dist/commands/smoke.js +133 -0
- package/dist/core/agent-progress/cleanup.js +134 -0
- package/dist/core/agent-progress/schema.js +144 -0
- package/dist/core/agent-progress/writer.js +101 -0
- package/dist/core/agents/adaptive-router.js +330 -0
- package/dist/core/agents/query-decomposer.js +297 -0
- package/dist/core/agents/registry.js +3 -3
- package/dist/core/approvals/shortcut-resolver.js +98 -0
- package/dist/core/artifact-chain/dispatcher.js +148 -0
- package/dist/core/artifact-chain/exporter.js +164 -0
- package/dist/core/artifact-chain/state.js +243 -0
- package/dist/core/artifact-chain/steps.js +169 -0
- package/dist/core/ask-user/question.js +92 -0
- package/dist/core/audit/audit-trail.js +275 -0
- package/dist/core/auth/ensure-authenticated.js +129 -0
- package/dist/core/auth/env-provider.js +238 -0
- package/dist/core/auto-open-browser.js +4 -4
- package/dist/core/auto-update/channels.js +122 -0
- package/dist/core/auto-update/checker.js +241 -0
- package/dist/core/auto-update/state.js +235 -0
- package/dist/core/bare-mode/index.js +107 -0
- package/dist/core/bash/redirect.js +281 -0
- package/dist/core/bash-classifier.js +436 -40
- package/dist/core/checkpoint/resumer.js +149 -0
- package/dist/core/checkpoint/rewinder.js +291 -0
- package/dist/core/checkpoints/shadow-git.js +670 -0
- package/dist/core/citations/parser.js +109 -0
- package/dist/core/classifier/yolo-classifier.js +88 -0
- package/dist/core/codegraph/decision-store.js +248 -0
- package/dist/core/codegraph/detect-repo.js +459 -0
- package/dist/core/codegraph/install.js +134 -0
- package/dist/core/codegraph/offer-hook.js +220 -0
- package/dist/core/compact/auto-trigger.js +96 -0
- package/dist/core/compact/buffer-rewriter.js +115 -0
- package/dist/core/compact/summarizer.js +208 -0
- package/dist/core/compact/token-counter.js +108 -0
- package/dist/core/consensus/anvil-fanout.js +25 -25
- package/dist/core/consensus/diff-capture.js +121 -12
- package/dist/core/consensus/rubric.js +21 -21
- package/dist/core/context/builder.js +6 -6
- package/dist/core/context/compaction-events.js +8 -8
- package/dist/core/context/compaction.js +31 -31
- package/dist/core/context/index.js +15 -8
- package/dist/core/context/invariants.js +51 -51
- package/dist/core/context/markdown-loader.js +28 -10
- package/dist/core/context/markdown-traverse.js +255 -0
- package/dist/core/context/pugiignore.js +41 -41
- package/dist/core/context/repo-skeleton.js +37 -37
- package/dist/core/context/tool-eviction.js +55 -0
- package/dist/core/context/watcher.js +32 -32
- package/dist/core/context/working-set.js +23 -23
- package/dist/core/coordinator/agent-tools.js +77 -0
- package/dist/core/coordinator/agent-toolset.js +65 -0
- package/dist/core/coordinator/fsm.js +73 -0
- package/dist/core/coordinator/mode-fsm.js +70 -0
- package/dist/core/cost/rate-card.js +129 -0
- package/dist/core/cost/tracker.js +221 -0
- package/dist/core/credentials.js +13 -13
- package/dist/core/cron/scheduler.js +138 -0
- package/dist/core/denial-tracking/index.js +8 -0
- package/dist/core/denial-tracking/state.js +264 -0
- package/dist/core/diagnostics/probe-runner.js +93 -0
- package/dist/core/diagnostics/probes/api.js +46 -0
- package/dist/core/diagnostics/probes/auth.js +93 -0
- package/dist/core/diagnostics/probes/bare-mode.js +42 -0
- package/dist/core/diagnostics/probes/cli-version.js +127 -0
- package/dist/core/diagnostics/probes/config.js +72 -0
- package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
- package/dist/core/diagnostics/probes/disk.js +81 -0
- package/dist/core/diagnostics/probes/engine-live.js +46 -0
- package/dist/core/diagnostics/probes/git.js +65 -0
- package/dist/core/diagnostics/probes/hooks.js +118 -0
- package/dist/core/diagnostics/probes/mcp.js +75 -0
- package/dist/core/diagnostics/probes/node.js +59 -0
- package/dist/core/diagnostics/probes/pnpm.js +36 -0
- package/dist/core/diagnostics/probes/pugi-md.js +89 -0
- package/dist/core/diagnostics/probes/sandbox.js +40 -0
- package/dist/core/diagnostics/probes/session.js +74 -0
- package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
- package/dist/core/diagnostics/probes/workspace.js +63 -0
- package/dist/core/diagnostics/types.js +70 -0
- package/dist/core/dispatch/cache-cleanup.js +197 -0
- package/dist/core/dispatch/cache-handoff.js +295 -0
- package/dist/core/edits/apply-patch-layer-e.js +189 -0
- package/dist/core/edits/dispatch.js +333 -7
- package/dist/core/edits/format-detector.js +260 -0
- package/dist/core/edits/format-matrix.js +26 -0
- package/dist/core/edits/fuzzy-ladder.js +650 -0
- package/dist/core/edits/index.js +5 -1
- package/dist/core/edits/journal.js +199 -0
- package/dist/core/edits/layer-a-apply.js +15 -15
- package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
- package/dist/core/edits/layer-b-apply.js +9 -9
- package/dist/core/edits/layer-c-apply.js +6 -6
- package/dist/core/edits/layer-d-ast.js +557 -14
- package/dist/core/edits/marker-parser.js +12 -12
- package/dist/core/edits/security-gate.js +27 -27
- package/dist/core/edits/verify-hook.js +273 -0
- package/dist/core/edits/worktree.js +29 -29
- package/dist/core/engine/anvil-client.js +214 -26
- package/dist/core/engine/auto-compact.js +179 -0
- package/dist/core/engine/budgets.js +186 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +158 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1295 -227
- package/dist/core/engine/prompts.js +129 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1731 -59
- package/dist/core/evaluation/golden-dataset.js +293 -0
- package/dist/core/feedback/queue.js +177 -0
- package/dist/core/feedback/submitter.js +145 -0
- package/dist/core/file-cache.js +113 -1
- package/dist/core/flatten/flatten-repo.js +439 -0
- package/dist/core/format/osc8-link.js +28 -0
- package/dist/core/hook-chains.js +392 -0
- package/dist/core/hooks/citation-verify-hook.js +138 -0
- package/dist/core/hooks/citation-verify.js +112 -0
- package/dist/core/hooks/events.js +46 -0
- package/dist/core/hooks/index.js +15 -0
- package/dist/core/hooks/registry.js +216 -0
- package/dist/core/hooks/runner.js +236 -0
- package/dist/core/hooks/v2/event-emitter.js +115 -0
- package/dist/core/hooks/v2/executor.js +282 -0
- package/dist/core/hooks/v2/index.js +25 -0
- package/dist/core/hooks/v2/lifecycle.js +104 -0
- package/dist/core/hooks/v2/loader.js +216 -0
- package/dist/core/hooks/v2/matcher.js +125 -0
- package/dist/core/hooks/v2/trust.js +143 -0
- package/dist/core/hooks/v2/types.js +86 -0
- package/dist/core/hooks/worktree-events.js +158 -0
- package/dist/core/image/renderer.js +71 -0
- package/dist/core/init/detector.js +582 -0
- package/dist/core/init/template-renderer.js +242 -0
- package/dist/core/jobs/registry.js +18 -18
- package/dist/core/ledger/results-tsv.js +142 -0
- package/dist/core/log-discipline/stdout-redirect.js +51 -0
- package/dist/core/lsp/cache.js +105 -0
- package/dist/core/lsp/client.js +551 -41
- package/dist/core/lsp/language-detect.js +66 -0
- package/dist/core/lsp/post-edit-diagnostics.js +171 -0
- package/dist/core/lsp/server-detect.js +173 -0
- package/dist/core/lsp/symbol-cache.js +162 -0
- package/dist/core/lsp/symbol-tools.js +664 -0
- package/dist/core/mcp/client.js +97 -28
- package/dist/core/mcp/http-server.js +553 -0
- package/dist/core/mcp/orchestrator-tools.js +662 -0
- package/dist/core/mcp/permission.js +190 -0
- package/dist/core/mcp/registry.js +39 -17
- package/dist/core/mcp/server-tools.js +219 -0
- package/dist/core/mcp/server.js +397 -0
- package/dist/core/mcp/trust.js +10 -10
- package/dist/core/memory/dual-write.js +416 -0
- package/dist/core/memory/passive-extract.js +130 -0
- package/dist/core/memory/phase1-kinds.js +20 -0
- package/dist/core/memory/secret-scanner.js +304 -0
- package/dist/core/memory-sync/queue.js +170 -0
- package/dist/core/metrics/extract.js +113 -0
- package/dist/core/modes/roo-modes.js +68 -0
- package/dist/core/onboarding/ensure-initialized.js +133 -0
- package/dist/core/onboarding/marker.js +111 -0
- package/dist/core/onboarding/telemetry-state.js +108 -0
- package/dist/core/output-style/presets.js +176 -0
- package/dist/core/output-style/state.js +185 -0
- package/dist/core/path-security.js +287 -5
- package/dist/core/permission.js +82 -22
- package/dist/core/permissions/auto-classifier.js +124 -0
- package/dist/core/permissions/bash-parser.js +371 -0
- package/dist/core/permissions/circuit-breaker.js +83 -0
- package/dist/core/permissions/constrained-edit.js +91 -0
- package/dist/core/permissions/gate.js +278 -0
- package/dist/core/permissions/index.js +20 -0
- package/dist/core/permissions/mode.js +174 -0
- package/dist/core/permissions/network-egress.js +137 -0
- package/dist/core/permissions/state.js +241 -0
- package/dist/core/permissions/tool-class.js +93 -0
- package/dist/core/plan-mode/ui-state.js +51 -0
- package/dist/core/plans/plan-artifact.js +721 -0
- package/dist/core/policy-limits/etag-store.js +122 -0
- package/dist/core/prd-check/parser.js +215 -0
- package/dist/core/prd-check/reporter.js +127 -0
- package/dist/core/prd-check/session-review.js +557 -0
- package/dist/core/prd-check/verifiers.js +223 -0
- package/dist/core/prompt-cache/client-cache.js +99 -0
- package/dist/core/prompts/assembly.js +29 -0
- package/dist/core/prompts/registry.js +364 -0
- package/dist/core/pugi-md/cc-compat-rules.js +735 -0
- package/dist/core/pugi-md/context-injector.js +76 -0
- package/dist/core/pugi-md/walk-up.js +207 -0
- package/dist/core/python/uv-installer.js +270 -0
- package/dist/core/python/uv-resolver.js +83 -0
- package/dist/core/rate-limit/narrator.js +146 -0
- package/dist/core/recipes/cli-types.js +20 -0
- package/dist/core/recipes/loader.js +103 -0
- package/dist/core/recipes/runner.js +345 -0
- package/dist/core/recipes/schema.js +587 -0
- package/dist/core/release-notes/parser.js +241 -0
- package/dist/core/release-notes/state.js +116 -0
- package/dist/core/repl/ask.js +37 -37
- package/dist/core/repl/cancellation.js +26 -26
- package/dist/core/repl/cap-warning.js +4 -4
- package/dist/core/repl/clipboard-read.js +11 -11
- package/dist/core/repl/dispatch-fsm.js +12 -12
- package/dist/core/repl/history-search.js +15 -15
- package/dist/core/repl/history.js +28 -18
- package/dist/core/repl/kill-ring.js +5 -5
- package/dist/core/repl/model-pricing.js +135 -0
- package/dist/core/repl/privacy-banner.js +22 -22
- package/dist/core/repl/session.js +2148 -217
- package/dist/core/repl/slash-commands.js +501 -41
- package/dist/core/repl/store/index.js +1 -1
- package/dist/core/repl/store/jsonl-log.js +22 -22
- package/dist/core/repl/store/lockfile.js +10 -10
- package/dist/core/repl/store/session-store.js +136 -107
- package/dist/core/repl/store/types.js +15 -15
- package/dist/core/repl/store/uuid-v7.js +12 -12
- package/dist/core/repl/workspace-context.js +43 -21
- package/dist/core/repo-map/build.js +125 -0
- package/dist/core/repo-map/cache.js +185 -0
- package/dist/core/repo-map/extractor.js +254 -0
- package/dist/core/repo-map/formatter.js +145 -0
- package/dist/core/repo-map/page-rank.js +105 -0
- package/dist/core/repo-map/scanner.js +211 -0
- package/dist/core/retry-budget/budget.js +284 -0
- package/dist/core/retry-budget/index.js +5 -0
- package/dist/core/retry-budget/retry-cap.js +74 -0
- package/dist/core/routing/lead-worker.js +43 -0
- package/dist/core/routing/pre-flight-estimator.js +108 -0
- package/dist/core/runs/run-tree.js +103 -0
- package/dist/core/security/injection-scanner.js +367 -0
- package/dist/core/security/output-filter.js +418 -0
- package/dist/core/session/env-file.js +105 -0
- package/dist/core/session/section-budgets.js +140 -0
- package/dist/core/session.js +92 -0
- package/dist/core/settings.js +324 -5
- package/dist/core/share/formatter.js +271 -0
- package/dist/core/share/redactor.js +221 -0
- package/dist/core/share/uploader.js +267 -0
- package/dist/core/skills/defaults.js +30 -30
- package/dist/core/skills/loader.js +22 -22
- package/dist/core/skills/sources.js +27 -27
- package/dist/core/smoke/headless-driver.js +174 -0
- package/dist/core/smoke/orchestrator.js +194 -0
- package/dist/core/smoke/runner.js +238 -0
- package/dist/core/smoke/scenario-parser.js +316 -0
- package/dist/core/statusline.js +99 -0
- package/dist/core/subagents/dispatcher-real.js +600 -0
- package/dist/core/subagents/dispatcher.js +132 -43
- package/dist/core/subagents/index.js +19 -6
- package/dist/core/subagents/isolation-matrix.js +213 -0
- package/dist/core/subagents/spawn.js +19 -4
- package/dist/core/telemetry/emitter.js +229 -0
- package/dist/core/telemetry/queue.js +251 -0
- package/dist/core/theme/context.js +91 -0
- package/dist/core/theme/presets.js +228 -0
- package/dist/core/theme/state.js +181 -0
- package/dist/core/todos/invariant.js +10 -0
- package/dist/core/todos/state.js +177 -0
- package/dist/core/tool-schema/compressor.js +89 -0
- package/dist/core/transport/version-interceptor.js +166 -0
- package/dist/core/trust.js +2 -2
- package/dist/core/tui/thinking-block.js +64 -0
- package/dist/core/vim/keymap.js +288 -0
- package/dist/core/vim/state.js +92 -0
- package/dist/core/watch-markers/marker-watcher.js +133 -0
- package/dist/core/worktree/include-parser.js +249 -0
- package/dist/core/worktree-manager/cleanup.js +123 -0
- package/dist/core/worktree-manager/manager.js +303 -0
- package/dist/index.js +36 -0
- package/dist/runtime/bootstrap.js +190 -0
- package/dist/runtime/cli.js +4185 -549
- package/dist/runtime/commands/agents.js +31 -31
- package/dist/runtime/commands/budget.js +5 -5
- package/dist/runtime/commands/cancel.js +231 -0
- package/dist/runtime/commands/chain.js +489 -0
- package/dist/runtime/commands/codegraph-status.js +227 -0
- package/dist/runtime/commands/compact.js +297 -0
- package/dist/runtime/commands/config.js +73 -39
- package/dist/runtime/commands/cost.js +199 -0
- package/dist/runtime/commands/delegate.js +27 -4
- package/dist/runtime/commands/dispatch.js +126 -0
- package/dist/runtime/commands/doctor.js +579 -0
- package/dist/runtime/commands/feedback.js +184 -0
- package/dist/runtime/commands/hooks.js +187 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +879 -0
- package/dist/runtime/commands/memory.js +582 -0
- package/dist/runtime/commands/model.js +237 -0
- package/dist/runtime/commands/onboarding.js +275 -0
- package/dist/runtime/commands/patch.js +12 -12
- package/dist/runtime/commands/permissions.js +112 -0
- package/dist/runtime/commands/plan.js +143 -0
- package/dist/runtime/commands/prd-check.js +285 -0
- package/dist/runtime/commands/privacy.js +17 -17
- package/dist/runtime/commands/recipe.js +325 -0
- package/dist/runtime/commands/redo-blob-store.js +92 -0
- package/dist/runtime/commands/redo.js +361 -0
- package/dist/runtime/commands/release-notes.js +229 -0
- package/dist/runtime/commands/repo-map.js +95 -0
- package/dist/runtime/commands/report.js +299 -0
- package/dist/runtime/commands/resume.js +118 -0
- package/dist/runtime/commands/review-consensus.js +68 -53
- package/dist/runtime/commands/rewind.js +333 -0
- package/dist/runtime/commands/roster.js +14 -14
- package/dist/runtime/commands/sessions.js +163 -0
- package/dist/runtime/commands/share.js +316 -0
- package/dist/runtime/commands/skills.js +31 -31
- package/dist/runtime/commands/status.js +186 -0
- package/dist/runtime/commands/stickers.js +82 -0
- package/dist/runtime/commands/style.js +194 -0
- package/dist/runtime/commands/theme.js +196 -0
- package/dist/runtime/commands/undo.js +54 -22
- package/dist/runtime/commands/update.js +289 -0
- package/dist/runtime/commands/vim.js +140 -0
- package/dist/runtime/commands/worktree.js +8 -8
- package/dist/runtime/commands/worktrees.js +155 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +543 -0
- package/dist/runtime/load-hooks-or-exit.js +71 -0
- package/dist/runtime/plan-decompose.js +22 -22
- package/dist/runtime/sigint-guard.js +272 -0
- package/dist/runtime/update-check.js +28 -28
- package/dist/runtime/version.js +65 -0
- package/dist/runtime/worktree-bootstrap.js +579 -0
- package/dist/skills/bundled/batch.js +617 -0
- package/dist/skills/bundled/index.js +45 -0
- package/dist/skills/bundled/loop.js +358 -0
- package/dist/skills/bundled/remember.js +383 -0
- package/dist/skills/bundled/simplify.js +289 -0
- package/dist/skills/bundled/skillify.js +373 -0
- package/dist/skills/bundled/stuck.js +558 -0
- package/dist/skills/bundled/verify.js +439 -0
- package/dist/testing/vcr.js +486 -0
- package/dist/tools/agent-tool.js +229 -0
- package/dist/tools/apply-patch.js +89 -28
- package/dist/tools/ask-user-question.js +337 -0
- package/dist/tools/ask-user.js +115 -0
- package/dist/tools/bash.js +624 -46
- package/dist/tools/brief.js +224 -0
- package/dist/tools/enter-worktree.js +250 -0
- package/dist/tools/exit-worktree.js +147 -0
- package/dist/tools/file-tools.js +161 -44
- package/dist/tools/lsp-tools.js +377 -1
- package/dist/tools/mcp-tool.js +260 -0
- package/dist/tools/multi-edit.js +361 -0
- package/dist/tools/powershell.js +268 -0
- package/dist/tools/registry.js +86 -4
- package/dist/tools/skill-tool.js +96 -0
- package/dist/tools/sleep.js +99 -0
- package/dist/tools/synthetic-output.js +133 -0
- package/dist/tools/tasks.js +208 -0
- package/dist/tools/todo-write.js +184 -0
- package/dist/tools/verify-plan-execution.js +295 -0
- package/dist/tools/web-fetch-injection-scanner.js +207 -0
- package/dist/tools/web-fetch.js +195 -10
- package/dist/tools/web-search.js +458 -0
- package/dist/tui/agent-progress-card.js +111 -0
- package/dist/tui/agent-tree.js +11 -1
- package/dist/tui/ask-modal.js +14 -14
- package/dist/tui/ask-user-question-chips.js +315 -0
- package/dist/tui/ask-user-question-prompt.js +203 -0
- package/dist/tui/compact-banner.js +81 -0
- package/dist/tui/conversation-pane.js +85 -11
- package/dist/tui/cost-table.js +111 -0
- package/dist/tui/device-flow.js +2 -2
- package/dist/tui/doctor-table.js +46 -0
- package/dist/tui/feedback-prompt.js +156 -0
- package/dist/tui/input-box.js +247 -32
- package/dist/tui/login-picker.js +3 -3
- package/dist/tui/markdown-render.js +6 -6
- package/dist/tui/onboarding-wizard.js +240 -0
- package/dist/tui/permissions-picker.js +86 -0
- package/dist/tui/render.js +36 -1
- package/dist/tui/repl-render.js +176 -25
- package/dist/tui/repl-splash-art.js +16 -16
- package/dist/tui/repl-splash-mascot.js +48 -24
- package/dist/tui/repl-splash.js +22 -22
- package/dist/tui/repl.js +125 -45
- package/dist/tui/slash-palette.js +6 -6
- package/dist/tui/splash.js +2 -2
- package/dist/tui/status-bar.js +109 -31
- package/dist/tui/status-table.js +7 -0
- package/dist/tui/stickers-art.js +136 -0
- package/dist/tui/style-table.js +28 -0
- package/dist/tui/theme-table.js +29 -0
- package/dist/tui/thinking-spinner.js +123 -0
- package/dist/tui/tool-stream-pane.js +53 -4
- package/dist/tui/update-banner.js +27 -2
- package/dist/tui/vim-input.js +267 -0
- package/dist/tui/welcome-banner.js +107 -0
- package/dist/tui/welcome-data.js +293 -0
- package/dist/tui/workspace-context.js +2 -2
- package/package.json +31 -16
- package/test/scenarios/codegen-create-file.scenario.txt +13 -0
- package/test/scenarios/compact-force.scenario.txt +12 -0
- package/test/scenarios/identity.scenario.txt +12 -0
- package/test/scenarios/persona-handoff.scenario.txt +12 -0
- package/test/scenarios/walkback.scenario.txt +12 -0
- package/dist/core/engine/compaction-hook.js +0 -154
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi recipe` — saved parameterized multi-step workflows (backlog #62).
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* pugi recipe list Show recipes (repo + global).
|
|
6
|
+
* pugi recipe show <id> Print recipe YAML.
|
|
7
|
+
* pugi recipe run <id> [--param=value...] Execute recipe.
|
|
8
|
+
* pugi recipe new <id> Scaffold a new template.
|
|
9
|
+
*
|
|
10
|
+
* Exit codes:
|
|
11
|
+
* 0 — happy path.
|
|
12
|
+
* 1 — recipe failed / denied / halted.
|
|
13
|
+
* 2 — argument / shape error (unknown subcommand, bad flag, etc.).
|
|
14
|
+
* 4 — recipe not found.
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
|
+
import { dirname, join } from 'node:path';
|
|
18
|
+
import { loadSettings } from '../../core/settings.js';
|
|
19
|
+
import { loadRecipes, resolveRecipe, repoRecipesDir, } from '../../core/recipes/loader.js';
|
|
20
|
+
import { resolveParams, runRecipe, } from '../../core/recipes/runner.js';
|
|
21
|
+
export async function runRecipeCommand(args, ctx) {
|
|
22
|
+
const flags = parseFlags(args);
|
|
23
|
+
const sub = flags.rest[0];
|
|
24
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
25
|
+
emitUsage(ctx, flags.json);
|
|
26
|
+
return sub ? 0 : 2;
|
|
27
|
+
}
|
|
28
|
+
if (sub === 'list')
|
|
29
|
+
return runList(ctx, flags.json);
|
|
30
|
+
if (sub === 'show')
|
|
31
|
+
return runShow(ctx, flags.rest.slice(1), flags.json);
|
|
32
|
+
if (sub === 'run')
|
|
33
|
+
return runRun(ctx, flags.rest.slice(1), flags.json);
|
|
34
|
+
if (sub === 'new')
|
|
35
|
+
return runNew(ctx, flags.rest.slice(1), flags.json);
|
|
36
|
+
ctx.writeOutput({ ok: false, error: `unknown subcommand: ${sub}` }, `pugi recipe: unknown subcommand '${sub}'. Try 'pugi recipe --help'.`);
|
|
37
|
+
return 2;
|
|
38
|
+
}
|
|
39
|
+
function parseFlags(args) {
|
|
40
|
+
const rest = [];
|
|
41
|
+
let json = false;
|
|
42
|
+
for (const arg of args) {
|
|
43
|
+
if (arg === '--json') {
|
|
44
|
+
json = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
rest.push(arg);
|
|
48
|
+
}
|
|
49
|
+
return { rest, json };
|
|
50
|
+
}
|
|
51
|
+
function emitUsage(ctx, json) {
|
|
52
|
+
const lines = [
|
|
53
|
+
'pugi recipe — saved parameterized multi-step workflows.',
|
|
54
|
+
'',
|
|
55
|
+
'Subcommands:',
|
|
56
|
+
' pugi recipe list List recipes (repo + global).',
|
|
57
|
+
' pugi recipe show <id> Print a recipe in YAML.',
|
|
58
|
+
' pugi recipe run <id> [--param=value] Execute a recipe.',
|
|
59
|
+
' pugi recipe new <id> Scaffold a new recipe template.',
|
|
60
|
+
'',
|
|
61
|
+
'Flags:',
|
|
62
|
+
' --json Emit a JSON envelope instead of human text.',
|
|
63
|
+
'',
|
|
64
|
+
'On-disk layout:',
|
|
65
|
+
' .pugi/recipes/<id>.yml repo-local (committed to project)',
|
|
66
|
+
' ~/.pugi/recipes/<id>.yml global (per-operator)',
|
|
67
|
+
'',
|
|
68
|
+
'Repo wins on id conflict — drop a same-id file in .pugi/recipes/ to override.',
|
|
69
|
+
];
|
|
70
|
+
const text = lines.join('\n');
|
|
71
|
+
ctx.writeOutput({ ok: true, command: 'recipe', usage: text }, text);
|
|
72
|
+
void json;
|
|
73
|
+
}
|
|
74
|
+
function runList(ctx, json) {
|
|
75
|
+
const { recipes, errors, globalDir, repoDir } = loadRecipes({
|
|
76
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
77
|
+
...(ctx.home !== undefined ? { home: ctx.home } : {}),
|
|
78
|
+
});
|
|
79
|
+
const entries = Array.from(recipes.values()).sort((a, b) => a.id.localeCompare(b.id));
|
|
80
|
+
const payload = {
|
|
81
|
+
ok: true,
|
|
82
|
+
globalDir,
|
|
83
|
+
repoDir,
|
|
84
|
+
total: entries.length,
|
|
85
|
+
recipes: entries.map((r) => ({
|
|
86
|
+
id: r.id,
|
|
87
|
+
name: r.name,
|
|
88
|
+
description: r.description ?? '',
|
|
89
|
+
origin: r.origin,
|
|
90
|
+
source: r.source,
|
|
91
|
+
steps: r.steps.length,
|
|
92
|
+
params: r.params.map((p) => p.name),
|
|
93
|
+
})),
|
|
94
|
+
errors,
|
|
95
|
+
};
|
|
96
|
+
if (json) {
|
|
97
|
+
ctx.writeOutput(payload, JSON.stringify(payload, null, 2));
|
|
98
|
+
return errors.length > 0 ? 0 : 0;
|
|
99
|
+
}
|
|
100
|
+
const lines = [];
|
|
101
|
+
lines.push(`pugi recipes (${entries.length} configured)`);
|
|
102
|
+
lines.push(` global: ${globalDir}`);
|
|
103
|
+
lines.push(` repo: ${repoDir}`);
|
|
104
|
+
if (entries.length === 0) {
|
|
105
|
+
lines.push(' no recipes found — `pugi recipe new <id>` scaffolds one.');
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
for (const recipe of entries) {
|
|
109
|
+
const desc = recipe.description ? ` — ${recipe.description}` : '';
|
|
110
|
+
const tag = recipe.origin === 'repo' ? '[repo]' : '[global]';
|
|
111
|
+
lines.push(` ${recipe.id} ${tag}${desc}`);
|
|
112
|
+
lines.push(` source: ${recipe.source}`);
|
|
113
|
+
lines.push(` steps: ${recipe.steps.length}; params: ${recipe.params.map((p) => p.name).join(', ') || '(none)'}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (errors.length > 0) {
|
|
117
|
+
lines.push('');
|
|
118
|
+
lines.push('Parse errors:');
|
|
119
|
+
for (const err of errors) {
|
|
120
|
+
lines.push(` ${err.source}: ${err.error}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
ctx.writeOutput(payload, lines.join('\n'));
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
function runShow(ctx, rest, json) {
|
|
127
|
+
const id = rest[0];
|
|
128
|
+
if (!id) {
|
|
129
|
+
ctx.writeOutput({ ok: false, error: 'recipe id is required' }, "pugi recipe show: needs a recipe id (try 'pugi recipe list').");
|
|
130
|
+
return 2;
|
|
131
|
+
}
|
|
132
|
+
const { recipes } = loadRecipes({
|
|
133
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
134
|
+
...(ctx.home !== undefined ? { home: ctx.home } : {}),
|
|
135
|
+
});
|
|
136
|
+
const resolved = resolveRecipe(id, recipes);
|
|
137
|
+
if (!resolved.ok) {
|
|
138
|
+
ctx.writeOutput({ ok: false, error: resolved.error }, `pugi recipe show: ${resolved.error}`);
|
|
139
|
+
return 4;
|
|
140
|
+
}
|
|
141
|
+
const body = readFileSync(resolved.recipe.source, 'utf8');
|
|
142
|
+
const payload = {
|
|
143
|
+
ok: true,
|
|
144
|
+
id: resolved.recipe.id,
|
|
145
|
+
source: resolved.recipe.source,
|
|
146
|
+
origin: resolved.recipe.origin,
|
|
147
|
+
body,
|
|
148
|
+
};
|
|
149
|
+
if (json) {
|
|
150
|
+
ctx.writeOutput(payload, JSON.stringify(payload, null, 2));
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
const header = `# ${resolved.recipe.source} (${resolved.recipe.origin})`;
|
|
154
|
+
ctx.writeOutput(payload, `${header}\n${body}`);
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
async function runRun(ctx, rest, json) {
|
|
158
|
+
const id = rest[0];
|
|
159
|
+
if (!id) {
|
|
160
|
+
ctx.writeOutput({ ok: false, error: 'recipe id is required' }, "pugi recipe run: needs a recipe id (try 'pugi recipe list').");
|
|
161
|
+
return 2;
|
|
162
|
+
}
|
|
163
|
+
const { recipes } = loadRecipes({
|
|
164
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
165
|
+
...(ctx.home !== undefined ? { home: ctx.home } : {}),
|
|
166
|
+
});
|
|
167
|
+
const resolved = resolveRecipe(id, recipes);
|
|
168
|
+
if (!resolved.ok) {
|
|
169
|
+
ctx.writeOutput({ ok: false, error: resolved.error }, `pugi recipe run: ${resolved.error}`);
|
|
170
|
+
return 4;
|
|
171
|
+
}
|
|
172
|
+
const paramArgs = parseParamArgs(rest.slice(1));
|
|
173
|
+
if (!paramArgs.ok) {
|
|
174
|
+
ctx.writeOutput({ ok: false, error: paramArgs.error }, `pugi recipe run: ${paramArgs.error}`);
|
|
175
|
+
return 2;
|
|
176
|
+
}
|
|
177
|
+
const resolvedParams = resolveParams(resolved.recipe, paramArgs.params);
|
|
178
|
+
if (!resolvedParams.ok) {
|
|
179
|
+
ctx.writeOutput({ ok: false, error: resolvedParams.error }, `pugi recipe run: ${resolvedParams.error}`);
|
|
180
|
+
return 2;
|
|
181
|
+
}
|
|
182
|
+
let permissionMode = ctx.permissionMode ?? 'ask';
|
|
183
|
+
if (ctx.permissionMode === undefined) {
|
|
184
|
+
try {
|
|
185
|
+
permissionMode = loadSettings(ctx.workspaceRoot).permissions.mode;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Fall back to 'ask' when no settings file exists (recipes ran
|
|
189
|
+
// from a fresh checkout before `pugi init`).
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const result = await runRecipe({
|
|
193
|
+
recipe: resolved.recipe,
|
|
194
|
+
params: resolvedParams.params,
|
|
195
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
196
|
+
permissionMode,
|
|
197
|
+
...(ctx.exec !== undefined ? { exec: ctx.exec } : {}),
|
|
198
|
+
...(ctx.now !== undefined ? { now: ctx.now } : {}),
|
|
199
|
+
});
|
|
200
|
+
emitRunResult(ctx, result, json);
|
|
201
|
+
return result.status === 'success' ? 0 : 1;
|
|
202
|
+
}
|
|
203
|
+
function parseParamArgs(args) {
|
|
204
|
+
const out = {};
|
|
205
|
+
let i = 0;
|
|
206
|
+
while (i < args.length) {
|
|
207
|
+
const arg = args[i];
|
|
208
|
+
if (!arg.startsWith('--')) {
|
|
209
|
+
return { ok: false, error: `unexpected argument '${arg}'` };
|
|
210
|
+
}
|
|
211
|
+
const body = arg.slice(2);
|
|
212
|
+
const eq = body.indexOf('=');
|
|
213
|
+
let key;
|
|
214
|
+
let value;
|
|
215
|
+
if (eq >= 0) {
|
|
216
|
+
key = body.slice(0, eq);
|
|
217
|
+
value = body.slice(eq + 1);
|
|
218
|
+
i += 1;
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
key = body;
|
|
222
|
+
const next = args[i + 1];
|
|
223
|
+
if (next === undefined || next.startsWith('--')) {
|
|
224
|
+
// Bare flag = boolean true (handy for `--skipTests`).
|
|
225
|
+
value = 'true';
|
|
226
|
+
i += 1;
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
value = next;
|
|
230
|
+
i += 2;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(key)) {
|
|
234
|
+
return { ok: false, error: `bad param name '${key}'` };
|
|
235
|
+
}
|
|
236
|
+
out[key] = value;
|
|
237
|
+
}
|
|
238
|
+
return { ok: true, params: out };
|
|
239
|
+
}
|
|
240
|
+
function emitRunResult(ctx, result, json) {
|
|
241
|
+
const payload = {
|
|
242
|
+
ok: result.status === 'success',
|
|
243
|
+
id: result.id,
|
|
244
|
+
status: result.status,
|
|
245
|
+
params: result.params,
|
|
246
|
+
durationMs: result.durationMs,
|
|
247
|
+
steps: result.steps.map((s) => ({
|
|
248
|
+
name: s.name,
|
|
249
|
+
status: s.status,
|
|
250
|
+
exitCode: s.exitCode,
|
|
251
|
+
command: s.command,
|
|
252
|
+
durationMs: s.durationMs,
|
|
253
|
+
stdoutBytes: s.stdout.length,
|
|
254
|
+
stderrBytes: s.stderr.length,
|
|
255
|
+
denyReason: s.denyReason || undefined,
|
|
256
|
+
})),
|
|
257
|
+
};
|
|
258
|
+
if (json) {
|
|
259
|
+
ctx.writeOutput(payload, JSON.stringify(payload, null, 2));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const lines = [];
|
|
263
|
+
lines.push(`pugi recipe run ${result.id} — ${result.status}`);
|
|
264
|
+
for (const step of result.steps) {
|
|
265
|
+
const exit = step.exitCode === null ? '-' : String(step.exitCode);
|
|
266
|
+
const duration = step.durationMs > 0 ? `${step.durationMs}ms` : '-';
|
|
267
|
+
lines.push(` [${step.status}] ${step.name} exit=${exit} ${duration}`);
|
|
268
|
+
if (step.command)
|
|
269
|
+
lines.push(` cmd: ${step.command}`);
|
|
270
|
+
if (step.denyReason)
|
|
271
|
+
lines.push(` deny: ${step.denyReason}`);
|
|
272
|
+
if (step.stderr) {
|
|
273
|
+
const preview = step.stderr.split('\n').slice(0, 3).join('\n ');
|
|
274
|
+
lines.push(` stderr: ${preview}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
ctx.writeOutput(payload, lines.join('\n'));
|
|
278
|
+
}
|
|
279
|
+
function runNew(ctx, rest, json) {
|
|
280
|
+
const id = rest[0];
|
|
281
|
+
if (!id) {
|
|
282
|
+
ctx.writeOutput({ ok: false, error: 'recipe id is required' }, 'pugi recipe new: needs an id (e.g. `pugi recipe new deploy-staging`).');
|
|
283
|
+
return 2;
|
|
284
|
+
}
|
|
285
|
+
if (!/^[a-z][a-z0-9-]*$/.test(id)) {
|
|
286
|
+
ctx.writeOutput({ ok: false, error: `bad recipe id '${id}'` }, `pugi recipe new: id must match [a-z][a-z0-9-]* (got '${id}').`);
|
|
287
|
+
return 2;
|
|
288
|
+
}
|
|
289
|
+
const dir = repoRecipesDir(ctx.workspaceRoot);
|
|
290
|
+
const target = join(dir, `${id}.yml`);
|
|
291
|
+
if (existsSync(target)) {
|
|
292
|
+
ctx.writeOutput({ ok: false, error: `${target} already exists` }, `pugi recipe new: ${target} already exists.`);
|
|
293
|
+
return 2;
|
|
294
|
+
}
|
|
295
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
296
|
+
writeFileSync(target, recipeTemplate(id), 'utf8');
|
|
297
|
+
const payload = { ok: true, id, source: target };
|
|
298
|
+
if (json) {
|
|
299
|
+
ctx.writeOutput(payload, JSON.stringify(payload, null, 2));
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
ctx.writeOutput(payload, `pugi recipe new: scaffolded ${target}.\nEdit it, then run \`pugi recipe run ${id}\`.`);
|
|
303
|
+
}
|
|
304
|
+
return 0;
|
|
305
|
+
}
|
|
306
|
+
function recipeTemplate(id) {
|
|
307
|
+
return [
|
|
308
|
+
`schema: 1`,
|
|
309
|
+
`name: ${id}`,
|
|
310
|
+
`description: TODO describe what this recipe does.`,
|
|
311
|
+
`params:`,
|
|
312
|
+
` branch: { type: string, default: main, required: false }`,
|
|
313
|
+
` skipTests: { type: boolean, default: false }`,
|
|
314
|
+
`steps:`,
|
|
315
|
+
` - name: build`,
|
|
316
|
+
` run: pnpm build`,
|
|
317
|
+
` - name: test`,
|
|
318
|
+
` run: pnpm test`,
|
|
319
|
+
` when: "!params.skipTests"`,
|
|
320
|
+
` - name: deploy`,
|
|
321
|
+
` run: ./scripts/deploy.sh \${params.branch} staging`,
|
|
322
|
+
``,
|
|
323
|
+
].join('\n');
|
|
324
|
+
}
|
|
325
|
+
//# sourceMappingURL=recipe.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redo blob store — .
|
|
3
|
+
*
|
|
4
|
+
* `/undo` walks the most recent successful mutating tool result and
|
|
5
|
+
* reverts each file on disk. `/redo` reapplies that change. To reapply,
|
|
6
|
+
* we need the post-mutation content — but the event log records only
|
|
7
|
+
* sha256 hashes, not content (see core/file-cache.ts comment).
|
|
8
|
+
*
|
|
9
|
+
* Solution: a content-addressable sidecar store at
|
|
10
|
+
* `<workspaceRoot>/.pugi/undo-blobs/<sha256>`. The undo runner captures
|
|
11
|
+
* each file's CURRENT content (which equals the original AFTER state)
|
|
12
|
+
* into the store BEFORE reverting on disk. The redo runner reads the
|
|
13
|
+
* blob keyed by `beforeHash` of the inverse mutation (which records
|
|
14
|
+
* the pre-revert hash = the original AFTER hash) and writes it back.
|
|
15
|
+
*
|
|
16
|
+
* The store is deliberately untracked (`.pugi/` already lives in
|
|
17
|
+
* .gitignore) and self-cleaning — after a successful redo we delete
|
|
18
|
+
* the blob so a second redo without a fresh undo is a noop. Stale
|
|
19
|
+
* blobs left behind by an interrupted undo are reaped after 7 days
|
|
20
|
+
* by the existing `.pugi/cleanup` cadence (best-effort; not a
|
|
21
|
+
* correctness requirement).
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
|
|
24
|
+
import { resolve } from 'node:path';
|
|
25
|
+
import { hashContent } from '../../core/file-cache.js';
|
|
26
|
+
/** Sha256-keyed blob path under `<root>/.pugi/undo-blobs/`. */
|
|
27
|
+
export function blobPathFor(root, sha) {
|
|
28
|
+
return resolve(root, '.pugi/undo-blobs', sha);
|
|
29
|
+
}
|
|
30
|
+
/** Ensure the blob directory exists. Idempotent. */
|
|
31
|
+
function ensureBlobDir(root) {
|
|
32
|
+
const dir = resolve(root, '.pugi/undo-blobs');
|
|
33
|
+
if (!existsSync(dir)) {
|
|
34
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
35
|
+
}
|
|
36
|
+
return dir;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Write `content` into the blob store. Returns the resolved blob path.
|
|
40
|
+
* Atomic tmp+rename so partial writes never present a half-blob к
|
|
41
|
+
* future redo invocations.
|
|
42
|
+
*
|
|
43
|
+
* Idempotent: if a blob with the same content already exists, the
|
|
44
|
+
* second write is a noop (the rename target already matches).
|
|
45
|
+
*/
|
|
46
|
+
export function writeBlob(root, content) {
|
|
47
|
+
ensureBlobDir(root);
|
|
48
|
+
const sha = hashContent(content);
|
|
49
|
+
const dst = blobPathFor(root, sha);
|
|
50
|
+
if (existsSync(dst)) {
|
|
51
|
+
// Same content already cached; nothing to do.
|
|
52
|
+
return { sha, path: dst };
|
|
53
|
+
}
|
|
54
|
+
const tmp = `${dst}.tmp-${process.pid}-${Date.now()}`;
|
|
55
|
+
writeFileSync(tmp, content, { encoding: 'utf8', mode: 0o600 });
|
|
56
|
+
renameSync(tmp, dst);
|
|
57
|
+
return { sha, path: dst };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read a blob by sha. Returns `undefined` when the blob is missing
|
|
61
|
+
* (cleanup ran, repo cloned fresh, blob never captured). Callers
|
|
62
|
+
* MUST treat undefined as "redo not available" rather than crashing.
|
|
63
|
+
*/
|
|
64
|
+
export function readBlob(root, sha) {
|
|
65
|
+
const path = blobPathFor(root, sha);
|
|
66
|
+
if (!existsSync(path))
|
|
67
|
+
return undefined;
|
|
68
|
+
try {
|
|
69
|
+
return readFileSync(path, 'utf8');
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Best-effort blob deletion. Used by the redo runner after a successful
|
|
77
|
+
* reapply so the blob does not get reused on a second redo (which would
|
|
78
|
+
* be incorrect — once redone, the next undo must capture fresh state).
|
|
79
|
+
* Missing-file errors are swallowed — the store self-heals.
|
|
80
|
+
*/
|
|
81
|
+
export function deleteBlob(root, sha) {
|
|
82
|
+
const path = blobPathFor(root, sha);
|
|
83
|
+
if (!existsSync(path))
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
unlinkSync(path);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Best-effort.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=redo-blob-store.js.map
|