one-code-extension 0.4.1 → 0.6.0
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/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +40 -28
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +26 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +124 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +642 -0
- package/extensions/background/index.ts +614 -248
- package/extensions/background/loop-fire.ts +173 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +56 -0
- package/extensions/background/wakeup.ts +178 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +66 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +250 -78
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +436 -63
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +219 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +35 -15
- package/extensions/context-management/index.ts +36 -28
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +141 -25
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +420 -42
- package/extensions/file-tracker/replay.ts +137 -24
- package/extensions/file-tracker/restore.ts +66 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +45 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +209 -58
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/prompt-context.ts +59 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +26 -12
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +146 -0
- package/extensions/lib/agent-loader.ts +18 -3
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-auth.ts +98 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +52 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +97 -0
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +359 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +71 -19
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +178 -0
- package/extensions/lib/model-catalog/models-dev.json +18533 -0
- package/extensions/lib/model-catalog/openrouter.json +5050 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +357 -73
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +38 -2
- package/extensions/lib/plan-mode-channels.ts +42 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +132 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +370 -358
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +547 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +24 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +112 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +129 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +159 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +76 -12
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +90 -43
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +220 -121
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +164 -73
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/model-default/index.ts +86 -19
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1426 -398
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +11 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +276 -92
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/run-state.ts +26 -0
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +64 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +14 -7
- package/extensions/recap/index.ts +73 -71
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/announce.ts +30 -0
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +469 -132
- package/extensions/skill/invoke.ts +118 -75
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +146 -31
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +82 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1594 -852
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +5 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +135 -80
- package/extensions/subagents/runs.ts +44 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +165 -47
- package/extensions/system-prompt/template.ts +34 -51
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +204 -70
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/index.ts +366 -144
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +108 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +157 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +33 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +162 -44
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +21 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
- package/extensions/tool-search/announce.ts +0 -42
|
@@ -12,27 +12,28 @@
|
|
|
12
12
|
* case-insensitive. Aliases PowerShell 7 dropped because they collide with
|
|
13
13
|
* native executables (`sort`, `sc`, `curl`, `wget`) are deliberately absent,
|
|
14
14
|
* as in CC: mapping `sort` to `Sort-Object` would judge the wrong program.
|
|
15
|
-
* - **Statement split.** A command line is split on `|`, `;`, `&&`,
|
|
16
|
-
* unquoted
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
15
|
+
* - **Statement split.** A command line is split on `|`, `;`, `&&`, `||`, a
|
|
16
|
+
* single (background) `&`, and unquoted line breaks (`\n`, or `\r` on its
|
|
17
|
+
* own), outside `'…'`, `"…"` and `@'…'@` / `@"…"@` here-strings (the
|
|
18
|
+
* typographic quotes included, as pwsh reads them) and comments, with
|
|
19
|
+
* backtick escapes honoured. For an allow rule every part must match (CC:
|
|
20
|
+
* "every subcommand must match"); a deny/ask rule fires on any part, and on
|
|
21
|
+
* a rough split as well. Unbalanced quoting is a parse failure → no allow,
|
|
22
|
+
* the user is asked.
|
|
23
|
+
* - **Textual rule matching.** Rule matching stays on this split, which
|
|
24
|
+
* matches more forms than the tree, never fewer; it is conservative in the
|
|
25
|
+
* only direction that matters: a wildcard or prefix allow never covers a
|
|
26
|
+
* line whose meaning is not on its face — `$(…)` subexpressions, backtick
|
|
27
|
+
* escapes, the `&`/`.` call operators, `Invoke-Expression`,
|
|
28
|
+
* `-EncodedCommand`, script blocks.
|
|
25
29
|
*
|
|
26
|
-
* `powershellReadOnly
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* `..` path is not auto-cleared (UNC paths always ask in CC). Anything the
|
|
30
|
-
* check cannot vouch for is simply "not read-only", which costs a classifier
|
|
31
|
-
* call or a prompt, never a bypass — docs/decisions/windows.md.
|
|
30
|
+
* `powershellReadOnly`, the read-only verdict the gate skips approval on, is
|
|
31
|
+
* judged from PowerShell's own parse instead (`powershell-tree.ts`,
|
|
32
|
+
* decisions/windows.md "The PowerShell pre-gate reads PowerShell's own parse").
|
|
32
33
|
*/
|
|
33
34
|
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
35
|
+
import type { PowerShellParse } from "../lib/powershell-parser.ts";
|
|
36
|
+
import { type PowerShellTreeOptions, powershellTreeReadOnly } from "./powershell-tree.ts";
|
|
36
37
|
|
|
37
38
|
/** Claude Code's `COMMON_ALIASES` (utils/powershell/parser.ts), alias → canonical cmdlet. */
|
|
38
39
|
export const POWERSHELL_ALIASES: Readonly<Record<string, string>> = Object.freeze({
|
|
@@ -160,11 +161,12 @@ export function canonicalizeStatement(statement: string): string {
|
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
163
|
* The separately executing statements of a PowerShell line — what `|`, `;`,
|
|
163
|
-
* `&&`,
|
|
164
|
-
* unbalanced. Single quotes, double quotes, here-strings
|
|
165
|
-
* whose closer must start a line) and backtick
|
|
166
|
-
* inside `Where-Object { $_
|
|
167
|
-
*
|
|
164
|
+
* `&&`, `||`, a single `&` and unquoted line breaks separate — or undefined
|
|
165
|
+
* when quoting is unbalanced. Single quotes, double quotes, here-strings
|
|
166
|
+
* (`@'…'@`, `@"…"@`, whose closer must start a line), comments and backtick
|
|
167
|
+
* escapes are honoured (`lexPowerShell`); a `|` inside `Where-Object { $_
|
|
168
|
+
* -match 'a|b' }` still splits, conservatively, so an allow rule over a
|
|
169
|
+
* script block has to cover the pieces.
|
|
168
170
|
*/
|
|
169
171
|
export function powershellStatements(command: string): string[] | undefined {
|
|
170
172
|
// One decision parses the same line from several places (read-only check,
|
|
@@ -177,103 +179,183 @@ export function powershellStatements(command: string): string[] | undefined {
|
|
|
177
179
|
}
|
|
178
180
|
let lastSplit: { command: string; statements: string[] | undefined } | undefined;
|
|
179
181
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
182
|
+
/**
|
|
183
|
+
* PowerShell's quote characters. The tokenizer takes the typographic quotes as
|
|
184
|
+
* quotes too, interchangeably with the ASCII ones: `'x’ ; Remove-Item y ; ‘z'`
|
|
185
|
+
* is three statements, not one string. Checked against pwsh 7.6's parser.
|
|
186
|
+
*/
|
|
187
|
+
const SINGLE_QUOTES = new Set(["'", "‘", "’", "‚", "‛"]);
|
|
188
|
+
const DOUBLE_QUOTES = new Set(['"', "“", "”", "„"]);
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* How the lexer sees one character of a line: unquoted code, where separators
|
|
192
|
+
* and operators mean what they say; a literal (inside a string or
|
|
193
|
+
* here-string, or escaped by a backtick); or a comment, which never runs.
|
|
194
|
+
*/
|
|
195
|
+
const CODE = 0;
|
|
196
|
+
const LITERAL = 1;
|
|
197
|
+
const COMMENT = 2;
|
|
198
|
+
type Lex = typeof CODE | typeof LITERAL | typeof COMMENT;
|
|
199
|
+
|
|
200
|
+
/** A line break as PowerShell reads one: `\n`, and a carriage return on its own too. */
|
|
201
|
+
function isLineBreak(ch: string | undefined): boolean {
|
|
202
|
+
return ch === "\n" || ch === "\r";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Whether position `i` starts a token, where `#` and `<#` open a comment. In
|
|
207
|
+
* `x#(Set-Content f y)` the `#` is part of the word, and the parenthesis after
|
|
208
|
+
* it still runs.
|
|
209
|
+
*/
|
|
210
|
+
function atTokenStart(text: string, i: number): boolean {
|
|
211
|
+
return i === 0 || /[\s;|&(){}]/.test(text[i - 1]);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Classify every character of a PowerShell line, or undefined when a
|
|
216
|
+
* string, here-string or block comment never closes. The one lexer the
|
|
217
|
+
* statement split and the grouping check share, so they cannot disagree about
|
|
218
|
+
* what is quoted. It follows pwsh's tokenizer on the points that decide where
|
|
219
|
+
* a statement ends: both quote families, doubled quotes, backtick escapes
|
|
220
|
+
* (outside quotes and inside double quotes), here-strings whose closer starts
|
|
221
|
+
* a line (after `\n` or a lone `\r`), and line and block comments at a token
|
|
222
|
+
* start. It does not follow a `$(…)` inside a double-quoted string; every
|
|
223
|
+
* consumer refuses or rough-splits a line with `$(` anyway.
|
|
224
|
+
*/
|
|
225
|
+
function lexPowerShell(text: string): Lex[] | undefined {
|
|
226
|
+
const kinds: Lex[] = new Array(text.length);
|
|
183
227
|
let i = 0;
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
let quote: Quote;
|
|
187
|
-
const push = () => {
|
|
188
|
-
const trimmed = current.trim();
|
|
189
|
-
if (trimmed) parts.push(trimmed);
|
|
190
|
-
current = "";
|
|
228
|
+
const mark = (from: number, to: number, kind: Lex) => {
|
|
229
|
+
for (let k = from; k < to; k++) kinds[k] = kind;
|
|
191
230
|
};
|
|
192
231
|
while (i < text.length) {
|
|
193
232
|
const ch = text[i];
|
|
194
233
|
const next = text[i + 1];
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
current += closer;
|
|
200
|
-
i += 2;
|
|
201
|
-
quote = undefined;
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
current += ch;
|
|
205
|
-
i++;
|
|
234
|
+
if (ch === "`" && i + 1 < text.length) {
|
|
235
|
+
kinds[i] = CODE;
|
|
236
|
+
kinds[i + 1] = LITERAL;
|
|
237
|
+
i += 2;
|
|
206
238
|
continue;
|
|
207
239
|
}
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
current += next;
|
|
214
|
-
i++; // doubled quote is a literal quote
|
|
215
|
-
} else quote = undefined;
|
|
216
|
-
}
|
|
240
|
+
if (ch === "#" && atTokenStart(text, i)) {
|
|
241
|
+
let end = i;
|
|
242
|
+
while (end < text.length && !isLineBreak(text[end])) end++;
|
|
243
|
+
mark(i, end, COMMENT);
|
|
244
|
+
i = end;
|
|
217
245
|
continue;
|
|
218
246
|
}
|
|
219
|
-
if (
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
i++;
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
if (ch === '"') {
|
|
228
|
-
if (next === '"') {
|
|
229
|
-
current += next;
|
|
230
|
-
i++;
|
|
231
|
-
} else quote = undefined;
|
|
232
|
-
}
|
|
247
|
+
if (ch === "<" && next === "#" && atTokenStart(text, i)) {
|
|
248
|
+
const close = text.indexOf("#>", i + 2);
|
|
249
|
+
if (close === -1) return undefined;
|
|
250
|
+
mark(i, close + 2, COMMENT);
|
|
251
|
+
i = close + 2;
|
|
233
252
|
continue;
|
|
234
253
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
i
|
|
254
|
+
if (ch === "@" && next !== undefined && (SINGLE_QUOTES.has(next) || DOUBLE_QUOTES.has(next))) {
|
|
255
|
+
// A here-string closes only with a quote of its family and `@` at the start of a line.
|
|
256
|
+
const family = SINGLE_QUOTES.has(next) ? SINGLE_QUOTES : DOUBLE_QUOTES;
|
|
257
|
+
let end = i + 2;
|
|
258
|
+
while (end < text.length && !(isLineBreak(text[end - 1]) && family.has(text[end]) && text[end + 1] === "@")) end++;
|
|
259
|
+
if (end >= text.length) return undefined;
|
|
260
|
+
mark(i, end + 2, LITERAL);
|
|
261
|
+
i = end + 2;
|
|
239
262
|
continue;
|
|
240
263
|
}
|
|
241
|
-
if (ch
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
264
|
+
if (SINGLE_QUOTES.has(ch)) {
|
|
265
|
+
let end = i + 1;
|
|
266
|
+
for (;;) {
|
|
267
|
+
if (end >= text.length) return undefined;
|
|
268
|
+
if (SINGLE_QUOTES.has(text[end])) {
|
|
269
|
+
if (SINGLE_QUOTES.has(text[end + 1] ?? "")) end += 2; // a doubled quote is a literal quote
|
|
270
|
+
else break;
|
|
271
|
+
} else end++;
|
|
272
|
+
}
|
|
273
|
+
mark(i, end + 1, LITERAL);
|
|
274
|
+
i = end + 1;
|
|
245
275
|
continue;
|
|
246
276
|
}
|
|
247
|
-
if (ch
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
277
|
+
if (DOUBLE_QUOTES.has(ch)) {
|
|
278
|
+
let end = i + 1;
|
|
279
|
+
for (;;) {
|
|
280
|
+
if (end >= text.length) return undefined;
|
|
281
|
+
if (text[end] === "`") end += 2;
|
|
282
|
+
else if (DOUBLE_QUOTES.has(text[end])) {
|
|
283
|
+
if (DOUBLE_QUOTES.has(text[end + 1] ?? "")) end += 2;
|
|
284
|
+
else break;
|
|
285
|
+
} else end++;
|
|
286
|
+
}
|
|
287
|
+
mark(i, end + 1, LITERAL);
|
|
288
|
+
i = end + 1;
|
|
251
289
|
continue;
|
|
252
290
|
}
|
|
253
|
-
|
|
254
|
-
|
|
291
|
+
kinds[i] = CODE;
|
|
292
|
+
i++;
|
|
293
|
+
}
|
|
294
|
+
return kinds;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Split on the statement separators PowerShell 7 runs: `&&`, `||`, `|`, `;`,
|
|
299
|
+
* a line break (`\n`, or `\r` on its own), and a single `&`. A single `&`
|
|
300
|
+
* that starts a statement is the call operator and stays in it; after `>` it
|
|
301
|
+
* is part of a redirection (`2>&1`); anywhere else it is the background
|
|
302
|
+
* operator, and the next statement runs at once (`Get-ChildItem & Remove-Item
|
|
303
|
+
* x` runs both). Comments are dropped.
|
|
304
|
+
*/
|
|
305
|
+
function splitStatements(command: string): string[] | undefined {
|
|
306
|
+
const kinds = lexPowerShell(command);
|
|
307
|
+
if (!kinds) return undefined;
|
|
308
|
+
const parts: string[] = [];
|
|
309
|
+
let current = "";
|
|
310
|
+
const push = () => {
|
|
311
|
+
const trimmed = current.trim();
|
|
312
|
+
if (trimmed) parts.push(trimmed);
|
|
313
|
+
current = "";
|
|
314
|
+
};
|
|
315
|
+
for (let i = 0; i < command.length; i++) {
|
|
316
|
+
const ch = command[i];
|
|
317
|
+
const kind = kinds[i];
|
|
318
|
+
if (kind === COMMENT) continue;
|
|
319
|
+
if (kind === LITERAL) {
|
|
255
320
|
current += ch;
|
|
256
|
-
i++;
|
|
257
321
|
continue;
|
|
258
322
|
}
|
|
323
|
+
const next = kinds[i + 1] === CODE ? command[i + 1] : undefined;
|
|
259
324
|
if ((ch === "&" && next === "&") || (ch === "|" && next === "|")) {
|
|
260
325
|
push();
|
|
261
|
-
i
|
|
326
|
+
i++;
|
|
262
327
|
continue;
|
|
263
328
|
}
|
|
264
|
-
if (ch === "|" || ch === ";" || ch
|
|
329
|
+
if (ch === "|" || ch === ";" || isLineBreak(ch)) {
|
|
330
|
+
push();
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (ch === "&" && current.trim() !== "" && command[i - 1] !== ">") {
|
|
265
334
|
push();
|
|
266
|
-
i++;
|
|
267
335
|
continue;
|
|
268
336
|
}
|
|
269
337
|
current += ch;
|
|
270
|
-
i++;
|
|
271
338
|
}
|
|
272
|
-
if (quote !== undefined) return undefined;
|
|
273
339
|
push();
|
|
274
340
|
return parts;
|
|
275
341
|
}
|
|
276
342
|
|
|
343
|
+
/**
|
|
344
|
+
* Whether the line has a `(` outside quotes and comments. PowerShell
|
|
345
|
+
* evaluates a grouping expression in argument position before calling the
|
|
346
|
+
* command, so `Write-Output (Set-Content f x)` writes `f` behind a read-only
|
|
347
|
+
* cmdlet (AUTO-MODE-SECURITY-REVIEW-2026-09-24 H2); Claude Code's parser
|
|
348
|
+
* marks the same node a subexpression. A quoted `(` is a literal (`"Program
|
|
349
|
+
* Files (x86)"`), and a backtick-escaped one is too. An unbalanced line
|
|
350
|
+
* counts as having one: nothing can vouch for it.
|
|
351
|
+
*/
|
|
352
|
+
function hasGroupingExpression(command: string): boolean {
|
|
353
|
+
const kinds = lexPowerShell(command);
|
|
354
|
+
if (!kinds) return true;
|
|
355
|
+
for (let i = 0; i < command.length; i++) if (kinds[i] === CODE && command[i] === "(") return true;
|
|
356
|
+
return false;
|
|
357
|
+
}
|
|
358
|
+
|
|
277
359
|
/** `& cmd` (call operator) or `. script.ps1` (dot-sourcing) at the head of a statement. */
|
|
278
360
|
function startsWithCallOperator(statement: string): boolean {
|
|
279
361
|
const trimmed = statement.trim();
|
|
@@ -286,15 +368,26 @@ export function statementCommand(statement: string): string {
|
|
|
286
368
|
return canonicalCommandName(word).toLowerCase();
|
|
287
369
|
}
|
|
288
370
|
|
|
371
|
+
/**
|
|
372
|
+
* A control character the lexer does not model: C0 other than tab, line feed
|
|
373
|
+
* and carriage return, DEL, C1 (NEL included), and the Unicode line and
|
|
374
|
+
* paragraph separators. pwsh reads some as whitespace and some as part of a
|
|
375
|
+
* word; the split here honours neither, so a line with one is never covered
|
|
376
|
+
* by a wildcard or prefix allow rule.
|
|
377
|
+
*/
|
|
378
|
+
const UNRECOGNISED_CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f\u2028\u2029]/;
|
|
379
|
+
|
|
289
380
|
/**
|
|
290
381
|
* Why a wildcard or prefix allow rule must not cover this line: constructs
|
|
291
382
|
* whose effect is not what the words say. Exact rules (the literal string the
|
|
292
383
|
* user approved) are unaffected. Returns undefined when none is present.
|
|
293
384
|
*/
|
|
294
385
|
export function powershellInjectionSyntax(command: string): string | undefined {
|
|
386
|
+
if (UNRECOGNISED_CONTROL.test(command)) return "a control character";
|
|
295
387
|
if (/\$\(/.test(command)) return "a $(…) subexpression";
|
|
296
388
|
if (/`/.test(command)) return "a backtick escape";
|
|
297
389
|
if (/\{/.test(command)) return "a script block";
|
|
390
|
+
if (hasGroupingExpression(command)) return "a (…) grouping expression";
|
|
298
391
|
if (/-e(nc|ncoded|ncodedcommand)?\b/i.test(command) && /\b(pwsh|powershell)(\.exe)?\b/i.test(command)) return "an encoded command";
|
|
299
392
|
const statements = powershellStatements(command);
|
|
300
393
|
for (const statement of statements ?? []) {
|
|
@@ -338,148 +431,28 @@ export function powershellMatchForms(command: string, depth = 0): string[] {
|
|
|
338
431
|
const nested = nestedShellScript(statement);
|
|
339
432
|
if (nested) for (const form of powershellMatchForms(nested, depth + 1)) forms.add(form);
|
|
340
433
|
}
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
* docs/decisions/windows.md) — the pure in-process pipeline cmdlets a
|
|
351
|
-
* read-only line is piped through: they shape objects already in memory and
|
|
352
|
-
* touch neither disk nor network. `Where-Object`/`ForEach-Object` are NOT
|
|
353
|
-
* here: they take script blocks, which the check refuses anyway. Claude Code's
|
|
354
|
-
* captured list has no pipeline cmdlets at all, so `Select-String … |
|
|
355
|
-
* Measure-Object` (the /doctor transcript scan) always went to the classifier.
|
|
356
|
-
*/
|
|
357
|
-
export const READ_ONLY_POWERSHELL_COMMANDS = new Set<string>([
|
|
358
|
-
// search
|
|
359
|
-
"select-string",
|
|
360
|
-
"get-childitem",
|
|
361
|
-
"findstr",
|
|
362
|
-
"where.exe",
|
|
363
|
-
// read
|
|
364
|
-
"get-content",
|
|
365
|
-
"get-item",
|
|
366
|
-
"test-path",
|
|
367
|
-
"resolve-path",
|
|
368
|
-
"get-process",
|
|
369
|
-
"get-service",
|
|
370
|
-
"get-location",
|
|
371
|
-
"get-filehash",
|
|
372
|
-
"get-acl",
|
|
373
|
-
"format-hex",
|
|
374
|
-
// neutral output
|
|
375
|
-
"write-output",
|
|
376
|
-
"write-host",
|
|
377
|
-
// pure pipeline transforms (One Code's addition)
|
|
378
|
-
"select-object",
|
|
379
|
-
"sort-object",
|
|
380
|
-
"measure-object",
|
|
381
|
-
"group-object",
|
|
382
|
-
"convertfrom-json",
|
|
383
|
-
"out-string",
|
|
384
|
-
"format-table",
|
|
385
|
-
"format-list",
|
|
386
|
-
"format-wide",
|
|
387
|
-
"format-custom",
|
|
388
|
-
]);
|
|
389
|
-
|
|
390
|
-
/** Parameters that turn a read-only cmdlet into a write. */
|
|
391
|
-
const WRITING_PARAMETERS = /^-(outfile|filepath|destination)\b/i;
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Whether a token names a path this check will not vouch for by shape: UNC
|
|
395
|
-
* (`\\server`), home (`~`), a PSDrive outside the filesystem (`HKLM:`,
|
|
396
|
-
* `env:`), or one that climbs (`..`). An absolute or drive-lettered path is
|
|
397
|
-
* judged by `pathOutsideRoots` when roots are known, and refused otherwise.
|
|
398
|
-
*/
|
|
399
|
-
function pathUnvouchable(value: string): boolean {
|
|
400
|
-
if (!value) return false;
|
|
401
|
-
if (value.startsWith("\\\\") || value.startsWith("//")) return true;
|
|
402
|
-
if (/^[A-Za-z][A-Za-z0-9]+:/.test(value)) return true; // HKLM:, env:, cert: (a drive is one letter)
|
|
403
|
-
// Drive-relative (`C:foo.txt`, no separator after the colon) means "foo.txt
|
|
404
|
-
// relative to PowerShell's current directory ON C:", which need not be the
|
|
405
|
-
// tool's cwd; `path.isAbsolute` does not call it absolute, so `toAbsolute`
|
|
406
|
-
// would join it onto the cwd and vouch for the wrong file. Refuse by shape.
|
|
407
|
-
if (/^[A-Za-z]:(?![\\/])/.test(value)) return true;
|
|
408
|
-
if (value.startsWith("~")) return true;
|
|
409
|
-
if (/(^|[\\/])\.\.([\\/]|$)/.test(value)) return true;
|
|
410
|
-
return false;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* PowerShell's array syntax splits an argument on commas — outside quotes.
|
|
415
|
-
* `"a,b.txt"` is one path with a comma in its name; `a,"b,c"` is two.
|
|
416
|
-
*/
|
|
417
|
-
export function splitPowerShellList(token: string): string[] {
|
|
418
|
-
const parts: string[] = [];
|
|
419
|
-
let current = "";
|
|
420
|
-
let quote: string | undefined;
|
|
421
|
-
for (const ch of token) {
|
|
422
|
-
if (quote) {
|
|
423
|
-
if (ch === quote) quote = undefined;
|
|
424
|
-
else current += ch;
|
|
425
|
-
} else if (ch === '"' || ch === "'") {
|
|
426
|
-
quote = ch;
|
|
427
|
-
} else if (ch === ",") {
|
|
428
|
-
parts.push(current);
|
|
429
|
-
current = "";
|
|
430
|
-
} else {
|
|
431
|
-
current += ch;
|
|
432
|
-
}
|
|
434
|
+
// A rough split on every separator and grouping character, quotes ignored:
|
|
435
|
+
// it catches a statement inside `$(…)`, `(…)` or `{…}`, and every
|
|
436
|
+
// statement of a line the lexer could not read (bash's counterpart adds
|
|
437
|
+
// the same rough split for an unparseable line).
|
|
438
|
+
for (const piece of trimmed.split(ROUGH_SEPARATORS)) {
|
|
439
|
+
const part = piece.trim();
|
|
440
|
+
if (!part) continue;
|
|
441
|
+
forms.add(part);
|
|
442
|
+
forms.add(canonicalizeStatement(part));
|
|
433
443
|
}
|
|
434
|
-
|
|
435
|
-
return parts;
|
|
444
|
+
return [...forms];
|
|
436
445
|
}
|
|
437
446
|
|
|
438
|
-
/**
|
|
439
|
-
|
|
440
|
-
return /^[A-Za-z]:/.test(value) || value.startsWith("/") || value.startsWith("\\");
|
|
441
|
-
}
|
|
447
|
+
/** Everything that can end or open a statement, for the deny/ask rough split. */
|
|
448
|
+
const ROUGH_SEPARATORS = /[;|&\r\n(){}]+/;
|
|
442
449
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
cwd: string;
|
|
446
|
-
/** The user's home, for `toAbsolute`. */
|
|
447
|
-
home: string;
|
|
448
|
-
/**
|
|
449
|
-
* REALPATH-resolved directories an absolute path may point into besides the
|
|
450
|
-
* working directory: the harness's own session dirs (memory, scratchpad,
|
|
451
|
-
* persisted results, this project's transcripts — matcher.ts `DecideInput`),
|
|
452
|
-
* resolved once per session by the caller.
|
|
453
|
-
*/
|
|
454
|
-
readableRoots?: string[];
|
|
455
|
-
}
|
|
450
|
+
// ---------------------------------------------------------------------------
|
|
451
|
+
// Read-only
|
|
456
452
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
* the name) is judged on its own: refused by
|
|
461
|
-
* shape when unvouchable; a relative part is inside by construction; an
|
|
462
|
-
* absolute part is resolved through `resolveForContainment` — the nearest
|
|
463
|
-
* existing ancestor's realpath, so a glob leaf (`C:\src\play\*.ts`) is
|
|
464
|
-
* judged by its directory and a symlink planted inside the project that
|
|
465
|
-
* points out of it is outside — and must lie inside a root. With no roots
|
|
466
|
-
* (no options) an absolute part is refused, the pre-2026-09-19 behaviour.
|
|
467
|
-
*/
|
|
468
|
-
function pathOutsideRoots(value: string, opts: PowerShellReadOnlyOptions | undefined, roots: string[]): boolean {
|
|
469
|
-
for (const part of splitPowerShellList(value)) {
|
|
470
|
-
const trimmed = part.trim();
|
|
471
|
-
if (!trimmed) continue;
|
|
472
|
-
if (pathUnvouchable(trimmed)) return true;
|
|
473
|
-
if (!isAbsoluteSpelling(trimmed)) continue;
|
|
474
|
-
if (!opts) return true;
|
|
475
|
-
// Resolve only what THIS platform's path module calls absolute: on a POSIX
|
|
476
|
-
// host `C:\x` is not, and `toAbsolute` would join it onto the cwd and
|
|
477
|
-
// vouch for a file the shell would never touch. Refuse it by shape there.
|
|
478
|
-
if (!isAbsolute(trimmed)) return true;
|
|
479
|
-
const resolved = resolveForContainment(toAbsolute(opts.cwd, trimmed, opts.home));
|
|
480
|
-
if (resolved === undefined || !roots.some((root) => isWithin(root, resolved))) return true;
|
|
481
|
-
}
|
|
482
|
-
return false;
|
|
453
|
+
export interface PowerShellReadOnlyOptions extends PowerShellTreeOptions {
|
|
454
|
+
/** PowerShell's own parse of the line (lib/powershell-parser.ts); without one nothing is read-only. */
|
|
455
|
+
parse?: PowerShellParse;
|
|
483
456
|
}
|
|
484
457
|
|
|
485
458
|
export interface PowerShellReadOnlyVerdict {
|
|
@@ -489,61 +462,15 @@ export interface PowerShellReadOnlyVerdict {
|
|
|
489
462
|
}
|
|
490
463
|
|
|
491
464
|
/**
|
|
492
|
-
* Whether every statement of the line
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
* the
|
|
496
|
-
*
|
|
497
|
-
*
|
|
465
|
+
* Whether every statement of the line provably only reads inside the working
|
|
466
|
+
* directory and the readable roots, judged from PowerShell's own parse
|
|
467
|
+
* (`powershell-tree.ts`). Positive proof only; without a parse, or with
|
|
468
|
+
* anything the tree check does not model, the line is not read-only and takes
|
|
469
|
+
* the ordinary route (the classifier in auto mode, a prompt elsewhere) —
|
|
470
|
+
* decisions/windows.md "The PowerShell pre-gate reads PowerShell's own parse".
|
|
498
471
|
*/
|
|
499
|
-
export function powershellReadOnly(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if (/[<>]/.test(command)) return { readOnly: false, reason: "redirection" };
|
|
504
|
-
if (/\$/.test(command)) return { readOnly: false, reason: "a variable or subexpression" };
|
|
505
|
-
if (/[`{}]/.test(command)) return { readOnly: false, reason: "an escape or script block" };
|
|
506
|
-
// The cwd's realpath once per command (the roots arrive resolved), not once per token.
|
|
507
|
-
const roots = opts ? [resolveForContainment(opts.cwd) ?? opts.cwd, ...(opts.readableRoots ?? [])] : [];
|
|
508
|
-
for (const statement of statements) {
|
|
509
|
-
if (startsWithCallOperator(statement)) return { readOnly: false, reason: "the call operator" };
|
|
510
|
-
const cmd = statementCommand(statement);
|
|
511
|
-
if (!READ_ONLY_POWERSHELL_COMMANDS.has(cmd)) return { readOnly: false, reason: `\`${cmd}\` is not a read-only cmdlet` };
|
|
512
|
-
const tokens = statement.trim().split(/\s+/).slice(1);
|
|
513
|
-
for (const token of tokens) {
|
|
514
|
-
if (WRITING_PARAMETERS.test(token)) return { readOnly: false, reason: `${token.split(":")[0]} writes or forwards` };
|
|
515
|
-
if (token.startsWith("-")) continue;
|
|
516
|
-
// By shape (UNC, ~, PSDrive, ..) a token is never vouched for. An absolute
|
|
517
|
-
// path is read-only only when it resolves inside the working directory or
|
|
518
|
-
// a harness session dir (2026-09-19: /doctor's transcript scan on Windows
|
|
519
|
-
// spelled `<agentDir>\sessions\…` and was classified —
|
|
520
|
-
// docs/decisions/auto-mode.md); a comma list is judged part by part.
|
|
521
|
-
if (pathOutsideRoots(token, opts, roots)) return { readOnly: false, reason: "a path outside the working directory" };
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
return { readOnly: true };
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/** git global flags that take a value and can precede the subcommand (`git -C x status`). */
|
|
528
|
-
const GIT_GLOBAL_VALUE_FLAGS = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--exec-path", "--config-env"]);
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Whether the statement is a bare `git status` (global flags before it and
|
|
532
|
-
* plain flags after it allowed, no other statements) — the call after which
|
|
533
|
-
* Claude Code's classifier transcript carries a
|
|
534
|
-
* `{"meta":{"gitStatus":{"clean":…}}}` ground-truth line (findings §22).
|
|
535
|
-
* Works for bash and PowerShell spellings alike. Global value-taking flags
|
|
536
|
-
* (`-C <dir>`, `-c <key=val>`, …) are skipped so `git -C <dir> status` is
|
|
537
|
-
* still recognised instead of mistaking the flag's value for the subcommand.
|
|
538
|
-
*/
|
|
539
|
-
export function isGitStatusCommand(command: string): boolean {
|
|
540
|
-
const statements = powershellStatements(command);
|
|
541
|
-
if (!statements || statements.length !== 1) return false;
|
|
542
|
-
if (statementCommand(statements[0]) !== "git") return false;
|
|
543
|
-
const words = statements[0].trim().split(/\s+/).slice(1);
|
|
544
|
-
let i = 0;
|
|
545
|
-
while (i < words.length && words[i].startsWith("-")) {
|
|
546
|
-
i += GIT_GLOBAL_VALUE_FLAGS.has(words[i]) ? 2 : 1;
|
|
547
|
-
}
|
|
548
|
-
return words[i] === "status" && words.slice(i + 1).every((w) => w.startsWith("-"));
|
|
472
|
+
export function powershellReadOnly(opts: PowerShellReadOnlyOptions): PowerShellReadOnlyVerdict {
|
|
473
|
+
if (!opts.parse) return { readOnly: false, reason: "PowerShell's own parse of the line is not available" };
|
|
474
|
+
const verdict = powershellTreeReadOnly(opts.parse, opts);
|
|
475
|
+
return verdict.ok ? { readOnly: true } : { readOnly: false, reason: verdict.reason };
|
|
549
476
|
}
|