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
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude Code's
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* the wiring, and lib/reminders.ts for
|
|
2
|
+
* Claude Code's three first-message context reminders: the instructions block
|
|
3
|
+
* (the CLAUDE.md files and the MEMORY.md index), the context block (the user's
|
|
4
|
+
* email and the git snapshot) and the date, each its own `<system-reminder>`
|
|
5
|
+
* (see extensions/claude-context for the wiring, and lib/reminders.ts for
|
|
6
|
+
* placement; on a model that takes a mid-conversation system message the date
|
|
7
|
+
* moves into it, lib/system-role.ts).
|
|
6
8
|
*
|
|
7
|
-
* This module is pure (no pi imports) and byte-exact
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* (handled in lib/memory.ts), never this block.
|
|
9
|
+
* This module is pure (no pi imports) and byte-exact with Claude Code. The
|
|
10
|
+
* blocks are identical across model tiers — only the `# Memory` *spec* in the
|
|
11
|
+
* system prompt varies by tier (handled in lib/memory.ts), never these blocks.
|
|
11
12
|
*
|
|
12
13
|
* Discovery mirrors Claude Code, not pi's own loader: global `~/.claude/CLAUDE.md`
|
|
13
14
|
* first, then project `CLAUDE.md` / `CLAUDE.local.md` from the farthest ancestor
|
|
@@ -26,24 +27,31 @@
|
|
|
26
27
|
* block so they take precedence over CLAUDE.md — keeping `# claudeMd` itself
|
|
27
28
|
* byte-exact with Claude Code.
|
|
28
29
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
30
|
+
* Nested subtree instruction files come later, when a file under them is read
|
|
31
|
+
* (`nestedInstructionFiles`, attached by the claude-context extension). Rules
|
|
32
|
+
* parsing, recursive discovery and path matching live in claude-rules.ts.
|
|
32
33
|
*/
|
|
33
34
|
|
|
34
35
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
35
|
-
import {
|
|
36
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
37
|
+
import { type InstructionFiles, readInstructionFiles } from "./claude-settings.ts";
|
|
38
|
+
import { claudeSourcesOn } from "./config-mode.ts";
|
|
39
|
+
import { findGitRoot } from "./git.ts";
|
|
36
40
|
import { tryReadFile } from "./plugins.ts";
|
|
37
|
-
import { absoluteFrom, expandTilde } from "./paths.ts";
|
|
41
|
+
import { absoluteFrom, claudeManagedDir, claudeUserDir, expandTilde, isPathAtOrUnder, oneCodeStateDir, tryRealpath } from "./paths.ts";
|
|
42
|
+
import { discoverRules, MAX_INSTRUCTION_BYTES, readRuleInstructions, type RuleFile, type RuleOptions } from "./claude-rules.ts";
|
|
38
43
|
|
|
39
|
-
/** One
|
|
44
|
+
/** One instruction section. Legacy content stays raw; new parsed files carry CC's trimmed startup body. */
|
|
40
45
|
export interface ContextFile {
|
|
41
46
|
path: string;
|
|
42
47
|
content: string;
|
|
43
48
|
descriptor: string;
|
|
49
|
+
/** Actual inline imports; parsed imports are separate sections and use [] to prevent raw @token inference. */
|
|
50
|
+
imported?: string[];
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
export const GLOBAL_DESCRIPTOR = "user's private global instructions for all projects";
|
|
54
|
+
export const MANAGED_DESCRIPTOR = "organization-managed policy instructions";
|
|
47
55
|
export const PROJECT_DESCRIPTOR = "project instructions, checked into the codebase";
|
|
48
56
|
export const LOCAL_DESCRIPTOR = "user's private project instructions, not checked in";
|
|
49
57
|
export const MEMORY_DESCRIPTOR = "user's auto-memory, persists across conversations";
|
|
@@ -58,18 +66,36 @@ const ONECODE_NAMES = ["ONECODE.md", "onecode.md", "One Code.md"] as const;
|
|
|
58
66
|
const MAX_IMPORT_DEPTH = 5;
|
|
59
67
|
|
|
60
68
|
const PREAMBLE =
|
|
61
|
-
"As you answer the user's questions, you can use the following context:\n" +
|
|
62
|
-
"# claudeMd\n" +
|
|
63
69
|
"Codebase and user instructions are shown below. Be sure to adhere to these instructions. " +
|
|
64
70
|
"IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.";
|
|
65
71
|
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
const CONTEXT_PREAMBLE = "As you answer the user's questions, you can use the following context:";
|
|
73
|
+
|
|
74
|
+
/** Claude Code's sentence after the email, with One Code named as the harness that attached it. */
|
|
75
|
+
const EMAIL_USE =
|
|
76
|
+
"Use it only to identify the user, such as for authorship, attribution, or filtering their own work. " +
|
|
77
|
+
"Never send it to an unrelated service, such as in a request header, URL, or payload, unless the user explicitly asks.";
|
|
78
|
+
|
|
79
|
+
const CONTEXT_FOOTER =
|
|
80
|
+
"One Code attached this context automatically; it isn't part of the user's message. " +
|
|
81
|
+
"It describes the user's own account and workspace, so they don't need it reported back.";
|
|
82
|
+
|
|
83
|
+
/** Independent mode excludes Claude locations even when reached through aliases or imports. */
|
|
84
|
+
export function isClaudeLocation(path: string, userDir: string): boolean {
|
|
85
|
+
return [path, tryRealpath(path)].some((candidate) => candidate !== undefined && (
|
|
86
|
+
/(?:^|[/\\])\.claude(?:[/\\]|$)/i.test(candidate) ||
|
|
87
|
+
/(?:^|[/\\])CLAUDE(?:\.local)?\.md$/i.test(candidate) ||
|
|
88
|
+
isPathAtOrUnder(candidate, tryRealpath(userDir) ?? userDir) ||
|
|
89
|
+
isPathAtOrUnder(candidate, claudeManagedDir())
|
|
90
|
+
));
|
|
91
|
+
}
|
|
69
92
|
|
|
70
|
-
|
|
93
|
+
/** A regular file within the instruction size limit (never a FIFO, which would block), else null. */
|
|
94
|
+
export function readFileIfPresent(path: string): string | null {
|
|
71
95
|
try {
|
|
72
|
-
if (!existsSync(path)
|
|
96
|
+
if (!existsSync(path)) return null;
|
|
97
|
+
const stat = statSync(path);
|
|
98
|
+
if (!stat.isFile() || stat.size > MAX_INSTRUCTION_BYTES) return null;
|
|
73
99
|
} catch {
|
|
74
100
|
return null;
|
|
75
101
|
}
|
|
@@ -101,7 +127,7 @@ function isPresentFile(path: string): boolean {
|
|
|
101
127
|
* costs a few stats and never lists the directory. Only when a probe hits do we
|
|
102
128
|
* read the directory once to recover the real casing.
|
|
103
129
|
*/
|
|
104
|
-
function firstOneCodeFile(dir: string): string | null {
|
|
130
|
+
export function firstOneCodeFile(dir: string): string | null {
|
|
105
131
|
if (!ONECODE_NAMES.some((n) => isPresentFile(join(dir, n)))) return null;
|
|
106
132
|
let entries: string[];
|
|
107
133
|
try {
|
|
@@ -266,55 +292,128 @@ export function ancestorDirs(cwd: string): string[] {
|
|
|
266
292
|
}
|
|
267
293
|
|
|
268
294
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
|
|
274
|
-
|
|
295
|
+
* Which instruction files load: Claude Code's `instructionFiles` values
|
|
296
|
+
* (findings §57) in Claude-compatible mode, or `agents-md` in independent mode
|
|
297
|
+
* (lib/config-mode.ts), where no CLAUDE.md-family file is read and every
|
|
298
|
+
* directory's `AGENTS.md` loads.
|
|
299
|
+
*/
|
|
300
|
+
export type InstructionRule = InstructionFiles | "agents-md";
|
|
301
|
+
|
|
302
|
+
/** The rule One Code runs under: independent mode's own, else Claude Code's `instructionFiles`. */
|
|
303
|
+
export function instructionRule(home: string): InstructionRule {
|
|
304
|
+
return claudeSourcesOn() ? readInstructionFiles(home) : "agents-md";
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Whether `dir` holds a CLAUDE.md that puts Claude's instructions in play for
|
|
309
|
+
* `claude-md-or-agents-md`: CLAUDE.md, .claude/CLAUDE.md (never at the
|
|
310
|
+
* filesystem root) or CLAUDE.local.md.
|
|
311
|
+
*/
|
|
312
|
+
function dirHasClaudeMd(dir: string, present: (path: string) => boolean = isPresentFile): boolean {
|
|
313
|
+
return present(join(dir, "CLAUDE.md")) || (dirname(dir) !== dir && present(join(dir, ".claude", "CLAUDE.md"))) || present(join(dir, "CLAUDE.local.md"));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* The ordered instruction-file paths that exist, WITHOUT reading their
|
|
318
|
+
* contents: global `~/.claude/CLAUDE.md` first, then per directory from the
|
|
319
|
+
* farthest ancestor down to cwd `CLAUDE.md`, the AGENTS.md files at its
|
|
320
|
+
* position, and `CLAUDE.local.md`. `discoverContextFiles` reads content on top
|
|
321
|
+
* of this; callers that only need paths/descriptors (e.g. `/memory`'s picker)
|
|
322
|
+
* use it directly to avoid loading files they will discard.
|
|
323
|
+
*
|
|
324
|
+
* `rule` (default `claude-md`) decides the AGENTS.md files, as Claude Code's
|
|
325
|
+
* `instructionFiles` does (findings §57): `claude-md-or-agents-md` loads every
|
|
326
|
+
* ancestor `AGENTS.md` and `.claude/AGENTS.md` only when the project has no
|
|
327
|
+
* `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` anywhere on that path (a per-project
|
|
328
|
+
* decision, so `# claudeMd` stays byte-exact with CC whenever CLAUDE.md is
|
|
329
|
+
* present); `claude-md-and-agents-md` loads them beside CLAUDE.md;
|
|
330
|
+
* `managed-only` drops the user's and the project's files, retaining managed
|
|
331
|
+
* instructions; `agents-md` reads only `AGENTS.md`, with no Claude Code
|
|
332
|
+
* file at all.
|
|
275
333
|
*
|
|
276
334
|
* When `homeOneCodeDir` is given, One Code's own `ONECODE.md` files join the
|
|
277
335
|
* list: the global one from `~/.onecode` right after the global CLAUDE.md, and
|
|
278
|
-
* each directory's
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* When `agentsFallback` is set, a directory with no `CLAUDE.md` falls back to its
|
|
284
|
-
* `AGENTS.md` (CLAUDE.md > AGENTS.md — the first-match Codex/opencode do, but
|
|
285
|
-
* CLAUDE-preferred for our CC-compat audience). A directory that has a CLAUDE.md
|
|
286
|
-
* ignores its AGENTS.md, so `# claudeMd` stays byte-exact with CC whenever
|
|
287
|
-
* CLAUDE.md is present.
|
|
336
|
+
* each directory's last. The `/memory` picker passes it (so ONECODE.md is an
|
|
337
|
+
* editable target); the model-facing `# claudeMd` block does NOT — there
|
|
338
|
+
* ONECODE.md rides its own `# oneCodeMd` block via `discoverOneCodeFiles`
|
|
339
|
+
* instead, so `# claudeMd` stays byte-exact with CC.
|
|
288
340
|
*/
|
|
289
|
-
|
|
341
|
+
interface DiscoveryOptions {
|
|
290
342
|
cwd: string;
|
|
291
343
|
homeClaudeDir: string;
|
|
292
344
|
homeOneCodeDir?: string;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
345
|
+
managedDir?: string;
|
|
346
|
+
rule?: InstructionRule;
|
|
347
|
+
/** User consent for this project's external instruction imports. Never sourced from project files. */
|
|
348
|
+
includeExternal?: boolean;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export function discoverContextFilePaths(opts: DiscoveryOptions): ContextFilePath[] {
|
|
352
|
+
return discoverInstructionEntries(opts);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
interface InstructionEntry extends ContextFilePath {
|
|
356
|
+
ruleFile?: RuleFile;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function discoverInstructionEntries(
|
|
360
|
+
opts: DiscoveryOptions,
|
|
361
|
+
rules: (dir: string, scope: RuleOptions["scope"], descriptor: string) => InstructionEntry[] = () => [],
|
|
362
|
+
): InstructionEntry[] {
|
|
363
|
+
const paths: InstructionEntry[] = [];
|
|
296
364
|
const seen = new Set<string>();
|
|
297
365
|
const includeOneCode = opts.homeOneCodeDir !== undefined;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
366
|
+
const rule = opts.rule ?? "claude-md";
|
|
367
|
+
const claudeFiles = rule !== "agents-md" && rule !== "managed-only";
|
|
368
|
+
const dirs = ancestorDirs(opts.cwd);
|
|
369
|
+
// Each path is checked once per call: the per-project test and the walk share it.
|
|
370
|
+
const presence = new Map<string, boolean>();
|
|
371
|
+
const present = (path: string): boolean => {
|
|
372
|
+
let found = presence.get(path);
|
|
373
|
+
if (found === undefined) presence.set(path, (found = isPresentFile(path)));
|
|
374
|
+
return found;
|
|
375
|
+
};
|
|
376
|
+
const projectHasClaude = () => dirs.some((d) => dirHasClaudeMd(d, present));
|
|
377
|
+
const agentsFiles =
|
|
378
|
+
rule === "agents-md" || rule === "claude-md-and-agents-md" || (rule === "claude-md-or-agents-md" && !projectHasClaude());
|
|
379
|
+
|
|
380
|
+
/** Adds the path if present. */
|
|
381
|
+
const push = (path: string, descriptor: string): void => {
|
|
382
|
+
if (!present(path) || (rule === "agents-md" && isClaudeLocation(path, opts.homeClaudeDir))) return;
|
|
383
|
+
const key = tryRealpath(path) ?? path;
|
|
384
|
+
if (seen.has(key)) return;
|
|
302
385
|
paths.push({ path, descriptor });
|
|
303
|
-
seen.add(
|
|
304
|
-
return true;
|
|
386
|
+
seen.add(key);
|
|
305
387
|
};
|
|
306
388
|
|
|
307
|
-
|
|
389
|
+
if (rule !== "agents-md") {
|
|
390
|
+
const managed = opts.managedDir ?? claudeManagedDir();
|
|
391
|
+
push(join(managed, "CLAUDE.md"), MANAGED_DESCRIPTOR);
|
|
392
|
+
paths.push(...rules(join(managed, ".claude", "rules"), "Managed", MANAGED_DESCRIPTOR));
|
|
393
|
+
}
|
|
394
|
+
if (claudeFiles) {
|
|
395
|
+
push(join(opts.homeClaudeDir, "CLAUDE.md"), GLOBAL_DESCRIPTOR);
|
|
396
|
+
paths.push(...rules(join(opts.homeClaudeDir, "rules"), "User", GLOBAL_DESCRIPTOR));
|
|
397
|
+
}
|
|
308
398
|
if (includeOneCode && opts.homeOneCodeDir) {
|
|
309
399
|
const globalOneCode = firstOneCodeFile(opts.homeOneCodeDir);
|
|
310
400
|
if (globalOneCode) push(globalOneCode, ONECODE_GLOBAL_DESCRIPTOR);
|
|
311
401
|
}
|
|
312
402
|
|
|
313
|
-
for (const d of
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
403
|
+
for (const d of dirs) {
|
|
404
|
+
if (claudeFiles) {
|
|
405
|
+
push(join(d, "CLAUDE.md"), PROJECT_DESCRIPTOR);
|
|
406
|
+
if (dirname(d) !== d) push(join(d, ".claude", "CLAUDE.md"), PROJECT_DESCRIPTOR);
|
|
407
|
+
}
|
|
408
|
+
if (agentsFiles) {
|
|
409
|
+
push(join(d, "AGENTS.md"), AGENTS_DESCRIPTOR);
|
|
410
|
+
// `.claude/AGENTS.md` is a Claude Code location; independent mode reads none.
|
|
411
|
+
if (claudeFiles) push(join(d, ".claude", "AGENTS.md"), AGENTS_DESCRIPTOR);
|
|
412
|
+
}
|
|
413
|
+
if (claudeFiles) {
|
|
414
|
+
if (dirname(d) !== d) paths.push(...rules(join(d, ".claude", "rules"), "Project", PROJECT_DESCRIPTOR));
|
|
415
|
+
push(join(d, "CLAUDE.local.md"), LOCAL_DESCRIPTOR);
|
|
416
|
+
}
|
|
318
417
|
if (includeOneCode) {
|
|
319
418
|
const oneCode = firstOneCodeFile(d);
|
|
320
419
|
if (oneCode) push(oneCode, ONECODE_DESCRIPTOR);
|
|
@@ -324,25 +423,269 @@ export function discoverContextFilePaths(opts: {
|
|
|
324
423
|
return paths;
|
|
325
424
|
}
|
|
326
425
|
|
|
327
|
-
|
|
426
|
+
/**
|
|
427
|
+
* The project's own instruction files in play under the running rule
|
|
428
|
+
* (`instructionRule`), from cwd up to the git root (or just cwd outside a
|
|
429
|
+
* repo), nearest directory first; with `homeOneCodeDir`, its ONECODE.md files
|
|
430
|
+
* too. The startup banner lists these.
|
|
431
|
+
*/
|
|
432
|
+
export function projectInstructionFiles(opts: { cwd: string; home: string; homeOneCodeDir?: string }): string[] {
|
|
433
|
+
const stop = findGitRoot(opts.cwd) ?? opts.cwd;
|
|
434
|
+
const files = discoverContextFilePaths({ cwd: opts.cwd, homeClaudeDir: claudeUserDir(opts.home), homeOneCodeDir: opts.homeOneCodeDir, rule: instructionRule(opts.home) });
|
|
435
|
+
// The directory a file belongs to: `.claude/AGENTS.md` counts as its parent's.
|
|
436
|
+
const owner = (path: string) => (basename(dirname(path)) === ".claude" ? dirname(dirname(path)) : dirname(path));
|
|
437
|
+
return files
|
|
438
|
+
.filter(({ path, descriptor }) => descriptor !== GLOBAL_DESCRIPTOR && descriptor !== ONECODE_GLOBAL_DESCRIPTOR && isPathAtOrUnder(path, stop))
|
|
439
|
+
.map(({ path }) => path)
|
|
440
|
+
.sort((a, b) => owner(b).length - owner(a).length);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* The instruction files Claude Code attaches when the model reads a file below
|
|
445
|
+
* the working directory (its `nested_memory` attachment): for each directory
|
|
446
|
+
* from just below cwd down to the file's own, `CLAUDE.md`, `.claude/CLAUDE.md`
|
|
447
|
+
* and `CLAUDE.local.md`, under the same rule as the startup block (AGENTS.md
|
|
448
|
+
* per `rule`, decided for the whole project as at startup; none of the
|
|
449
|
+
* CLAUDE.md family and no `.claude` directory in independent mode), plus One
|
|
450
|
+
* Code's ONECODE.md. Directories at or above cwd are left out: the startup
|
|
451
|
+
* block already carries them. Rules attach in Claude Code's order: managed/user
|
|
452
|
+
* conditional, nested files/rules, then ancestor conditional rules.
|
|
453
|
+
*
|
|
454
|
+
* Named nested files retain One Code's real-path confinement to the project;
|
|
455
|
+
* rules use claude-rules.ts's source-specific link/include policy. A symlink
|
|
456
|
+
* and its target are one file (`key`), and the file being read is never its
|
|
457
|
+
* own attachment. `imported` lists the real paths the legacy inline expander
|
|
458
|
+
* pulled in, so the caller can treat them as shown; parsed imports instead
|
|
459
|
+
* appear as their own files.
|
|
460
|
+
*/
|
|
461
|
+
export function nestedInstructionFiles(opts: {
|
|
462
|
+
filePath: string;
|
|
328
463
|
cwd: string;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
464
|
+
rule: InstructionRule;
|
|
465
|
+
home: string;
|
|
466
|
+
homeClaudeDir?: string;
|
|
467
|
+
managedDir?: string;
|
|
468
|
+
includeExternal?: boolean;
|
|
469
|
+
}): { path: string; key: string; content: string; imported: string[] }[] {
|
|
470
|
+
const target = absoluteFrom(opts.cwd, opts.filePath);
|
|
471
|
+
if (target === absoluteFrom(opts.cwd, ".")) return [];
|
|
472
|
+
const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
|
|
473
|
+
const inProject = (path: string | undefined): path is string => path !== undefined && isPathAtOrUnder(path, realCwd);
|
|
474
|
+
// Named nested files stay confined to the project. An authorized read of a
|
|
475
|
+
// sibling can still match an ancestor's rule, whose base is that ancestor.
|
|
476
|
+
const realParent = tryRealpath(dirname(target));
|
|
477
|
+
let nestedParent = dirname(target);
|
|
478
|
+
if (!isPathAtOrUnder(nestedParent, opts.cwd) && inProject(realParent)) nestedParent = join(opts.cwd, relative(realCwd, realParent));
|
|
479
|
+
const walkNested = isPathAtOrUnder(nestedParent, opts.cwd) && inProject(realParent);
|
|
480
|
+
const realTarget = tryRealpath(target);
|
|
481
|
+
const claudeFiles = opts.rule !== "agents-md" && opts.rule !== "managed-only";
|
|
482
|
+
const dirs: string[] = [];
|
|
483
|
+
for (let dir = nestedParent; walkNested && isPathAtOrUnder(dir, opts.cwd) && !isPathAtOrUnder(opts.cwd, dir); dir = dirname(dir)) {
|
|
484
|
+
dirs.unshift(dir);
|
|
485
|
+
if (dirname(dir) === dir) break;
|
|
486
|
+
}
|
|
487
|
+
// Independent mode reads no Claude Code location, so it never walks into a `.claude` directory.
|
|
488
|
+
const userDir = opts.homeClaudeDir ?? claudeUserDir(opts.home);
|
|
489
|
+
const allowedLocation = (path: string) => opts.rule !== "agents-md" || !isClaudeLocation(path, userDir);
|
|
490
|
+
const walked = dirs.filter(allowedLocation);
|
|
491
|
+
// claude-md-or-agents-md is decided per project, as at startup: AGENTS.md only where no CLAUDE.md is in play.
|
|
492
|
+
const projectHasClaude = () =>
|
|
493
|
+
[...ancestorDirs(opts.cwd), ...walked].some((dir) => dirHasClaudeMd(dir));
|
|
494
|
+
const agents = opts.rule === "agents-md" || opts.rule === "claude-md-and-agents-md" || (opts.rule === "claude-md-or-agents-md" && !projectHasClaude());
|
|
495
|
+
// Approved external chains use the reference parser below; the legacy fallback stays confined.
|
|
496
|
+
const readAllowed = (path: string) => (allowedLocation(path) ? readFileIfPresent(path) : null);
|
|
497
|
+
const readInProject = (path: string) => (inProject(tryRealpath(path)) ? readAllowed(path) : null);
|
|
498
|
+
const seen = new Set<string>(realTarget ? [realTarget] : []);
|
|
499
|
+
const files: { path: string; key: string; content: string; imported: string[] }[] = [];
|
|
500
|
+
const addRules = (rulesDir: string, scope: RuleOptions["scope"], conditional: boolean) => {
|
|
501
|
+
for (const file of discoverRules({ rulesDir, scope, cwd: opts.cwd, home: opts.home, ...(scope !== "User" ? { includeExternal: opts.includeExternal } : {}), ...(conditional ? { filePath: target } : {}) })) {
|
|
502
|
+
if (seen.has(file.key)) continue;
|
|
503
|
+
seen.add(file.key);
|
|
504
|
+
files.push({ ...file, imported: [] });
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
if (opts.rule !== "agents-md") addRules(join(opts.managedDir ?? claudeManagedDir(), ".claude", "rules"), "Managed", true);
|
|
508
|
+
if (claudeFiles) addRules(join(opts.homeClaudeDir ?? claudeUserDir(opts.home), "rules"), "User", true);
|
|
509
|
+
for (const dir of walked) {
|
|
510
|
+
const candidates: string[] = [];
|
|
511
|
+
if (claudeFiles) candidates.push(join(dir, "CLAUDE.md"), join(dir, ".claude", "CLAUDE.md"));
|
|
512
|
+
if (agents) {
|
|
513
|
+
candidates.push(join(dir, "AGENTS.md"));
|
|
514
|
+
if (claudeFiles) candidates.push(join(dir, ".claude", "AGENTS.md"));
|
|
515
|
+
}
|
|
516
|
+
if (claudeFiles) candidates.push(join(dir, "CLAUDE.local.md"));
|
|
517
|
+
const oneCode = firstOneCodeFile(dir);
|
|
518
|
+
if (oneCode) candidates.push(oneCode);
|
|
519
|
+
for (const path of candidates) {
|
|
520
|
+
const key = tryRealpath(path);
|
|
521
|
+
if (!inProject(key) || seen.has(key) || !allowedLocation(path)) continue;
|
|
522
|
+
if (basename(dirname(path)) === ".claude" && basename(path) === "CLAUDE.md") {
|
|
523
|
+
for (const file of readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: "Project", includeExternal: opts.includeExternal })) {
|
|
524
|
+
if (seen.has(file.key)) continue;
|
|
525
|
+
seen.add(file.key);
|
|
526
|
+
files.push({ ...file, imported: [] });
|
|
527
|
+
}
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
const external = path !== oneCode ? approvedExternalInstructions(path, opts) : undefined;
|
|
531
|
+
if (external) {
|
|
532
|
+
for (const file of external) {
|
|
533
|
+
if (seen.has(file.key)) continue;
|
|
534
|
+
seen.add(file.key);
|
|
535
|
+
files.push({ ...file, imported: [] });
|
|
536
|
+
}
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
const content = readFileIfPresent(path);
|
|
540
|
+
if (content === null || content.trim() === "") continue;
|
|
541
|
+
seen.add(key);
|
|
542
|
+
// ONECODE.md keeps its inline expander; approval lifts the confinement, as at startup.
|
|
543
|
+
const read = path === oneCode && opts.includeExternal ? readAllowed : readInProject;
|
|
544
|
+
const imported = [...collectImportedPaths(content, dirname(path), { home: opts.home, read })].map((p) => tryRealpath(p) ?? p);
|
|
545
|
+
for (const p of imported) seen.add(p);
|
|
546
|
+
files.push({ path, key, content: expandImports(content, dirname(path), { home: opts.home, read }), imported });
|
|
547
|
+
}
|
|
548
|
+
if (claudeFiles) {
|
|
549
|
+
addRules(join(dir, ".claude", "rules"), "Project", false);
|
|
550
|
+
addRules(join(dir, ".claude", "rules"), "Project", true);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (claudeFiles) for (const dir of ancestorDirs(opts.cwd)) if (dirname(dir) !== dir) addRules(join(dir, ".claude", "rules"), "Project", true);
|
|
554
|
+
return files;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** Claude Code's `nested_memory` rendering (the reminder queue adds the `<system-reminder>` frame). */
|
|
558
|
+
export function nestedInstructionText(file: { path: string; content: string }): string {
|
|
559
|
+
return `Contents of ${file.path}:\n\n${file.content}`;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export function discoverContextFiles(opts: DiscoveryOptions & {
|
|
332
563
|
/** Home directory for resolving `~` in `@path` imports. */
|
|
333
564
|
home: string;
|
|
334
565
|
}): ContextFile[] {
|
|
335
566
|
const files: ContextFile[] = [];
|
|
336
|
-
|
|
567
|
+
// Under claude-md-and-agents-md an AGENTS.md that a loaded file already
|
|
568
|
+
// imports, or whose content one already carries, is not loaded twice (CC).
|
|
569
|
+
const dedupe = opts.rule === "claude-md-and-agents-md";
|
|
570
|
+
const imported = new Set<string>();
|
|
571
|
+
const contents = new Set<string>();
|
|
572
|
+
const emitted = new Set<string>();
|
|
573
|
+
const parsedShown = new Set<string>();
|
|
574
|
+
const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
|
|
575
|
+
const readAllowedImport = (path: string) => opts.rule !== "agents-md" || !isClaudeLocation(path, opts.homeClaudeDir) ? readFileIfPresent(path) : null;
|
|
576
|
+
const readProjectImport = (path: string) => {
|
|
577
|
+
const key = tryRealpath(path);
|
|
578
|
+
return key && isPathAtOrUnder(key, realCwd) ? readAllowedImport(path) : null;
|
|
579
|
+
};
|
|
580
|
+
const appendParsed = (file: RuleFile, descriptor: string) => {
|
|
581
|
+
if (emitted.has(file.key)) return;
|
|
582
|
+
emitted.add(file.key);
|
|
583
|
+
parsedShown.add(file.key);
|
|
584
|
+
imported.add(file.key);
|
|
585
|
+
contents.add(file.content.trim());
|
|
586
|
+
files.push({ path: file.path, descriptor, content: file.content.trim(), imported: [] });
|
|
587
|
+
};
|
|
588
|
+
const entries = discoverInstructionEntries(opts, (rulesDir, scope, descriptor) =>
|
|
589
|
+
discoverRules({ rulesDir, scope, cwd: opts.cwd, home: opts.home, ...(scope !== "User" ? { includeExternal: opts.includeExternal } : {}) }).map((ruleFile) => ({ path: ruleFile.path, descriptor, ruleFile })),
|
|
590
|
+
);
|
|
591
|
+
for (const { path, descriptor, ruleFile } of entries) {
|
|
592
|
+
const key = tryRealpath(path) ?? path;
|
|
593
|
+
if (ruleFile) {
|
|
594
|
+
appendParsed(ruleFile, descriptor);
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
// ~/.claude/CLAUDE.md is the pre-existing global surface, not the new
|
|
598
|
+
// project location: leave its legacy bytes alone when no new files exist.
|
|
599
|
+
const dotClaude = descriptor === PROJECT_DESCRIPTOR && basename(dirname(path)) === ".claude" && basename(path) === "CLAUDE.md";
|
|
600
|
+
if (dotClaude || descriptor === MANAGED_DESCRIPTOR) {
|
|
601
|
+
for (const file of readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: descriptor === MANAGED_DESCRIPTOR ? "Managed" : "Project", includeExternal: opts.includeExternal, ...(dotClaude ? { ownerDir: dirname(dirname(path)) } : {}) })) appendParsed(file, descriptor);
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (parsedShown.has(key)) continue;
|
|
337
605
|
// Re-check the read: a file present at enumeration but unreadable now is
|
|
338
606
|
// omitted, exactly as before (the block must never carry empty entries).
|
|
339
607
|
const content = readFileIfPresent(path);
|
|
340
608
|
if (content === null) continue;
|
|
341
|
-
|
|
609
|
+
if (dedupe) {
|
|
610
|
+
const trimmed = content.trim();
|
|
611
|
+
// Compared by real path, so a `./`-spelled or symlinked import still matches.
|
|
612
|
+
if (descriptor === AGENTS_DESCRIPTOR && (imported.has(tryRealpath(path) ?? path) || (trimmed !== "" && contents.has(trimmed)))) continue;
|
|
613
|
+
contents.add(trimmed);
|
|
614
|
+
}
|
|
615
|
+
// Keep legacy bytes when no external import exists. Approved external
|
|
616
|
+
// chains use the same parser as the warning probe (escaped spaces,
|
|
617
|
+
// fragments, depth/extension limits and separate imported sections).
|
|
618
|
+
const projectSource = descriptor === PROJECT_DESCRIPTOR || descriptor === LOCAL_DESCRIPTOR || descriptor === AGENTS_DESCRIPTOR;
|
|
619
|
+
const external = projectSource ? approvedExternalInstructions(path, opts) : undefined;
|
|
620
|
+
if (external) {
|
|
621
|
+
for (const file of external) appendParsed(file, descriptor);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
const importOptions = { home: opts.home, read: projectSource ? readProjectImport : readAllowedImport };
|
|
625
|
+
const included = [...collectImportedPaths(content, dirname(path), importOptions)];
|
|
626
|
+
for (const p of included) {
|
|
627
|
+
const importedKey = tryRealpath(p) ?? p;
|
|
628
|
+
imported.add(importedKey);
|
|
629
|
+
emitted.add(importedKey);
|
|
630
|
+
}
|
|
631
|
+
files.push({ path, content: expandImports(content, dirname(path), importOptions), descriptor, ...(content.includes("@") ? { imported: included } : {}) });
|
|
632
|
+
emitted.add(key);
|
|
342
633
|
}
|
|
343
634
|
return files;
|
|
344
635
|
}
|
|
345
636
|
|
|
637
|
+
/** Use CC's parsed records for an approved external chain, without changing legacy no-import bytes. */
|
|
638
|
+
function approvedExternalInstructions(path: string, opts: { cwd: string; home: string; homeClaudeDir?: string; rule?: InstructionRule; includeExternal?: boolean }): RuleFile[] | undefined {
|
|
639
|
+
if (!opts.includeExternal) return;
|
|
640
|
+
const files = readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: "Project", includeExternal: true,
|
|
641
|
+
allowPath: (path) => opts.rule !== "agents-md" || !isClaudeLocation(path, opts.homeClaudeDir ?? claudeUserDir(opts.home)),
|
|
642
|
+
});
|
|
643
|
+
const cwd = tryRealpath(opts.cwd) ?? opts.cwd;
|
|
644
|
+
return files.some((file) => file.parent && !isPathAtOrUnder(file.key, cwd)) ? files : undefined;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* The startup warning probe: load with external includes enabled, then keep
|
|
649
|
+
* non-user imports/links outside cwd. Missing, empty, binary-extension and
|
|
650
|
+
* over-depth targets never become records, so they cannot cause a question.
|
|
651
|
+
* This scan is UI-only; its contents must never enter the reminder queue.
|
|
652
|
+
*/
|
|
653
|
+
export function externalInstructionIncludes(opts: DiscoveryOptions & { home: string }): string[] {
|
|
654
|
+
const outside: string[] = [];
|
|
655
|
+
const seen = new Set<string>();
|
|
656
|
+
const cwd = tryRealpath(opts.cwd) ?? opts.cwd;
|
|
657
|
+
const add = (file: RuleFile) => {
|
|
658
|
+
if ((!file.parent && !file.linkedFrom) || isPathAtOrUnder(file.key, cwd) || seen.has(file.key)) return;
|
|
659
|
+
seen.add(file.key);
|
|
660
|
+
outside.push(file.key);
|
|
661
|
+
};
|
|
662
|
+
const entries = discoverInstructionEntries(opts, (rulesDir, scope, descriptor) => {
|
|
663
|
+
if (scope === "User") return [];
|
|
664
|
+
return discoverRules({ rulesDir, scope, cwd: opts.cwd, home: opts.home, includeExternal: true })
|
|
665
|
+
.map((ruleFile) => ({ path: ruleFile.path, descriptor, ruleFile }));
|
|
666
|
+
});
|
|
667
|
+
for (const { path, descriptor, ruleFile } of entries) {
|
|
668
|
+
if (descriptor === GLOBAL_DESCRIPTOR || descriptor === ONECODE_GLOBAL_DESCRIPTOR || descriptor === ONECODE_DESCRIPTOR) continue;
|
|
669
|
+
if (ruleFile) { add(ruleFile); continue; }
|
|
670
|
+
const dotClaude = descriptor === PROJECT_DESCRIPTOR && basename(dirname(path)) === ".claude" && basename(path) === "CLAUDE.md";
|
|
671
|
+
for (const file of readRuleInstructions(path, { cwd: opts.cwd, home: opts.home, scope: descriptor === MANAGED_DESCRIPTOR ? "Managed" : "Project", includeExternal: true,
|
|
672
|
+
allowPath: (path) => opts.rule !== "agents-md" || !isClaudeLocation(path, opts.homeClaudeDir),
|
|
673
|
+
...(dotClaude ? { ownerDir: dirname(dirname(path)) } : {}),
|
|
674
|
+
})) add(file);
|
|
675
|
+
}
|
|
676
|
+
// A project ONECODE.md's imports, resolved by the expander that would load them.
|
|
677
|
+
for (const source of oneCodeSources({ cwd: opts.cwd, home: opts.home, homeOneCodeDir: opts.homeOneCodeDir ?? oneCodeStateDir(process.env, opts.home) })) {
|
|
678
|
+
if (source.descriptor !== ONECODE_DESCRIPTOR) continue;
|
|
679
|
+
for (const imported of collectImportedPaths(source.content, dirname(source.path), { home: opts.home, read: source.unconfined })) {
|
|
680
|
+
const key = tryRealpath(imported) ?? imported;
|
|
681
|
+
if (isPathAtOrUnder(key, cwd) || seen.has(key)) continue;
|
|
682
|
+
seen.add(key);
|
|
683
|
+
outside.push(key);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return outside;
|
|
687
|
+
}
|
|
688
|
+
|
|
346
689
|
/**
|
|
347
690
|
* The ONECODE.md files that exist, ordered global-first then farthest ancestor
|
|
348
691
|
* down to cwd (nearer wins). These ride in their own `# oneCodeMd` block, not the
|
|
@@ -352,31 +695,66 @@ function discoverOneCodeFilePaths(opts: { cwd: string; homeOneCodeDir: string })
|
|
|
352
695
|
const paths: ContextFilePath[] = [];
|
|
353
696
|
const seen = new Set<string>();
|
|
354
697
|
const push = (path: string | null, descriptor: string) => {
|
|
355
|
-
if (!path
|
|
698
|
+
if (!path) return;
|
|
699
|
+
const key = tryRealpath(path) ?? path;
|
|
700
|
+
if (seen.has(key)) return;
|
|
356
701
|
paths.push({ path, descriptor });
|
|
357
|
-
seen.add(
|
|
702
|
+
seen.add(key);
|
|
358
703
|
};
|
|
359
704
|
push(firstOneCodeFile(opts.homeOneCodeDir), ONECODE_GLOBAL_DESCRIPTOR);
|
|
360
705
|
for (const d of ancestorDirs(opts.cwd)) push(firstOneCodeFile(d), ONECODE_DESCRIPTOR);
|
|
361
706
|
return paths;
|
|
362
707
|
}
|
|
363
708
|
|
|
364
|
-
/**
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
709
|
+
/**
|
|
710
|
+
* The ONECODE.md files present, each with the readers for its `@import`s:
|
|
711
|
+
* `confined` is what loads without consent (anywhere for the user's global
|
|
712
|
+
* file, which the user wrote; at or under the cwd's real path for a project
|
|
713
|
+
* file, like CLAUDE.md), `unconfined` what loads once the user approved.
|
|
714
|
+
*/
|
|
715
|
+
function oneCodeSources(opts: { cwd: string; homeOneCodeDir: string; home: string }): {
|
|
716
|
+
path: string;
|
|
717
|
+
descriptor: string;
|
|
718
|
+
content: string;
|
|
719
|
+
confined: (path: string) => string | null;
|
|
720
|
+
unconfined: (path: string) => string | null;
|
|
721
|
+
}[] {
|
|
722
|
+
const allowedLocation = (path: string) => claudeSourcesOn() || !isClaudeLocation(path, claudeUserDir(opts.home));
|
|
723
|
+
const unconfined = (path: string) => allowedLocation(path) ? readFileIfPresent(path) : null;
|
|
724
|
+
const realCwd = tryRealpath(opts.cwd) ?? opts.cwd;
|
|
725
|
+
const inProject = (path: string) => {
|
|
726
|
+
const key = tryRealpath(path);
|
|
727
|
+
return key && isPathAtOrUnder(key, realCwd) ? unconfined(path) : null;
|
|
728
|
+
};
|
|
729
|
+
return discoverOneCodeFilePaths(opts).flatMap(({ path, descriptor }) => {
|
|
730
|
+
const content = unconfined(path);
|
|
731
|
+
return content === null ? [] : [{ path, descriptor, content, unconfined, confined: descriptor === ONECODE_DESCRIPTOR ? inProject : unconfined }];
|
|
732
|
+
});
|
|
373
733
|
}
|
|
374
734
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
735
|
+
/**
|
|
736
|
+
* ONECODE.md files with `@import`s expanded, for the `# oneCodeMd` block. A
|
|
737
|
+
* project file's imports outside the cwd load only with `includeExternal`
|
|
738
|
+
* (the user's consent, as for CLAUDE.md), which `externalInstructionIncludes` asks for.
|
|
739
|
+
*/
|
|
740
|
+
export function discoverOneCodeFiles(opts: { cwd: string; homeOneCodeDir: string; home: string; includeExternal?: boolean }): ContextFile[] {
|
|
741
|
+
return oneCodeSources(opts).map(({ path, descriptor, content, confined, unconfined }) => {
|
|
742
|
+
const importOptions = { home: opts.home, read: opts.includeExternal ? unconfined : confined };
|
|
743
|
+
const imported = [...collectImportedPaths(content, dirname(path), importOptions)];
|
|
744
|
+
return { path, content: expandImports(content, dirname(path), importOptions), descriptor, ...(content.includes("@") ? { imported } : {}) };
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/** The block's preamble, naming the files above it: AGENTS.md in independent mode (lib/config-mode.ts). */
|
|
749
|
+
function oneCodePreamble(): string {
|
|
750
|
+
const above = claudeSourcesOn() ? "CLAUDE.md" : "AGENTS.md";
|
|
751
|
+
return (
|
|
752
|
+
"# oneCodeMd\n" +
|
|
753
|
+
"The following are One Code-specific instructions, read only by One Code and not by other tools. " +
|
|
754
|
+
`IMPORTANT: they take precedence over the ${above} instructions above and over any default behavior — ` +
|
|
755
|
+
`where they conflict with ${above}, follow these. Follow them exactly as written.`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
380
758
|
|
|
381
759
|
/**
|
|
382
760
|
* Assemble the `# oneCodeMd` block's inner text (wrapper added by lib/reminders.ts):
|
|
@@ -386,35 +764,62 @@ const ONECODE_PREAMBLE =
|
|
|
386
764
|
*/
|
|
387
765
|
export function buildOneCodeBlock(files: ContextFile[]): string | null {
|
|
388
766
|
if (files.length === 0) return null;
|
|
389
|
-
|
|
390
|
-
return `${ONECODE_PREAMBLE}\n\n${sections}`;
|
|
767
|
+
return withoutFinalNewline(`${oneCodePreamble()}\n\n${files.map(section).join("\n")}`);
|
|
391
768
|
}
|
|
392
769
|
|
|
393
770
|
export const AGENTS_DESCRIPTOR = "cross-tool agent instructions, AGENTS.md standard";
|
|
394
771
|
|
|
395
772
|
/**
|
|
396
|
-
*
|
|
397
|
-
*
|
|
773
|
+
* One file's section. Its content is raw, but always ends in a newline: the
|
|
774
|
+
* join rule relies on it, and a file without one would glue the next section,
|
|
775
|
+
* or the next section's header, onto its last line.
|
|
776
|
+
*/
|
|
777
|
+
function section(file: ContextFile): string {
|
|
778
|
+
const content = file.content.endsWith("\n") ? file.content : `${file.content}\n`;
|
|
779
|
+
return `Contents of ${file.path} (${file.descriptor}):\n\n${content}`;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* The date reminder's value: the user's LOCAL calendar date as YYYY-MM-DD, the
|
|
784
|
+
* date their own clock shows (a UTC date is a day off every evening west of
|
|
785
|
+
* UTC and every early morning east of it).
|
|
786
|
+
*/
|
|
787
|
+
export function localDate(now: Date = new Date()): string {
|
|
788
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
789
|
+
return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Claude Code's notice when the local date moves on mid-session. The block
|
|
794
|
+
* carrying the date is frozen after the first request, so the new date
|
|
795
|
+
* rides a one-shot where the model reads next, the way Claude Code does.
|
|
796
|
+
*/
|
|
797
|
+
export function dateChangeReminder(date: string): string {
|
|
798
|
+
return `The date has changed. Today's date is now ${date}. No need to announce the new date \u2014 the user's own clock shows it.`;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** The text without one trailing newline: the reminder frame supplies the newline before its closing tag. */
|
|
802
|
+
function withoutFinalNewline(text: string): string {
|
|
803
|
+
return text.endsWith("\n") ? text.slice(0, -1) : text;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* The instructions block's inner text (the `<system-reminder>` wrapper is added
|
|
808
|
+
* by lib/reminders.ts):
|
|
398
809
|
*
|
|
399
810
|
* {PREAMBLE}\n\n
|
|
400
|
-
* {section}\n{section}\n…
|
|
401
|
-
* # userEmail\nThe user's email address is {email}.\n
|
|
402
|
-
* # currentDate\nToday's date is {date}.\n
|
|
403
|
-
* \n{TRAILER}
|
|
811
|
+
* {section}\n{section}\n…{last section without its final newline}
|
|
404
812
|
*
|
|
405
813
|
* where each section is `Contents of {path} ({descriptor}):\n\n{content}` and
|
|
406
814
|
* `content` keeps its own trailing newline (files are read raw, never trimmed —
|
|
407
|
-
* a file ending in "\n" plus the join "\n" is the "\n\n" seen between
|
|
408
|
-
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
* memory descriptor. Returns null when there is nothing at all to inject.
|
|
815
|
+
* a file ending in "\n" plus the join "\n" is the "\n\n" seen between
|
|
816
|
+
* sections). A file that does not end in "\n" gets one, so no header lands on
|
|
817
|
+
* its last line. `memoryIndex`, when present, is appended as a final section
|
|
818
|
+
* with the memory descriptor. Returns null when there is nothing to inject.
|
|
412
819
|
*/
|
|
413
820
|
export function buildClaudeMdBlock(opts: {
|
|
414
821
|
contextFiles: ContextFile[];
|
|
415
822
|
memoryIndex?: { path: string; content: string } | null;
|
|
416
|
-
email?: string | null;
|
|
417
|
-
date: string;
|
|
418
823
|
}): string | null {
|
|
419
824
|
const sections = [...opts.contextFiles];
|
|
420
825
|
if (opts.memoryIndex && opts.memoryIndex.content.trim()) {
|
|
@@ -424,15 +829,25 @@ export function buildClaudeMdBlock(opts: {
|
|
|
424
829
|
descriptor: MEMORY_DESCRIPTOR,
|
|
425
830
|
});
|
|
426
831
|
}
|
|
832
|
+
if (sections.length === 0) return null;
|
|
833
|
+
return withoutFinalNewline(`${PREAMBLE}\n\n${sections.map(section).join("\n")}`);
|
|
834
|
+
}
|
|
427
835
|
|
|
428
|
-
|
|
836
|
+
/**
|
|
837
|
+
* The context block's inner text: the preamble, `# userEmail` with Claude
|
|
838
|
+
* Code's use-only-to-identify sentence, the `# gitStatus` snapshot
|
|
839
|
+
* (lib/git-status.ts), then a blank line and the footer. Null with neither an
|
|
840
|
+
* email nor a snapshot.
|
|
841
|
+
*/
|
|
842
|
+
export function buildContextBlock(opts: { email?: string | null; gitStatus?: string | null }): string | null {
|
|
843
|
+
const parts: string[] = [];
|
|
844
|
+
if (opts.email) parts.push(`# userEmail\nThe user's email address is ${opts.email}. ${EMAIL_USE}`);
|
|
845
|
+
if (opts.gitStatus) parts.push(opts.gitStatus);
|
|
846
|
+
if (parts.length === 0) return null;
|
|
847
|
+
return `${CONTEXT_PREAMBLE}\n${parts.join("\n")}\n\n${CONTEXT_FOOTER}`;
|
|
848
|
+
}
|
|
429
849
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
inner += `# userEmail\nThe user's email address is ${opts.email}.\n`;
|
|
434
|
-
}
|
|
435
|
-
inner += `# currentDate\nToday's date is ${opts.date}.\n`;
|
|
436
|
-
inner += `\n${TRAILER}`;
|
|
437
|
-
return inner;
|
|
850
|
+
/** The date reminder's inner text, sent whether or not any CLAUDE.md exists. */
|
|
851
|
+
export function dateBlock(date: string): string {
|
|
852
|
+
return `Today's date is ${date}.`;
|
|
438
853
|
}
|