@neurocode-ai/core 1.18.8
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/bin/neurocode +199 -0
- package/bunfig.toml +2 -0
- package/drizzle.config.ts +10 -0
- package/package.json +131 -0
- package/schema.json +2071 -0
- package/script/fix-node-pty.ts +28 -0
- package/script/migration.ts +196 -0
- package/src/account/sql.ts +39 -0
- package/src/account.ts +101 -0
- package/src/agent.ts +111 -0
- package/src/aisdk.ts +235 -0
- package/src/background-job.ts +365 -0
- package/src/catalog.ts +301 -0
- package/src/command.ts +64 -0
- package/src/config/agent.ts +25 -0
- package/src/config/attachments.ts +15 -0
- package/src/config/command.ts +12 -0
- package/src/config/compaction.ts +15 -0
- package/src/config/experimental.ts +18 -0
- package/src/config/formatter.ts +12 -0
- package/src/config/lsp.ts +18 -0
- package/src/config/markdown.ts +36 -0
- package/src/config/mcp.ts +48 -0
- package/src/config/plugin/agent.ts +179 -0
- package/src/config/plugin/command.ts +83 -0
- package/src/config/plugin/external.ts +91 -0
- package/src/config/plugin/provider.ts +113 -0
- package/src/config/plugin/reference.ts +70 -0
- package/src/config/plugin/skill.ts +50 -0
- package/src/config/plugin.ts +13 -0
- package/src/config/provider.ts +71 -0
- package/src/config/reference.ts +22 -0
- package/src/config/tool-output.ts +9 -0
- package/src/config/watcher.ts +7 -0
- package/src/config.ts +227 -0
- package/src/control-plane/move-session.ts +148 -0
- package/src/control-plane/workspace.sql.ts +20 -0
- package/src/credential/sql.ts +14 -0
- package/src/credential.ts +138 -0
- package/src/cross-spawn-spawner.ts +507 -0
- package/src/data-migration.sql.ts +6 -0
- package/src/database/database.ts +57 -0
- package/src/database/migration/20260127222353_familiar_lady_ursula.ts +107 -0
- package/src/database/migration/20260211171708_add_project_commands.ts +11 -0
- package/src/database/migration/20260213144116_wakeful_the_professor.ts +23 -0
- package/src/database/migration/20260225215848_workspace.ts +19 -0
- package/src/database/migration/20260227213759_add_session_workspace_id.ts +12 -0
- package/src/database/migration/20260228203230_blue_harpoon.ts +30 -0
- package/src/database/migration/20260303231226_add_workspace_fields.ts +15 -0
- package/src/database/migration/20260309230000_move_org_to_state.ts +15 -0
- package/src/database/migration/20260312043431_session_message_cursor.ts +16 -0
- package/src/database/migration/20260323234822_events.ts +26 -0
- package/src/database/migration/20260410174513_workspace-name.ts +29 -0
- package/src/database/migration/20260413175956_chief_energizer.ts +24 -0
- package/src/database/migration/20260423070820_add_icon_url_override.ts +14 -0
- package/src/database/migration/20260427172553_slow_nightmare.ts +30 -0
- package/src/database/migration/20260428004200_add_session_path.ts +11 -0
- package/src/database/migration/20260501142318_next_venus.ts +12 -0
- package/src/database/migration/20260504145000_add_sync_owner.ts +11 -0
- package/src/database/migration/20260507164347_add_workspace_time.ts +11 -0
- package/src/database/migration/20260510033149_session_usage.ts +56 -0
- package/src/database/migration/20260511000411_data_migration_state.ts +16 -0
- package/src/database/migration/20260511173437_session-metadata.ts +16 -0
- package/src/database/migration/20260601010001_normalize_storage_paths.ts +22 -0
- package/src/database/migration/20260601202201_amazing_prowler.ts +11 -0
- package/src/database/migration/20260602002951_lowly_union_jack.ts +24 -0
- package/src/database/migration/20260602182828_add_project_directories.ts +20 -0
- package/src/database/migration/20260603001617_session_message_projection_indexes.ts +19 -0
- package/src/database/migration/20260603040000_session_message_projection_order.ts +19 -0
- package/src/database/migration/20260603141458_session_input_inbox.ts +25 -0
- package/src/database/migration/20260603160727_jittery_ezekiel_stane.ts +20 -0
- package/src/database/migration/20260604172448_event_sourced_session_input.ts +47 -0
- package/src/database/migration/20260605003541_add_session_context_snapshot.ts +21 -0
- package/src/database/migration/20260605042240_add_context_epoch_agent.ts +11 -0
- package/src/database/migration/20260611035744_credential.ts +25 -0
- package/src/database/migration/20260611192811_lush_chimera.ts +25 -0
- package/src/database/migration/20260612174303_project_dir_strategy.ts +29 -0
- package/src/database/migration/20260622142730_simplify_session_context_epoch.ts +13 -0
- package/src/database/migration/20260622170816_reset_v2_session_state.ts +17 -0
- package/src/database/migration/20260622202450_simplify_session_input.ts +17 -0
- package/src/database/migration.gen.ts +44 -0
- package/src/database/migration.ts +81 -0
- package/src/database/path.ts +91 -0
- package/src/database/schema.gen.ts +274 -0
- package/src/database/schema.sql.ts +10 -0
- package/src/database/sqlite.bun.ts +183 -0
- package/src/database/sqlite.node.ts +178 -0
- package/src/database/sqlite.ts +8 -0
- package/src/effect/app-node-builder.ts +23 -0
- package/src/effect/app-node-platform.ts +18 -0
- package/src/effect/app-node.ts +14 -0
- package/src/effect/dfdf +1 -0
- package/src/effect/keyed-mutex.ts +45 -0
- package/src/effect/layer-node.ts +333 -0
- package/src/effect/memo-map.ts +3 -0
- package/src/effect/runtime.ts +21 -0
- package/src/effect/service-use.ts +43 -0
- package/src/event/sql.ts +25 -0
- package/src/event.ts +638 -0
- package/src/file-mutation.ts +207 -0
- package/src/file.ts +6 -0
- package/src/filesystem/fff.bun.ts +140 -0
- package/src/filesystem/fff.node.ts +138 -0
- package/src/filesystem/ignore.ts +67 -0
- package/src/filesystem/protected.ts +53 -0
- package/src/filesystem/search.ts +239 -0
- package/src/filesystem/watcher.ts +140 -0
- package/src/filesystem.ts +118 -0
- package/src/flag/flag.ts +78 -0
- package/src/fs-util.ts +274 -0
- package/src/git.ts +987 -0
- package/src/github-copilot/README.md +5 -0
- package/src/github-copilot/chat/convert-to-openai-compatible-chat-messages.ts +170 -0
- package/src/github-copilot/chat/get-response-metadata.ts +15 -0
- package/src/github-copilot/chat/map-openai-compatible-finish-reason.ts +19 -0
- package/src/github-copilot/chat/openai-compatible-api-types.ts +64 -0
- package/src/github-copilot/chat/openai-compatible-chat-language-model.ts +815 -0
- package/src/github-copilot/chat/openai-compatible-chat-options.ts +28 -0
- package/src/github-copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
- package/src/github-copilot/chat/openai-compatible-prepare-tools.ts +83 -0
- package/src/github-copilot/copilot-provider.ts +100 -0
- package/src/github-copilot/openai-compatible-error.ts +27 -0
- package/src/github-copilot/responses/convert-to-openai-responses-input.ts +335 -0
- package/src/github-copilot/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/github-copilot/responses/openai-config.ts +18 -0
- package/src/github-copilot/responses/openai-error.ts +22 -0
- package/src/github-copilot/responses/openai-responses-api-types.ts +214 -0
- package/src/github-copilot/responses/openai-responses-language-model.ts +1770 -0
- package/src/github-copilot/responses/openai-responses-prepare-tools.ts +173 -0
- package/src/github-copilot/responses/openai-responses-settings.ts +1 -0
- package/src/github-copilot/responses/tool/code-interpreter.ts +87 -0
- package/src/github-copilot/responses/tool/file-search.ts +127 -0
- package/src/github-copilot/responses/tool/image-generation.ts +114 -0
- package/src/github-copilot/responses/tool/local-shell.ts +64 -0
- package/src/github-copilot/responses/tool/web-search-preview.ts +103 -0
- package/src/github-copilot/responses/tool/web-search.ts +102 -0
- package/src/global.ts +87 -0
- package/src/id/id.ts +47 -0
- package/src/image/photon.ts +94 -0
- package/src/image.ts +81 -0
- package/src/installation/version.ts +8 -0
- package/src/instruction-context.ts +101 -0
- package/src/integration/connection.ts +12 -0
- package/src/integration.ts +520 -0
- package/src/location-mutation.ts +162 -0
- package/src/location-service-map.ts +18 -0
- package/src/location-services.ts +115 -0
- package/src/location.ts +39 -0
- package/src/markdown.d.ts +4 -0
- package/src/model.ts +41 -0
- package/src/models-dev.ts +260 -0
- package/src/npm-config.ts +40 -0
- package/src/npm.ts +269 -0
- package/src/oauth/page.ts +276 -0
- package/src/observability/logging.ts +71 -0
- package/src/observability/otlp.ts +79 -0
- package/src/observability/shared.ts +1 -0
- package/src/observability.ts +24 -0
- package/src/patch.ts +197 -0
- package/src/permission/saved.ts +79 -0
- package/src/permission/sql.ts +20 -0
- package/src/permission.ts +310 -0
- package/src/plugin/agent.ts +206 -0
- package/src/plugin/command/initialize.txt +65 -0
- package/src/plugin/command/review.txt +100 -0
- package/src/plugin/command.ts +25 -0
- package/src/plugin/host.ts +219 -0
- package/src/plugin/internal.ts +153 -0
- package/src/plugin/layer-map.example.ts +94 -0
- package/src/plugin/models-dev.ts +183 -0
- package/src/plugin/promise.ts +93 -0
- package/src/plugin/provider/alibaba.ts +15 -0
- package/src/plugin/provider/amazon-bedrock.ts +126 -0
- package/src/plugin/provider/anthropic.ts +27 -0
- package/src/plugin/provider/azure.ts +82 -0
- package/src/plugin/provider/cerebras.ts +26 -0
- package/src/plugin/provider/cloudflare-ai-gateway.ts +81 -0
- package/src/plugin/provider/cloudflare-workers-ai.ts +81 -0
- package/src/plugin/provider/cohere.ts +15 -0
- package/src/plugin/provider/deepinfra.ts +15 -0
- package/src/plugin/provider/dynamic.ts +31 -0
- package/src/plugin/provider/gateway.ts +15 -0
- package/src/plugin/provider/github-copilot.ts +52 -0
- package/src/plugin/provider/gitlab.ts +65 -0
- package/src/plugin/provider/google-vertex.ts +173 -0
- package/src/plugin/provider/google.ts +15 -0
- package/src/plugin/provider/groq.ts +15 -0
- package/src/plugin/provider/kilo.ts +21 -0
- package/src/plugin/provider/llmgateway.ts +26 -0
- package/src/plugin/provider/mistral.ts +15 -0
- package/src/plugin/provider/nvidia.ts +22 -0
- package/src/plugin/provider/openai-compatible.ts +17 -0
- package/src/plugin/provider/openai.ts +292 -0
- package/src/plugin/provider/opencode.ts +311 -0
- package/src/plugin/provider/openrouter.ts +36 -0
- package/src/plugin/provider/perplexity.ts +15 -0
- package/src/plugin/provider/sap-ai-core.ts +46 -0
- package/src/plugin/provider/snowflake-cortex.ts +89 -0
- package/src/plugin/provider/togetherai.ts +15 -0
- package/src/plugin/provider/venice.ts +15 -0
- package/src/plugin/provider/vercel.ts +27 -0
- package/src/plugin/provider/xai.ts +22 -0
- package/src/plugin/provider/zenmux.ts +21 -0
- package/src/plugin/provider.ts +71 -0
- package/src/plugin/skill/customize-neurocode.md +452 -0
- package/src/plugin/skill.ts +31 -0
- package/src/plugin/variant.ts +39 -0
- package/src/plugin.ts +167 -0
- package/src/policy.ts +49 -0
- package/src/process.ts +261 -0
- package/src/project/copy-strategies.ts +35 -0
- package/src/project/copy.ts +292 -0
- package/src/project/directories.ts +158 -0
- package/src/project/schema.ts +16 -0
- package/src/project/sql.ts +35 -0
- package/src/project.ts +136 -0
- package/src/provider.ts +25 -0
- package/src/pty/protocol.ts +37 -0
- package/src/pty/pty.bun.ts +26 -0
- package/src/pty/pty.node.ts +29 -0
- package/src/pty/pty.ts +25 -0
- package/src/pty/schema.ts +1 -0
- package/src/pty/ticket.ts +56 -0
- package/src/pty.ts +318 -0
- package/src/public-event-manifest.ts +7 -0
- package/src/question.ts +153 -0
- package/src/reference/guidance.ts +69 -0
- package/src/reference.ts +125 -0
- package/src/repository-cache.ts +259 -0
- package/src/repository.ts +214 -0
- package/src/ripgrep/binary.ts +132 -0
- package/src/ripgrep.ts +281 -0
- package/src/schema.ts +79 -0
- package/src/session/compaction.ts +241 -0
- package/src/session/context-epoch.ts +174 -0
- package/src/session/error.ts +24 -0
- package/src/session/event.ts +2 -0
- package/src/session/execution/local.ts +46 -0
- package/src/session/execution.ts +34 -0
- package/src/session/history.ts +101 -0
- package/src/session/info.ts +50 -0
- package/src/session/input.ts +288 -0
- package/src/session/message-updater.ts +397 -0
- package/src/session/message.ts +2 -0
- package/src/session/projector.ts +458 -0
- package/src/session/prompt.ts +1 -0
- package/src/session/revert.ts +121 -0
- package/src/session/run-coordinator.ts +104 -0
- package/src/session/runner/index.ts +28 -0
- package/src/session/runner/llm.ts +432 -0
- package/src/session/runner/max-steps.ts +16 -0
- package/src/session/runner/model.ts +218 -0
- package/src/session/runner/publish-llm-event.ts +423 -0
- package/src/session/runner/to-llm-message.ts +171 -0
- package/src/session/schema.ts +9 -0
- package/src/session/sql.ts +176 -0
- package/src/session/store.ts +63 -0
- package/src/session/todo.ts +78 -0
- package/src/session.ts +486 -0
- package/src/share/sql.ts +13 -0
- package/src/shell.ts +226 -0
- package/src/skill/discovery.ts +213 -0
- package/src/skill/guidance.ts +76 -0
- package/src/skill.ts +132 -0
- package/src/snapshot.ts +266 -0
- package/src/state.ts +128 -0
- package/src/system-context/builtins.ts +50 -0
- package/src/system-context/index.ts +320 -0
- package/src/system-context/registry.ts +49 -0
- package/src/tool/AGENTS.md +59 -0
- package/src/tool/application-tools.ts +57 -0
- package/src/tool/apply-patch.ts +219 -0
- package/src/tool/bash.ts +207 -0
- package/src/tool/builtins.ts +48 -0
- package/src/tool/edit.ts +223 -0
- package/src/tool/glob.ts +105 -0
- package/src/tool/grep.ts +137 -0
- package/src/tool/http-body.ts +30 -0
- package/src/tool/question.ts +94 -0
- package/src/tool/read-filesystem.ts +366 -0
- package/src/tool/read.ts +117 -0
- package/src/tool/registry.ts +147 -0
- package/src/tool/skill.ts +109 -0
- package/src/tool/todowrite.ts +62 -0
- package/src/tool/tool.ts +162 -0
- package/src/tool/tools.ts +13 -0
- package/src/tool/webfetch.ts +218 -0
- package/src/tool/websearch.ts +260 -0
- package/src/tool/write.ts +101 -0
- package/src/tool-output-store.ts +211 -0
- package/src/util/array.ts +10 -0
- package/src/util/binary.ts +41 -0
- package/src/util/effect-flock.ts +284 -0
- package/src/util/encode.ts +51 -0
- package/src/util/error.ts +70 -0
- package/src/util/flock.ts +358 -0
- package/src/util/glob.ts +34 -0
- package/src/util/hash.ts +11 -0
- package/src/util/identifier.ts +1 -0
- package/src/util/iife.ts +3 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/module.ts +10 -0
- package/src/util/path.ts +37 -0
- package/src/util/retry.ts +42 -0
- package/src/util/slug.ts +74 -0
- package/src/util/token.ts +5 -0
- package/src/util/which.ts +14 -0
- package/src/util/wildcard.ts +14 -0
- package/src/v1/config/agent.ts +89 -0
- package/src/v1/config/attachment.ts +25 -0
- package/src/v1/config/command.ts +13 -0
- package/src/v1/config/config.ts +192 -0
- package/src/v1/config/console-state.ts +16 -0
- package/src/v1/config/error.ts +39 -0
- package/src/v1/config/formatter.ts +13 -0
- package/src/v1/config/layout.ts +6 -0
- package/src/v1/config/lsp.ts +80 -0
- package/src/v1/config/mcp.ts +63 -0
- package/src/v1/config/migrate.ts +254 -0
- package/src/v1/config/permission.ts +50 -0
- package/src/v1/config/plugin.ts +9 -0
- package/src/v1/config/provider-options.ts +227 -0
- package/src/v1/config/provider.ts +121 -0
- package/src/v1/config/server.ts +19 -0
- package/src/v1/config/skills.ts +13 -0
- package/src/v1/permission.ts +33 -0
- package/src/v1/session.ts +68 -0
- package/src/v2-schema.ts +3 -0
- package/src/workspace.ts +6 -0
- package/sst-env.d.ts +10 -0
- package/test/agent.test.ts +131 -0
- package/test/application-tools.test.ts +287 -0
- package/test/background-job.test.ts +106 -0
- package/test/catalog.test.ts +353 -0
- package/test/command.test.ts +56 -0
- package/test/config/agent.test.ts +349 -0
- package/test/config/command.test.ts +83 -0
- package/test/config/config.test.ts +798 -0
- package/test/config/fixtures/plugin/directory-plugin.ts +13 -0
- package/test/config/plugin.test.ts +248 -0
- package/test/config/provider-options.test.ts +224 -0
- package/test/config/provider.test.ts +269 -0
- package/test/config/skill.test.ts +80 -0
- package/test/credential.test.ts +36 -0
- package/test/database-migration.test.ts +644 -0
- package/test/effect/cross-spawn-spawner.test.ts +426 -0
- package/test/effect/keyed-mutex.test.ts +73 -0
- package/test/effect/layer-node/layer-node-types.test.ts +143 -0
- package/test/effect/layer-node/layer-node.test.ts +261 -0
- package/test/effect/layer-node/node-build.test.ts +125 -0
- package/test/effect/observability.test.ts +109 -0
- package/test/event.test.ts +1124 -0
- package/test/file-mutation.test.ts +368 -0
- package/test/filesystem/filesystem.test.ts +387 -0
- package/test/filesystem/ignore.test.ts +10 -0
- package/test/filesystem/search.test.ts +44 -0
- package/test/filesystem/watcher.test.ts +266 -0
- package/test/fixture/effect-flock-worker.ts +60 -0
- package/test/fixture/flock-worker.ts +72 -0
- package/test/fixture/git.ts +49 -0
- package/test/fixture/location.ts +26 -0
- package/test/fixture/tmpdir.ts +25 -0
- package/test/fixtures/recordings/session-runner/openai-chat-streams-text.json +27 -0
- package/test/git.test.ts +164 -0
- package/test/github-copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/github-copilot/copilot-chat-model.test.ts +592 -0
- package/test/github-copilot/openai-responses-language-model.test.ts +206 -0
- package/test/global.test.ts +16 -0
- package/test/instruction-context.test.ts +323 -0
- package/test/integration.test.ts +349 -0
- package/test/legacy-event-schema.test.ts +16 -0
- package/test/lib/effect.ts +53 -0
- package/test/lib/tool.ts +20 -0
- package/test/location-filesystem.test.ts +69 -0
- package/test/location-layer.test.ts +226 -0
- package/test/location-mutation.test.ts +178 -0
- package/test/location.test.ts +42 -0
- package/test/model.test.ts +23 -0
- package/test/models.test.ts +290 -0
- package/test/move-session.test.ts +235 -0
- package/test/npm-config.test.ts +51 -0
- package/test/npm.test.ts +84 -0
- package/test/oauth-page.test.ts +15 -0
- package/test/patch.test.ts +68 -0
- package/test/permission.test.ts +315 -0
- package/test/plugin/command.test.ts +47 -0
- package/test/plugin/fixture.ts +52 -0
- package/test/plugin/fixtures/config-effect-plugin.ts +15 -0
- package/test/plugin/fixtures/config-promise-plugin.ts +13 -0
- package/test/plugin/fixtures/invalid-plugin.ts +1 -0
- package/test/plugin/fixtures/models-dev.json +14 -0
- package/test/plugin/fixtures/provider-factory.ts +9 -0
- package/test/plugin/host.ts +303 -0
- package/test/plugin/models-dev.test.ts +170 -0
- package/test/plugin/promise.test.ts +67 -0
- package/test/plugin/provider-alibaba.test.ts +92 -0
- package/test/plugin/provider-amazon-bedrock.test.ts +622 -0
- package/test/plugin/provider-anthropic.test.ts +94 -0
- package/test/plugin/provider-azure-cognitive-services.test.ts +199 -0
- package/test/plugin/provider-azure.test.ts +293 -0
- package/test/plugin/provider-cerebras.test.ts +136 -0
- package/test/plugin/provider-cloudflare-ai-gateway.test.ts +426 -0
- package/test/plugin/provider-cloudflare-workers-ai.test.ts +279 -0
- package/test/plugin/provider-cohere.test.ts +123 -0
- package/test/plugin/provider-deepinfra.test.ts +159 -0
- package/test/plugin/provider-dynamic.test.ts +184 -0
- package/test/plugin/provider-gateway.test.ts +115 -0
- package/test/plugin/provider-github-copilot.test.ts +278 -0
- package/test/plugin/provider-gitlab.test.ts +308 -0
- package/test/plugin/provider-google-vertex-anthropic.test.ts +264 -0
- package/test/plugin/provider-google-vertex.test.ts +387 -0
- package/test/plugin/provider-google.test.ts +80 -0
- package/test/plugin/provider-groq.test.ts +121 -0
- package/test/plugin/provider-kilo.test.ts +99 -0
- package/test/plugin/provider-llmgateway.test.ts +80 -0
- package/test/plugin/provider-mistral.test.ts +129 -0
- package/test/plugin/provider-nvidia.test.ts +97 -0
- package/test/plugin/provider-openai-compatible.test.ts +107 -0
- package/test/plugin/provider-openai.test.ts +176 -0
- package/test/plugin/provider-opencode.test.ts +423 -0
- package/test/plugin/provider-openrouter.test.ts +113 -0
- package/test/plugin/provider-perplexity.test.ts +122 -0
- package/test/plugin/provider-sap-ai-core.test.ts +159 -0
- package/test/plugin/provider-snowflake-cortex.test.ts +261 -0
- package/test/plugin/provider-togetherai.test.ts +129 -0
- package/test/plugin/provider-venice.test.ts +115 -0
- package/test/plugin/provider-vercel.test.ts +83 -0
- package/test/plugin/provider-xai.test.ts +125 -0
- package/test/plugin/provider-zenmux.test.ts +110 -0
- package/test/plugin/skill.test.ts +25 -0
- package/test/plugin/variant.test.ts +67 -0
- package/test/plugin.test.ts +71 -0
- package/test/policy.test.ts +85 -0
- package/test/preload.ts +5 -0
- package/test/process/process.test.ts +368 -0
- package/test/project-copy.test.ts +400 -0
- package/test/project-directories.test.ts +61 -0
- package/test/project.test.ts +221 -0
- package/test/provider-mistral.test.ts +282 -0
- package/test/provider-xai-responses.test.ts +32 -0
- package/test/pty/info-schema.test.ts +32 -0
- package/test/pty/protocol.test.ts +27 -0
- package/test/pty/pty-session.test.ts +240 -0
- package/test/pty/ticket.test.ts +62 -0
- package/test/question.test.ts +114 -0
- package/test/reference-guidance.test.ts +76 -0
- package/test/reference.test.ts +78 -0
- package/test/repository-cache.test.ts +153 -0
- package/test/repository.test.ts +71 -0
- package/test/ripgrep.test.ts +65 -0
- package/test/session-compaction.test.ts +27 -0
- package/test/session-create.test.ts +425 -0
- package/test/session-history.test.ts +165 -0
- package/test/session-projector.test.ts +533 -0
- package/test/session-prompt.test.ts +584 -0
- package/test/session-run-coordinator.test.ts +418 -0
- package/test/session-runner-message.test.ts +501 -0
- package/test/session-runner-model.test.ts +347 -0
- package/test/session-runner-recorded.test.ts +193 -0
- package/test/session-runner-tool-events.test.ts +136 -0
- package/test/session-runner-tool-registry.test.ts +452 -0
- package/test/session-runner.test.ts +3365 -0
- package/test/session-todo.test.ts +94 -0
- package/test/session-tool-progress.test.ts +158 -0
- package/test/shared-schema.test.ts +206 -0
- package/test/shell.test.ts +108 -0
- package/test/skill/guidance.test.ts +144 -0
- package/test/skill-discovery.test.ts +165 -0
- package/test/skill.test.ts +125 -0
- package/test/snapshot.test.ts +178 -0
- package/test/state.test.ts +115 -0
- package/test/system-context/builtins.test.ts +129 -0
- package/test/system-context/index.test.ts +307 -0
- package/test/system-context/registry.test.ts +114 -0
- package/test/tool-apply-patch.test.ts +436 -0
- package/test/tool-bash.test.ts +437 -0
- package/test/tool-edit.test.ts +434 -0
- package/test/tool-output-store.test.ts +247 -0
- package/test/tool-question.test.ts +162 -0
- package/test/tool-read-filesystem.test.ts +118 -0
- package/test/tool-read.test.ts +674 -0
- package/test/tool-skill.test.ts +149 -0
- package/test/tool-todowrite.test.ts +125 -0
- package/test/tool-webfetch.test.ts +281 -0
- package/test/tool-websearch.test.ts +316 -0
- package/test/tool-write.test.ts +305 -0
- package/test/util/effect-flock.test.ts +392 -0
- package/test/util/flock.test.ts +428 -0
- package/test/util/which.test.ts +100 -0
- package/tsconfig.json +7 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local tracking checkouts for remote Git references, one per remote and
|
|
3
|
+
* branch. Each checkout permanently tracks a single ref: the requested branch
|
|
4
|
+
* when the cache key has one, otherwise the remote default branch. Content
|
|
5
|
+
* follows "newest wins": refresh fetches and hard-resets, so readers may
|
|
6
|
+
* observe the checkout move underneath them.
|
|
7
|
+
*/
|
|
8
|
+
import path from "path"
|
|
9
|
+
import { Context, Effect, Layer, Schema } from "effect"
|
|
10
|
+
import { FSUtil } from "./fs-util"
|
|
11
|
+
import { Git } from "./git"
|
|
12
|
+
import { Global } from "./global"
|
|
13
|
+
import { Repository } from "./repository"
|
|
14
|
+
import { AbsolutePath } from "./schema"
|
|
15
|
+
import { makeGlobalNode } from "./effect/app-node"
|
|
16
|
+
import { EffectFlock } from "./util/effect-flock"
|
|
17
|
+
|
|
18
|
+
export type Result = {
|
|
19
|
+
readonly repository: string
|
|
20
|
+
readonly host: string
|
|
21
|
+
readonly remote: string
|
|
22
|
+
readonly localPath: string
|
|
23
|
+
readonly status: "cached" | "cloned" | "refreshed"
|
|
24
|
+
readonly head?: string
|
|
25
|
+
readonly branch?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type EnsureInput = {
|
|
29
|
+
readonly reference: Repository.RemoteReference
|
|
30
|
+
readonly refresh?: boolean
|
|
31
|
+
readonly branch?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class InvalidRepositoryError extends Schema.TaggedErrorClass<InvalidRepositoryError>()(
|
|
35
|
+
"RepositoryCacheInvalidRepositoryError",
|
|
36
|
+
{
|
|
37
|
+
repository: Schema.String,
|
|
38
|
+
message: Schema.String,
|
|
39
|
+
},
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()(
|
|
43
|
+
"RepositoryCacheInvalidBranchError",
|
|
44
|
+
{
|
|
45
|
+
branch: Schema.String,
|
|
46
|
+
message: Schema.String,
|
|
47
|
+
},
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
export class CloneFailedError extends Schema.TaggedErrorClass<CloneFailedError>()("RepositoryCacheCloneFailedError", {
|
|
51
|
+
repository: Schema.String,
|
|
52
|
+
message: Schema.String,
|
|
53
|
+
}) {}
|
|
54
|
+
|
|
55
|
+
export class FetchFailedError extends Schema.TaggedErrorClass<FetchFailedError>()("RepositoryCacheFetchFailedError", {
|
|
56
|
+
repository: Schema.String,
|
|
57
|
+
message: Schema.String,
|
|
58
|
+
}) {}
|
|
59
|
+
|
|
60
|
+
export class CheckoutFailedError extends Schema.TaggedErrorClass<CheckoutFailedError>()(
|
|
61
|
+
"RepositoryCacheCheckoutFailedError",
|
|
62
|
+
{
|
|
63
|
+
repository: Schema.String,
|
|
64
|
+
branch: Schema.String,
|
|
65
|
+
message: Schema.String,
|
|
66
|
+
},
|
|
67
|
+
) {}
|
|
68
|
+
|
|
69
|
+
export class ResetFailedError extends Schema.TaggedErrorClass<ResetFailedError>()("RepositoryCacheResetFailedError", {
|
|
70
|
+
repository: Schema.String,
|
|
71
|
+
message: Schema.String,
|
|
72
|
+
}) {}
|
|
73
|
+
|
|
74
|
+
export class LockFailedError extends Schema.TaggedErrorClass<LockFailedError>()("RepositoryCacheLockFailedError", {
|
|
75
|
+
localPath: Schema.String,
|
|
76
|
+
message: Schema.String,
|
|
77
|
+
}) {}
|
|
78
|
+
|
|
79
|
+
export class CacheOperationError extends Schema.TaggedErrorClass<CacheOperationError>()(
|
|
80
|
+
"RepositoryCacheOperationError",
|
|
81
|
+
{
|
|
82
|
+
operation: Schema.String,
|
|
83
|
+
path: Schema.String,
|
|
84
|
+
message: Schema.String,
|
|
85
|
+
},
|
|
86
|
+
) {}
|
|
87
|
+
|
|
88
|
+
export type Error =
|
|
89
|
+
| InvalidRepositoryError
|
|
90
|
+
| InvalidBranchError
|
|
91
|
+
| CloneFailedError
|
|
92
|
+
| FetchFailedError
|
|
93
|
+
| CheckoutFailedError
|
|
94
|
+
| ResetFailedError
|
|
95
|
+
| LockFailedError
|
|
96
|
+
| CacheOperationError
|
|
97
|
+
|
|
98
|
+
export interface Interface {
|
|
99
|
+
readonly ensure: (input: EnsureInput) => Effect.Effect<Result, Error>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class Service extends Context.Service<Service, Interface>()("@neurocode/RepositoryCache") {}
|
|
103
|
+
|
|
104
|
+
export function isError(error: unknown): error is Error {
|
|
105
|
+
return (
|
|
106
|
+
error instanceof InvalidRepositoryError ||
|
|
107
|
+
error instanceof InvalidBranchError ||
|
|
108
|
+
error instanceof CloneFailedError ||
|
|
109
|
+
error instanceof FetchFailedError ||
|
|
110
|
+
error instanceof CheckoutFailedError ||
|
|
111
|
+
error instanceof ResetFailedError ||
|
|
112
|
+
error instanceof LockFailedError ||
|
|
113
|
+
error instanceof CacheOperationError
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const parseRemote = Effect.fn("RepositoryCache.parseRemote")(function* (repository: string) {
|
|
118
|
+
return yield* Effect.try({
|
|
119
|
+
try: () => Repository.parseRemote(repository),
|
|
120
|
+
catch: (error) => new InvalidRepositoryError({ repository, message: errorMessage(error) }),
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
export const validateBranch = Effect.fn("RepositoryCache.validateBranch")(function* (branch: string) {
|
|
125
|
+
return yield* Effect.try({
|
|
126
|
+
try: () => Repository.validateBranch(branch),
|
|
127
|
+
catch: (error) => new InvalidBranchError({ branch, message: errorMessage(error) }),
|
|
128
|
+
})
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const layer: Layer.Layer<Service, never, FSUtil.Service | Git.Service | EffectFlock.Service | Global.Service> =
|
|
132
|
+
Layer.effect(
|
|
133
|
+
Service,
|
|
134
|
+
Effect.gen(function* () {
|
|
135
|
+
const fs = yield* FSUtil.Service
|
|
136
|
+
const git = yield* Git.Service
|
|
137
|
+
const flock = yield* EffectFlock.Service
|
|
138
|
+
const global = yield* Global.Service
|
|
139
|
+
|
|
140
|
+
return Service.of({
|
|
141
|
+
ensure: Effect.fn("RepositoryCache.ensure")(function* (input) {
|
|
142
|
+
if (input.branch) yield* validateBranch(input.branch)
|
|
143
|
+
|
|
144
|
+
const repository = input.reference.label
|
|
145
|
+
const localPath = Repository.cachePath(global.repos, input.reference, input.branch)
|
|
146
|
+
const cloneTarget = Repository.parse(input.reference.remote) ?? input.reference
|
|
147
|
+
|
|
148
|
+
return yield* flock
|
|
149
|
+
.withLock(
|
|
150
|
+
Effect.gen(function* () {
|
|
151
|
+
yield* cacheOperation(fs.ensureDir(path.dirname(localPath)), "ensure cache directory", localPath)
|
|
152
|
+
|
|
153
|
+
const existing = yield* git.repo.discover(AbsolutePath.make(localPath))
|
|
154
|
+
const origin = existing ? yield* git.remote.get(existing) : undefined
|
|
155
|
+
const originReference = origin ? Repository.parse(origin) : undefined
|
|
156
|
+
// Discovery walks upward, so an enclosing repository with a
|
|
157
|
+
// matching origin could masquerade as the cache entry; reuse
|
|
158
|
+
// requires the checkout to live exactly at the cache path.
|
|
159
|
+
const worktree = existing ? yield* fs.resolve(localPath) : undefined
|
|
160
|
+
const reuse = Boolean(
|
|
161
|
+
existing &&
|
|
162
|
+
existing.worktree === worktree &&
|
|
163
|
+
originReference &&
|
|
164
|
+
Repository.same(originReference, cloneTarget),
|
|
165
|
+
)
|
|
166
|
+
if (!reuse && (yield* fs.existsSafe(localPath))) {
|
|
167
|
+
yield* cacheOperation(fs.remove(localPath, { recursive: true }), "remove stale cache", localPath)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const status = !reuse
|
|
171
|
+
? ("cloned" as const)
|
|
172
|
+
: input.refresh
|
|
173
|
+
? ("refreshed" as const)
|
|
174
|
+
: ("cached" as const)
|
|
175
|
+
|
|
176
|
+
if (status === "cloned") {
|
|
177
|
+
yield* git.repo
|
|
178
|
+
.clone({
|
|
179
|
+
remote: input.reference.remote,
|
|
180
|
+
directory: AbsolutePath.make(localPath),
|
|
181
|
+
branch: input.branch,
|
|
182
|
+
})
|
|
183
|
+
.pipe(Effect.mapError((error) => new CloneFailedError({ repository, message: error.message })))
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (status === "refreshed") {
|
|
187
|
+
if (!existing)
|
|
188
|
+
return yield* new FetchFailedError({ repository, message: "Repository is unavailable" })
|
|
189
|
+
yield* git.sync
|
|
190
|
+
.fetchRemotes(existing)
|
|
191
|
+
.pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
|
|
192
|
+
|
|
193
|
+
if (input.branch) {
|
|
194
|
+
yield* git.sync
|
|
195
|
+
.fetchBranch(existing, { branch: input.branch })
|
|
196
|
+
.pipe(Effect.mapError((error) => new FetchFailedError({ repository, message: error.message })))
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Checking out the tracked ref before resetting keeps the
|
|
200
|
+
// checkout self-healing even if it was left on another
|
|
201
|
+
// branch.
|
|
202
|
+
const branch = input.branch ?? (yield* git.history.defaultRemoteBranch(existing))
|
|
203
|
+
if (branch) {
|
|
204
|
+
yield* git.sync
|
|
205
|
+
.checkoutRemoteBranch(existing, { branch })
|
|
206
|
+
.pipe(
|
|
207
|
+
Effect.mapError(
|
|
208
|
+
(error) => new CheckoutFailedError({ repository, branch, message: error.message }),
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const target = branch ?? (yield* git.history.branch(existing))
|
|
214
|
+
yield* git.sync
|
|
215
|
+
.resetHard(existing, target ? `origin/${target}` : "HEAD")
|
|
216
|
+
.pipe(Effect.mapError((error) => new ResetFailedError({ repository, message: error.message })))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const checkout = yield* git.repo.discover(AbsolutePath.make(localPath))
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
repository,
|
|
223
|
+
host: input.reference.host,
|
|
224
|
+
remote: input.reference.remote,
|
|
225
|
+
localPath,
|
|
226
|
+
status,
|
|
227
|
+
head: checkout ? yield* git.history.head(checkout) : undefined,
|
|
228
|
+
branch: checkout ? yield* git.history.branch(checkout) : undefined,
|
|
229
|
+
} satisfies Result
|
|
230
|
+
}),
|
|
231
|
+
`repository-cache:${localPath}`,
|
|
232
|
+
)
|
|
233
|
+
.pipe(
|
|
234
|
+
Effect.mapError((error) =>
|
|
235
|
+
isError(error) ? error : new LockFailedError({ localPath, message: errorMessage(error) }),
|
|
236
|
+
),
|
|
237
|
+
)
|
|
238
|
+
}),
|
|
239
|
+
})
|
|
240
|
+
}),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
export const node = makeGlobalNode({
|
|
244
|
+
service: Service,
|
|
245
|
+
layer,
|
|
246
|
+
deps: [EffectFlock.node, FSUtil.node, Git.node, Global.node],
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
function errorMessage(error: unknown) {
|
|
250
|
+
return error instanceof globalThis.Error ? error.message : String(error)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function cacheOperation<A, E, R>(effect: Effect.Effect<A, E, R>, operation: string, target: string) {
|
|
254
|
+
return effect.pipe(
|
|
255
|
+
Effect.mapError((error) => new CacheOperationError({ operation, path: target, message: errorMessage(error) })),
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export * as RepositoryCache from "./repository-cache"
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { fileURLToPath } from "url"
|
|
3
|
+
import { Schema } from "effect"
|
|
4
|
+
|
|
5
|
+
type BaseReference = {
|
|
6
|
+
readonly host: string
|
|
7
|
+
readonly path: string
|
|
8
|
+
readonly segments: string[]
|
|
9
|
+
readonly owner?: string
|
|
10
|
+
readonly repo: string
|
|
11
|
+
readonly remote: string
|
|
12
|
+
readonly label: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type RemoteReference = BaseReference & {
|
|
16
|
+
readonly protocol?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type FileReference = BaseReference & {
|
|
20
|
+
readonly host: "file"
|
|
21
|
+
readonly protocol: "file:"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type Reference = RemoteReference | FileReference
|
|
25
|
+
|
|
26
|
+
export class InvalidReferenceError extends Schema.TaggedErrorClass<InvalidReferenceError>()(
|
|
27
|
+
"RepositoryInvalidReferenceError",
|
|
28
|
+
{
|
|
29
|
+
repository: Schema.String,
|
|
30
|
+
message: Schema.String,
|
|
31
|
+
},
|
|
32
|
+
) {}
|
|
33
|
+
|
|
34
|
+
export class UnsupportedLocalRepositoryError extends Schema.TaggedErrorClass<UnsupportedLocalRepositoryError>()(
|
|
35
|
+
"RepositoryUnsupportedLocalRepositoryError",
|
|
36
|
+
{
|
|
37
|
+
repository: Schema.String,
|
|
38
|
+
message: Schema.String,
|
|
39
|
+
},
|
|
40
|
+
) {}
|
|
41
|
+
|
|
42
|
+
export class InvalidBranchError extends Schema.TaggedErrorClass<InvalidBranchError>()("RepositoryInvalidBranchError", {
|
|
43
|
+
branch: Schema.String,
|
|
44
|
+
message: Schema.String,
|
|
45
|
+
}) {}
|
|
46
|
+
|
|
47
|
+
export type Error = InvalidReferenceError | UnsupportedLocalRepositoryError | InvalidBranchError
|
|
48
|
+
|
|
49
|
+
export function isError(error: unknown): error is Error {
|
|
50
|
+
return (
|
|
51
|
+
error instanceof InvalidReferenceError ||
|
|
52
|
+
error instanceof UnsupportedLocalRepositoryError ||
|
|
53
|
+
error instanceof InvalidBranchError
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function parse(input: string): Reference | undefined {
|
|
58
|
+
const cleaned = normalizeInput(input)
|
|
59
|
+
if (!cleaned) return
|
|
60
|
+
|
|
61
|
+
const githubPrefixed = cleaned.match(/^github:([^/\s]+)\/([^/\s]+)$/)
|
|
62
|
+
if (githubPrefixed) return buildRemote({ host: "github.com", segments: [githubPrefixed[1], githubPrefixed[2]] })
|
|
63
|
+
|
|
64
|
+
if (!cleaned.includes("://")) {
|
|
65
|
+
const scp = cleaned.match(/^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/)
|
|
66
|
+
if (scp) return buildRemote({ host: scp[1], segments: parts(scp[2]), remote: cleaned })
|
|
67
|
+
|
|
68
|
+
const direct = parts(cleaned)
|
|
69
|
+
if (direct.length >= 2 && hostLike(direct[0])) return buildRemote({ host: direct[0], segments: direct.slice(1) })
|
|
70
|
+
if (direct.length === 2) return buildRemote({ host: "github.com", segments: direct })
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
const url = new URL(cleaned)
|
|
75
|
+
if (url.protocol === "file:") return buildFile({ url, remote: cleaned })
|
|
76
|
+
const segments = parts(url.pathname)
|
|
77
|
+
return buildRemote({
|
|
78
|
+
host: url.host,
|
|
79
|
+
segments,
|
|
80
|
+
remote: url.host === "github.com" ? githubRemote(segments.join("/")) : cleaned,
|
|
81
|
+
protocol: url.protocol,
|
|
82
|
+
})
|
|
83
|
+
} catch {
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function parseRemote(input: string): RemoteReference {
|
|
89
|
+
const reference = parse(input)
|
|
90
|
+
if (!reference) {
|
|
91
|
+
throw new InvalidReferenceError({
|
|
92
|
+
repository: input,
|
|
93
|
+
message: "Repository must be a git URL, host/path reference, or GitHub owner/repo shorthand",
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
if (!isRemote(reference)) {
|
|
97
|
+
throw new UnsupportedLocalRepositoryError({
|
|
98
|
+
repository: input,
|
|
99
|
+
message: "Local file repositories are not supported",
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
return reference
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function validateBranch(branch: string): void {
|
|
106
|
+
if (/^[A-Za-z0-9/_.-]+$/.test(branch) && !branch.startsWith("-") && !branch.includes("..")) return
|
|
107
|
+
throw new InvalidBranchError({
|
|
108
|
+
branch,
|
|
109
|
+
message: "Branch must contain only alphanumeric characters, /, _, ., and -, and cannot start with - or contain ..",
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function isFile(reference: Reference): reference is FileReference {
|
|
114
|
+
return reference.protocol === "file:"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function isRemote(reference: Reference): reference is RemoteReference {
|
|
118
|
+
return !isFile(reference)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Checkouts are keyed by remote and branch: a branch-specific reference gets
|
|
123
|
+
* its own directory so branchless refreshes can never move it. The branch is
|
|
124
|
+
* percent-encoded because valid branch names may contain `/`.
|
|
125
|
+
*/
|
|
126
|
+
export function cachePath(root: string, reference: Reference, branch?: string): string {
|
|
127
|
+
const base = path.join(root, ...reference.host.split(":"), ...reference.segments)
|
|
128
|
+
return branch ? `${base}@${encodeURIComponent(branch)}` : base
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function cacheIdentity(reference: Reference): string {
|
|
132
|
+
return `${reference.host}/${reference.path}`
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function same(left: Reference, right: Reference): boolean {
|
|
136
|
+
return cacheIdentity(left) === cacheIdentity(right)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function normalizeInput(input: string) {
|
|
140
|
+
return input
|
|
141
|
+
.trim()
|
|
142
|
+
.replace(/^git\+/, "")
|
|
143
|
+
.replace(/#.*$/, "")
|
|
144
|
+
.replace(/\/+$/, "")
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function trimGitSuffix(input: string) {
|
|
148
|
+
return input.replace(/\.git$/, "")
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function parts(input: string) {
|
|
152
|
+
return input
|
|
153
|
+
.split("/")
|
|
154
|
+
.map((item) => trimGitSuffix(item.trim()))
|
|
155
|
+
.filter(Boolean)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function safeHost(input: string) {
|
|
159
|
+
return Boolean(input) && !input.startsWith("-") && !/[\s/\\]/.test(input)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function safeSegment(input: string) {
|
|
163
|
+
return input !== "." && input !== ".." && !input.includes(":") && !/[\s/\\]/.test(input)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function hostLike(input: string) {
|
|
167
|
+
return input.includes(".") || input.includes(":") || input === "localhost"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function withSlash(input: string) {
|
|
171
|
+
return input.endsWith("/") ? input : `${input}/`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function githubRemote(pathname: string) {
|
|
175
|
+
const base = process.env.NEUROCODE_REPO_CLONE_GITHUB_BASE_URL
|
|
176
|
+
if (!base) return `https://github.com/${pathname}.git`
|
|
177
|
+
return new URL(`${pathname}.git`, withSlash(base)).href
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildRemote(input: { host: string; segments: string[]; remote?: string; protocol?: string }) {
|
|
181
|
+
const segments = input.segments.map(trimGitSuffix).filter(Boolean)
|
|
182
|
+
if (!safeHost(input.host) || !segments.length || segments.some((segment) => !safeSegment(segment))) return
|
|
183
|
+
const repositoryPath = segments.join("/")
|
|
184
|
+
const host = input.host.toLowerCase()
|
|
185
|
+
return {
|
|
186
|
+
host,
|
|
187
|
+
path: repositoryPath,
|
|
188
|
+
segments,
|
|
189
|
+
owner: segments.length === 2 ? segments[0] : undefined,
|
|
190
|
+
repo: segments[segments.length - 1],
|
|
191
|
+
remote:
|
|
192
|
+
input.remote ?? (host === "github.com" ? githubRemote(repositoryPath) : `https://${host}/${repositoryPath}.git`),
|
|
193
|
+
label: host === "github.com" && segments.length === 2 ? repositoryPath : `${host}/${repositoryPath}`,
|
|
194
|
+
protocol: input.protocol,
|
|
195
|
+
} satisfies RemoteReference
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildFile(input: { url: URL; remote: string }) {
|
|
199
|
+
const filePath = path.normalize(fileURLToPath(input.url))
|
|
200
|
+
const segments = filePath.split(/[\\/]+/).filter(Boolean)
|
|
201
|
+
if (!segments.length) return
|
|
202
|
+
return {
|
|
203
|
+
host: "file",
|
|
204
|
+
path: filePath,
|
|
205
|
+
segments: segments.map((segment) => segment.replace(/:$/, "")),
|
|
206
|
+
owner: undefined,
|
|
207
|
+
repo: trimGitSuffix(segments[segments.length - 1]),
|
|
208
|
+
remote: input.remote,
|
|
209
|
+
label: filePath,
|
|
210
|
+
protocol: "file:",
|
|
211
|
+
} satisfies FileReference
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export * as Repository from "./repository"
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import path from "path"
|
|
2
|
+
import { Context, Effect, Layer, Stream } from "effect"
|
|
3
|
+
import { FetchHttpClient, HttpClient, HttpClientRequest } from "effect/unstable/http"
|
|
4
|
+
import { ChildProcess } from "effect/unstable/process"
|
|
5
|
+
import { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
|
|
6
|
+
import { CrossSpawnSpawner } from "../cross-spawn-spawner"
|
|
7
|
+
import { makeGlobalNode } from "../effect/app-node"
|
|
8
|
+
import { httpClient } from "../effect/app-node-platform"
|
|
9
|
+
import { FSUtil } from "../fs-util"
|
|
10
|
+
import { Global } from "../global"
|
|
11
|
+
import { which } from "../util/which"
|
|
12
|
+
|
|
13
|
+
export namespace RipgrepBinary {
|
|
14
|
+
const VERSION = "15.1.0"
|
|
15
|
+
const PLATFORM = {
|
|
16
|
+
"arm64-darwin": { platform: "aarch64-apple-darwin", extension: "tar.gz" },
|
|
17
|
+
"arm64-linux": { platform: "aarch64-unknown-linux-gnu", extension: "tar.gz" },
|
|
18
|
+
"x64-darwin": { platform: "x86_64-apple-darwin", extension: "tar.gz" },
|
|
19
|
+
"x64-linux": { platform: "x86_64-unknown-linux-musl", extension: "tar.gz" },
|
|
20
|
+
"arm64-win32": { platform: "aarch64-pc-windows-msvc", extension: "zip" },
|
|
21
|
+
"ia32-win32": { platform: "i686-pc-windows-msvc", extension: "zip" },
|
|
22
|
+
"x64-win32": { platform: "x86_64-pc-windows-msvc", extension: "zip" },
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
interface Interface {
|
|
26
|
+
readonly filepath: Effect.Effect<string, Error>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class Service extends Context.Service<Service, Interface>()("@neurocode/RipgrepBinary") {}
|
|
30
|
+
|
|
31
|
+
const layer = Layer.effect(
|
|
32
|
+
Service,
|
|
33
|
+
Effect.gen(function* () {
|
|
34
|
+
const fs = yield* FSUtil.Service
|
|
35
|
+
const http = HttpClient.filterStatusOk(yield* HttpClient.HttpClient)
|
|
36
|
+
const spawner = yield* ChildProcessSpawner
|
|
37
|
+
|
|
38
|
+
const run = Effect.fnUntraced(function* (command: string, args: string[]) {
|
|
39
|
+
const handle = yield* spawner.spawn(ChildProcess.make(command, args, { extendEnv: true, stdin: "ignore" }))
|
|
40
|
+
const [stdout, stderr, code] = yield* Effect.all(
|
|
41
|
+
[
|
|
42
|
+
Stream.mkString(Stream.decodeText(handle.stdout)),
|
|
43
|
+
Stream.mkString(Stream.decodeText(handle.stderr)),
|
|
44
|
+
handle.exitCode,
|
|
45
|
+
],
|
|
46
|
+
{ concurrency: "unbounded" },
|
|
47
|
+
)
|
|
48
|
+
return { stdout, stderr, code }
|
|
49
|
+
}, Effect.scoped)
|
|
50
|
+
|
|
51
|
+
const extract = Effect.fnUntraced(function* (
|
|
52
|
+
archive: string,
|
|
53
|
+
config: (typeof PLATFORM)[keyof typeof PLATFORM],
|
|
54
|
+
target: string,
|
|
55
|
+
) {
|
|
56
|
+
const dir = yield* fs.makeTempDirectoryScoped({ directory: Global.Path.bin, prefix: "ripgrep-" })
|
|
57
|
+
|
|
58
|
+
if (config.extension === "zip") {
|
|
59
|
+
const shell = (yield* Effect.sync(() => which("powershell.exe") ?? which("pwsh.exe"))) ?? "powershell.exe"
|
|
60
|
+
const result = yield* run(shell, [
|
|
61
|
+
"-NoProfile",
|
|
62
|
+
"-NonInteractive",
|
|
63
|
+
"-Command",
|
|
64
|
+
`$global:ProgressPreference = 'SilentlyContinue'; Expand-Archive -LiteralPath '${archive.replaceAll("'", "''")}' -DestinationPath '${dir.replaceAll("'", "''")}' -Force`,
|
|
65
|
+
])
|
|
66
|
+
if (result.code !== 0)
|
|
67
|
+
throw new Error(
|
|
68
|
+
result.stderr.trim() || result.stdout.trim() || `ripgrep extraction failed with code ${result.code}`,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (config.extension === "tar.gz") {
|
|
73
|
+
const result = yield* run("tar", ["-xzf", archive, "-C", dir])
|
|
74
|
+
if (result.code !== 0)
|
|
75
|
+
throw new Error(
|
|
76
|
+
result.stderr.trim() || result.stdout.trim() || `ripgrep extraction failed with code ${result.code}`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const extracted = path.join(
|
|
81
|
+
dir,
|
|
82
|
+
`ripgrep-${VERSION}-${config.platform}`,
|
|
83
|
+
process.platform === "win32" ? "rg.exe" : "rg",
|
|
84
|
+
)
|
|
85
|
+
if (!(yield* fs.isFile(extracted))) throw new Error(`ripgrep archive did not contain executable: ${extracted}`)
|
|
86
|
+
|
|
87
|
+
yield* fs.copyFile(extracted, target)
|
|
88
|
+
if (process.platform !== "win32") yield* fs.chmod(target, 0o755)
|
|
89
|
+
}, Effect.scoped)
|
|
90
|
+
|
|
91
|
+
return Service.of({
|
|
92
|
+
filepath: yield* Effect.cached(
|
|
93
|
+
Effect.gen(function* () {
|
|
94
|
+
const system = yield* Effect.sync(() => which(process.platform === "win32" ? "rg.exe" : "rg"))
|
|
95
|
+
if (system && (yield* fs.isFile(system).pipe(Effect.orDie))) return system
|
|
96
|
+
|
|
97
|
+
const target = path.join(Global.Path.bin, `rg${process.platform === "win32" ? ".exe" : ""}`)
|
|
98
|
+
if (yield* fs.isFile(target).pipe(Effect.orDie)) return target
|
|
99
|
+
|
|
100
|
+
const platformKey = `${process.arch}-${process.platform}` as keyof typeof PLATFORM
|
|
101
|
+
const config = PLATFORM[platformKey]
|
|
102
|
+
if (!config) throw new Error(`unsupported platform for ripgrep: ${platformKey}`)
|
|
103
|
+
|
|
104
|
+
const filename = `ripgrep-${VERSION}-${config.platform}.${config.extension}`
|
|
105
|
+
const url = `https://github.com/BurntSushi/ripgrep/releases/download/${VERSION}/${filename}`
|
|
106
|
+
const archive = path.join(Global.Path.bin, filename)
|
|
107
|
+
|
|
108
|
+
yield* Effect.logInfo("downloading ripgrep", { url })
|
|
109
|
+
yield* fs.ensureDir(Global.Path.bin).pipe(Effect.orDie)
|
|
110
|
+
const bytes = yield* HttpClientRequest.get(url).pipe(
|
|
111
|
+
http.execute,
|
|
112
|
+
Effect.flatMap((response) => response.arrayBuffer),
|
|
113
|
+
Effect.mapError((cause) => (cause instanceof Error ? cause : new Error(String(cause)))),
|
|
114
|
+
)
|
|
115
|
+
if (bytes.byteLength === 0) throw new Error(`failed to download ripgrep from ${url}`)
|
|
116
|
+
|
|
117
|
+
yield* fs.writeWithDirs(archive, new Uint8Array(bytes))
|
|
118
|
+
yield* extract(archive, config, target)
|
|
119
|
+
yield* fs.remove(archive, { force: true }).pipe(Effect.ignore)
|
|
120
|
+
return target
|
|
121
|
+
}),
|
|
122
|
+
),
|
|
123
|
+
})
|
|
124
|
+
}),
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
export const node = makeGlobalNode({
|
|
128
|
+
service: Service,
|
|
129
|
+
layer: layer,
|
|
130
|
+
deps: [FSUtil.node, httpClient, CrossSpawnSpawner.node],
|
|
131
|
+
})
|
|
132
|
+
}
|