opencode-repos 0.2.0 → 0.3.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/AGENTS.md +180 -0
- package/README.md +103 -3
- package/TODO.md +3 -0
- package/index.ts +1590 -158
- package/oh-my-opencode/.github/FUNDING.yml +15 -0
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/bug_report.yml +129 -0
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/feature_request.yml +100 -0
- package/oh-my-opencode/.github/ISSUE_TEMPLATE/general.yml +83 -0
- package/oh-my-opencode/.github/assets/google.jpg +0 -0
- package/oh-my-opencode/.github/assets/hero.jpg +0 -0
- package/oh-my-opencode/.github/assets/indent.jpg +0 -0
- package/oh-my-opencode/.github/assets/microsoft.jpg +0 -0
- package/oh-my-opencode/.github/assets/omo.png +0 -0
- package/oh-my-opencode/.github/assets/orchestrator-atlas.png +0 -0
- package/oh-my-opencode/.github/assets/sisyphus.png +0 -0
- package/oh-my-opencode/.github/assets/sisyphuslabs.png +0 -0
- package/oh-my-opencode/.github/pull_request_template.md +34 -0
- package/oh-my-opencode/.github/workflows/ci.yml +138 -0
- package/oh-my-opencode/.github/workflows/cla.yml +41 -0
- package/oh-my-opencode/.github/workflows/lint-workflows.yml +22 -0
- package/oh-my-opencode/.github/workflows/publish.yml +165 -0
- package/oh-my-opencode/.github/workflows/sisyphus-agent.yml +500 -0
- package/oh-my-opencode/.opencode/background-tasks.json +27 -0
- package/oh-my-opencode/.opencode/command/get-unpublished-changes.md +84 -0
- package/oh-my-opencode/.opencode/command/omomomo.md +37 -0
- package/oh-my-opencode/.opencode/command/publish.md +257 -0
- package/oh-my-opencode/AGENTS.md +179 -0
- package/oh-my-opencode/CLA.md +58 -0
- package/oh-my-opencode/CONTRIBUTING.md +268 -0
- package/oh-my-opencode/LICENSE.md +82 -0
- package/oh-my-opencode/README.ja.md +370 -0
- package/oh-my-opencode/README.md +376 -0
- package/oh-my-opencode/README.zh-cn.md +380 -0
- package/oh-my-opencode/assets/oh-my-opencode.schema.json +2171 -0
- package/oh-my-opencode/bin/oh-my-opencode.js +80 -0
- package/oh-my-opencode/bin/platform.js +38 -0
- package/oh-my-opencode/bin/platform.test.ts +148 -0
- package/oh-my-opencode/bun.lock +314 -0
- package/oh-my-opencode/bunfig.toml +2 -0
- package/oh-my-opencode/docs/category-skill-guide.md +200 -0
- package/oh-my-opencode/docs/cli-guide.md +272 -0
- package/oh-my-opencode/docs/configurations.md +654 -0
- package/oh-my-opencode/docs/features.md +550 -0
- package/oh-my-opencode/docs/guide/installation.md +288 -0
- package/oh-my-opencode/docs/guide/overview.md +97 -0
- package/oh-my-opencode/docs/guide/understanding-orchestration-system.md +445 -0
- package/oh-my-opencode/docs/orchestration-guide.md +152 -0
- package/oh-my-opencode/docs/ultrawork-manifesto.md +197 -0
- package/oh-my-opencode/package.json +89 -0
- package/oh-my-opencode/packages/darwin-arm64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/darwin-arm64/package.json +22 -0
- package/oh-my-opencode/packages/darwin-x64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/darwin-x64/package.json +22 -0
- package/oh-my-opencode/packages/linux-arm64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-arm64/package.json +25 -0
- package/oh-my-opencode/packages/linux-arm64-musl/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-arm64-musl/package.json +25 -0
- package/oh-my-opencode/packages/linux-x64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-x64/package.json +25 -0
- package/oh-my-opencode/packages/linux-x64-musl/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/linux-x64-musl/package.json +25 -0
- package/oh-my-opencode/packages/windows-x64/bin/.gitkeep +0 -0
- package/oh-my-opencode/packages/windows-x64/package.json +22 -0
- package/oh-my-opencode/postinstall.mjs +43 -0
- package/oh-my-opencode/script/build-binaries.ts +103 -0
- package/oh-my-opencode/script/build-schema.ts +28 -0
- package/oh-my-opencode/script/generate-changelog.ts +92 -0
- package/oh-my-opencode/script/publish.ts +344 -0
- package/oh-my-opencode/signatures/cla.json +676 -0
- package/oh-my-opencode/src/agents/AGENTS.md +67 -0
- package/oh-my-opencode/src/agents/atlas.ts +1383 -0
- package/oh-my-opencode/src/agents/dynamic-agent-prompt-builder.ts +400 -0
- package/oh-my-opencode/src/agents/explore.ts +122 -0
- package/oh-my-opencode/src/agents/index.ts +13 -0
- package/oh-my-opencode/src/agents/librarian.ts +326 -0
- package/oh-my-opencode/src/agents/metis.ts +315 -0
- package/oh-my-opencode/src/agents/momus.test.ts +57 -0
- package/oh-my-opencode/src/agents/momus.ts +444 -0
- package/oh-my-opencode/src/agents/multimodal-looker.ts +56 -0
- package/oh-my-opencode/src/agents/oracle.ts +122 -0
- package/oh-my-opencode/src/agents/prometheus-prompt.test.ts +22 -0
- package/oh-my-opencode/src/agents/prometheus-prompt.ts +1196 -0
- package/oh-my-opencode/src/agents/sisyphus-junior.test.ts +232 -0
- package/oh-my-opencode/src/agents/sisyphus-junior.ts +134 -0
- package/oh-my-opencode/src/agents/sisyphus.ts +633 -0
- package/oh-my-opencode/src/agents/types.ts +80 -0
- package/oh-my-opencode/src/agents/utils.test.ts +311 -0
- package/oh-my-opencode/src/agents/utils.ts +240 -0
- package/oh-my-opencode/src/cli/AGENTS.md +91 -0
- package/oh-my-opencode/src/cli/config-manager.test.ts +364 -0
- package/oh-my-opencode/src/cli/config-manager.ts +641 -0
- package/oh-my-opencode/src/cli/doctor/checks/auth.test.ts +114 -0
- package/oh-my-opencode/src/cli/doctor/checks/auth.ts +115 -0
- package/oh-my-opencode/src/cli/doctor/checks/config.test.ts +103 -0
- package/oh-my-opencode/src/cli/doctor/checks/config.ts +123 -0
- package/oh-my-opencode/src/cli/doctor/checks/dependencies.test.ts +152 -0
- package/oh-my-opencode/src/cli/doctor/checks/dependencies.ts +163 -0
- package/oh-my-opencode/src/cli/doctor/checks/gh.test.ts +151 -0
- package/oh-my-opencode/src/cli/doctor/checks/gh.ts +171 -0
- package/oh-my-opencode/src/cli/doctor/checks/index.ts +34 -0
- package/oh-my-opencode/src/cli/doctor/checks/lsp.test.ts +134 -0
- package/oh-my-opencode/src/cli/doctor/checks/lsp.ts +77 -0
- package/oh-my-opencode/src/cli/doctor/checks/mcp.test.ts +115 -0
- package/oh-my-opencode/src/cli/doctor/checks/mcp.ts +128 -0
- package/oh-my-opencode/src/cli/doctor/checks/opencode.test.ts +227 -0
- package/oh-my-opencode/src/cli/doctor/checks/opencode.ts +178 -0
- package/oh-my-opencode/src/cli/doctor/checks/plugin.test.ts +109 -0
- package/oh-my-opencode/src/cli/doctor/checks/plugin.ts +124 -0
- package/oh-my-opencode/src/cli/doctor/checks/version.test.ts +148 -0
- package/oh-my-opencode/src/cli/doctor/checks/version.ts +135 -0
- package/oh-my-opencode/src/cli/doctor/constants.ts +72 -0
- package/oh-my-opencode/src/cli/doctor/formatter.test.ts +218 -0
- package/oh-my-opencode/src/cli/doctor/formatter.ts +140 -0
- package/oh-my-opencode/src/cli/doctor/index.ts +11 -0
- package/oh-my-opencode/src/cli/doctor/runner.test.ts +153 -0
- package/oh-my-opencode/src/cli/doctor/runner.ts +132 -0
- package/oh-my-opencode/src/cli/doctor/types.ts +113 -0
- package/oh-my-opencode/src/cli/get-local-version/formatter.ts +66 -0
- package/oh-my-opencode/src/cli/get-local-version/index.ts +106 -0
- package/oh-my-opencode/src/cli/get-local-version/types.ts +14 -0
- package/oh-my-opencode/src/cli/index.ts +153 -0
- package/oh-my-opencode/src/cli/install.ts +523 -0
- package/oh-my-opencode/src/cli/model-fallback.ts +246 -0
- package/oh-my-opencode/src/cli/run/completion.test.ts +170 -0
- package/oh-my-opencode/src/cli/run/completion.ts +79 -0
- package/oh-my-opencode/src/cli/run/events.test.ts +155 -0
- package/oh-my-opencode/src/cli/run/events.ts +325 -0
- package/oh-my-opencode/src/cli/run/index.ts +2 -0
- package/oh-my-opencode/src/cli/run/runner.ts +159 -0
- package/oh-my-opencode/src/cli/run/types.ts +76 -0
- package/oh-my-opencode/src/cli/types.ts +40 -0
- package/oh-my-opencode/src/config/index.ts +26 -0
- package/oh-my-opencode/src/config/schema.test.ts +444 -0
- package/oh-my-opencode/src/config/schema.ts +339 -0
- package/oh-my-opencode/src/features/AGENTS.md +77 -0
- package/oh-my-opencode/src/features/background-agent/concurrency.test.ts +418 -0
- package/oh-my-opencode/src/features/background-agent/concurrency.ts +137 -0
- package/oh-my-opencode/src/features/background-agent/index.ts +3 -0
- package/oh-my-opencode/src/features/background-agent/manager.test.ts +1928 -0
- package/oh-my-opencode/src/features/background-agent/manager.ts +1335 -0
- package/oh-my-opencode/src/features/background-agent/types.ts +66 -0
- package/oh-my-opencode/src/features/boulder-state/constants.ts +13 -0
- package/oh-my-opencode/src/features/boulder-state/index.ts +3 -0
- package/oh-my-opencode/src/features/boulder-state/storage.test.ts +250 -0
- package/oh-my-opencode/src/features/boulder-state/storage.ts +150 -0
- package/oh-my-opencode/src/features/boulder-state/types.ts +26 -0
- package/oh-my-opencode/src/features/builtin-commands/commands.ts +89 -0
- package/oh-my-opencode/src/features/builtin-commands/index.ts +2 -0
- package/oh-my-opencode/src/features/builtin-commands/templates/init-deep.ts +300 -0
- package/oh-my-opencode/src/features/builtin-commands/templates/ralph-loop.ts +38 -0
- package/oh-my-opencode/src/features/builtin-commands/templates/refactor.ts +619 -0
- package/oh-my-opencode/src/features/builtin-commands/templates/start-work.ts +72 -0
- package/oh-my-opencode/src/features/builtin-commands/types.ts +9 -0
- package/oh-my-opencode/src/features/builtin-skills/frontend-ui-ux/SKILL.md +78 -0
- package/oh-my-opencode/src/features/builtin-skills/git-master/SKILL.md +1105 -0
- package/oh-my-opencode/src/features/builtin-skills/index.ts +2 -0
- package/oh-my-opencode/src/features/builtin-skills/skills.ts +1203 -0
- package/oh-my-opencode/src/features/builtin-skills/types.ts +16 -0
- package/oh-my-opencode/src/features/claude-code-agent-loader/index.ts +2 -0
- package/oh-my-opencode/src/features/claude-code-agent-loader/loader.ts +90 -0
- package/oh-my-opencode/src/features/claude-code-agent-loader/types.ts +17 -0
- package/oh-my-opencode/src/features/claude-code-command-loader/index.ts +2 -0
- package/oh-my-opencode/src/features/claude-code-command-loader/loader.ts +144 -0
- package/oh-my-opencode/src/features/claude-code-command-loader/types.ts +46 -0
- package/oh-my-opencode/src/features/claude-code-mcp-loader/env-expander.ts +27 -0
- package/oh-my-opencode/src/features/claude-code-mcp-loader/index.ts +11 -0
- package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.test.ts +162 -0
- package/oh-my-opencode/src/features/claude-code-mcp-loader/loader.ts +113 -0
- package/oh-my-opencode/src/features/claude-code-mcp-loader/transformer.ts +53 -0
- package/oh-my-opencode/src/features/claude-code-mcp-loader/types.ts +42 -0
- package/oh-my-opencode/src/features/claude-code-plugin-loader/index.ts +3 -0
- package/oh-my-opencode/src/features/claude-code-plugin-loader/loader.ts +486 -0
- package/oh-my-opencode/src/features/claude-code-plugin-loader/types.ts +210 -0
- package/oh-my-opencode/src/features/claude-code-session-state/index.ts +1 -0
- package/oh-my-opencode/src/features/claude-code-session-state/state.test.ts +126 -0
- package/oh-my-opencode/src/features/claude-code-session-state/state.ts +37 -0
- package/oh-my-opencode/src/features/context-injector/collector.test.ts +330 -0
- package/oh-my-opencode/src/features/context-injector/collector.ts +85 -0
- package/oh-my-opencode/src/features/context-injector/index.ts +14 -0
- package/oh-my-opencode/src/features/context-injector/injector.test.ts +122 -0
- package/oh-my-opencode/src/features/context-injector/injector.ts +167 -0
- package/oh-my-opencode/src/features/context-injector/types.ts +91 -0
- package/oh-my-opencode/src/features/hook-message-injector/constants.ts +6 -0
- package/oh-my-opencode/src/features/hook-message-injector/index.ts +4 -0
- package/oh-my-opencode/src/features/hook-message-injector/injector.ts +195 -0
- package/oh-my-opencode/src/features/hook-message-injector/types.ts +47 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.test.ts +448 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/async-loader.ts +180 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/blocking.test.ts +210 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/blocking.ts +62 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/discover-worker.ts +59 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/index.ts +4 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/loader.test.ts +273 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/loader.ts +259 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/merger.ts +267 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.test.ts +267 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/skill-content.ts +206 -0
- package/oh-my-opencode/src/features/opencode-skill-loader/types.ts +38 -0
- package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.test.ts +201 -0
- package/oh-my-opencode/src/features/skill-mcp-manager/env-cleaner.ts +27 -0
- package/oh-my-opencode/src/features/skill-mcp-manager/index.ts +2 -0
- package/oh-my-opencode/src/features/skill-mcp-manager/manager.test.ts +611 -0
- package/oh-my-opencode/src/features/skill-mcp-manager/manager.ts +520 -0
- package/oh-my-opencode/src/features/skill-mcp-manager/types.ts +14 -0
- package/oh-my-opencode/src/features/task-toast-manager/index.ts +2 -0
- package/oh-my-opencode/src/features/task-toast-manager/manager.test.ts +249 -0
- package/oh-my-opencode/src/features/task-toast-manager/manager.ts +215 -0
- package/oh-my-opencode/src/features/task-toast-manager/types.ts +24 -0
- package/oh-my-opencode/src/hooks/AGENTS.md +73 -0
- package/oh-my-opencode/src/hooks/agent-usage-reminder/constants.ts +54 -0
- package/oh-my-opencode/src/hooks/agent-usage-reminder/index.ts +109 -0
- package/oh-my-opencode/src/hooks/agent-usage-reminder/storage.ts +42 -0
- package/oh-my-opencode/src/hooks/agent-usage-reminder/types.ts +6 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.test.ts +307 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/executor.ts +485 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/index.ts +151 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/parser.ts +201 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.test.ts +33 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-deduplication.ts +184 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/pruning-types.ts +44 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.test.ts +77 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/storage.ts +250 -0
- package/oh-my-opencode/src/hooks/anthropic-context-window-limit-recovery/types.ts +42 -0
- package/oh-my-opencode/src/hooks/atlas/index.test.ts +953 -0
- package/oh-my-opencode/src/hooks/atlas/index.ts +771 -0
- package/oh-my-opencode/src/hooks/auto-slash-command/constants.ts +12 -0
- package/oh-my-opencode/src/hooks/auto-slash-command/detector.test.ts +296 -0
- package/oh-my-opencode/src/hooks/auto-slash-command/detector.ts +65 -0
- package/oh-my-opencode/src/hooks/auto-slash-command/executor.ts +205 -0
- package/oh-my-opencode/src/hooks/auto-slash-command/index.test.ts +254 -0
- package/oh-my-opencode/src/hooks/auto-slash-command/index.ts +89 -0
- package/oh-my-opencode/src/hooks/auto-slash-command/types.ts +23 -0
- package/oh-my-opencode/src/hooks/auto-update-checker/cache.ts +93 -0
- package/oh-my-opencode/src/hooks/auto-update-checker/checker.test.ts +24 -0
- package/oh-my-opencode/src/hooks/auto-update-checker/checker.ts +284 -0
- package/oh-my-opencode/src/hooks/auto-update-checker/constants.ts +64 -0
- package/oh-my-opencode/src/hooks/auto-update-checker/index.test.ts +254 -0
- package/oh-my-opencode/src/hooks/auto-update-checker/index.ts +260 -0
- package/oh-my-opencode/src/hooks/auto-update-checker/types.ts +29 -0
- package/oh-my-opencode/src/hooks/background-compaction/index.ts +87 -0
- package/oh-my-opencode/src/hooks/background-notification/index.ts +28 -0
- package/oh-my-opencode/src/hooks/background-notification/types.ts +5 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/AGENTS.md +70 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/config-loader.ts +107 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/config.ts +103 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/index.ts +401 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/plugin-config.ts +12 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/post-tool-use.ts +199 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/pre-compact.ts +109 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/pre-tool-use.ts +172 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/stop.ts +118 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/todo.ts +76 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/tool-input-cache.ts +47 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/transcript.ts +252 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/types.ts +204 -0
- package/oh-my-opencode/src/hooks/claude-code-hooks/user-prompt-submit.ts +117 -0
- package/oh-my-opencode/src/hooks/comment-checker/cli.test.ts +68 -0
- package/oh-my-opencode/src/hooks/comment-checker/cli.ts +221 -0
- package/oh-my-opencode/src/hooks/comment-checker/downloader.ts +196 -0
- package/oh-my-opencode/src/hooks/comment-checker/index.ts +171 -0
- package/oh-my-opencode/src/hooks/comment-checker/types.ts +33 -0
- package/oh-my-opencode/src/hooks/compaction-context-injector/index.ts +61 -0
- package/oh-my-opencode/src/hooks/context-window-monitor.ts +99 -0
- package/oh-my-opencode/src/hooks/delegate-task-retry/index.test.ts +119 -0
- package/oh-my-opencode/src/hooks/delegate-task-retry/index.ts +136 -0
- package/oh-my-opencode/src/hooks/directory-agents-injector/constants.ts +9 -0
- package/oh-my-opencode/src/hooks/directory-agents-injector/index.ts +182 -0
- package/oh-my-opencode/src/hooks/directory-agents-injector/storage.ts +48 -0
- package/oh-my-opencode/src/hooks/directory-agents-injector/types.ts +5 -0
- package/oh-my-opencode/src/hooks/directory-readme-injector/constants.ts +9 -0
- package/oh-my-opencode/src/hooks/directory-readme-injector/index.ts +177 -0
- package/oh-my-opencode/src/hooks/directory-readme-injector/storage.ts +48 -0
- package/oh-my-opencode/src/hooks/directory-readme-injector/types.ts +5 -0
- package/oh-my-opencode/src/hooks/edit-error-recovery/index.test.ts +126 -0
- package/oh-my-opencode/src/hooks/edit-error-recovery/index.ts +57 -0
- package/oh-my-opencode/src/hooks/empty-task-response-detector.ts +27 -0
- package/oh-my-opencode/src/hooks/index.ts +32 -0
- package/oh-my-opencode/src/hooks/interactive-bash-session/constants.ts +15 -0
- package/oh-my-opencode/src/hooks/interactive-bash-session/index.ts +262 -0
- package/oh-my-opencode/src/hooks/interactive-bash-session/storage.ts +59 -0
- package/oh-my-opencode/src/hooks/interactive-bash-session/types.ts +11 -0
- package/oh-my-opencode/src/hooks/keyword-detector/constants.ts +300 -0
- package/oh-my-opencode/src/hooks/keyword-detector/detector.ts +52 -0
- package/oh-my-opencode/src/hooks/keyword-detector/index.test.ts +529 -0
- package/oh-my-opencode/src/hooks/keyword-detector/index.ts +100 -0
- package/oh-my-opencode/src/hooks/keyword-detector/types.ts +4 -0
- package/oh-my-opencode/src/hooks/non-interactive-env/constants.ts +70 -0
- package/oh-my-opencode/src/hooks/non-interactive-env/detector.ts +19 -0
- package/oh-my-opencode/src/hooks/non-interactive-env/index.test.ts +323 -0
- package/oh-my-opencode/src/hooks/non-interactive-env/index.ts +63 -0
- package/oh-my-opencode/src/hooks/non-interactive-env/types.ts +3 -0
- package/oh-my-opencode/src/hooks/prometheus-md-only/constants.ts +32 -0
- package/oh-my-opencode/src/hooks/prometheus-md-only/index.test.ts +488 -0
- package/oh-my-opencode/src/hooks/prometheus-md-only/index.ts +136 -0
- package/oh-my-opencode/src/hooks/ralph-loop/constants.ts +5 -0
- package/oh-my-opencode/src/hooks/ralph-loop/index.test.ts +835 -0
- package/oh-my-opencode/src/hooks/ralph-loop/index.ts +417 -0
- package/oh-my-opencode/src/hooks/ralph-loop/storage.ts +115 -0
- package/oh-my-opencode/src/hooks/ralph-loop/types.ts +19 -0
- package/oh-my-opencode/src/hooks/rules-injector/constants.ts +30 -0
- package/oh-my-opencode/src/hooks/rules-injector/finder.test.ts +381 -0
- package/oh-my-opencode/src/hooks/rules-injector/finder.ts +263 -0
- package/oh-my-opencode/src/hooks/rules-injector/index.ts +223 -0
- package/oh-my-opencode/src/hooks/rules-injector/matcher.ts +63 -0
- package/oh-my-opencode/src/hooks/rules-injector/parser.test.ts +226 -0
- package/oh-my-opencode/src/hooks/rules-injector/parser.ts +211 -0
- package/oh-my-opencode/src/hooks/rules-injector/storage.ts +59 -0
- package/oh-my-opencode/src/hooks/rules-injector/types.ts +57 -0
- package/oh-my-opencode/src/hooks/session-notification-utils.ts +140 -0
- package/oh-my-opencode/src/hooks/session-notification.test.ts +361 -0
- package/oh-my-opencode/src/hooks/session-notification.ts +330 -0
- package/oh-my-opencode/src/hooks/session-recovery/constants.ts +10 -0
- package/oh-my-opencode/src/hooks/session-recovery/index.test.ts +223 -0
- package/oh-my-opencode/src/hooks/session-recovery/index.ts +435 -0
- package/oh-my-opencode/src/hooks/session-recovery/storage.ts +390 -0
- package/oh-my-opencode/src/hooks/session-recovery/types.ts +98 -0
- package/oh-my-opencode/src/hooks/start-work/index.test.ts +402 -0
- package/oh-my-opencode/src/hooks/start-work/index.ts +242 -0
- package/oh-my-opencode/src/hooks/task-resume-info/index.ts +36 -0
- package/oh-my-opencode/src/hooks/think-mode/detector.ts +57 -0
- package/oh-my-opencode/src/hooks/think-mode/index.test.ts +353 -0
- package/oh-my-opencode/src/hooks/think-mode/index.ts +89 -0
- package/oh-my-opencode/src/hooks/think-mode/switcher.test.ts +461 -0
- package/oh-my-opencode/src/hooks/think-mode/switcher.ts +222 -0
- package/oh-my-opencode/src/hooks/think-mode/types.ts +21 -0
- package/oh-my-opencode/src/hooks/thinking-block-validator/index.ts +171 -0
- package/oh-my-opencode/src/hooks/todo-continuation-enforcer.test.ts +876 -0
- package/oh-my-opencode/src/hooks/todo-continuation-enforcer.ts +480 -0
- package/oh-my-opencode/src/hooks/tool-output-truncator.test.ts +168 -0
- package/oh-my-opencode/src/hooks/tool-output-truncator.ts +61 -0
- package/oh-my-opencode/src/index.ts +589 -0
- package/oh-my-opencode/src/mcp/AGENTS.md +70 -0
- package/oh-my-opencode/src/mcp/context7.ts +6 -0
- package/oh-my-opencode/src/mcp/grep-app.ts +6 -0
- package/oh-my-opencode/src/mcp/index.test.ts +86 -0
- package/oh-my-opencode/src/mcp/index.ts +32 -0
- package/oh-my-opencode/src/mcp/types.ts +9 -0
- package/oh-my-opencode/src/mcp/websearch.ts +10 -0
- package/oh-my-opencode/src/plugin-config.test.ts +119 -0
- package/oh-my-opencode/src/plugin-config.ts +135 -0
- package/oh-my-opencode/src/plugin-handlers/config-handler.test.ts +103 -0
- package/oh-my-opencode/src/plugin-handlers/config-handler.ts +399 -0
- package/oh-my-opencode/src/plugin-handlers/index.ts +1 -0
- package/oh-my-opencode/src/plugin-state.ts +30 -0
- package/oh-my-opencode/src/shared/AGENTS.md +63 -0
- package/oh-my-opencode/src/shared/agent-tool-restrictions.ts +44 -0
- package/oh-my-opencode/src/shared/agent-variant.test.ts +83 -0
- package/oh-my-opencode/src/shared/agent-variant.ts +40 -0
- package/oh-my-opencode/src/shared/claude-config-dir.test.ts +60 -0
- package/oh-my-opencode/src/shared/claude-config-dir.ts +11 -0
- package/oh-my-opencode/src/shared/command-executor.ts +225 -0
- package/oh-my-opencode/src/shared/config-errors.ts +18 -0
- package/oh-my-opencode/src/shared/config-path.ts +47 -0
- package/oh-my-opencode/src/shared/data-path.ts +22 -0
- package/oh-my-opencode/src/shared/deep-merge.test.ts +336 -0
- package/oh-my-opencode/src/shared/deep-merge.ts +53 -0
- package/oh-my-opencode/src/shared/dynamic-truncator.ts +193 -0
- package/oh-my-opencode/src/shared/external-plugin-detector.test.ts +133 -0
- package/oh-my-opencode/src/shared/external-plugin-detector.ts +132 -0
- package/oh-my-opencode/src/shared/file-reference-resolver.ts +85 -0
- package/oh-my-opencode/src/shared/file-utils.ts +40 -0
- package/oh-my-opencode/src/shared/first-message-variant.test.ts +32 -0
- package/oh-my-opencode/src/shared/first-message-variant.ts +28 -0
- package/oh-my-opencode/src/shared/frontmatter.test.ts +262 -0
- package/oh-my-opencode/src/shared/frontmatter.ts +31 -0
- package/oh-my-opencode/src/shared/hook-disabled.ts +22 -0
- package/oh-my-opencode/src/shared/index.ts +29 -0
- package/oh-my-opencode/src/shared/jsonc-parser.test.ts +266 -0
- package/oh-my-opencode/src/shared/jsonc-parser.ts +66 -0
- package/oh-my-opencode/src/shared/logger.ts +20 -0
- package/oh-my-opencode/src/shared/migration.test.ts +602 -0
- package/oh-my-opencode/src/shared/migration.ts +191 -0
- package/oh-my-opencode/src/shared/model-resolver.test.ts +101 -0
- package/oh-my-opencode/src/shared/model-resolver.ts +35 -0
- package/oh-my-opencode/src/shared/model-sanitizer.ts +12 -0
- package/oh-my-opencode/src/shared/opencode-config-dir.test.ts +318 -0
- package/oh-my-opencode/src/shared/opencode-config-dir.ts +142 -0
- package/oh-my-opencode/src/shared/opencode-version.test.ts +223 -0
- package/oh-my-opencode/src/shared/opencode-version.ts +72 -0
- package/oh-my-opencode/src/shared/pattern-matcher.ts +29 -0
- package/oh-my-opencode/src/shared/permission-compat.test.ts +134 -0
- package/oh-my-opencode/src/shared/permission-compat.ts +77 -0
- package/oh-my-opencode/src/shared/session-cursor.test.ts +66 -0
- package/oh-my-opencode/src/shared/session-cursor.ts +85 -0
- package/oh-my-opencode/src/shared/shell-env.test.ts +278 -0
- package/oh-my-opencode/src/shared/shell-env.ts +111 -0
- package/oh-my-opencode/src/shared/snake-case.ts +49 -0
- package/oh-my-opencode/src/shared/system-directive.ts +40 -0
- package/oh-my-opencode/src/shared/tool-name.ts +26 -0
- package/oh-my-opencode/src/shared/zip-extractor.ts +83 -0
- package/oh-my-opencode/src/tools/AGENTS.md +74 -0
- package/oh-my-opencode/src/tools/ast-grep/cli.ts +230 -0
- package/oh-my-opencode/src/tools/ast-grep/constants.ts +261 -0
- package/oh-my-opencode/src/tools/ast-grep/downloader.ts +128 -0
- package/oh-my-opencode/src/tools/ast-grep/index.ts +13 -0
- package/oh-my-opencode/src/tools/ast-grep/tools.ts +112 -0
- package/oh-my-opencode/src/tools/ast-grep/types.ts +61 -0
- package/oh-my-opencode/src/tools/ast-grep/utils.ts +102 -0
- package/oh-my-opencode/src/tools/background-task/constants.ts +7 -0
- package/oh-my-opencode/src/tools/background-task/index.ts +7 -0
- package/oh-my-opencode/src/tools/background-task/tools.ts +479 -0
- package/oh-my-opencode/src/tools/background-task/types.ts +16 -0
- package/oh-my-opencode/src/tools/call-omo-agent/constants.ts +7 -0
- package/oh-my-opencode/src/tools/call-omo-agent/index.ts +3 -0
- package/oh-my-opencode/src/tools/call-omo-agent/tools.ts +338 -0
- package/oh-my-opencode/src/tools/call-omo-agent/types.ts +27 -0
- package/oh-my-opencode/src/tools/delegate-task/constants.ts +205 -0
- package/oh-my-opencode/src/tools/delegate-task/index.ts +3 -0
- package/oh-my-opencode/src/tools/delegate-task/tools.test.ts +1575 -0
- package/oh-my-opencode/src/tools/delegate-task/tools.ts +885 -0
- package/oh-my-opencode/src/tools/delegate-task/types.ts +9 -0
- package/oh-my-opencode/src/tools/glob/cli.test.ts +158 -0
- package/oh-my-opencode/src/tools/glob/cli.ts +191 -0
- package/oh-my-opencode/src/tools/glob/constants.ts +12 -0
- package/oh-my-opencode/src/tools/glob/index.ts +3 -0
- package/oh-my-opencode/src/tools/glob/tools.ts +41 -0
- package/oh-my-opencode/src/tools/glob/types.ts +22 -0
- package/oh-my-opencode/src/tools/glob/utils.ts +26 -0
- package/oh-my-opencode/src/tools/grep/cli.ts +229 -0
- package/oh-my-opencode/src/tools/grep/constants.ts +127 -0
- package/oh-my-opencode/src/tools/grep/downloader.test.ts +103 -0
- package/oh-my-opencode/src/tools/grep/downloader.ts +145 -0
- package/oh-my-opencode/src/tools/grep/index.ts +3 -0
- package/oh-my-opencode/src/tools/grep/tools.ts +40 -0
- package/oh-my-opencode/src/tools/grep/types.ts +39 -0
- package/oh-my-opencode/src/tools/grep/utils.ts +53 -0
- package/oh-my-opencode/src/tools/index.ts +72 -0
- package/oh-my-opencode/src/tools/interactive-bash/constants.ts +18 -0
- package/oh-my-opencode/src/tools/interactive-bash/index.ts +4 -0
- package/oh-my-opencode/src/tools/interactive-bash/tools.ts +126 -0
- package/oh-my-opencode/src/tools/interactive-bash/utils.ts +71 -0
- package/oh-my-opencode/src/tools/look-at/constants.ts +3 -0
- package/oh-my-opencode/src/tools/look-at/index.ts +3 -0
- package/oh-my-opencode/src/tools/look-at/tools.test.ts +73 -0
- package/oh-my-opencode/src/tools/look-at/tools.ts +173 -0
- package/oh-my-opencode/src/tools/look-at/types.ts +4 -0
- package/oh-my-opencode/src/tools/lsp/client.ts +596 -0
- package/oh-my-opencode/src/tools/lsp/config.test.ts +130 -0
- package/oh-my-opencode/src/tools/lsp/config.ts +285 -0
- package/oh-my-opencode/src/tools/lsp/constants.ts +390 -0
- package/oh-my-opencode/src/tools/lsp/index.ts +7 -0
- package/oh-my-opencode/src/tools/lsp/tools.ts +261 -0
- package/oh-my-opencode/src/tools/lsp/types.ts +124 -0
- package/oh-my-opencode/src/tools/lsp/utils.ts +406 -0
- package/oh-my-opencode/src/tools/session-manager/constants.ts +97 -0
- package/oh-my-opencode/src/tools/session-manager/index.ts +3 -0
- package/oh-my-opencode/src/tools/session-manager/storage.test.ts +315 -0
- package/oh-my-opencode/src/tools/session-manager/storage.ts +238 -0
- package/oh-my-opencode/src/tools/session-manager/tools.test.ts +124 -0
- package/oh-my-opencode/src/tools/session-manager/tools.ts +146 -0
- package/oh-my-opencode/src/tools/session-manager/types.ts +99 -0
- package/oh-my-opencode/src/tools/session-manager/utils.test.ts +160 -0
- package/oh-my-opencode/src/tools/session-manager/utils.ts +199 -0
- package/oh-my-opencode/src/tools/skill/constants.ts +8 -0
- package/oh-my-opencode/src/tools/skill/index.ts +3 -0
- package/oh-my-opencode/src/tools/skill/tools.test.ts +239 -0
- package/oh-my-opencode/src/tools/skill/tools.ts +200 -0
- package/oh-my-opencode/src/tools/skill/types.ts +31 -0
- package/oh-my-opencode/src/tools/skill-mcp/constants.ts +3 -0
- package/oh-my-opencode/src/tools/skill-mcp/index.ts +3 -0
- package/oh-my-opencode/src/tools/skill-mcp/tools.test.ts +215 -0
- package/oh-my-opencode/src/tools/skill-mcp/tools.ts +172 -0
- package/oh-my-opencode/src/tools/skill-mcp/types.ts +8 -0
- package/oh-my-opencode/src/tools/slashcommand/index.ts +2 -0
- package/oh-my-opencode/src/tools/slashcommand/tools.ts +252 -0
- package/oh-my-opencode/src/tools/slashcommand/types.ts +28 -0
- package/oh-my-opencode/test-setup.ts +6 -0
- package/oh-my-opencode/tsconfig.json +20 -0
- package/package.json +1 -1
- package/src/__tests__/git.test.ts +7 -2
- package/src/__tests__/manifest.test.ts +5 -5
- package/src/agents/repo-explorer.ts +2 -1
- package/src/git.ts +18 -3
- package/src/manifest.ts +22 -15
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { findProjectRoot, findRuleFiles } from "./finder";
|
|
6
|
+
|
|
7
|
+
describe("findRuleFiles", () => {
|
|
8
|
+
const TEST_DIR = join(tmpdir(), `rules-injector-test-${Date.now()}`);
|
|
9
|
+
const homeDir = join(TEST_DIR, "home");
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
13
|
+
mkdirSync(homeDir, { recursive: true });
|
|
14
|
+
mkdirSync(join(TEST_DIR, ".git"), { recursive: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
if (existsSync(TEST_DIR)) {
|
|
19
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe(".github/instructions/ discovery", () => {
|
|
24
|
+
it("should discover .github/instructions/*.instructions.md files", () => {
|
|
25
|
+
// #given .github/instructions/ with valid files
|
|
26
|
+
const instructionsDir = join(TEST_DIR, ".github", "instructions");
|
|
27
|
+
mkdirSync(instructionsDir, { recursive: true });
|
|
28
|
+
writeFileSync(
|
|
29
|
+
join(instructionsDir, "typescript.instructions.md"),
|
|
30
|
+
"TS rules"
|
|
31
|
+
);
|
|
32
|
+
writeFileSync(
|
|
33
|
+
join(instructionsDir, "python.instructions.md"),
|
|
34
|
+
"PY rules"
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const srcDir = join(TEST_DIR, "src");
|
|
38
|
+
mkdirSync(srcDir, { recursive: true });
|
|
39
|
+
const currentFile = join(srcDir, "index.ts");
|
|
40
|
+
writeFileSync(currentFile, "code");
|
|
41
|
+
|
|
42
|
+
// #when finding rules for a file
|
|
43
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
44
|
+
|
|
45
|
+
// #then should find both instruction files
|
|
46
|
+
const paths = candidates.map((c) => c.path);
|
|
47
|
+
expect(
|
|
48
|
+
paths.some((p) => p.includes("typescript.instructions.md"))
|
|
49
|
+
).toBe(true);
|
|
50
|
+
expect(paths.some((p) => p.includes("python.instructions.md"))).toBe(
|
|
51
|
+
true
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should ignore non-.instructions.md files in .github/instructions/", () => {
|
|
56
|
+
// #given .github/instructions/ with invalid files
|
|
57
|
+
const instructionsDir = join(TEST_DIR, ".github", "instructions");
|
|
58
|
+
mkdirSync(instructionsDir, { recursive: true });
|
|
59
|
+
writeFileSync(
|
|
60
|
+
join(instructionsDir, "valid.instructions.md"),
|
|
61
|
+
"valid"
|
|
62
|
+
);
|
|
63
|
+
writeFileSync(join(instructionsDir, "invalid.md"), "invalid");
|
|
64
|
+
writeFileSync(join(instructionsDir, "readme.txt"), "readme");
|
|
65
|
+
|
|
66
|
+
const currentFile = join(TEST_DIR, "index.ts");
|
|
67
|
+
writeFileSync(currentFile, "code");
|
|
68
|
+
|
|
69
|
+
// #when finding rules
|
|
70
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
71
|
+
|
|
72
|
+
// #then should only find .instructions.md file
|
|
73
|
+
const paths = candidates.map((c) => c.path);
|
|
74
|
+
expect(paths.some((p) => p.includes("valid.instructions.md"))).toBe(
|
|
75
|
+
true
|
|
76
|
+
);
|
|
77
|
+
expect(paths.some((p) => p.endsWith("invalid.md"))).toBe(false);
|
|
78
|
+
expect(paths.some((p) => p.includes("readme.txt"))).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should discover nested .instructions.md files in subdirectories", () => {
|
|
82
|
+
// #given nested .github/instructions/ structure
|
|
83
|
+
const instructionsDir = join(TEST_DIR, ".github", "instructions");
|
|
84
|
+
const frontendDir = join(instructionsDir, "frontend");
|
|
85
|
+
mkdirSync(frontendDir, { recursive: true });
|
|
86
|
+
writeFileSync(
|
|
87
|
+
join(frontendDir, "react.instructions.md"),
|
|
88
|
+
"React rules"
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const currentFile = join(TEST_DIR, "app.tsx");
|
|
92
|
+
writeFileSync(currentFile, "code");
|
|
93
|
+
|
|
94
|
+
// #when finding rules
|
|
95
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
96
|
+
|
|
97
|
+
// #then should find nested instruction file
|
|
98
|
+
const paths = candidates.map((c) => c.path);
|
|
99
|
+
expect(paths.some((p) => p.includes("react.instructions.md"))).toBe(
|
|
100
|
+
true
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe(".github/copilot-instructions.md (single file)", () => {
|
|
106
|
+
it("should discover copilot-instructions.md at project root", () => {
|
|
107
|
+
// #given .github/copilot-instructions.md at root
|
|
108
|
+
const githubDir = join(TEST_DIR, ".github");
|
|
109
|
+
mkdirSync(githubDir, { recursive: true });
|
|
110
|
+
writeFileSync(
|
|
111
|
+
join(githubDir, "copilot-instructions.md"),
|
|
112
|
+
"Global instructions"
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const currentFile = join(TEST_DIR, "index.ts");
|
|
116
|
+
writeFileSync(currentFile, "code");
|
|
117
|
+
|
|
118
|
+
// #when finding rules
|
|
119
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
120
|
+
|
|
121
|
+
// #then should find the single file rule
|
|
122
|
+
const singleFile = candidates.find((c) =>
|
|
123
|
+
c.path.includes("copilot-instructions.md")
|
|
124
|
+
);
|
|
125
|
+
expect(singleFile).toBeDefined();
|
|
126
|
+
expect(singleFile?.isSingleFile).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("should mark single file rules with isSingleFile: true", () => {
|
|
130
|
+
// #given copilot-instructions.md
|
|
131
|
+
const githubDir = join(TEST_DIR, ".github");
|
|
132
|
+
mkdirSync(githubDir, { recursive: true });
|
|
133
|
+
writeFileSync(
|
|
134
|
+
join(githubDir, "copilot-instructions.md"),
|
|
135
|
+
"Instructions"
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const currentFile = join(TEST_DIR, "file.ts");
|
|
139
|
+
writeFileSync(currentFile, "code");
|
|
140
|
+
|
|
141
|
+
// #when finding rules
|
|
142
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
143
|
+
|
|
144
|
+
// #then isSingleFile should be true
|
|
145
|
+
const copilotFile = candidates.find((c) => c.isSingleFile);
|
|
146
|
+
expect(copilotFile).toBeDefined();
|
|
147
|
+
expect(copilotFile?.path).toContain("copilot-instructions.md");
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("should set distance to 0 for single file rules", () => {
|
|
151
|
+
// #given copilot-instructions.md at project root
|
|
152
|
+
const githubDir = join(TEST_DIR, ".github");
|
|
153
|
+
mkdirSync(githubDir, { recursive: true });
|
|
154
|
+
writeFileSync(
|
|
155
|
+
join(githubDir, "copilot-instructions.md"),
|
|
156
|
+
"Instructions"
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const srcDir = join(TEST_DIR, "src", "deep", "nested");
|
|
160
|
+
mkdirSync(srcDir, { recursive: true });
|
|
161
|
+
const currentFile = join(srcDir, "file.ts");
|
|
162
|
+
writeFileSync(currentFile, "code");
|
|
163
|
+
|
|
164
|
+
// #when finding rules from deeply nested file
|
|
165
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
166
|
+
|
|
167
|
+
// #then single file should have distance 0
|
|
168
|
+
const copilotFile = candidates.find((c) => c.isSingleFile);
|
|
169
|
+
expect(copilotFile?.distance).toBe(0);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("backward compatibility", () => {
|
|
174
|
+
it("should still discover .claude/rules/ files", () => {
|
|
175
|
+
// #given .claude/rules/ directory
|
|
176
|
+
const rulesDir = join(TEST_DIR, ".claude", "rules");
|
|
177
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
178
|
+
writeFileSync(join(rulesDir, "typescript.md"), "TS rules");
|
|
179
|
+
|
|
180
|
+
const currentFile = join(TEST_DIR, "index.ts");
|
|
181
|
+
writeFileSync(currentFile, "code");
|
|
182
|
+
|
|
183
|
+
// #when finding rules
|
|
184
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
185
|
+
|
|
186
|
+
// #then should find claude rules
|
|
187
|
+
const paths = candidates.map((c) => c.path);
|
|
188
|
+
expect(paths.some((p) => p.includes(".claude/rules/"))).toBe(true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should still discover .cursor/rules/ files", () => {
|
|
192
|
+
// #given .cursor/rules/ directory
|
|
193
|
+
const rulesDir = join(TEST_DIR, ".cursor", "rules");
|
|
194
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
195
|
+
writeFileSync(join(rulesDir, "python.md"), "PY rules");
|
|
196
|
+
|
|
197
|
+
const currentFile = join(TEST_DIR, "main.py");
|
|
198
|
+
writeFileSync(currentFile, "code");
|
|
199
|
+
|
|
200
|
+
// #when finding rules
|
|
201
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
202
|
+
|
|
203
|
+
// #then should find cursor rules
|
|
204
|
+
const paths = candidates.map((c) => c.path);
|
|
205
|
+
expect(paths.some((p) => p.includes(".cursor/rules/"))).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should discover .mdc files in rule directories", () => {
|
|
209
|
+
// #given .mdc file in .claude/rules/
|
|
210
|
+
const rulesDir = join(TEST_DIR, ".claude", "rules");
|
|
211
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
212
|
+
writeFileSync(join(rulesDir, "advanced.mdc"), "MDC rules");
|
|
213
|
+
|
|
214
|
+
const currentFile = join(TEST_DIR, "app.ts");
|
|
215
|
+
writeFileSync(currentFile, "code");
|
|
216
|
+
|
|
217
|
+
// #when finding rules
|
|
218
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
219
|
+
|
|
220
|
+
// #then should find .mdc file
|
|
221
|
+
const paths = candidates.map((c) => c.path);
|
|
222
|
+
expect(paths.some((p) => p.endsWith("advanced.mdc"))).toBe(true);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("mixed sources", () => {
|
|
227
|
+
it("should discover rules from all sources", () => {
|
|
228
|
+
// #given rules in multiple directories
|
|
229
|
+
const claudeRules = join(TEST_DIR, ".claude", "rules");
|
|
230
|
+
const cursorRules = join(TEST_DIR, ".cursor", "rules");
|
|
231
|
+
const githubInstructions = join(TEST_DIR, ".github", "instructions");
|
|
232
|
+
const githubDir = join(TEST_DIR, ".github");
|
|
233
|
+
|
|
234
|
+
mkdirSync(claudeRules, { recursive: true });
|
|
235
|
+
mkdirSync(cursorRules, { recursive: true });
|
|
236
|
+
mkdirSync(githubInstructions, { recursive: true });
|
|
237
|
+
|
|
238
|
+
writeFileSync(join(claudeRules, "claude.md"), "claude");
|
|
239
|
+
writeFileSync(join(cursorRules, "cursor.md"), "cursor");
|
|
240
|
+
writeFileSync(
|
|
241
|
+
join(githubInstructions, "copilot.instructions.md"),
|
|
242
|
+
"copilot"
|
|
243
|
+
);
|
|
244
|
+
writeFileSync(join(githubDir, "copilot-instructions.md"), "global");
|
|
245
|
+
|
|
246
|
+
const currentFile = join(TEST_DIR, "index.ts");
|
|
247
|
+
writeFileSync(currentFile, "code");
|
|
248
|
+
|
|
249
|
+
// #when finding rules
|
|
250
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
251
|
+
|
|
252
|
+
// #then should find all rules
|
|
253
|
+
expect(candidates.length).toBeGreaterThanOrEqual(4);
|
|
254
|
+
const paths = candidates.map((c) => c.path);
|
|
255
|
+
expect(paths.some((p) => p.includes(".claude/rules/"))).toBe(true);
|
|
256
|
+
expect(paths.some((p) => p.includes(".cursor/rules/"))).toBe(true);
|
|
257
|
+
expect(paths.some((p) => p.includes(".github/instructions/"))).toBe(
|
|
258
|
+
true
|
|
259
|
+
);
|
|
260
|
+
expect(paths.some((p) => p.includes("copilot-instructions.md"))).toBe(
|
|
261
|
+
true
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should not duplicate single file rules", () => {
|
|
266
|
+
// #given copilot-instructions.md
|
|
267
|
+
const githubDir = join(TEST_DIR, ".github");
|
|
268
|
+
mkdirSync(githubDir, { recursive: true });
|
|
269
|
+
writeFileSync(
|
|
270
|
+
join(githubDir, "copilot-instructions.md"),
|
|
271
|
+
"Instructions"
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
const currentFile = join(TEST_DIR, "file.ts");
|
|
275
|
+
writeFileSync(currentFile, "code");
|
|
276
|
+
|
|
277
|
+
// #when finding rules
|
|
278
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
279
|
+
|
|
280
|
+
// #then should only have one copilot-instructions.md entry
|
|
281
|
+
const copilotFiles = candidates.filter((c) =>
|
|
282
|
+
c.path.includes("copilot-instructions.md")
|
|
283
|
+
);
|
|
284
|
+
expect(copilotFiles.length).toBe(1);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe("user-level rules", () => {
|
|
289
|
+
it("should discover user-level .claude/rules/ files", () => {
|
|
290
|
+
// #given user-level rules
|
|
291
|
+
const userRulesDir = join(homeDir, ".claude", "rules");
|
|
292
|
+
mkdirSync(userRulesDir, { recursive: true });
|
|
293
|
+
writeFileSync(join(userRulesDir, "global.md"), "Global user rules");
|
|
294
|
+
|
|
295
|
+
const currentFile = join(TEST_DIR, "app.ts");
|
|
296
|
+
writeFileSync(currentFile, "code");
|
|
297
|
+
|
|
298
|
+
// #when finding rules
|
|
299
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
300
|
+
|
|
301
|
+
// #then should find user-level rules
|
|
302
|
+
const userRule = candidates.find((c) => c.isGlobal);
|
|
303
|
+
expect(userRule).toBeDefined();
|
|
304
|
+
expect(userRule?.path).toContain("global.md");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("should mark user-level rules as isGlobal: true", () => {
|
|
308
|
+
// #given user-level rules
|
|
309
|
+
const userRulesDir = join(homeDir, ".claude", "rules");
|
|
310
|
+
mkdirSync(userRulesDir, { recursive: true });
|
|
311
|
+
writeFileSync(join(userRulesDir, "user.md"), "User rules");
|
|
312
|
+
|
|
313
|
+
const currentFile = join(TEST_DIR, "app.ts");
|
|
314
|
+
writeFileSync(currentFile, "code");
|
|
315
|
+
|
|
316
|
+
// #when finding rules
|
|
317
|
+
const candidates = findRuleFiles(TEST_DIR, homeDir, currentFile);
|
|
318
|
+
|
|
319
|
+
// #then isGlobal should be true
|
|
320
|
+
const userRule = candidates.find((c) => c.path.includes("user.md"));
|
|
321
|
+
expect(userRule?.isGlobal).toBe(true);
|
|
322
|
+
expect(userRule?.distance).toBe(9999);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("findProjectRoot", () => {
|
|
328
|
+
const TEST_DIR = join(tmpdir(), `project-root-test-${Date.now()}`);
|
|
329
|
+
|
|
330
|
+
beforeEach(() => {
|
|
331
|
+
mkdirSync(TEST_DIR, { recursive: true });
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
afterEach(() => {
|
|
335
|
+
if (existsSync(TEST_DIR)) {
|
|
336
|
+
rmSync(TEST_DIR, { recursive: true, force: true });
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should find project root with .git directory", () => {
|
|
341
|
+
// #given directory with .git
|
|
342
|
+
mkdirSync(join(TEST_DIR, ".git"), { recursive: true });
|
|
343
|
+
const nestedFile = join(TEST_DIR, "src", "components", "Button.tsx");
|
|
344
|
+
mkdirSync(join(TEST_DIR, "src", "components"), { recursive: true });
|
|
345
|
+
writeFileSync(nestedFile, "code");
|
|
346
|
+
|
|
347
|
+
// #when finding project root from nested file
|
|
348
|
+
const root = findProjectRoot(nestedFile);
|
|
349
|
+
|
|
350
|
+
// #then should return the directory with .git
|
|
351
|
+
expect(root).toBe(TEST_DIR);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should find project root with package.json", () => {
|
|
355
|
+
// #given directory with package.json
|
|
356
|
+
writeFileSync(join(TEST_DIR, "package.json"), "{}");
|
|
357
|
+
const nestedFile = join(TEST_DIR, "lib", "index.js");
|
|
358
|
+
mkdirSync(join(TEST_DIR, "lib"), { recursive: true });
|
|
359
|
+
writeFileSync(nestedFile, "code");
|
|
360
|
+
|
|
361
|
+
// #when finding project root
|
|
362
|
+
const root = findProjectRoot(nestedFile);
|
|
363
|
+
|
|
364
|
+
// #then should find the package.json directory
|
|
365
|
+
expect(root).toBe(TEST_DIR);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("should return null when no project markers found", () => {
|
|
369
|
+
// #given directory without any project markers
|
|
370
|
+
const isolatedDir = join(TEST_DIR, "isolated");
|
|
371
|
+
mkdirSync(isolatedDir, { recursive: true });
|
|
372
|
+
const file = join(isolatedDir, "file.txt");
|
|
373
|
+
writeFileSync(file, "content");
|
|
374
|
+
|
|
375
|
+
// #when finding project root
|
|
376
|
+
const root = findProjectRoot(file);
|
|
377
|
+
|
|
378
|
+
// #then should return null
|
|
379
|
+
expect(root).toBeNull();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
readdirSync,
|
|
4
|
+
realpathSync,
|
|
5
|
+
statSync,
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import { dirname, join, relative } from "node:path";
|
|
8
|
+
import {
|
|
9
|
+
GITHUB_INSTRUCTIONS_PATTERN,
|
|
10
|
+
PROJECT_MARKERS,
|
|
11
|
+
PROJECT_RULE_FILES,
|
|
12
|
+
PROJECT_RULE_SUBDIRS,
|
|
13
|
+
RULE_EXTENSIONS,
|
|
14
|
+
USER_RULE_DIR,
|
|
15
|
+
} from "./constants";
|
|
16
|
+
import type { RuleFileCandidate } from "./types";
|
|
17
|
+
|
|
18
|
+
function isGitHubInstructionsDir(dir: string): boolean {
|
|
19
|
+
return dir.includes(".github/instructions") || dir.endsWith(".github/instructions");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isValidRuleFile(fileName: string, dir: string): boolean {
|
|
23
|
+
if (isGitHubInstructionsDir(dir)) {
|
|
24
|
+
return GITHUB_INSTRUCTIONS_PATTERN.test(fileName);
|
|
25
|
+
}
|
|
26
|
+
return RULE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Find project root by walking up from startPath.
|
|
31
|
+
* Checks for PROJECT_MARKERS (.git, pyproject.toml, package.json, etc.)
|
|
32
|
+
*
|
|
33
|
+
* @param startPath - Starting path to search from (file or directory)
|
|
34
|
+
* @returns Project root path or null if not found
|
|
35
|
+
*/
|
|
36
|
+
export function findProjectRoot(startPath: string): string | null {
|
|
37
|
+
let current: string;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const stat = statSync(startPath);
|
|
41
|
+
current = stat.isDirectory() ? startPath : dirname(startPath);
|
|
42
|
+
} catch {
|
|
43
|
+
current = dirname(startPath);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
while (true) {
|
|
47
|
+
for (const marker of PROJECT_MARKERS) {
|
|
48
|
+
const markerPath = join(current, marker);
|
|
49
|
+
if (existsSync(markerPath)) {
|
|
50
|
+
return current;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const parent = dirname(current);
|
|
55
|
+
if (parent === current) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
current = parent;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Recursively find all rule files (*.md, *.mdc) in a directory
|
|
64
|
+
*
|
|
65
|
+
* @param dir - Directory to search
|
|
66
|
+
* @param results - Array to accumulate results
|
|
67
|
+
*/
|
|
68
|
+
function findRuleFilesRecursive(dir: string, results: string[]): void {
|
|
69
|
+
if (!existsSync(dir)) return;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
const fullPath = join(dir, entry.name);
|
|
75
|
+
|
|
76
|
+
if (entry.isDirectory()) {
|
|
77
|
+
findRuleFilesRecursive(fullPath, results);
|
|
78
|
+
} else if (entry.isFile()) {
|
|
79
|
+
if (isValidRuleFile(entry.name, dir)) {
|
|
80
|
+
results.push(fullPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Permission denied or other errors - silently skip
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Resolve symlinks safely with fallback to original path
|
|
91
|
+
*
|
|
92
|
+
* @param filePath - Path to resolve
|
|
93
|
+
* @returns Real path or original path if resolution fails
|
|
94
|
+
*/
|
|
95
|
+
function safeRealpathSync(filePath: string): string {
|
|
96
|
+
try {
|
|
97
|
+
return realpathSync(filePath);
|
|
98
|
+
} catch {
|
|
99
|
+
return filePath;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Calculate directory distance between a rule file and current file.
|
|
105
|
+
* Distance is based on common ancestor within project root.
|
|
106
|
+
*
|
|
107
|
+
* @param rulePath - Path to the rule file
|
|
108
|
+
* @param currentFile - Path to the current file being edited
|
|
109
|
+
* @param projectRoot - Project root for relative path calculation
|
|
110
|
+
* @returns Distance (0 = same directory, higher = further)
|
|
111
|
+
*/
|
|
112
|
+
export function calculateDistance(
|
|
113
|
+
rulePath: string,
|
|
114
|
+
currentFile: string,
|
|
115
|
+
projectRoot: string | null,
|
|
116
|
+
): number {
|
|
117
|
+
if (!projectRoot) {
|
|
118
|
+
return 9999;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const ruleDir = dirname(rulePath);
|
|
123
|
+
const currentDir = dirname(currentFile);
|
|
124
|
+
|
|
125
|
+
const ruleRel = relative(projectRoot, ruleDir);
|
|
126
|
+
const currentRel = relative(projectRoot, currentDir);
|
|
127
|
+
|
|
128
|
+
// Handle paths outside project root
|
|
129
|
+
if (ruleRel.startsWith("..") || currentRel.startsWith("..")) {
|
|
130
|
+
return 9999;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Split by both forward and back slashes for cross-platform compatibility
|
|
134
|
+
// path.relative() returns OS-native separators (backslashes on Windows)
|
|
135
|
+
const ruleParts = ruleRel ? ruleRel.split(/[/\\]/) : [];
|
|
136
|
+
const currentParts = currentRel ? currentRel.split(/[/\\]/) : [];
|
|
137
|
+
|
|
138
|
+
// Find common prefix length
|
|
139
|
+
let common = 0;
|
|
140
|
+
for (let i = 0; i < Math.min(ruleParts.length, currentParts.length); i++) {
|
|
141
|
+
if (ruleParts[i] === currentParts[i]) {
|
|
142
|
+
common++;
|
|
143
|
+
} else {
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Distance is how many directories up from current file to common ancestor
|
|
149
|
+
return currentParts.length - common;
|
|
150
|
+
} catch {
|
|
151
|
+
return 9999;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Find all rule files for a given context.
|
|
157
|
+
* Searches from currentFile upward to projectRoot for rule directories,
|
|
158
|
+
* then user-level directory (~/.claude/rules).
|
|
159
|
+
*
|
|
160
|
+
* IMPORTANT: This searches EVERY directory from file to project root.
|
|
161
|
+
* Not just the project root itself.
|
|
162
|
+
*
|
|
163
|
+
* @param projectRoot - Project root path (or null if outside any project)
|
|
164
|
+
* @param homeDir - User home directory
|
|
165
|
+
* @param currentFile - Current file being edited (for distance calculation)
|
|
166
|
+
* @returns Array of rule file candidates sorted by distance
|
|
167
|
+
*/
|
|
168
|
+
export function findRuleFiles(
|
|
169
|
+
projectRoot: string | null,
|
|
170
|
+
homeDir: string,
|
|
171
|
+
currentFile: string,
|
|
172
|
+
): RuleFileCandidate[] {
|
|
173
|
+
const candidates: RuleFileCandidate[] = [];
|
|
174
|
+
const seenRealPaths = new Set<string>();
|
|
175
|
+
|
|
176
|
+
// Search from current file's directory up to project root
|
|
177
|
+
let currentDir = dirname(currentFile);
|
|
178
|
+
let distance = 0;
|
|
179
|
+
|
|
180
|
+
while (true) {
|
|
181
|
+
// Search rule directories in current directory
|
|
182
|
+
for (const [parent, subdir] of PROJECT_RULE_SUBDIRS) {
|
|
183
|
+
const ruleDir = join(currentDir, parent, subdir);
|
|
184
|
+
const files: string[] = [];
|
|
185
|
+
findRuleFilesRecursive(ruleDir, files);
|
|
186
|
+
|
|
187
|
+
for (const filePath of files) {
|
|
188
|
+
const realPath = safeRealpathSync(filePath);
|
|
189
|
+
if (seenRealPaths.has(realPath)) continue;
|
|
190
|
+
seenRealPaths.add(realPath);
|
|
191
|
+
|
|
192
|
+
candidates.push({
|
|
193
|
+
path: filePath,
|
|
194
|
+
realPath,
|
|
195
|
+
isGlobal: false,
|
|
196
|
+
distance,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Stop at project root or filesystem root
|
|
202
|
+
if (projectRoot && currentDir === projectRoot) break;
|
|
203
|
+
const parentDir = dirname(currentDir);
|
|
204
|
+
if (parentDir === currentDir) break;
|
|
205
|
+
currentDir = parentDir;
|
|
206
|
+
distance++;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check for single-file rules at project root (e.g., .github/copilot-instructions.md)
|
|
210
|
+
if (projectRoot) {
|
|
211
|
+
for (const ruleFile of PROJECT_RULE_FILES) {
|
|
212
|
+
const filePath = join(projectRoot, ruleFile);
|
|
213
|
+
if (existsSync(filePath)) {
|
|
214
|
+
try {
|
|
215
|
+
const stat = statSync(filePath);
|
|
216
|
+
if (stat.isFile()) {
|
|
217
|
+
const realPath = safeRealpathSync(filePath);
|
|
218
|
+
if (!seenRealPaths.has(realPath)) {
|
|
219
|
+
seenRealPaths.add(realPath);
|
|
220
|
+
candidates.push({
|
|
221
|
+
path: filePath,
|
|
222
|
+
realPath,
|
|
223
|
+
isGlobal: false,
|
|
224
|
+
distance: 0,
|
|
225
|
+
isSingleFile: true,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
// Skip if file can't be read
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Search user-level rule directory (~/.claude/rules)
|
|
237
|
+
const userRuleDir = join(homeDir, USER_RULE_DIR);
|
|
238
|
+
const userFiles: string[] = [];
|
|
239
|
+
findRuleFilesRecursive(userRuleDir, userFiles);
|
|
240
|
+
|
|
241
|
+
for (const filePath of userFiles) {
|
|
242
|
+
const realPath = safeRealpathSync(filePath);
|
|
243
|
+
if (seenRealPaths.has(realPath)) continue;
|
|
244
|
+
seenRealPaths.add(realPath);
|
|
245
|
+
|
|
246
|
+
candidates.push({
|
|
247
|
+
path: filePath,
|
|
248
|
+
realPath,
|
|
249
|
+
isGlobal: true,
|
|
250
|
+
distance: 9999, // Global rules always have max distance
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Sort by distance (closest first, then global rules last)
|
|
255
|
+
candidates.sort((a, b) => {
|
|
256
|
+
if (a.isGlobal !== b.isGlobal) {
|
|
257
|
+
return a.isGlobal ? 1 : -1;
|
|
258
|
+
}
|
|
259
|
+
return a.distance - b.distance;
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
return candidates;
|
|
263
|
+
}
|