@pugi/cli 0.1.0-beta.10 → 0.1.0-beta.100
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +132 -0
- package/LICENSE +1 -1
- package/README.md +53 -11
- 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/retro.js +210 -0
- 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/db.js +506 -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/codegraph/parser.js +71 -0
- package/dist/core/codegraph/types.js +34 -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 +72 -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 +247 -0
- package/dist/core/engine/budgets.js +220 -0
- package/dist/core/engine/compact-llm-summarizer.js +124 -0
- package/dist/core/engine/context-prefix.js +155 -0
- package/dist/core/engine/index.js +1 -1
- package/dist/core/engine/intensity.js +163 -0
- package/dist/core/engine/intent.js +260 -0
- package/dist/core/engine/native-pugi.js +1559 -227
- package/dist/core/engine/prompts.js +187 -19
- package/dist/core/engine/strip-internal-fields.js +124 -0
- package/dist/core/engine/tool-bridge.js +1887 -59
- package/dist/core/engine/verification-patterns.js +195 -0
- 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-config.js +192 -0
- package/dist/core/mcp/orchestrator-tools.js +806 -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/notes/notes-paths.js +113 -0
- package/dist/core/notes/notes-recorder.js +140 -0
- package/dist/core/notes/notes-writer.js +53 -0
- package/dist/core/notes/renderers.js +0 -0
- package/dist/core/notes/slug.js +105 -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 +107 -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-gitignore.js +52 -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/engine-bridge.js +303 -0
- 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 +2690 -229
- package/dist/core/repl/slash-commands.js +540 -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/tool-route.js +382 -0
- 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/retro/git-collector.js +251 -0
- package/dist/core/retro/health-card.js +25 -0
- package/dist/core/retro/metrics.js +342 -0
- package/dist/core/retro/narrative.js +249 -0
- package/dist/core/retro/plane-collector.js +274 -0
- package/dist/core/retro/pr-issue-link.js +65 -0
- package/dist/core/retro/types.js +16 -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/sandboxing/adapter.js +29 -0
- package/dist/core/sandboxing/index.js +49 -0
- package/dist/core/sandboxing/none.js +19 -0
- package/dist/core/sandboxing/seatbelt.js +183 -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 +119 -0
- package/dist/core/settings.js +378 -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 +146 -52
- 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 +4345 -561
- 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 +74 -40
- 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/index-cmd.js +353 -0
- package/dist/runtime/commands/init.js +254 -0
- package/dist/runtime/commands/lsp.js +200 -38
- package/dist/runtime/commands/mcp.js +935 -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/servers.js +236 -0
- 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/deprecation-warning.js +69 -0
- package/dist/runtime/engine-exit-code.js +50 -0
- package/dist/runtime/headless-repl.js +195 -0
- package/dist/runtime/headless.js +548 -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/stream-renderer.js +195 -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/cron.js +433 -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/http-request.js +336 -0
- 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 +120 -5
- package/dist/tools/server-tools.js +892 -0
- 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 +22 -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/multi-file-diff-approval.js +375 -0
- 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 +239 -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 +21 -5
- 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 +11 -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,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pugi CLI ↔ admin-api version handshake — shared constants.
|
|
3
|
+
*
|
|
4
|
+
* The CLI declares its installed version on every outbound request to
|
|
5
|
+
* the admin-api so the server can enforce a minClientVersion floor and
|
|
6
|
+
* surface a soft-warn for stale-but-allowed installs. See ADR-225 and
|
|
7
|
+
* `apps/admin-api/src/runtime/version.contract.ts` for the server side.
|
|
8
|
+
*
|
|
9
|
+
* # Why the constants live here, not in `@pugi/sdk`
|
|
10
|
+
*
|
|
11
|
+
* The SDK ships to customers as part of the public npm package; bumping
|
|
12
|
+
* a header name there would force a coupled CLI ↔ SDK release. Keeping
|
|
13
|
+
* the constants in the CLI gives us room to evolve the handshake
|
|
14
|
+
* without coupling the SDK's release cadence to it.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Defensive semver sanitizer — single source of truth for the
|
|
18
|
+
* `0.0.0-unknown` fallback when a partially-published pnpm release
|
|
19
|
+
* leaks `workspace:*` into the version literal. `runtime/cli.ts`
|
|
20
|
+
* re-exports this under its `__test__` surface so the existing
|
|
21
|
+
* cli.ts test corpus keeps working with zero churn.
|
|
22
|
+
*
|
|
23
|
+
* Lives here (not in cli.ts) because the version handshake interceptor
|
|
24
|
+
* needs the same guarantee without pulling in the heavyweight cli.ts
|
|
25
|
+
* module graph during transport bootstrap.
|
|
26
|
+
*/
|
|
27
|
+
export function sanitizeSemver(raw) {
|
|
28
|
+
if (typeof raw !== 'string')
|
|
29
|
+
return '0.0.0-unknown';
|
|
30
|
+
const trimmed = raw.trim();
|
|
31
|
+
if (!trimmed)
|
|
32
|
+
return '0.0.0-unknown';
|
|
33
|
+
const stripped = trimmed.replace(/^(workspace:|npm:|file:)/, '');
|
|
34
|
+
if (/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(stripped)) {
|
|
35
|
+
return stripped;
|
|
36
|
+
}
|
|
37
|
+
return '0.0.0-unknown';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Installed CLI version — single source of truth for the handshake
|
|
41
|
+
* header value. Mirrors the literal in `runtime/cli.ts` (intentional
|
|
42
|
+
* duplication because the cli.ts module pulls in Ink + half the CLI
|
|
43
|
+
* surface and the transport interceptor must remain side-effect-free
|
|
44
|
+
* during import). When bumping the CLI version BOTH literals must be
|
|
45
|
+
* updated; the release smoke-test (`pack:smoke`) verifies they agree.
|
|
46
|
+
*/
|
|
47
|
+
export const PUGI_CLI_VERSION = sanitizeSemver('0.1.0-beta.100');
|
|
48
|
+
/**
|
|
49
|
+
* Outbound: the CLI's installed semver. Read at request time by
|
|
50
|
+
* `version-interceptor.ts` and injected on every `fetch` call.
|
|
51
|
+
*/
|
|
52
|
+
export const PUGI_CLI_VERSION_HEADER = 'X-Pugi-Cli-Version';
|
|
53
|
+
/**
|
|
54
|
+
* Inbound: the server's recommendation for which CLI version operators
|
|
55
|
+
* should be on. Surfaces through the UpdateBanner alongside the npm
|
|
56
|
+
* registry poll (the higher of the two wins).
|
|
57
|
+
*/
|
|
58
|
+
export const PUGI_CLI_UPGRADE_RECOMMENDED_HEADER = 'X-Pugi-Cli-Upgrade-Recommended';
|
|
59
|
+
/**
|
|
60
|
+
* Inbound: server's own admin-api version. Useful for diagnostics
|
|
61
|
+
* (`pugi doctor --json`) but no enforcement logic keys off it — server
|
|
62
|
+
* drift between admin-api releases is normal during rolling deploys.
|
|
63
|
+
*/
|
|
64
|
+
export const PUGI_SERVER_VERSION_HEADER = 'X-Pugi-Server-Version';
|
|
65
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pugi --worktree` bootstrap - PUGI-487.
|
|
3
|
+
*
|
|
4
|
+
* User-facing parity surface for parallel-agent isolation patterns
|
|
5
|
+
* seen across competitor AI coding CLIs. The flag creates an isolated
|
|
6
|
+
* git worktree at `.claude/worktrees/<name>/` on a new branch
|
|
7
|
+
* `worktree-<name>` and starts the REPL inside it. Operators can run
|
|
8
|
+
* several Pugi sessions in parallel without their filesystems
|
|
9
|
+
* colliding.
|
|
10
|
+
*
|
|
11
|
+
* Distinct namespace from the existing dispatcher worktree manager:
|
|
12
|
+
*
|
|
13
|
+
* - This module owns `.claude/worktrees/<name>/` (USER-facing,
|
|
14
|
+
* short kebab names like `feat-x` or auto-generated `bright-fox`).
|
|
15
|
+
* - `core/worktree-manager/` owns `.pugi/worktrees/<agent-id>/`
|
|
16
|
+
* (DISPATCHER-facing, agent-id keyed).
|
|
17
|
+
*
|
|
18
|
+
* Public surface:
|
|
19
|
+
*
|
|
20
|
+
* - `bootstrapWorktree(options)` - validate inputs, run hooks,
|
|
21
|
+
* create the worktree, copy `.worktreeinclude` matches, return
|
|
22
|
+
* a typed envelope ready for the CLI to chdir + REPL.
|
|
23
|
+
* - `generateWhimsicalName()` - adjective + animal pair for the
|
|
24
|
+
* auto-named case (`pugi --worktree` with no name).
|
|
25
|
+
* - `runUserWorktreeCleanup(options)` - the daily sweep that removes
|
|
26
|
+
* user-facing worktrees > N days old with no uncommitted state.
|
|
27
|
+
*
|
|
28
|
+
* Brand voice: ASCII only, no emoji, no banned words.
|
|
29
|
+
*/
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
31
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync, } from 'node:fs';
|
|
32
|
+
import { dirname, isAbsolute, posix, resolve, sep } from 'node:path';
|
|
33
|
+
import { loadHooksConfig } from '../core/hooks/registry.js';
|
|
34
|
+
import { fireWorktreeHooks, } from '../core/hooks/worktree-events.js';
|
|
35
|
+
import { collectWorktreeIncludeFiles, loadWorktreeInclude, } from '../core/worktree/include-parser.js';
|
|
36
|
+
import { isTrustedWorkspace } from '../core/trust.js';
|
|
37
|
+
/** Canonical directory prefix for user-facing worktrees. */
|
|
38
|
+
export const USER_WORKTREES_PREFIX = '.claude/worktrees';
|
|
39
|
+
/**
|
|
40
|
+
* Branch prefix for user-facing worktree branches. The full name is
|
|
41
|
+
* `worktree-<name>` so the operator can quickly see which branches in
|
|
42
|
+
* `git branch` came from `pugi --worktree`.
|
|
43
|
+
*/
|
|
44
|
+
export const USER_WORKTREE_BRANCH_PREFIX = 'worktree-';
|
|
45
|
+
/** Strict slug regex for the operator-supplied name. */
|
|
46
|
+
export const WORKTREE_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
47
|
+
/** Strict regex for the PR-shorthand sugar. */
|
|
48
|
+
export const PR_SHORTHAND_PATTERN = /^#(\d{1,8})$/;
|
|
49
|
+
/**
|
|
50
|
+
* The main entry point invoked by `runtime/cli.ts` when `--worktree`
|
|
51
|
+
* is set on the bare invocation. Returns the typed envelope so the
|
|
52
|
+
* CLI layer can decide whether to chdir + mount the REPL or surface
|
|
53
|
+
* an error.
|
|
54
|
+
*/
|
|
55
|
+
export async function bootstrapWorktree(options) {
|
|
56
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
57
|
+
const repoRoot = resolve(options.repoRoot);
|
|
58
|
+
// Step 1: confirm we are actually in a git repo. The standard
|
|
59
|
+
// `rev-parse --git-dir` probe surfaces the most useful error when
|
|
60
|
+
// the operator runs `pugi --worktree` in `$HOME` by mistake.
|
|
61
|
+
const probe = runGit(['rev-parse', '--git-dir'], repoRoot);
|
|
62
|
+
if (probe.status !== 0) {
|
|
63
|
+
return failure({
|
|
64
|
+
name: '',
|
|
65
|
+
branch: '',
|
|
66
|
+
directory: '',
|
|
67
|
+
baseRef: options.baseRef ?? 'fresh',
|
|
68
|
+
resolvedBase: '',
|
|
69
|
+
reason: 'not_a_git_repo',
|
|
70
|
+
message: `not a git repository: ${repoRoot}`,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Step 2: trust gate. The default-restricted policy (see
|
|
74
|
+
// `core/trust.ts`) refuses to load workspace-scoped hooks unless the
|
|
75
|
+
// operator has explicitly trusted the directory. Worktrees inherit
|
|
76
|
+
// their parent's trust state so a `pugi init`-trusted root carries
|
|
77
|
+
// through to every child created via this flag.
|
|
78
|
+
const trustGate = options.isTrusted ?? isTrustedWorkspace;
|
|
79
|
+
const skipTrust = options.skipTrustGate === true ||
|
|
80
|
+
process.env.PUGI_SKIP_WORKTREE_TRUST === '1';
|
|
81
|
+
if (!skipTrust) {
|
|
82
|
+
const trusted = await trustGate(repoRoot);
|
|
83
|
+
if (!trusted) {
|
|
84
|
+
return failure({
|
|
85
|
+
name: '',
|
|
86
|
+
branch: '',
|
|
87
|
+
directory: '',
|
|
88
|
+
baseRef: options.baseRef ?? 'fresh',
|
|
89
|
+
resolvedBase: '',
|
|
90
|
+
reason: 'untrusted_workspace',
|
|
91
|
+
message: `workspace ${repoRoot} is not trusted. Run \`pugi init\` or \`pugi config trust .\` first.`,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Step 3: resolve the requested name. Three shapes:
|
|
96
|
+
// - PR shorthand `#1234`
|
|
97
|
+
// - operator-supplied slug
|
|
98
|
+
// - undefined => auto-generate
|
|
99
|
+
const nameResolution = resolveName(options.nameArg, options.generateName);
|
|
100
|
+
if (typeof nameResolution === 'string') {
|
|
101
|
+
return failure({
|
|
102
|
+
name: typeof options.nameArg === 'string' ? options.nameArg : '',
|
|
103
|
+
branch: '',
|
|
104
|
+
directory: '',
|
|
105
|
+
baseRef: options.baseRef ?? 'fresh',
|
|
106
|
+
resolvedBase: '',
|
|
107
|
+
reason: 'invalid_name',
|
|
108
|
+
message: nameResolution,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const { name, branch, prNumber } = nameResolution;
|
|
112
|
+
const baseRefMode = options.baseRef ?? 'fresh';
|
|
113
|
+
// Step 4: compute the target directory + resolved base ref.
|
|
114
|
+
const defaultDirectory = resolve(repoRoot, USER_WORKTREES_PREFIX, name);
|
|
115
|
+
if (existsSync(defaultDirectory)) {
|
|
116
|
+
return failure({
|
|
117
|
+
name,
|
|
118
|
+
branch,
|
|
119
|
+
directory: defaultDirectory,
|
|
120
|
+
baseRef: baseRefMode,
|
|
121
|
+
resolvedBase: '',
|
|
122
|
+
reason: 'directory_exists',
|
|
123
|
+
message: `worktree already exists at ${defaultDirectory}`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const baseSha = resolveBaseSha(runGit, repoRoot, baseRefMode, prNumber);
|
|
127
|
+
if (typeof baseSha === 'string' && baseSha.startsWith('ERR:')) {
|
|
128
|
+
return failure({
|
|
129
|
+
name,
|
|
130
|
+
branch,
|
|
131
|
+
directory: defaultDirectory,
|
|
132
|
+
baseRef: baseRefMode,
|
|
133
|
+
resolvedBase: '',
|
|
134
|
+
reason: prNumber !== undefined ? 'pr_fetch_failed' : 'git_command_failed',
|
|
135
|
+
message: baseSha.slice('ERR:'.length),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// Step 5: fire the WorktreeCreate hook. Hooks may veto via non-zero
|
|
139
|
+
// exit (with `blocking: true`) or override the directory location.
|
|
140
|
+
const hooks = (options.loadHooks ?? loadHooksConfig)();
|
|
141
|
+
const createPayload = {
|
|
142
|
+
event: 'WorktreeCreate',
|
|
143
|
+
name,
|
|
144
|
+
defaultDirectory,
|
|
145
|
+
baseRef: typeof baseSha === 'string' ? baseSha : '',
|
|
146
|
+
repoRoot,
|
|
147
|
+
};
|
|
148
|
+
const createOutcome = fireWorktreeHooks(hooks, 'WorktreeCreate', createPayload);
|
|
149
|
+
if (createOutcome.anyBlocked) {
|
|
150
|
+
return failure({
|
|
151
|
+
name,
|
|
152
|
+
branch,
|
|
153
|
+
directory: defaultDirectory,
|
|
154
|
+
baseRef: baseRefMode,
|
|
155
|
+
resolvedBase: typeof baseSha === 'string' ? baseSha : '',
|
|
156
|
+
reason: 'hook_blocked',
|
|
157
|
+
message: `WorktreeCreate hook blocked the operation`,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
// Override resolution: a hook stdout `{ "directory": "<abs-path>" }`
|
|
161
|
+
// wins ONLY when the path is inside the repo root. We refuse
|
|
162
|
+
// out-of-repo overrides defensively because the cleanup sweep would
|
|
163
|
+
// never reach them.
|
|
164
|
+
let directory = defaultDirectory;
|
|
165
|
+
if (createOutcome.directoryOverride) {
|
|
166
|
+
const override = resolve(repoRoot, createOutcome.directoryOverride);
|
|
167
|
+
if (override.startsWith(repoRoot + sep)) {
|
|
168
|
+
directory = override;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Step 6: git worktree add. We resolve the base SHA above so the
|
|
172
|
+
// branch ref does not depend on a runtime-mutable `origin/HEAD` at
|
|
173
|
+
// creation time.
|
|
174
|
+
mkdirSync(dirname(directory), { recursive: true });
|
|
175
|
+
const addArgs = [
|
|
176
|
+
'worktree',
|
|
177
|
+
'add',
|
|
178
|
+
'-b',
|
|
179
|
+
branch,
|
|
180
|
+
'--',
|
|
181
|
+
directory,
|
|
182
|
+
typeof baseSha === 'string' ? baseSha : 'HEAD',
|
|
183
|
+
];
|
|
184
|
+
const add = runGit(addArgs, repoRoot);
|
|
185
|
+
if (add.status !== 0) {
|
|
186
|
+
return failure({
|
|
187
|
+
name,
|
|
188
|
+
branch,
|
|
189
|
+
directory,
|
|
190
|
+
baseRef: baseRefMode,
|
|
191
|
+
resolvedBase: typeof baseSha === 'string' ? baseSha : '',
|
|
192
|
+
reason: 'git_command_failed',
|
|
193
|
+
message: (add.stderr || add.stdout || `exit=${add.status}`).trim(),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
// Step 7: copy `.worktreeinclude` matches (e.g. .env). Tracked files
|
|
197
|
+
// are never duplicated - see the include-parser doc-block.
|
|
198
|
+
const includeResult = loadWorktreeInclude(repoRoot);
|
|
199
|
+
const collector = options.collectIncludes ?? collectWorktreeIncludeFiles;
|
|
200
|
+
const copyFile = options.copyFile ?? defaultCopyFile;
|
|
201
|
+
const copyOpts = {};
|
|
202
|
+
const matches = collector(repoRoot, includeResult.rules, copyOpts);
|
|
203
|
+
const copiedFiles = [];
|
|
204
|
+
for (const rel of matches) {
|
|
205
|
+
const src = resolve(repoRoot, rel);
|
|
206
|
+
const dest = resolve(directory, rel);
|
|
207
|
+
try {
|
|
208
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
209
|
+
copyFile(src, dest);
|
|
210
|
+
copiedFiles.push(rel);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Best-effort copy. A single failure (e.g. permission denied on
|
|
214
|
+
// one entry) should not abort the bootstrap - surface via the
|
|
215
|
+
// envelope and let the operator decide.
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
ok: true,
|
|
220
|
+
name,
|
|
221
|
+
branch,
|
|
222
|
+
directory,
|
|
223
|
+
baseRef: baseRefMode,
|
|
224
|
+
resolvedBase: typeof baseSha === 'string' ? baseSha : '',
|
|
225
|
+
copiedFiles,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Parse the operator-supplied name argument. Returns a `ResolvedName`
|
|
230
|
+
* on success or a human-readable error string on rejection. Exposed
|
|
231
|
+
* for spec coverage.
|
|
232
|
+
*/
|
|
233
|
+
export function resolveName(nameArg, generator) {
|
|
234
|
+
if (nameArg === undefined || nameArg.trim().length === 0) {
|
|
235
|
+
const generated = (generator ?? generateWhimsicalName)();
|
|
236
|
+
if (!WORKTREE_NAME_PATTERN.test(generated)) {
|
|
237
|
+
return `auto-generated name "${generated}" failed validation`;
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
name: generated,
|
|
241
|
+
branch: `${USER_WORKTREE_BRANCH_PREFIX}${generated}`,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const trimmed = nameArg.trim();
|
|
245
|
+
const prMatch = PR_SHORTHAND_PATTERN.exec(trimmed);
|
|
246
|
+
if (prMatch && typeof prMatch[1] === 'string') {
|
|
247
|
+
const n = Number(prMatch[1]);
|
|
248
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
249
|
+
return `invalid PR number: ${trimmed}`;
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
name: `pr-${n}`,
|
|
253
|
+
branch: `${USER_WORKTREE_BRANCH_PREFIX}pr-${n}`,
|
|
254
|
+
prNumber: n,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
if (!WORKTREE_NAME_PATTERN.test(trimmed)) {
|
|
258
|
+
return `name must match ${WORKTREE_NAME_PATTERN}`;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
name: trimmed,
|
|
262
|
+
branch: `${USER_WORKTREE_BRANCH_PREFIX}${trimmed}`,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/* ------------------------------------------------------------------ */
|
|
266
|
+
/* Base ref resolution */
|
|
267
|
+
/* ------------------------------------------------------------------ */
|
|
268
|
+
/**
|
|
269
|
+
* Resolve the SHA the new branch should point at. PR-shorthand wins
|
|
270
|
+
* regardless of `baseRef` mode (the operator's clear intent). For the
|
|
271
|
+
* `fresh` mode we resolve `origin/<default-branch>` and fall back to
|
|
272
|
+
* local HEAD when no remote is configured. The `head` mode always
|
|
273
|
+
* carries local HEAD.
|
|
274
|
+
*
|
|
275
|
+
* Returns the resolved SHA on success or an `ERR:<detail>` string on
|
|
276
|
+
* failure so the caller can decide whether to surface
|
|
277
|
+
* `git_command_failed` or `pr_fetch_failed`.
|
|
278
|
+
*/
|
|
279
|
+
function resolveBaseSha(runGit, repoRoot, baseRef, prNumber) {
|
|
280
|
+
if (prNumber !== undefined) {
|
|
281
|
+
// `pull/N/head` is the GitHub convention. Fetch the PR head into
|
|
282
|
+
// the local refs cache before resolving so the worktree-add can
|
|
283
|
+
// find it.
|
|
284
|
+
const ref = `pull/${prNumber}/head`;
|
|
285
|
+
const fetch = runGit(['fetch', 'origin', ref], repoRoot);
|
|
286
|
+
if (fetch.status !== 0) {
|
|
287
|
+
return `ERR:failed to fetch ${ref} from origin: ${(fetch.stderr || fetch.stdout).trim()}`;
|
|
288
|
+
}
|
|
289
|
+
const sha = runGit(['rev-parse', 'FETCH_HEAD'], repoRoot);
|
|
290
|
+
if (sha.status !== 0) {
|
|
291
|
+
return `ERR:failed to resolve FETCH_HEAD after fetching ${ref}`;
|
|
292
|
+
}
|
|
293
|
+
return sha.stdout.trim();
|
|
294
|
+
}
|
|
295
|
+
if (baseRef === 'head') {
|
|
296
|
+
const sha = runGit(['rev-parse', 'HEAD'], repoRoot);
|
|
297
|
+
if (sha.status !== 0) {
|
|
298
|
+
return `ERR:failed to resolve HEAD: ${(sha.stderr || sha.stdout).trim()}`;
|
|
299
|
+
}
|
|
300
|
+
return sha.stdout.trim();
|
|
301
|
+
}
|
|
302
|
+
// baseRef === 'fresh': try origin/<default-branch>, fall back to HEAD.
|
|
303
|
+
const remoteBranch = detectOriginHead(runGit, repoRoot);
|
|
304
|
+
if (remoteBranch) {
|
|
305
|
+
const sha = runGit(['rev-parse', remoteBranch], repoRoot);
|
|
306
|
+
if (sha.status === 0) {
|
|
307
|
+
return sha.stdout.trim();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const headSha = runGit(['rev-parse', 'HEAD'], repoRoot);
|
|
311
|
+
if (headSha.status !== 0) {
|
|
312
|
+
return `ERR:failed to resolve any base ref (no origin/HEAD and no local HEAD)`;
|
|
313
|
+
}
|
|
314
|
+
return headSha.stdout.trim();
|
|
315
|
+
}
|
|
316
|
+
function detectOriginHead(runGit, repoRoot) {
|
|
317
|
+
const symRef = runGit(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], repoRoot);
|
|
318
|
+
if (symRef.status === 0) {
|
|
319
|
+
const value = symRef.stdout.trim();
|
|
320
|
+
if (value.length > 0)
|
|
321
|
+
return value;
|
|
322
|
+
}
|
|
323
|
+
// Common default fallbacks. Try `origin/main`, then `origin/master`.
|
|
324
|
+
for (const candidate of ['origin/main', 'origin/master']) {
|
|
325
|
+
const verify = runGit(['rev-parse', '--verify', candidate], repoRoot);
|
|
326
|
+
if (verify.status === 0)
|
|
327
|
+
return candidate;
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
/* ------------------------------------------------------------------ */
|
|
332
|
+
/* Whimsical name generator */
|
|
333
|
+
/* ------------------------------------------------------------------ */
|
|
334
|
+
const WHIMSICAL_ADJECTIVES = [
|
|
335
|
+
'bright',
|
|
336
|
+
'brave',
|
|
337
|
+
'calm',
|
|
338
|
+
'clever',
|
|
339
|
+
'eager',
|
|
340
|
+
'fancy',
|
|
341
|
+
'gentle',
|
|
342
|
+
'happy',
|
|
343
|
+
'jolly',
|
|
344
|
+
'keen',
|
|
345
|
+
'lucky',
|
|
346
|
+
'mighty',
|
|
347
|
+
'noble',
|
|
348
|
+
'quick',
|
|
349
|
+
'royal',
|
|
350
|
+
'silent',
|
|
351
|
+
'swift',
|
|
352
|
+
'tidy',
|
|
353
|
+
'vivid',
|
|
354
|
+
'witty',
|
|
355
|
+
];
|
|
356
|
+
const WHIMSICAL_VERBS = [
|
|
357
|
+
'running',
|
|
358
|
+
'sleeping',
|
|
359
|
+
'flying',
|
|
360
|
+
'leaping',
|
|
361
|
+
'roaring',
|
|
362
|
+
'singing',
|
|
363
|
+
'climbing',
|
|
364
|
+
'gliding',
|
|
365
|
+
'dancing',
|
|
366
|
+
'soaring',
|
|
367
|
+
];
|
|
368
|
+
const WHIMSICAL_ANIMALS = [
|
|
369
|
+
'fox',
|
|
370
|
+
'otter',
|
|
371
|
+
'lynx',
|
|
372
|
+
'puma',
|
|
373
|
+
'eagle',
|
|
374
|
+
'hawk',
|
|
375
|
+
'whale',
|
|
376
|
+
'dolphin',
|
|
377
|
+
'badger',
|
|
378
|
+
'panda',
|
|
379
|
+
'tiger',
|
|
380
|
+
'wolf',
|
|
381
|
+
'bear',
|
|
382
|
+
'koala',
|
|
383
|
+
'raven',
|
|
384
|
+
'falcon',
|
|
385
|
+
'crow',
|
|
386
|
+
'sparrow',
|
|
387
|
+
];
|
|
388
|
+
/**
|
|
389
|
+
* Three-word adjective-verb-animal slug for the auto-name case.
|
|
390
|
+
* Uses `Math.random` because there is no security boundary at the
|
|
391
|
+
* naming layer - the WORKTREE_NAME_PATTERN regex below validates the
|
|
392
|
+
* shape after generation.
|
|
393
|
+
*/
|
|
394
|
+
export function generateWhimsicalName() {
|
|
395
|
+
const adj = pick(WHIMSICAL_ADJECTIVES);
|
|
396
|
+
const verb = pick(WHIMSICAL_VERBS);
|
|
397
|
+
const animal = pick(WHIMSICAL_ANIMALS);
|
|
398
|
+
return `${adj}-${verb}-${animal}`;
|
|
399
|
+
}
|
|
400
|
+
function pick(items) {
|
|
401
|
+
if (items.length === 0) {
|
|
402
|
+
throw new Error('cannot pick from empty list');
|
|
403
|
+
}
|
|
404
|
+
const idx = Math.floor(Math.random() * items.length);
|
|
405
|
+
// Bounded by the modulo above so the result is always defined.
|
|
406
|
+
return items[idx];
|
|
407
|
+
}
|
|
408
|
+
/* ------------------------------------------------------------------ */
|
|
409
|
+
/* Helpers */
|
|
410
|
+
/* ------------------------------------------------------------------ */
|
|
411
|
+
function defaultGitRunner(argv, cwd) {
|
|
412
|
+
const result = spawnSync('git', [...argv], {
|
|
413
|
+
cwd,
|
|
414
|
+
encoding: 'utf8',
|
|
415
|
+
shell: false,
|
|
416
|
+
timeout: 60_000,
|
|
417
|
+
maxBuffer: 16 * 1024 * 1024,
|
|
418
|
+
});
|
|
419
|
+
return {
|
|
420
|
+
status: result.status,
|
|
421
|
+
stdout: typeof result.stdout === 'string' ? result.stdout : '',
|
|
422
|
+
stderr: typeof result.stderr === 'string' ? result.stderr : '',
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
function defaultCopyFile(src, dest) {
|
|
426
|
+
copyFileSync(src, dest);
|
|
427
|
+
}
|
|
428
|
+
function failure(args) {
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
name: args.name,
|
|
432
|
+
branch: args.branch,
|
|
433
|
+
directory: args.directory,
|
|
434
|
+
baseRef: args.baseRef,
|
|
435
|
+
resolvedBase: args.resolvedBase,
|
|
436
|
+
copiedFiles: [],
|
|
437
|
+
reason: args.reason,
|
|
438
|
+
message: args.message,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* The daily cleanup sweep. Walks `.claude/worktrees/`, computes each
|
|
443
|
+
* entry's age (mtime), and removes ones older than `periodDays` that
|
|
444
|
+
* have no uncommitted/untracked/unpushed state. Crashed-looking trees
|
|
445
|
+
* with local work are preserved by design.
|
|
446
|
+
*/
|
|
447
|
+
export function runUserWorktreeCleanup(options) {
|
|
448
|
+
const repoRoot = resolve(options.repoRoot);
|
|
449
|
+
const now = options.now ?? Date.now;
|
|
450
|
+
const runGit = options.runGit ?? defaultGitRunner;
|
|
451
|
+
const root = resolve(repoRoot, USER_WORKTREES_PREFIX);
|
|
452
|
+
const lister = options.listWorktreeDir ?? defaultListWorktreeDir;
|
|
453
|
+
const remover = options.removeWorktree ?? defaultRemoveWorktree;
|
|
454
|
+
const hooks = (options.loadHooks ?? loadHooksConfig)();
|
|
455
|
+
if (!existsSync(root)) {
|
|
456
|
+
return { scanned: 0, removed: [], preserved: [] };
|
|
457
|
+
}
|
|
458
|
+
const entries = lister(root);
|
|
459
|
+
const removed = [];
|
|
460
|
+
const preserved = [];
|
|
461
|
+
const periodMs = options.periodDays * 24 * 60 * 60 * 1000;
|
|
462
|
+
for (const name of entries) {
|
|
463
|
+
if (!WORKTREE_NAME_PATTERN.test(name))
|
|
464
|
+
continue;
|
|
465
|
+
const dir = resolve(root, name);
|
|
466
|
+
let mtimeMs;
|
|
467
|
+
try {
|
|
468
|
+
mtimeMs = statSync(dir).mtimeMs;
|
|
469
|
+
}
|
|
470
|
+
catch {
|
|
471
|
+
continue;
|
|
472
|
+
}
|
|
473
|
+
const ageMs = now() - mtimeMs;
|
|
474
|
+
const ageDays = Math.floor(ageMs / (24 * 60 * 60 * 1000));
|
|
475
|
+
if (ageMs < periodMs) {
|
|
476
|
+
preserved.push({ name, directory: dir, ageDays, removed: false, preservedReason: 'too-young' });
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (hasUncommittedChanges(runGit, dir)) {
|
|
480
|
+
preserved.push({
|
|
481
|
+
name,
|
|
482
|
+
directory: dir,
|
|
483
|
+
ageDays,
|
|
484
|
+
removed: false,
|
|
485
|
+
preservedReason: 'has-uncommitted-changes',
|
|
486
|
+
});
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (hasUntrackedFiles(runGit, dir)) {
|
|
490
|
+
preserved.push({
|
|
491
|
+
name,
|
|
492
|
+
directory: dir,
|
|
493
|
+
ageDays,
|
|
494
|
+
removed: false,
|
|
495
|
+
preservedReason: 'has-untracked-files',
|
|
496
|
+
});
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (hasUnpushedCommits(runGit, dir)) {
|
|
500
|
+
preserved.push({
|
|
501
|
+
name,
|
|
502
|
+
directory: dir,
|
|
503
|
+
ageDays,
|
|
504
|
+
removed: false,
|
|
505
|
+
preservedReason: 'has-unpushed-commits',
|
|
506
|
+
});
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
// Fire WorktreeRemove hook before pulling the trigger.
|
|
510
|
+
const removePayload = {
|
|
511
|
+
event: 'WorktreeRemove',
|
|
512
|
+
name,
|
|
513
|
+
directory: dir,
|
|
514
|
+
reason: 'auto-cleanup',
|
|
515
|
+
repoRoot,
|
|
516
|
+
};
|
|
517
|
+
const outcome = fireWorktreeHooks(hooks, 'WorktreeRemove', removePayload);
|
|
518
|
+
if (outcome.anyBlocked) {
|
|
519
|
+
preserved.push({
|
|
520
|
+
name,
|
|
521
|
+
directory: dir,
|
|
522
|
+
ageDays,
|
|
523
|
+
removed: false,
|
|
524
|
+
preservedReason: 'hook-blocked',
|
|
525
|
+
});
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
if (options.dryRun) {
|
|
529
|
+
removed.push({ name, directory: dir, ageDays, removed: false });
|
|
530
|
+
continue;
|
|
531
|
+
}
|
|
532
|
+
const ok = remover(dir, runGit, repoRoot);
|
|
533
|
+
removed.push({ name, directory: dir, ageDays, removed: ok });
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
scanned: entries.length,
|
|
537
|
+
removed,
|
|
538
|
+
preserved,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function defaultListWorktreeDir(dir) {
|
|
542
|
+
try {
|
|
543
|
+
return readdirSync(dir);
|
|
544
|
+
}
|
|
545
|
+
catch {
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
function defaultRemoveWorktree(path, runGit, repoRoot) {
|
|
550
|
+
const result = runGit(['worktree', 'remove', '--force', path], repoRoot);
|
|
551
|
+
return result.status === 0;
|
|
552
|
+
}
|
|
553
|
+
function hasUncommittedChanges(runGit, dir) {
|
|
554
|
+
// `git diff --quiet` only inspects the working tree against the
|
|
555
|
+
// index, so a `git add`-staged but uncommitted hunk passes that
|
|
556
|
+
// check. Catch the staged case explicitly via `--cached` so the
|
|
557
|
+
// cleanup sweep never drops a worktree with staged work.
|
|
558
|
+
const unstaged = runGit(['diff', '--quiet'], dir);
|
|
559
|
+
if (unstaged.status !== 0)
|
|
560
|
+
return true;
|
|
561
|
+
const staged = runGit(['diff', '--cached', '--quiet'], dir);
|
|
562
|
+
return staged.status !== 0;
|
|
563
|
+
}
|
|
564
|
+
function hasUntrackedFiles(runGit, dir) {
|
|
565
|
+
const r = runGit(['ls-files', '--others', '--exclude-standard'], dir);
|
|
566
|
+
return r.status === 0 && r.stdout.trim().length > 0;
|
|
567
|
+
}
|
|
568
|
+
function hasUnpushedCommits(runGit, dir) {
|
|
569
|
+
const r = runGit(['log', '--branches', '--not', '--remotes', '--oneline'], dir);
|
|
570
|
+
return r.status === 0 && r.stdout.trim().length > 0;
|
|
571
|
+
}
|
|
572
|
+
/** Re-export for symmetry with the dispatcher manager. */
|
|
573
|
+
export function userWorktreePathFor(repoRoot, name) {
|
|
574
|
+
// Posix-join the prefix so the slash separators stay correct on
|
|
575
|
+
// every host, then resolve to absolute on the local FS.
|
|
576
|
+
const rel = posix.join(USER_WORKTREES_PREFIX, name);
|
|
577
|
+
return isAbsolute(rel) ? rel : resolve(repoRoot, rel);
|
|
578
|
+
}
|
|
579
|
+
//# sourceMappingURL=worktree-bootstrap.js.map
|