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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/** Claude Code's project-scoped external instruction consent; pure fs/text, no pi imports. */
|
|
2
|
+
import { readJsonFile } from "./atomic-write.ts";
|
|
3
|
+
import { oneCodeProjectRoot, oneCodeProjectSettingsPathFor, readSettingsForWrite, writeSettings } from "./one-code-settings.ts";
|
|
4
|
+
import { comparablePath, tildify } from "./paths.ts";
|
|
5
|
+
import { escapeControlText } from "./terminal-text.ts";
|
|
6
|
+
|
|
7
|
+
export const EXTERNAL_INCLUDES_TITLE = "Allow external CLAUDE.md file imports?";
|
|
8
|
+
export const EXTERNAL_INCLUDES_YES = "Yes, allow external imports";
|
|
9
|
+
export const EXTERNAL_INCLUDES_NO = "No, disable external imports";
|
|
10
|
+
export const EXTERNAL_INCLUDES_SETTING = "External CLAUDE.md includes";
|
|
11
|
+
/**
|
|
12
|
+
* `/config` with nothing to show. Claude Code's /config is a full settings
|
|
13
|
+
* panel and hides this row when there are no external includes; One Code's
|
|
14
|
+
* holds only this row so far (decisions/tools.md), so the empty case names
|
|
15
|
+
* where the other settings live instead of ending the command.
|
|
16
|
+
*/
|
|
17
|
+
export const EXTERNAL_INCLUDES_NONE =
|
|
18
|
+
"No external CLAUDE.md includes in this project, so /config has nothing to change. Other settings: /settings (pi's settings), /model, /permissions, /memory.";
|
|
19
|
+
|
|
20
|
+
export interface ExternalIncludesApproval {
|
|
21
|
+
approved: boolean;
|
|
22
|
+
warningShown: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The project root an answer was given for. The settings file's slug is lossy
|
|
27
|
+
* (`my_app` and `my-app` share one file), so the answer counts only for this
|
|
28
|
+
* exact root; an answer recorded without one is asked again.
|
|
29
|
+
*/
|
|
30
|
+
const APPROVAL_ROOT_KEY = "claudeMdExternalIncludesRoot";
|
|
31
|
+
|
|
32
|
+
/** Only One Code's out-of-checkout project state grants consent, never merged project settings. */
|
|
33
|
+
export function readExternalIncludesApproval(cwd: string, home: string): ExternalIncludesApproval {
|
|
34
|
+
const root = oneCodeProjectRoot(cwd);
|
|
35
|
+
const file = readJsonFile<Record<string, unknown>>(oneCodeProjectSettingsPathFor(root, home));
|
|
36
|
+
const recorded = file?.[APPROVAL_ROOT_KEY];
|
|
37
|
+
if (!file || typeof recorded !== "string" || comparablePath(recorded) !== comparablePath(root)) return { approved: false, warningShown: false };
|
|
38
|
+
return {
|
|
39
|
+
approved: file.hasClaudeMdExternalIncludesApproved === true,
|
|
40
|
+
warningShown: file.hasClaudeMdExternalIncludesWarningShown === true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Both answers are remembered, and cover future imports, not just the files in the preview. */
|
|
45
|
+
export function persistExternalIncludesApproval(cwd: string, home: string, approved: boolean): void {
|
|
46
|
+
const root = oneCodeProjectRoot(cwd);
|
|
47
|
+
const path = oneCodeProjectSettingsPathFor(root, home);
|
|
48
|
+
writeSettings(path, {
|
|
49
|
+
...readSettingsForWrite(path),
|
|
50
|
+
hasClaudeMdExternalIncludesApproved: approved,
|
|
51
|
+
hasClaudeMdExternalIncludesWarningShown: true,
|
|
52
|
+
[APPROVAL_ROOT_KEY]: root,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The dialog text; pi's plain select carries the body in its multiline title, including over RPC. */
|
|
57
|
+
export function externalIncludesDialog(paths: string[], home: string): string {
|
|
58
|
+
const shown = paths.length <= 8 ? paths : paths.slice(0, 6);
|
|
59
|
+
const hidden = paths.length - shown.length;
|
|
60
|
+
const preview = shown.map((path) => ` ${escapeControlText(tildify(path, home))}`);
|
|
61
|
+
if (hidden) preview.push(` … +${hidden} ${hidden === 1 ? "import" : "imports"} not shown.`, " Yes covers those too, plus any this project adds later.");
|
|
62
|
+
return [
|
|
63
|
+
EXTERNAL_INCLUDES_TITLE,
|
|
64
|
+
"This project's CLAUDE.md or .claude/rules imports files outside the current working directory. Never allow this for third-party repositories.",
|
|
65
|
+
...(paths.length ? [`External imports:\n${preview.join("\n")}`] : []),
|
|
66
|
+
"Important: Only use One Code with files you trust. Accessing untrusted files may pose security risks.",
|
|
67
|
+
].join("\n\n");
|
|
68
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude model ids in any provider's spelling (pure, no imports), for the
|
|
3
|
+
* gates that must not pull in the tier module's catalog graph.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A Claude model id in any provider's spelling (`claude-opus-5-5`,
|
|
8
|
+
* `claude-haiku-4-5-20251001`, `anthropic/claude-sonnet-5.5`, Bedrock's
|
|
9
|
+
* `us.anthropic.claude-fable-5-1-v1`): family, major, and minor when the id
|
|
10
|
+
* has one. A dated suffix is never read as the minor version. Undefined for
|
|
11
|
+
* anything else. `parseClaudeVersion` (model-tier.ts) stays first-party only, for the gates.
|
|
12
|
+
*/
|
|
13
|
+
export function parseClaudeId(id: string): { family: "opus" | "sonnet" | "haiku" | "fable"; major: number; minor?: number } | undefined {
|
|
14
|
+
const match = id.match(/(?:^|[/.])claude-(opus|sonnet|haiku|fable)-(\d+)(?:[-.](\d{1,2}))?(?=$|[-@:[])/);
|
|
15
|
+
if (!match) return undefined;
|
|
16
|
+
return { family: match[1] as "opus" | "sonnet" | "haiku" | "fable", major: Number(match[2]), ...(match[3] ? { minor: Number(match[3]) } : {}) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** The Claude family of a model id in any provider's spelling. */
|
|
20
|
+
export function claudeFamily(id: string): "opus" | "sonnet" | "haiku" | "fable" | undefined {
|
|
21
|
+
return parseClaudeId(id)?.family;
|
|
22
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/** Claude Code 2.1.289 rule parsing/discovery. Pure filesystem/text helpers; no pi state. */
|
|
2
|
+
import { lstatSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, relative } from "node:path";
|
|
4
|
+
import ignore, { type Ignore } from "ignore";
|
|
5
|
+
import { Lexer, type Token } from "marked";
|
|
6
|
+
import { parse as parseYaml } from "yaml";
|
|
7
|
+
import { absoluteFrom, comparablePath, expandTilde, forwardSlashes, isPathAtOrUnder, isRelativeInside, tryRealpath } from "./paths.ts";
|
|
8
|
+
|
|
9
|
+
/** pO/Oee: instruction files larger than 4 MiB are skipped, never truncated. */
|
|
10
|
+
export const MAX_INSTRUCTION_BYTES = 4_194_304;
|
|
11
|
+
|
|
12
|
+
export interface RuleFile {
|
|
13
|
+
path: string;
|
|
14
|
+
key: string;
|
|
15
|
+
content: string;
|
|
16
|
+
globs?: string[];
|
|
17
|
+
/** Import/link provenance for the startup external-includes warning. */
|
|
18
|
+
parent?: string;
|
|
19
|
+
linkedFrom?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** f_t/H in the binary: comma lists and brace alternatives, not numeric brace ranges. */
|
|
23
|
+
export function splitRulePaths(value: unknown): string[] {
|
|
24
|
+
const budget = { results: 1000, bytes: 4_194_304 };
|
|
25
|
+
const expand = (pattern: string): string[] => {
|
|
26
|
+
if (!pattern.includes("{")) return [pattern];
|
|
27
|
+
const result: string[] = [];
|
|
28
|
+
const pending = [pattern];
|
|
29
|
+
for (let next = pending.pop(); next !== undefined; next = pending.pop()) {
|
|
30
|
+
const match = next.match(/^([^{]*)\{([^}]+)\}(.*)$/);
|
|
31
|
+
if (!match) { result.push(next); continue; }
|
|
32
|
+
const alternatives = match[2].split(",").map((part) => part.trim());
|
|
33
|
+
budget.bytes -= next.length;
|
|
34
|
+
const count = result.length + pending.length + alternatives.length;
|
|
35
|
+
if (budget.bytes < 0 || count > budget.results || count * pattern.length > budget.bytes) return [pattern];
|
|
36
|
+
for (let i = alternatives.length - 1; i >= 0; i--) pending.push(match[1] + alternatives[i] + match[3]);
|
|
37
|
+
}
|
|
38
|
+
budget.results -= result.length;
|
|
39
|
+
budget.bytes -= result.length * pattern.length;
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
const split = (input: unknown): string[] => {
|
|
43
|
+
if (Array.isArray(input)) return input.flatMap(split);
|
|
44
|
+
if (typeof input !== "string") return [];
|
|
45
|
+
const parts: string[] = [];
|
|
46
|
+
let part = "", depth = 0;
|
|
47
|
+
for (const char of input) {
|
|
48
|
+
if (char === "{") depth++;
|
|
49
|
+
if (char === "}") depth--;
|
|
50
|
+
if (char === "," && depth === 0) { if (part.trim()) parts.push(part.trim()); part = ""; }
|
|
51
|
+
else part += char;
|
|
52
|
+
}
|
|
53
|
+
if (part.trim()) parts.push(part.trim());
|
|
54
|
+
return parts.flatMap(expand);
|
|
55
|
+
};
|
|
56
|
+
return split(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Fs retries hand-written YAML after quoting special scalars and expanding leading tabs. */
|
|
60
|
+
function frontmatterValue(yaml: string): Record<string, unknown> {
|
|
61
|
+
const object = (value: unknown): Record<string, unknown> =>
|
|
62
|
+
value !== null && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : {};
|
|
63
|
+
try { return object(parseYaml(yaml)); } catch { /* retry below */ }
|
|
64
|
+
const repaired = yaml.split("\n").map((line) => {
|
|
65
|
+
const match = line.match(/^([a-zA-Z_-]+):\s+(\S.*)$/);
|
|
66
|
+
if (!match) return line;
|
|
67
|
+
const [, key, value] = match;
|
|
68
|
+
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) return line;
|
|
69
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
70
|
+
try { if (Array.isArray(parseYaml(value))) return line; } catch { /* quote it below */ }
|
|
71
|
+
}
|
|
72
|
+
const escaped = value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
|
|
73
|
+
return /[{}[\]*&#!|>%@`]|: /.test(value) ? `${key}: "${escaped}"` : line;
|
|
74
|
+
}).join("\n").replace(/^\t+/gm, (tabs) => " ".repeat(tabs.length));
|
|
75
|
+
try { return object(parseYaml(repaired)); } catch { return {}; }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** W1n/tgt/Sxe: remove frontmatter and block HTML comments, retain raw body whitespace. */
|
|
79
|
+
export function parseRule(content: string): { content: string; globs?: string[] } {
|
|
80
|
+
const withoutBom = content.replace(/^\uFEFF/, "");
|
|
81
|
+
const match = withoutBom.match(/^---\s*\n([\s\S]*?)---\s*\n?/);
|
|
82
|
+
const metadata = match ? frontmatterValue(match[1]) : {};
|
|
83
|
+
let body = match ? withoutBom.slice(match[0].length) : content;
|
|
84
|
+
if (body.includes("<!--")) {
|
|
85
|
+
body = new Lexer({ gfm: false }).lex(body).map((token) => {
|
|
86
|
+
if (token.type !== "html" || !token.raw.trimStart().startsWith("<!--") || !token.raw.includes("-->")) return token.raw;
|
|
87
|
+
const residue = token.raw.replace(/<!--[\s\S]*?-->/g, "");
|
|
88
|
+
return residue.trim() ? residue : "";
|
|
89
|
+
}).join("");
|
|
90
|
+
}
|
|
91
|
+
const paths = splitRulePaths(metadata.paths).map((path) => path.endsWith("/**") ? path.slice(0, -3) : path).filter(Boolean);
|
|
92
|
+
return { content: body, ...(paths.length && !paths.every((path) => path === "**") ? { globs: paths } : {}) };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** $ee/lje: ignore's gitignore semantics (including negation), never a glob-library approximation. */
|
|
96
|
+
export function ruleMatches(globs: string[] | undefined, filePath: string, base: string): boolean {
|
|
97
|
+
if (!globs?.length) return false;
|
|
98
|
+
let candidate = isAbsolute(filePath) ? relative(base, filePath) : filePath;
|
|
99
|
+
if (isAbsolute(filePath) && !isRelativeInside(candidate)) {
|
|
100
|
+
// Both sides resolved: a cwd reached through a symlink (or a Windows 8.3
|
|
101
|
+
// short name) spells the base differently from the file's real directory.
|
|
102
|
+
const parent = tryRealpath(dirname(filePath));
|
|
103
|
+
if (parent) candidate = relative(tryRealpath(base) ?? base, join(parent, basename(filePath)));
|
|
104
|
+
}
|
|
105
|
+
if (!isRelativeInside(candidate)) return false;
|
|
106
|
+
return globMatcher(globs).ignores(forwardSlashes(candidate));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Compiled matchers by glob list: a read checks every conditional rule, so each list compiles once. */
|
|
110
|
+
const matchers = new Map<string, Ignore>();
|
|
111
|
+
|
|
112
|
+
function globMatcher(globs: string[]): Ignore {
|
|
113
|
+
const key = JSON.stringify(globs);
|
|
114
|
+
let matcher = matchers.get(key);
|
|
115
|
+
if (!matcher) {
|
|
116
|
+
const valid = globs.filter((glob) => {
|
|
117
|
+
try { ignore().add(glob).test("probe"); return true; } catch { return false; }
|
|
118
|
+
});
|
|
119
|
+
if (matchers.size >= 1024) matchers.clear();
|
|
120
|
+
matchers.set(key, (matcher = ignore().add(valid)));
|
|
121
|
+
}
|
|
122
|
+
return matcher;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The paths a walk being cached has looked at, each with its stat signature
|
|
127
|
+
* from before it was read: a change while the walk runs makes the next call
|
|
128
|
+
* walk again rather than keep what it read.
|
|
129
|
+
*/
|
|
130
|
+
let touched: Map<string, string> | undefined;
|
|
131
|
+
|
|
132
|
+
function touch(path: string): void {
|
|
133
|
+
if (touched && !touched.has(path)) touched.set(path, signature(path));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** What a stat can see of a path (through a link too); "-" when it is missing. */
|
|
137
|
+
function signature(path: string): string {
|
|
138
|
+
try {
|
|
139
|
+
const link = lstatSync(path);
|
|
140
|
+
const stat = link.isSymbolicLink() ? statSync(path) : link;
|
|
141
|
+
return `${link.ino}:${stat.ino}:${stat.mtimeMs}:${stat.size}:${stat.nlink}`;
|
|
142
|
+
} catch {
|
|
143
|
+
return "-";
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isLink(path: string): boolean {
|
|
148
|
+
touch(path);
|
|
149
|
+
try { return lstatSync(path).isSymbolicLink(); } catch { return false; }
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* One Code addition, matching the inline expander (claude-context.ts
|
|
154
|
+
* `readImportTarget`): an import ending a sentence (`@docs/x.md.`) names
|
|
155
|
+
* `docs/x.md` when only that is a file. Claude Code reads the literal path,
|
|
156
|
+
* which wins whenever it exists.
|
|
157
|
+
*/
|
|
158
|
+
function withoutTrailingPunctuation(target: string): string {
|
|
159
|
+
for (let candidate = target; ; candidate = candidate.slice(0, -1)) {
|
|
160
|
+
touch(candidate);
|
|
161
|
+
try { if (statSync(candidate).isFile()) return candidate; } catch { /* not a file: peel */ }
|
|
162
|
+
if (candidate.length <= 1 || !/[.,;:!?)\]]$/.test(candidate)) return target;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Y1n: imports are separate instruction files, not substitutions into the rule's body. */
|
|
167
|
+
function ruleImports(content: string, path: string, home: string): string[] {
|
|
168
|
+
const found = new Set<string>();
|
|
169
|
+
const text = (body: string) => {
|
|
170
|
+
for (const match of body.matchAll(/(?:^|\s)@((?:[^\s\\]|\\ )+)/g)) {
|
|
171
|
+
const ref = match[1].split("#")[0].replaceAll("\\ ", " ");
|
|
172
|
+
if (!ref || !(ref.startsWith("./") || ref.startsWith("~/") || (ref.startsWith("/") && ref !== "/") || /^[a-zA-Z0-9._-]/.test(ref))) continue;
|
|
173
|
+
found.add(withoutTrailingPunctuation(absoluteFrom(dirname(path), expandTilde(ref, home))));
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
const walk = (tokens: Token[]) => {
|
|
177
|
+
for (const token of tokens) {
|
|
178
|
+
if (token.type === "code" || token.type === "codespan") continue;
|
|
179
|
+
if (token.type === "html") {
|
|
180
|
+
if (token.raw.trimStart().startsWith("<!--") && token.raw.includes("-->")) text(token.raw.replace(/<!--[\s\S]*?-->/g, ""));
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (token.type === "text") text(token.text);
|
|
184
|
+
if ("tokens" in token && token.tokens) walk(token.tokens);
|
|
185
|
+
if ("items" in token) walk(token.items);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
walk(new Lexer({ gfm: false }).lex(content));
|
|
189
|
+
return [...found];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const TEXT_EXTENSIONS = new Set([
|
|
193
|
+
".md", ".txt", ".text", ".json", ".yaml", ".yml", ".toml", ".xml", ".csv", ".html", ".htm", ".css", ".scss", ".sass", ".less",
|
|
194
|
+
".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs", ".mts", ".cts", ".py", ".pyi", ".pyw", ".rb", ".erb", ".rake", ".go", ".rs",
|
|
195
|
+
".java", ".kt", ".kts", ".scala", ".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx", ".cs", ".swift", ".sh", ".bash", ".zsh",
|
|
196
|
+
".fish", ".ps1", ".bat", ".cmd", ".env", ".ini", ".cfg", ".conf", ".config", ".properties", ".sql", ".graphql", ".gql", ".proto",
|
|
197
|
+
".vue", ".svelte", ".astro", ".ejs", ".hbs", ".pug", ".jade", ".php", ".pl", ".pm", ".lua", ".r", ".dart", ".ex", ".exs",
|
|
198
|
+
".erl", ".hrl", ".clj", ".cljs", ".cljc", ".edn", ".hs", ".lhs", ".elm", ".ml", ".mli", ".f", ".f90", ".f95", ".for",
|
|
199
|
+
".cmake", ".make", ".makefile", ".gradle", ".sbt", ".rst", ".adoc", ".asciidoc", ".org", ".tex", ".latex", ".lock", ".log", ".diff", ".patch",
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
export interface RuleOptions {
|
|
203
|
+
rulesDir: string;
|
|
204
|
+
cwd: string;
|
|
205
|
+
home: string;
|
|
206
|
+
scope: "Project" | "User" | "Managed";
|
|
207
|
+
/** Undefined selects unconditional files; a target selects only matching conditional files. */
|
|
208
|
+
filePath?: string;
|
|
209
|
+
/** Select every conditional file, whatever its globs (the auto-mode classifier's instructions). */
|
|
210
|
+
allConditional?: boolean;
|
|
211
|
+
processed?: Set<string>;
|
|
212
|
+
/** Normal CLI user rules allow external includes; project/managed rules need approval. */
|
|
213
|
+
includeExternal?: boolean;
|
|
214
|
+
/** Independent mode excludes Claude locations before reading or following their imports. */
|
|
215
|
+
allowPath?: (path: string) => boolean;
|
|
216
|
+
/**
|
|
217
|
+
* The directory a project file belongs to (`<dir>` for `<dir>/.claude/CLAUDE.md`).
|
|
218
|
+
* A file linked out of both it and the cwd is an external include, like an import.
|
|
219
|
+
*/
|
|
220
|
+
ownerDir?: string;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A text extension, or none: checked for the name read and, through a link, the file it points to. */
|
|
224
|
+
function textExtension(path: string): boolean {
|
|
225
|
+
const ext = extname(path).toLowerCase();
|
|
226
|
+
return !ext || TEXT_EXTENSIONS.has(ext);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** lJ: parent first, separate parsed imports, canonical dedupe, depths zero through four. */
|
|
230
|
+
export function readRuleInstructions(path: string, opts: Pick<RuleOptions, "cwd" | "home" | "scope" | "processed" | "includeExternal" | "allowPath" | "ownerDir">): RuleFile[] {
|
|
231
|
+
const includeExternal = opts.includeExternal ?? opts.scope === "User";
|
|
232
|
+
const processed = opts.processed ?? new Set<string>();
|
|
233
|
+
const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
|
|
234
|
+
const inside = (target: string) => isPathAtOrUnder(target, realCwd);
|
|
235
|
+
const realOwner = opts.ownerDir === undefined ? undefined : tryRealpath(opts.ownerDir) ?? opts.ownerDir;
|
|
236
|
+
const load = (path: string, depth = 0, parent?: string): RuleFile[] => {
|
|
237
|
+
touch(path);
|
|
238
|
+
const key = tryRealpath(path);
|
|
239
|
+
if (key) touch(key);
|
|
240
|
+
if (!key || depth >= 5 || processed.has(comparablePath(key)) || (opts.allowPath && (!opts.allowPath(path) || !opts.allowPath(key))) || (depth > 0 && !includeExternal && !inside(key))) return [];
|
|
241
|
+
// One Code addition: a project file linked out of the project is an include the user must approve.
|
|
242
|
+
const linkedOut = depth === 0 && realOwner !== undefined && !inside(key) && !isPathAtOrUnder(key, realOwner);
|
|
243
|
+
if (linkedOut && !includeExternal) return [];
|
|
244
|
+
try {
|
|
245
|
+
const stat = statSync(key);
|
|
246
|
+
if (!stat.isFile() || stat.size > MAX_INSTRUCTION_BYTES || (opts.scope === "User" && !includeExternal && (stat.nlink > 1 || (depth === 0 && isLink(path))))) return [];
|
|
247
|
+
processed.add(comparablePath(key));
|
|
248
|
+
if (!textExtension(path) || !textExtension(key)) return [];
|
|
249
|
+
const parsed = parseRule(readFileSync(key, "utf8"));
|
|
250
|
+
if (!parsed.content.trim()) return [];
|
|
251
|
+
return [{ path, key, ...parsed, ...(parent ? { parent } : {}), ...(linkedOut ? { linkedFrom: path } : {}) }, ...ruleImports(parsed.content, key, opts.home).flatMap((ref) => load(ref, depth + 1, key))];
|
|
252
|
+
} catch { return []; }
|
|
253
|
+
};
|
|
254
|
+
return load(path);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Parsed walks by rules directory, scope, consent, cwd and home. Every `read`
|
|
259
|
+
* attaches rules, so a walk (readdir, realpath, read, lex and YAML per file) is
|
|
260
|
+
* reused while a stat of every path it looked at, imports and missing paths
|
|
261
|
+
* included, still matches. Consent is part of the key: a walk made without it
|
|
262
|
+
* is never served with it, or the other way round.
|
|
263
|
+
*/
|
|
264
|
+
const walks = new Map<string, { stamps: Map<string, string>; files: RuleFile[] }>();
|
|
265
|
+
|
|
266
|
+
/** o$e: readdir order, depth-first, lowercase .md only; canonical identities stop link cycles. */
|
|
267
|
+
export function discoverRules(opts: RuleOptions): RuleFile[] {
|
|
268
|
+
const base = opts.scope === "Project" ? dirname(dirname(opts.rulesDir)) : opts.cwd;
|
|
269
|
+
const select = (files: RuleFile[]) =>
|
|
270
|
+
files
|
|
271
|
+
.filter((file) => (opts.allConditional ? Boolean(file.globs) : opts.filePath === undefined ? !file.globs : ruleMatches(file.globs, opts.filePath, base)))
|
|
272
|
+
.map((file) => ({ ...file }));
|
|
273
|
+
// A shared dedupe set or a path filter makes the walk depend on its caller: never cached.
|
|
274
|
+
if (opts.processed || opts.allowPath || touched) return select(walkRules(opts));
|
|
275
|
+
const key = JSON.stringify([opts.rulesDir, opts.scope, opts.includeExternal ?? opts.scope === "User", opts.cwd, opts.home]);
|
|
276
|
+
const cached = walks.get(key);
|
|
277
|
+
if (cached && [...cached.stamps].every(([path, stamp]) => signature(path) === stamp)) return select(cached.files);
|
|
278
|
+
const stamps = new Map<string, string>();
|
|
279
|
+
touched = stamps;
|
|
280
|
+
let files: RuleFile[];
|
|
281
|
+
try { files = walkRules(opts); } finally { touched = undefined; }
|
|
282
|
+
if (walks.size >= 256) walks.clear();
|
|
283
|
+
walks.set(key, { stamps, files });
|
|
284
|
+
return select(files);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function walkRules(opts: RuleOptions): RuleFile[] {
|
|
288
|
+
const includeExternal = opts.includeExternal ?? opts.scope === "User";
|
|
289
|
+
const visited = new Set<string>();
|
|
290
|
+
const processed = opts.processed ?? new Set<string>();
|
|
291
|
+
const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
|
|
292
|
+
const inside = (path: string) => isPathAtOrUnder(path, realCwd);
|
|
293
|
+
const walk = (dir: string, linkedFrom?: string): RuleFile[] => {
|
|
294
|
+
touch(dir);
|
|
295
|
+
const real = tryRealpath(dir);
|
|
296
|
+
if (real) touch(real);
|
|
297
|
+
if (!real || visited.has(comparablePath(real))) return [];
|
|
298
|
+
// o2n/Tgt allow external user rules. o$e's J1n gate checks a linked
|
|
299
|
+
// .claude parent only on the cwd/ancestor walk, not on nested directories.
|
|
300
|
+
const linkedProjectParent = opts.scope === "Project" && isPathAtOrUnder(opts.cwd, dirname(dirname(dir))) && isLink(dirname(dir));
|
|
301
|
+
if (!includeExternal && !inside(real) && (isLink(dir) || linkedProjectParent)) return [];
|
|
302
|
+
visited.add(comparablePath(real));
|
|
303
|
+
const files: RuleFile[] = [];
|
|
304
|
+
try {
|
|
305
|
+
for (const entry of readdirSync(real, { withFileTypes: true })) {
|
|
306
|
+
const path = join(real, entry.name);
|
|
307
|
+
touch(path);
|
|
308
|
+
const key = tryRealpath(path);
|
|
309
|
+
if (!key || (!includeExternal && key !== path && !inside(key))) continue;
|
|
310
|
+
const stat = entry.isSymbolicLink() ? statSync(key) : entry;
|
|
311
|
+
const source = linkedFrom ? join(linkedFrom, entry.name) : join(dir, entry.name);
|
|
312
|
+
if (stat.isDirectory()) files.push(...walk(key, source !== key ? source : undefined));
|
|
313
|
+
else if (stat.isFile() && entry.name.endsWith(".md")) {
|
|
314
|
+
files.push(...readRuleInstructions(key, { ...opts, processed }).map((file) =>
|
|
315
|
+
file.key === key && source !== key ? { ...file, linkedFrom: source } : file,
|
|
316
|
+
));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return files;
|
|
320
|
+
} catch { return []; }
|
|
321
|
+
};
|
|
322
|
+
return walk(opts.rulesDir);
|
|
323
|
+
}
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { existsSync, readFileSync } from "node:fs";
|
|
15
|
-
import { join } from "node:path";
|
|
16
|
-
import {
|
|
15
|
+
import { join, posix, win32 } from "node:path";
|
|
16
|
+
import { claudeSourcesOn, userConfigDir } from "./config-mode.ts";
|
|
17
|
+
import { oneCodeProjectSettingsPath, oneCodeSettingsPath } from "./one-code-settings.ts";
|
|
18
|
+
import { claudeManagedDir, claudeUserDir } from "./paths.ts";
|
|
17
19
|
|
|
18
20
|
export interface ClaudeSettingsFile {
|
|
19
21
|
enabledPlugins?: Record<string, unknown>;
|
|
@@ -29,6 +31,75 @@ export function claudeUserSettingsPath(home: string): string {
|
|
|
29
31
|
return join(claudeUserDir(home), "settings.json");
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/** Managed-settings locations, highest authority: `managed-settings.json` in Claude Code's managed root. */
|
|
35
|
+
export function managedSettingsPaths(platform: NodeJS.Platform = process.platform): string[] {
|
|
36
|
+
const { join: joinFor } = platform === "win32" ? win32 : posix;
|
|
37
|
+
return [joinFor(claudeManagedDir(platform), "managed-settings.json")];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Claude Code's `instructionFiles` values (findings §57); the default is `claude-md-or-agents-md`. */
|
|
41
|
+
export type InstructionFiles = "claude-md" | "claude-md-or-agents-md" | "claude-md-and-agents-md" | "managed-only";
|
|
42
|
+
const INSTRUCTION_FILES: readonly InstructionFiles[] = ["claude-md", "claude-md-or-agents-md", "claude-md-and-agents-md", "managed-only"];
|
|
43
|
+
const LEGACY_PROJECT_INSTRUCTIONS: Record<string, InstructionFiles> = {
|
|
44
|
+
none: "managed-only",
|
|
45
|
+
claude: "claude-md",
|
|
46
|
+
"agents-fallback": "claude-md-or-agents-md",
|
|
47
|
+
both: "claude-md-and-agents-md",
|
|
48
|
+
};
|
|
49
|
+
/** The AGENTS.md plugin's `pluginConfigs` keys: the id is inferred, so the bare name counts too. */
|
|
50
|
+
const AGENTS_MD_PLUGIN_KEYS = ["cc-plugin-agents-md@builtin", "cc-plugin-agents-md"];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Claude Code's `instructionFiles` (findings §57): the AGENTS.md plugin's option
|
|
54
|
+
* under `pluginConfigs[<id>].options`, from the user and managed settings
|
|
55
|
+
* (Claude Code reads no project file for it), managed winning. The legacy
|
|
56
|
+
* top-level `projectInstructions` applies only while `instructionFiles` is unset,
|
|
57
|
+
* mapped as Claude Code maps it (an unknown string reads as `claude-md`).
|
|
58
|
+
*/
|
|
59
|
+
export function readInstructionFiles(home: string): InstructionFiles {
|
|
60
|
+
let instructionFiles: InstructionFiles | undefined;
|
|
61
|
+
let legacy: InstructionFiles | undefined;
|
|
62
|
+
for (const path of [claudeUserSettingsPath(home), ...managedSettingsPaths()]) {
|
|
63
|
+
const file = readSettingsFile(path);
|
|
64
|
+
if (!file) continue;
|
|
65
|
+
const configs = file.pluginConfigs as Record<string, { options?: Record<string, unknown> }> | undefined;
|
|
66
|
+
for (const key of AGENTS_MD_PLUGIN_KEYS) {
|
|
67
|
+
const value = configs?.[key]?.options?.instructionFiles;
|
|
68
|
+
if (INSTRUCTION_FILES.includes(value as InstructionFiles)) instructionFiles = value as InstructionFiles;
|
|
69
|
+
}
|
|
70
|
+
if (typeof file.projectInstructions === "string") legacy = LEGACY_PROJECT_INSTRUCTIONS[file.projectInstructions] ?? "claude-md";
|
|
71
|
+
}
|
|
72
|
+
return instructionFiles ?? legacy ?? "claude-md-or-agents-md";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Where a settings file sits: Claude Code's ladder, One Code's two files, or managed policy. */
|
|
76
|
+
export type SettingsSource = "claude-user" | "onecode-user" | "project" | "project-local" | "onecode-project" | "managed";
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Every settings file One Code reads Claude Code's keys from, lowest
|
|
80
|
+
* precedence first, with its source. Managed settings last: Claude Code's
|
|
81
|
+
* organisation policy outranks every user/project file (review P14).
|
|
82
|
+
* Independent mode (lib/config-mode.ts) reads One Code's own two files only,
|
|
83
|
+
* managed settings included in what it drops (decisions/memory-state.md).
|
|
84
|
+
* Each consumer keeps the sources its keys may come from.
|
|
85
|
+
*/
|
|
86
|
+
export function settingsSources(cwd: string, home: string): Array<[SettingsSource, string]> {
|
|
87
|
+
const oneCode: Array<[SettingsSource, string]> = [
|
|
88
|
+
["onecode-user", oneCodeSettingsPath(home)],
|
|
89
|
+
["onecode-project", oneCodeProjectSettingsPath(cwd, home)],
|
|
90
|
+
];
|
|
91
|
+
if (!claudeSourcesOn()) return oneCode;
|
|
92
|
+
const paths = settingsPaths(cwd, home);
|
|
93
|
+
return [
|
|
94
|
+
["claude-user", paths.user],
|
|
95
|
+
oneCode[0],
|
|
96
|
+
["project", paths.project],
|
|
97
|
+
["project-local", paths.local],
|
|
98
|
+
oneCode[1],
|
|
99
|
+
...managedSettingsPaths().map((path): [SettingsSource, string] => ["managed", path]),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
|
|
32
103
|
export function settingsPaths(cwd: string, home: string): { user: string; project: string; local: string } {
|
|
33
104
|
return {
|
|
34
105
|
user: claudeUserSettingsPath(home),
|
|
@@ -56,7 +127,8 @@ export function readSettingsFile(path: string): ClaudeSettingsFile | undefined {
|
|
|
56
127
|
* its project-trust prompt).
|
|
57
128
|
*/
|
|
58
129
|
export function readSettingsEnv(home: string): Record<string, string> {
|
|
59
|
-
|
|
130
|
+
// The mode's user file: One Code's own in independent mode (lib/config-mode.ts).
|
|
131
|
+
const env = readSettingsFile(join(userConfigDir(home), "settings.json"))?.env;
|
|
60
132
|
if (!env || typeof env !== "object" || Array.isArray(env)) return {};
|
|
61
133
|
const out: Record<string, string> = {};
|
|
62
134
|
for (const [key, value] of Object.entries(env as Record<string, unknown>)) {
|
|
@@ -70,8 +142,8 @@ export function readSettingsEnv(home: string): Record<string, string> {
|
|
|
70
142
|
* merged across the three settings files — later files win per key.
|
|
71
143
|
*/
|
|
72
144
|
export function readEnabledPlugins(cwd: string, home: string): Record<string, boolean> {
|
|
73
|
-
const paths = settingsPaths(cwd, home);
|
|
74
145
|
const merged: Record<string, boolean> = {};
|
|
146
|
+
const paths = settingsPaths(cwd, home);
|
|
75
147
|
for (const path of [paths.user, paths.project, paths.local]) {
|
|
76
148
|
const enabled = readSettingsFile(path)?.enabledPlugins;
|
|
77
149
|
if (!enabled || typeof enabled !== "object") continue;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The latest compaction on a session branch, and where the entries still in
|
|
3
|
+
* context start. Pure: no pi imports; pi's `SessionEntry` fits the shape.
|
|
4
|
+
*
|
|
5
|
+
* pi builds the context after a compaction as the summary, then the kept
|
|
6
|
+
* entries from `firstKeptEntryId` (which lie before the compaction entry on the
|
|
7
|
+
* branch), then everything after the compaction (`buildContextEntries`). A
|
|
8
|
+
* first kept entry not found before the compaction keeps nothing.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
type BranchEntry = { id?: string; type?: string; timestamp?: string; firstKeptEntryId?: string | null };
|
|
12
|
+
|
|
13
|
+
export interface CompactionBoundary {
|
|
14
|
+
/** Index of the latest compaction entry on the branch. */
|
|
15
|
+
index: number;
|
|
16
|
+
/** Index of its first kept entry, or `index` when it keeps nothing. */
|
|
17
|
+
keptStart: number;
|
|
18
|
+
/** The compaction's time in ms: every message produced since is newer, every kept one older. */
|
|
19
|
+
time: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** The latest compaction on the branch, or undefined when there is none. */
|
|
23
|
+
export function latestCompaction(entries: readonly unknown[]): CompactionBoundary | undefined {
|
|
24
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
25
|
+
const entry = entries[index] as BranchEntry | undefined;
|
|
26
|
+
if (entry?.type !== "compaction") continue;
|
|
27
|
+
const kept = entries.findIndex((candidate) => (candidate as BranchEntry | undefined)?.id === entry.firstKeptEntryId);
|
|
28
|
+
return { index, keptStart: kept >= 0 && kept < index ? kept : index, time: Date.parse(entry.timestamp ?? "") };
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The branch entries still in context: from the latest compaction's kept tail on, else all of them. */
|
|
34
|
+
export function inContextEntries<E>(entries: readonly E[]): E[] {
|
|
35
|
+
return entries.slice(latestCompaction(entries)?.keptStart ?? 0);
|
|
36
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi's compaction keep window (`compaction.keepRecentTokens`), set to 0 for
|
|
3
|
+
* One Code.
|
|
4
|
+
*
|
|
5
|
+
* One Code's compaction keeps only the last assistant reply, as Claude Code
|
|
6
|
+
* does (`compaction/cut.ts`), so pi's window of recent tokens kept verbatim
|
|
7
|
+
* (20,000 by default) no longer decides what stays. It still decides whether
|
|
8
|
+
* a compaction can happen: pi's `prepareCompaction` gives up with "Nothing to
|
|
9
|
+
* compact (session too small)" when the whole session fits in the window,
|
|
10
|
+
* before any extension sees the request, so `/compact` on a short session
|
|
11
|
+
* failed. At 0, pi cuts at the last reply itself and any session with one
|
|
12
|
+
* finished turn compacts (findings §8).
|
|
13
|
+
*
|
|
14
|
+
* The bundled app writes 0 into its own settings unless the user set a value;
|
|
15
|
+
* on the user's own pi, One Code asks once (`branding/compaction-keep.ts`) and
|
|
16
|
+
* `/doctor` shows the setting.
|
|
17
|
+
*
|
|
18
|
+
* Plain JS with no pi imports, like replaced-builtins.mjs: app/bin.mjs loads it
|
|
19
|
+
* on bare Node before pi does.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The keep window One Code wants. */
|
|
23
|
+
export const ONE_CODE_KEEP_RECENT_TOKENS = 0;
|
|
24
|
+
|
|
25
|
+
/** pi's default when no settings file sets one. */
|
|
26
|
+
const PI_DEFAULT_KEEP_RECENT_TOKENS = 20000;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Settings with the keep window set to One Code's when the user has not set
|
|
30
|
+
* one; `changed` says whether anything was written. An explicit value, pi's
|
|
31
|
+
* default included, is the user's and stays.
|
|
32
|
+
*/
|
|
33
|
+
export function withCompactionKeepBackfilled(settings) {
|
|
34
|
+
const compaction = settings?.compaction;
|
|
35
|
+
if (compaction !== undefined && (typeof compaction !== "object" || compaction === null || Array.isArray(compaction))) return { settings, changed: false };
|
|
36
|
+
if (compaction?.keepRecentTokens !== undefined) return { settings, changed: false };
|
|
37
|
+
return { settings: { ...settings, compaction: { ...compaction, keepRecentTokens: ONE_CODE_KEEP_RECENT_TOKENS } }, changed: true };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The keep window pi will use for `modelKey` (`provider/id`), and where it
|
|
42
|
+
* comes from, as pi resolves it: the model's `compaction.modelOverrides` entry
|
|
43
|
+
* first, then the ordinary setting; the project's settings win over the
|
|
44
|
+
* user's in each; `default` when nothing sets it. `model` names the override
|
|
45
|
+
* that supplied the value.
|
|
46
|
+
*/
|
|
47
|
+
export function compactionKeepView(userSettings, projectSettings, modelKey) {
|
|
48
|
+
// Set means defined, as for the backfill; pi itself rejects a non-integer.
|
|
49
|
+
const read = (value) => (value === undefined ? undefined : Number(value));
|
|
50
|
+
const scopes = [
|
|
51
|
+
["project", projectSettings],
|
|
52
|
+
["user", userSettings],
|
|
53
|
+
];
|
|
54
|
+
if (modelKey !== undefined) {
|
|
55
|
+
for (const [source, settings] of scopes) {
|
|
56
|
+
const value = read(settings?.compaction?.modelOverrides?.[modelKey]?.keepRecentTokens);
|
|
57
|
+
if (value !== undefined) return { value, source, model: modelKey };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const [source, settings] of scopes) {
|
|
61
|
+
const value = read(settings?.compaction?.keepRecentTokens);
|
|
62
|
+
if (value !== undefined) return { value, source };
|
|
63
|
+
}
|
|
64
|
+
return { value: PI_DEFAULT_KEEP_RECENT_TOKENS, source: "default" };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const KEEP_YES = "Yes, set it to 0";
|
|
68
|
+
export const KEEP_NO = "No, leave the setting";
|
|
69
|
+
|
|
70
|
+
/** The startup question on the user's own pi. */
|
|
71
|
+
export function keepWindowPrompt(settingsPath) {
|
|
72
|
+
return [
|
|
73
|
+
`One Code compacts like Claude Code: it keeps only the last reply after the summary.`,
|
|
74
|
+
`pi's compaction.keepRecentTokens (${PI_DEFAULT_KEEP_RECENT_TOKENS} by default) no longer changes what is kept; it only makes /compact refuse a session shorter than that.`,
|
|
75
|
+
`Set it to 0 in ${settingsPath}? It takes effect from the next start.`,
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The fix /doctor names for a keep window above 0, in the settings file (and model override) that sets it. */
|
|
80
|
+
export function keepWindowFix(settingsPath, model) {
|
|
81
|
+
const key = model === undefined ? `"keepRecentTokens": 0` : `"modelOverrides": { "${model}": { "keepRecentTokens": 0 } }`;
|
|
82
|
+
return `Set "compaction": { ${key} } in ${settingsPath}, then restart.`;
|
|
83
|
+
}
|