@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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Shared security gate for the
|
|
2
|
+
* Shared security gate for the diff escalation Layer A/B/C
|
|
3
3
|
* applicators.
|
|
4
4
|
*
|
|
5
|
-
* Before
|
|
5
|
+
* Before (triple-review remediation) every layer
|
|
6
6
|
* built its own path resolver via `isAbsolute(file) ? file : resolve(cwd, file)`.
|
|
7
7
|
* That bypassed the workspace-scoped resolver + protected-basename
|
|
8
8
|
* gate + symlink-escape check used by the `edit`/`write` tools in
|
|
@@ -14,34 +14,34 @@
|
|
|
14
14
|
* This module is the single chokepoint every layer goes through.
|
|
15
15
|
* Centralised here so:
|
|
16
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
17
|
+
* 1. Future layers (D = AST, planned β-tier) inherit the gate for
|
|
18
|
+
* free instead of re-implementing it.
|
|
19
|
+
* 2. New protected-file rules added to `decidePermission` propagate
|
|
20
|
+
* to dispatcher writes uniformly.
|
|
21
|
+
* 3. A single spec surface covers every layer's path-handling
|
|
22
|
+
* contract (`apps/pugi-cli/test/edits-security-gate.spec.ts`).
|
|
23
23
|
*
|
|
24
24
|
* Defense layers, in order (fail-fast — first reject wins):
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
26
|
+
* a. `resolveWorkspacePath` — workspace-scoped resolver with URL-decode
|
|
27
|
+
* and realpath gate at the target. Throws on traversal; we
|
|
28
|
+
* translate the throw into `path_outside_workspace`.
|
|
29
|
+
* b. `decidePermission` (kind: 'edit') — protected basename / suffix /
|
|
30
|
+
* credential-path check (`.env`, `*.pem`, `id_rsa`, `~/.ssh/**`,
|
|
31
|
+
* etc.). Mirrors what `writeTool` / `editTool` already enforce.
|
|
32
|
+
* c. Explicit `realpathSync.native` re-check on the target (or
|
|
33
|
+
* target's parent when the target does not yet exist) to defend
|
|
34
|
+
* against TOCTOU symlink swap between `resolveWorkspacePath` and
|
|
35
|
+
* the apply-time write.
|
|
36
|
+
* d. Re-run `decidePermission` on the REAL target's workspace-relative
|
|
37
|
+
* form. R2 triple-review (2026-05-25, Codex P1) caught the
|
|
38
|
+
* symlink → protected-file bypass: a workspace-local symlink
|
|
39
|
+
* `alias -> .env` passes step (b) because `alias` is not a
|
|
40
|
+
* protected basename, and passes step (c) because `.env`
|
|
41
|
+
* legitimately lives inside the workspace realpath. Without step
|
|
42
|
+
* (d) the gate would allow the atomic write to land on `.env`
|
|
43
|
+
* via the symlink's resolved target. Re-running the protected-file
|
|
44
|
+
* check against the resolved name closes that loop.
|
|
45
45
|
*/
|
|
46
46
|
import { realpathSync } from 'node:fs';
|
|
47
47
|
import { basename, resolve, sep, relative } from 'node:path';
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verify hook — β1b Pl10 .
|
|
3
|
+
*
|
|
4
|
+
* After the edit-dispatcher writes a multi-file change to the
|
|
5
|
+
* workspace, this hook fires three lightweight checks against the
|
|
6
|
+
* post-state and reports each result back to the engine loop as a
|
|
7
|
+
* status event:
|
|
8
|
+
*
|
|
9
|
+
* 1. tsc — if `tsconfig.json` is present in the workspace root, run
|
|
10
|
+
* `tsc --noEmit` to catch compile-time breakage. Pass/fail tracked
|
|
11
|
+
* per file is overkill at this stage; we surface the exit code +
|
|
12
|
+
* first ~40 lines of stderr.
|
|
13
|
+
* 2. tests — if package.json has a `test` script AND a test runner
|
|
14
|
+
* is available (jest / vitest / node --test), run `pnpm test
|
|
15
|
+
* --bail` (or `npm test --bail`). Same exit-code + tail of output
|
|
16
|
+
* contract.
|
|
17
|
+
* 3. URL probes — extract every `https?://...` literal from the diff
|
|
18
|
+
* (README + code) and HEAD-probe each. A response < 400 counts as
|
|
19
|
+
* live; >=400 surfaces as a warning. Capped at 8 unique URLs per
|
|
20
|
+
* hook to avoid spending the budget on doc rot.
|
|
21
|
+
*
|
|
22
|
+
* Retry contract (β1b r1 rescope): the hook itself is stateless and
|
|
23
|
+
* runs ONCE per invocation, returning a structured report. The
|
|
24
|
+
* "feedback → model → re-edit" retry orchestrator does NOT exist yet
|
|
25
|
+
* in the engine loop — it was promised as part of β1b Pl10 but never
|
|
26
|
+
* shipped because the engine refactor that hosts it is bigger than
|
|
27
|
+
* the verify-hook itself can absorb. Retry orchestration is deferred
|
|
28
|
+
* to β6 plan-mode integration where the loop already needs a
|
|
29
|
+
* model→hook feedback channel for plan replay. Today the operator
|
|
30
|
+
* re-runs `pugi code` to re-drive verification after a fail. The
|
|
31
|
+
* stateless hook ships unchanged so the β6 driver can wrap it.
|
|
32
|
+
*
|
|
33
|
+
* Why HEAD and not GET for URL probes:
|
|
34
|
+
* - HEAD avoids the body fetch; cheaper + faster.
|
|
35
|
+
* - SSRF guard from `web-fetch.ts::validateHostnameForFetch` runs
|
|
36
|
+
* before every probe so private IPs / localhost cannot ride.
|
|
37
|
+
* - Some servers reject HEAD (rare but real); on 4xx-from-HEAD we
|
|
38
|
+
* do NOT escalate to GET — that would burn the budget. We surface
|
|
39
|
+
* a `head_rejected` warning so the operator decides.
|
|
40
|
+
*
|
|
41
|
+
* Skip cases (return early with `skipped: true` on the relevant
|
|
42
|
+
* check):
|
|
43
|
+
* - tsc: no `tsconfig.json` at workspace root.
|
|
44
|
+
* - tests: no `package.json` OR no `test` script.
|
|
45
|
+
* - urls: no `https?://...` literals in the diff.
|
|
46
|
+
*
|
|
47
|
+
* Best-effort: every failure mode degrades to a structured report; the
|
|
48
|
+
* hook itself NEVER throws. The engine loop decides whether to
|
|
49
|
+
* surface as a hard fail vs a model-correctable warning.
|
|
50
|
+
*
|
|
51
|
+
* Brand voice: ASCII only, no banned words.
|
|
52
|
+
*/
|
|
53
|
+
import { spawnSync } from 'node:child_process';
|
|
54
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
55
|
+
import { resolve } from 'node:path';
|
|
56
|
+
import { validateHostnameForFetch } from '../../tools/web-fetch.js';
|
|
57
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
58
|
+
const DEFAULT_MAX_URL_PROBES = 8;
|
|
59
|
+
const URL_LITERAL_RE = /(https?:\/\/[^\s"'<>()`\\\]]+)/g;
|
|
60
|
+
/**
|
|
61
|
+
* Drive one verify pass. Synchronous tsc + test child processes,
|
|
62
|
+
* concurrent URL probes (up to `maxUrlProbes`). Returns once every
|
|
63
|
+
* check has completed (no streaming events — the caller wraps the
|
|
64
|
+
* report into its own status event format).
|
|
65
|
+
*/
|
|
66
|
+
export async function runVerifyHook(input) {
|
|
67
|
+
const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
68
|
+
const runProc = input.runProc ?? defaultRunProc;
|
|
69
|
+
return {
|
|
70
|
+
tsc: runTscCheck(input.workspaceRoot, runProc, timeoutMs),
|
|
71
|
+
tests: runTestsCheck(input.workspaceRoot, runProc, timeoutMs),
|
|
72
|
+
urls: await runUrlChecks(input),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/* ----------------------- tsc check ---------------------- */
|
|
76
|
+
function runTscCheck(workspaceRoot, runProc, timeoutMs) {
|
|
77
|
+
const tsconfig = resolve(workspaceRoot, 'tsconfig.json');
|
|
78
|
+
if (!existsSync(tsconfig)) {
|
|
79
|
+
return { ok: true, skipped: true, reason: 'no_tsconfig' };
|
|
80
|
+
}
|
|
81
|
+
// Prefer `pnpm exec tsc` because pnpm-aware monorepos hoist tsc into
|
|
82
|
+
// `node_modules/.bin`; fallback to bare `tsc` for global installs.
|
|
83
|
+
// We try `pnpm exec tsc` first only if a `pnpm-lock.yaml` is at the
|
|
84
|
+
// workspace root; otherwise we go straight to `tsc`.
|
|
85
|
+
const pnpmLock = existsSync(resolve(workspaceRoot, 'pnpm-lock.yaml'));
|
|
86
|
+
const cmd = pnpmLock ? 'pnpm' : 'tsc';
|
|
87
|
+
const args = pnpmLock ? ['exec', 'tsc', '--noEmit'] : ['--noEmit'];
|
|
88
|
+
const result = runProc(cmd, args, workspaceRoot, timeoutMs);
|
|
89
|
+
if (result.exitCode === 0)
|
|
90
|
+
return { ok: true };
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
reason: `tsc_exit_${result.exitCode}`,
|
|
94
|
+
detail: tailOutput(result.stdout, result.stderr, 40),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/* ----------------------- tests check ---------------------- */
|
|
98
|
+
function runTestsCheck(workspaceRoot, runProc, timeoutMs) {
|
|
99
|
+
const pkgPath = resolve(workspaceRoot, 'package.json');
|
|
100
|
+
if (!existsSync(pkgPath)) {
|
|
101
|
+
return { ok: true, skipped: true, reason: 'no_package_json' };
|
|
102
|
+
}
|
|
103
|
+
let pkg;
|
|
104
|
+
try {
|
|
105
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return { ok: true, skipped: true, reason: 'malformed_package_json' };
|
|
109
|
+
}
|
|
110
|
+
if (!pkg.scripts || typeof pkg.scripts.test !== 'string') {
|
|
111
|
+
return { ok: true, skipped: true, reason: 'no_test_script' };
|
|
112
|
+
}
|
|
113
|
+
const pnpmLock = existsSync(resolve(workspaceRoot, 'pnpm-lock.yaml'));
|
|
114
|
+
// Prefer pnpm test --bail; some test runners reject the flag (node
|
|
115
|
+
// --test ignores it), so we surface non-zero exits clearly but do
|
|
116
|
+
// not retry without --bail.
|
|
117
|
+
// Both pnpm + npm accept `<cmd> test -- --bail`; the runner-side
|
|
118
|
+
// flag-forwarding contract is identical, so the ternary collapses to
|
|
119
|
+
// a single args literal.
|
|
120
|
+
const cmd = pnpmLock ? 'pnpm' : 'npm';
|
|
121
|
+
const args = ['test', '--', '--bail'];
|
|
122
|
+
const result = runProc(cmd, args, workspaceRoot, timeoutMs);
|
|
123
|
+
if (result.exitCode === 0)
|
|
124
|
+
return { ok: true };
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
reason: `tests_exit_${result.exitCode}`,
|
|
128
|
+
detail: tailOutput(result.stdout, result.stderr, 60),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/* ----------------------- url probes ---------------------- */
|
|
132
|
+
async function runUrlChecks(input) {
|
|
133
|
+
const diffText = input.diffText ?? '';
|
|
134
|
+
if (diffText.length === 0) {
|
|
135
|
+
return { ok: true, skipped: true, reason: 'no_diff_text' };
|
|
136
|
+
}
|
|
137
|
+
const urls = extractUrls(diffText);
|
|
138
|
+
if (urls.length === 0) {
|
|
139
|
+
return { ok: true, skipped: true, reason: 'no_urls' };
|
|
140
|
+
}
|
|
141
|
+
const cap = input.maxUrlProbes ?? DEFAULT_MAX_URL_PROBES;
|
|
142
|
+
const probed = urls.slice(0, cap);
|
|
143
|
+
const probeFn = input.probeFn ?? defaultProbeFn;
|
|
144
|
+
const failures = [];
|
|
145
|
+
for (const url of probed) {
|
|
146
|
+
// Hostname SSRF guard — never probe a localhost / private IP even
|
|
147
|
+
// when a literal in the diff points there.
|
|
148
|
+
let parsed;
|
|
149
|
+
try {
|
|
150
|
+
parsed = new URL(url);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
failures.push({ url, error: 'invalid_url' });
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
157
|
+
failures.push({ url, error: `unsupported_scheme_${parsed.protocol}` });
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const hostname = parsed.hostname.replace(/^\[|\]$/g, '');
|
|
161
|
+
const guard = await validateHostnameForFetch(hostname);
|
|
162
|
+
if (guard) {
|
|
163
|
+
failures.push({ url, error: `ssrf_refused: ${guard}` });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const r = await probeFn(url);
|
|
168
|
+
if ('error' in r) {
|
|
169
|
+
failures.push({ url, error: r.error });
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
if (r.status >= 400) {
|
|
173
|
+
failures.push({ url, error: `http_${r.status}` });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
failures.push({
|
|
178
|
+
url,
|
|
179
|
+
error: error instanceof Error ? error.message : String(error),
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (failures.length === 0) {
|
|
184
|
+
return { ok: true, probedCount: probed.length };
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
reason: `url_probe_failed`,
|
|
189
|
+
probedCount: probed.length,
|
|
190
|
+
failures,
|
|
191
|
+
detail: failures.map((f) => `${f.url} → ${f.error}`).join('; '),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Extract unique http(s) URLs from a diff/text blob. Order preserved
|
|
196
|
+
* (first-seen) so the cap picks the earliest-mentioned ones, which
|
|
197
|
+
* intuitively matches the operator's expectation.
|
|
198
|
+
*/
|
|
199
|
+
export function extractUrls(text) {
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
const out = [];
|
|
202
|
+
let match;
|
|
203
|
+
// Reset the regex's lastIndex; URL_LITERAL_RE is module-scoped and
|
|
204
|
+
// /g means stateful exec calls.
|
|
205
|
+
URL_LITERAL_RE.lastIndex = 0;
|
|
206
|
+
while ((match = URL_LITERAL_RE.exec(text)) !== null) {
|
|
207
|
+
const raw = match[1];
|
|
208
|
+
if (!raw)
|
|
209
|
+
continue;
|
|
210
|
+
// Strip a trailing punctuation that the regex tolerates inside
|
|
211
|
+
// the match (sentences in Markdown often end `... https://x).`).
|
|
212
|
+
// We also strip `!` and `?` so prose like "see https://x!" lands
|
|
213
|
+
// as `https://x`.
|
|
214
|
+
const cleaned = raw.replace(/[.,;:!?)\]>]+$/, '');
|
|
215
|
+
if (cleaned.length === 0)
|
|
216
|
+
continue;
|
|
217
|
+
if (seen.has(cleaned))
|
|
218
|
+
continue;
|
|
219
|
+
seen.add(cleaned);
|
|
220
|
+
out.push(cleaned);
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
/* ----------------------- defaults ---------------------- */
|
|
225
|
+
function defaultRunProc(cmd, args, cwd, timeoutMs) {
|
|
226
|
+
const result = spawnSync(cmd, [...args], {
|
|
227
|
+
cwd,
|
|
228
|
+
encoding: 'utf8',
|
|
229
|
+
timeout: timeoutMs,
|
|
230
|
+
// Inherit a minimal env — every check is read-only against the
|
|
231
|
+
// workspace and we do not want to leak PUGI_API_KEY into a
|
|
232
|
+
// sub-process accidentally.
|
|
233
|
+
env: { ...process.env, PUGI_API_KEY: undefined, PUGI_LOGIN_TOKEN: undefined },
|
|
234
|
+
});
|
|
235
|
+
return {
|
|
236
|
+
exitCode: typeof result.status === 'number' ? result.status : -1,
|
|
237
|
+
stdout: result.stdout ?? '',
|
|
238
|
+
stderr: result.stderr ?? '',
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async function defaultProbeFn(url) {
|
|
242
|
+
// Lazy-import undici so the verify-hook module stays cheap when
|
|
243
|
+
// url probes are skipped.
|
|
244
|
+
const { request } = await import('undici');
|
|
245
|
+
try {
|
|
246
|
+
const response = await request(url, {
|
|
247
|
+
method: 'HEAD',
|
|
248
|
+
bodyTimeout: 5_000,
|
|
249
|
+
headersTimeout: 5_000,
|
|
250
|
+
});
|
|
251
|
+
// Drain so the connection releases promptly.
|
|
252
|
+
try {
|
|
253
|
+
await response.body.dump();
|
|
254
|
+
}
|
|
255
|
+
catch {
|
|
256
|
+
/* swallow */
|
|
257
|
+
}
|
|
258
|
+
return { status: response.statusCode };
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function tailOutput(stdout, stderr, maxLines) {
|
|
265
|
+
const merged = `${stdout}\n${stderr}`.trim();
|
|
266
|
+
if (merged.length === 0)
|
|
267
|
+
return '';
|
|
268
|
+
const lines = merged.split('\n');
|
|
269
|
+
if (lines.length <= maxLines)
|
|
270
|
+
return merged;
|
|
271
|
+
return `... (${lines.length - maxLines} earlier lines elided)\n${lines.slice(-maxLines).join('\n')}`;
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=verify-hook.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Worktree isolation —
|
|
2
|
+
* Worktree isolation — Phase 1.
|
|
3
3
|
*
|
|
4
4
|
* Wraps `git worktree add` so a long agent loop (build / consensus
|
|
5
5
|
* review / multi-file refactor) can land its edits into a scratch
|
|
@@ -10,22 +10,22 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Three operations:
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
13
|
+
* - `createWorktree(branch)` — spawns `git worktree add --detach`
|
|
14
|
+
* under `.pugi/worktrees/<uuid>` based on the supplied branch (or
|
|
15
|
+
* HEAD when omitted). Returns the absolute path + a `cleanup()`
|
|
16
|
+
* callback. The dir lives under `.pugi/` so the existing `.gitignore`
|
|
17
|
+
* for that subtree applies (no accidental commits of the scratch
|
|
18
|
+
* state to the main repo).
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* - `promoteWorktree(worktreePath, cwd)` — diffs the worktree against
|
|
21
|
+
* its base commit and applies the diff to the main `cwd` via
|
|
22
|
+
* `git apply`. Refuses if the main cwd has staged changes that
|
|
23
|
+
* would conflict; the operator must commit or stash first.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* - `dropWorktree(worktreePath)` — removes the worktree both from
|
|
26
|
+
* git's bookkeeping (`git worktree remove --force`) and from disk.
|
|
27
|
+
* Idempotent; a partially-removed worktree (`git` already cleaned
|
|
28
|
+
* up but dir survived) is handled.
|
|
29
29
|
*
|
|
30
30
|
* Brand voice: ASCII only, no emoji, no banned words.
|
|
31
31
|
*/
|
|
@@ -103,14 +103,14 @@ export function createWorktree(opts) {
|
|
|
103
103
|
*
|
|
104
104
|
* Implementation notes:
|
|
105
105
|
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
106
|
+
* - We run `git diff --binary <baseSha>` inside the worktree (NOT
|
|
107
|
+
* `git diff <worktree>..HEAD` from the main tree — the worktree's
|
|
108
|
+
* HEAD is detached at `baseSha`, so the meaningful diff is the
|
|
109
|
+
* UNCOMMITTED changes the agent wrote into it).
|
|
110
|
+
* - `--binary` ensures non-text files (assets, images) survive the
|
|
111
|
+
* round-trip; without it `git apply` fails on any binary delta.
|
|
112
|
+
* - We always run `git apply --check` first so a refusal does not
|
|
113
|
+
* leave the main tree half-modified.
|
|
114
114
|
*/
|
|
115
115
|
export function promoteWorktree(opts) {
|
|
116
116
|
if (opts.cancellation && opts.cancellation.isAborted) {
|
|
@@ -147,7 +147,7 @@ export function promoteWorktree(opts) {
|
|
|
147
147
|
if (diffText.trim().length === 0) {
|
|
148
148
|
return { ok: true, value: { filesChanged: 0 } };
|
|
149
149
|
}
|
|
150
|
-
// SECURITY GATE (R1 fix
|
|
150
|
+
// SECURITY GATE (R1 fix, PR r1) — every path mentioned
|
|
151
151
|
// in the worktree's diff goes through the same `applySecurityGate`
|
|
152
152
|
// chokepoint as the apply_patch + Layer A/B/C applicators. A staged
|
|
153
153
|
// `.env` (or `../../etc/passwd`, or a symlink into a protected target)
|
|
@@ -200,7 +200,7 @@ export function promoteWorktree(opts) {
|
|
|
200
200
|
* a missing path returns `worktree_missing` which the caller can ignore
|
|
201
201
|
* on the cleanup-after-error path.
|
|
202
202
|
*
|
|
203
|
-
* Security (R1 fix
|
|
203
|
+
* Security (R1 fix, PR r1): we MUST validate the path is
|
|
204
204
|
* a real subdirectory of `<cwd>/.pugi/worktrees/` BEFORE running either
|
|
205
205
|
* `git worktree remove --force` or `rmSync`. Without this gate, a
|
|
206
206
|
* typo like `pugi worktree drop ../some-dir` recursively deleted an
|
|
@@ -218,10 +218,10 @@ export function promoteWorktree(opts) {
|
|
|
218
218
|
export function dropWorktree(worktreePath, cwd) {
|
|
219
219
|
// SECURITY GATE — validate containment under `<cwd>/.pugi/worktrees/`
|
|
220
220
|
// BEFORE any destructive call. Two-tier check:
|
|
221
|
-
//
|
|
222
|
-
//
|
|
223
|
-
//
|
|
224
|
-
//
|
|
221
|
+
// 1. lexical containment using resolved (but not realpath'd) paths,
|
|
222
|
+
// catches the operator-typo + missing-worktree cases.
|
|
223
|
+
// 2. realpath containment when the path exists, catches symlink
|
|
224
|
+
// shenanigans.
|
|
225
225
|
const scratchRootLexical = resolve(cwd, '.pugi', 'worktrees');
|
|
226
226
|
const worktreeLexical = resolve(cwd, worktreePath);
|
|
227
227
|
const insideLexical = worktreeLexical.startsWith(scratchRootLexical + sep) &&
|