gsd-pi 2.20.0 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cli.js +21 -18
- package/dist/help-text.d.ts +2 -0
- package/dist/help-text.js +47 -0
- package/dist/loader.js +2 -16
- package/dist/mcp-server.d.ts +18 -0
- package/dist/mcp-server.js +53 -0
- package/dist/onboarding.d.ts +0 -6
- package/dist/onboarding.js +0 -28
- package/dist/resources/agents/javascript-pro.md +280 -0
- package/dist/resources/agents/typescript-pro.md +255 -0
- package/dist/resources/extensions/bg-shell/index.ts +14 -2
- package/dist/resources/extensions/bg-shell/utilities.ts +14 -0
- package/dist/resources/extensions/browser-tools/{core.js → core.ts} +329 -190
- package/dist/resources/extensions/gsd/auto-dashboard.ts +2 -1
- package/dist/resources/extensions/gsd/auto-prompts.ts +42 -1
- package/dist/resources/extensions/gsd/auto-worktree.ts +35 -2
- package/dist/resources/extensions/gsd/auto.ts +116 -10
- package/dist/resources/extensions/gsd/claude-import.ts +656 -0
- package/dist/resources/extensions/gsd/collision-diagnostics.ts +332 -0
- package/dist/resources/extensions/gsd/commands.ts +60 -7
- package/dist/resources/extensions/gsd/diff-context.ts +220 -0
- package/dist/resources/extensions/gsd/docs/claude-marketplace-import.md +214 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -1
- package/dist/resources/extensions/gsd/doctor.ts +12 -4
- package/dist/resources/extensions/gsd/file-watcher.ts +97 -0
- package/dist/resources/extensions/gsd/files.ts +1 -1
- package/dist/resources/extensions/gsd/forensics.ts +596 -0
- package/dist/resources/extensions/gsd/git-service.ts +2 -1
- package/dist/resources/extensions/gsd/github-client.ts +235 -0
- package/dist/resources/extensions/gsd/gitignore.ts +1 -0
- package/dist/resources/extensions/gsd/marketplace-discovery.ts +507 -0
- package/dist/resources/extensions/gsd/mcp-server.ts +87 -0
- package/dist/resources/extensions/gsd/namespaced-registry.ts +467 -0
- package/dist/resources/extensions/gsd/namespaced-resolver.ts +307 -0
- package/dist/resources/extensions/gsd/plugin-importer.ts +410 -0
- package/dist/resources/extensions/gsd/preferences.ts +14 -3
- package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/forensics.md +71 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/system.md +12 -3
- package/dist/resources/extensions/gsd/roadmap-slices.ts +1 -1
- package/dist/resources/extensions/gsd/session-forensics.ts +5 -5
- package/dist/resources/extensions/gsd/tests/claude-import-tui.test.ts +351 -0
- package/dist/resources/extensions/gsd/tests/collision-diagnostics.test.ts +705 -0
- package/dist/resources/extensions/gsd/tests/context-compression.test.ts +13 -0
- package/dist/resources/extensions/gsd/tests/diff-context.test.ts +136 -0
- package/dist/resources/extensions/gsd/tests/doctor-git.test.ts +118 -0
- package/dist/resources/extensions/gsd/tests/marketplace-discovery.test.ts +202 -0
- package/dist/resources/extensions/gsd/tests/marketplace-test-fixtures.ts +91 -0
- package/dist/resources/extensions/gsd/tests/namespaced-registry.test.ts +1027 -0
- package/dist/resources/extensions/gsd/tests/namespaced-resolver.test.ts +671 -0
- package/dist/resources/extensions/gsd/tests/none-mode-gates.test.ts +105 -0
- package/dist/resources/extensions/gsd/tests/plugin-importer-live.test.ts +481 -0
- package/dist/resources/extensions/gsd/tests/plugin-importer.test.ts +1383 -0
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +21 -2
- package/dist/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +8 -1
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +11 -0
- package/dist/resources/extensions/gsd/tests/worktree-bugfix.test.ts +120 -0
- package/dist/resources/extensions/gsd/token-counter.ts +45 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +2 -2
- package/dist/resources/extensions/gsd/worktree-manager.ts +29 -1
- package/dist/resources/extensions/gsd/worktree.ts +3 -0
- package/dist/resources/extensions/mcporter/index.ts +90 -7
- package/dist/resources/extensions/search-the-web/native-search.ts +2 -0
- package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +9 -1
- package/dist/resources/extensions/search-the-web/url-utils.ts +35 -1
- package/dist/resources/extensions/shared/terminal.ts +1 -1
- package/dist/resources/extensions/universal-config/discovery.ts +4 -0
- package/dist/resources/extensions/universal-config/format.ts +35 -4
- package/dist/resources/extensions/universal-config/index.ts +5 -3
- package/dist/resources/extensions/universal-config/scanners.ts +65 -2
- package/dist/resources/extensions/universal-config/tests/discovery.test.ts +9 -1
- package/dist/resources/extensions/universal-config/tests/format.test.ts +22 -6
- package/dist/resources/extensions/universal-config/tests/scanners.test.ts +18 -0
- package/dist/resources/extensions/universal-config/types.ts +20 -1
- package/dist/resources/skills/lint/SKILL.md +141 -0
- package/dist/resources/skills/review/SKILL.md +214 -0
- package/dist/resources/skills/test/SKILL.md +201 -0
- package/dist/tool-bootstrap.js +2 -1
- package/dist/wizard.js +2 -0
- package/package.json +12 -7
- package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -1
- package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-responses.js +12 -1
- package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/LICENSE +201 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/README.md +9 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-cjs/index.js +762 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/build-abort-error.js +19 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/constants.js +1 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/get-transformed-headers.js +9 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/index.js +3 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http-handler.js +230 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http2-connection-manager.js +87 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http2-connection-pool.js +32 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/node-http2-handler.js +169 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/readable.mock.js +21 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/server.mock.js +88 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-connection-timeout.js +36 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-request-timeout.js +21 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-socket-keep-alive.js +22 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/set-socket-timeout.js +23 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/stream-collector/collector.js +8 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/stream-collector/index.js +41 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/stream-collector/readable.mock.js +21 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/timing.js +4 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-es/write-request-body.js +63 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/build-abort-error.d.ts +10 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/constants.d.ts +5 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/get-transformed-headers.d.ts +4 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/index.d.ts +3 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http-handler.d.ts +46 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http2-connection-manager.d.ts +24 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http2-connection-pool.d.ts +12 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/node-http2-handler.d.ts +63 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/readable.mock.d.ts +13 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/server.mock.d.ts +12 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-connection-timeout.d.ts +2 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-request-timeout.d.ts +6 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-socket-keep-alive.d.ts +6 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/set-socket-timeout.d.ts +2 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/stream-collector/collector.d.ts +5 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/stream-collector/index.d.ts +6 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/stream-collector/readable.mock.d.ts +13 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/timing.d.ts +8 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/build-abort-error.d.ts +10 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/constants.d.ts +5 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/get-transformed-headers.d.ts +4 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/index.d.ts +3 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http-handler.d.ts +46 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-connection-manager.d.ts +24 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-connection-pool.d.ts +12 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/node-http2-handler.d.ts +63 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/readable.mock.d.ts +13 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/server.mock.d.ts +12 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-connection-timeout.d.ts +2 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-request-timeout.d.ts +6 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-socket-keep-alive.d.ts +6 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/set-socket-timeout.d.ts +2 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/stream-collector/collector.d.ts +5 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/stream-collector/index.d.ts +6 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/stream-collector/readable.mock.d.ts +13 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/timing.d.ts +8 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/ts3.4/write-request-body.d.ts +12 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/dist-types/write-request-body.d.ts +12 -0
- package/packages/pi-ai/node_modules/@smithy/node-http-handler/package.json +68 -0
- package/packages/pi-ai/package.json +3 -0
- package/packages/pi-ai/pnpm-lock.yaml +2022 -0
- package/packages/pi-ai/src/providers/azure-openai-responses.ts +12 -1
- package/packages/pi-ai/src/providers/openai-responses.ts +12 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +12 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +113 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/project-trust.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/extensions/project-trust.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/extensions/project-trust.js +42 -0
- package/packages/pi-coding-agent/dist/core/extensions/project-trust.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js +18 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +114 -0
- package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js +8 -2
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +23 -9
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +67 -0
- package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -0
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/pnpm-lock.yaml +454 -0
- package/packages/pi-coding-agent/scripts/copy-assets.cjs +24 -0
- package/packages/pi-coding-agent/src/core/extensions/index.ts +3 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +141 -0
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +14 -1
- package/packages/pi-coding-agent/src/core/extensions/project-trust.ts +51 -0
- package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +132 -0
- package/packages/pi-coding-agent/src/core/resolve-config-value.ts +20 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +7 -2
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +98 -0
- package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +24 -9
- package/packages/pi-tui/dist/tui.d.ts.map +1 -1
- package/packages/pi-tui/dist/tui.js +2 -2
- package/packages/pi-tui/dist/tui.js.map +1 -1
- package/packages/pi-tui/package.json +3 -1
- package/packages/pi-tui/src/tui.ts +2 -2
- package/src/resources/agents/javascript-pro.md +280 -0
- package/src/resources/agents/typescript-pro.md +255 -0
- package/src/resources/extensions/bg-shell/index.ts +14 -2
- package/src/resources/extensions/bg-shell/utilities.ts +14 -0
- package/src/resources/extensions/browser-tools/{core.js → core.ts} +329 -190
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +42 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +35 -2
- package/src/resources/extensions/gsd/auto.ts +116 -10
- package/src/resources/extensions/gsd/claude-import.ts +656 -0
- package/src/resources/extensions/gsd/collision-diagnostics.ts +332 -0
- package/src/resources/extensions/gsd/commands.ts +60 -7
- package/src/resources/extensions/gsd/diff-context.ts +220 -0
- package/src/resources/extensions/gsd/docs/claude-marketplace-import.md +214 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -1
- package/src/resources/extensions/gsd/doctor.ts +12 -4
- package/src/resources/extensions/gsd/file-watcher.ts +97 -0
- package/src/resources/extensions/gsd/files.ts +1 -1
- package/src/resources/extensions/gsd/forensics.ts +596 -0
- package/src/resources/extensions/gsd/git-service.ts +2 -1
- package/src/resources/extensions/gsd/github-client.ts +235 -0
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/marketplace-discovery.ts +507 -0
- package/src/resources/extensions/gsd/mcp-server.ts +87 -0
- package/src/resources/extensions/gsd/namespaced-registry.ts +467 -0
- package/src/resources/extensions/gsd/namespaced-resolver.ts +307 -0
- package/src/resources/extensions/gsd/plugin-importer.ts +410 -0
- package/src/resources/extensions/gsd/preferences.ts +14 -3
- package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/forensics.md +71 -0
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/system.md +12 -3
- package/src/resources/extensions/gsd/roadmap-slices.ts +1 -1
- package/src/resources/extensions/gsd/session-forensics.ts +5 -5
- package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +351 -0
- package/src/resources/extensions/gsd/tests/collision-diagnostics.test.ts +705 -0
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +13 -0
- package/src/resources/extensions/gsd/tests/diff-context.test.ts +136 -0
- package/src/resources/extensions/gsd/tests/doctor-git.test.ts +118 -0
- package/src/resources/extensions/gsd/tests/marketplace-discovery.test.ts +202 -0
- package/src/resources/extensions/gsd/tests/marketplace-test-fixtures.ts +91 -0
- package/src/resources/extensions/gsd/tests/namespaced-registry.test.ts +1027 -0
- package/src/resources/extensions/gsd/tests/namespaced-resolver.test.ts +671 -0
- package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +105 -0
- package/src/resources/extensions/gsd/tests/plugin-importer-live.test.ts +481 -0
- package/src/resources/extensions/gsd/tests/plugin-importer.test.ts +1383 -0
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +21 -2
- package/src/resources/extensions/gsd/tests/preferences-schema-validation.test.ts +8 -1
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +120 -0
- package/src/resources/extensions/gsd/token-counter.ts +45 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +2 -2
- package/src/resources/extensions/gsd/worktree-manager.ts +29 -1
- package/src/resources/extensions/gsd/worktree.ts +3 -0
- package/src/resources/extensions/mcporter/index.ts +90 -7
- package/src/resources/extensions/search-the-web/native-search.ts +2 -0
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +9 -1
- package/src/resources/extensions/search-the-web/url-utils.ts +35 -1
- package/src/resources/extensions/shared/terminal.ts +1 -1
- package/src/resources/extensions/universal-config/discovery.ts +4 -0
- package/src/resources/extensions/universal-config/format.ts +35 -4
- package/src/resources/extensions/universal-config/index.ts +5 -3
- package/src/resources/extensions/universal-config/scanners.ts +65 -2
- package/src/resources/extensions/universal-config/tests/discovery.test.ts +9 -1
- package/src/resources/extensions/universal-config/tests/format.test.ts +22 -6
- package/src/resources/extensions/universal-config/tests/scanners.test.ts +18 -0
- package/src/resources/extensions/universal-config/types.ts +20 -1
- package/src/resources/skills/lint/SKILL.md +141 -0
- package/src/resources/skills/review/SKILL.md +214 -0
- package/src/resources/skills/test/SKILL.md +201 -0
- package/dist/resources/extensions/browser-tools/core.d.ts +0 -205
- package/src/resources/extensions/browser-tools/core.d.ts +0 -205
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GSD Git Preferences Tests — validates git.isolation and git.merge_to_main handling
|
|
2
2
|
|
|
3
3
|
import { createTestContext } from "./test-helpers.ts";
|
|
4
|
-
import { validatePreferences } from "../preferences.ts";
|
|
4
|
+
import { validatePreferences, getIsolationMode } from "../preferences.ts";
|
|
5
5
|
|
|
6
6
|
const { assertEq, assertTrue, report } = createTestContext();
|
|
7
7
|
|
|
@@ -21,12 +21,18 @@ async function main(): Promise<void> {
|
|
|
21
21
|
assertEq(warnings.length, 0, "isolation: branch — no warnings");
|
|
22
22
|
assertEq(preferences.git?.isolation, "branch", "isolation: branch — value preserved");
|
|
23
23
|
}
|
|
24
|
+
{
|
|
25
|
+
const { preferences, warnings, errors } = validatePreferences({ git: { isolation: "none" } });
|
|
26
|
+
assertEq(errors.length, 0, "isolation: none — no errors");
|
|
27
|
+
assertEq(warnings.length, 0, "isolation: none — no warnings");
|
|
28
|
+
assertEq(preferences.git?.isolation, "none", "isolation: none — value preserved");
|
|
29
|
+
}
|
|
24
30
|
|
|
25
31
|
// Invalid values produce errors
|
|
26
32
|
{
|
|
27
33
|
const { errors } = validatePreferences({ git: { isolation: "invalid" as any } });
|
|
28
34
|
assertTrue(errors.length > 0, "isolation: invalid — produces error");
|
|
29
|
-
assertTrue(errors[0].includes("worktree, branch"), "isolation: invalid — error mentions valid values");
|
|
35
|
+
assertTrue(errors[0].includes("worktree, branch, none"), "isolation: invalid — error mentions valid values");
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
// Undefined passes through without warning
|
|
@@ -95,6 +101,19 @@ async function main(): Promise<void> {
|
|
|
95
101
|
assertEq(preferences.git?.commit_docs, undefined, "commit_docs: undefined — not set");
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
console.log("\n=== getIsolationMode() ===");
|
|
105
|
+
|
|
106
|
+
// Returns "none" when set to "none"
|
|
107
|
+
// Note: getIsolationMode() reads from disk via loadEffectiveGSDPreferences,
|
|
108
|
+
// so we test it indirectly by verifying the function is exported and callable.
|
|
109
|
+
// The validation tests above prove the preference value is stored correctly.
|
|
110
|
+
// Direct mode tests require mocking the filesystem, so we test the function's
|
|
111
|
+
// default return value (no preferences file in test context).
|
|
112
|
+
{
|
|
113
|
+
const mode = getIsolationMode();
|
|
114
|
+
assertEq(mode, "worktree", "getIsolationMode: returns worktree as default when no prefs file");
|
|
115
|
+
}
|
|
116
|
+
|
|
98
117
|
report();
|
|
99
118
|
}
|
|
100
119
|
|
|
@@ -155,7 +155,7 @@ async function main(): Promise<void> {
|
|
|
155
155
|
|
|
156
156
|
console.log("\n=== existing behavior preserved ===");
|
|
157
157
|
|
|
158
|
-
// git.isolation is a valid active setting (worktree | branch) — no warnings or errors
|
|
158
|
+
// git.isolation is a valid active setting (worktree | branch | none) — no warnings or errors
|
|
159
159
|
{
|
|
160
160
|
const { warnings, errors, preferences } = validatePreferences({ git: { isolation: "worktree" } } as GSDPreferences);
|
|
161
161
|
const unknownWarnings = warnings.filter(w => w.includes("unknown"));
|
|
@@ -163,6 +163,13 @@ async function main(): Promise<void> {
|
|
|
163
163
|
assertEq(errors.length, 0, "valid git.isolation produces no errors");
|
|
164
164
|
assertEq(preferences.git?.isolation, "worktree", "git.isolation value passes through");
|
|
165
165
|
}
|
|
166
|
+
{
|
|
167
|
+
const { warnings, errors, preferences } = validatePreferences({ git: { isolation: "none" } } as GSDPreferences);
|
|
168
|
+
const unknownWarnings = warnings.filter(w => w.includes("unknown"));
|
|
169
|
+
assertEq(unknownWarnings.length, 0, "git.isolation none — no unknown-key warning");
|
|
170
|
+
assertEq(errors.length, 0, "git.isolation none produces no errors");
|
|
171
|
+
assertEq(preferences.git?.isolation, "none", "git.isolation none value passes through");
|
|
172
|
+
}
|
|
166
173
|
|
|
167
174
|
// git.merge_to_main is deprecated — still produces deprecation warning
|
|
168
175
|
{
|
|
@@ -165,6 +165,17 @@ assertTrue(
|
|
|
165
165
|
"VisualizerData has changelog field",
|
|
166
166
|
);
|
|
167
167
|
|
|
168
|
+
// completedAt must be coerced to String() to handle YAML Date objects (issue #644)
|
|
169
|
+
assertTrue(
|
|
170
|
+
dataSrc.includes("String(summary.frontmatter.completed_at"),
|
|
171
|
+
"completedAt assignment coerces to String() for YAML Date safety",
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
assertTrue(
|
|
175
|
+
dataSrc.includes("String(b.completedAt") && dataSrc.includes("String(a.completedAt"),
|
|
176
|
+
"changelog sort coerces completedAt to String() for YAML Date safety",
|
|
177
|
+
);
|
|
178
|
+
|
|
168
179
|
// Verify overlay source exists and imports data module
|
|
169
180
|
const overlayPath = join(__dirname, "..", "visualizer-overlay.ts");
|
|
170
181
|
const overlaySrc = readFileSync(overlayPath, "utf-8");
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for worktree edge-case bugfixes:
|
|
3
|
+
*
|
|
4
|
+
* 1. resolveGitDir() follows gitdir: pointer in worktrees
|
|
5
|
+
* 2. captureIntegrationBranch() is a no-op in worktrees
|
|
6
|
+
* 3. detectWorktreeName() correctly identifies worktree paths
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
mkdtempSync, mkdirSync, writeFileSync, rmSync,
|
|
11
|
+
existsSync, readFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { tmpdir } from "node:os";
|
|
15
|
+
import { execSync } from "node:child_process";
|
|
16
|
+
import { describe, it, after } from "node:test";
|
|
17
|
+
|
|
18
|
+
import { resolveGitDir } from "../worktree-manager.ts";
|
|
19
|
+
import { detectWorktreeName, captureIntegrationBranch } from "../worktree.ts";
|
|
20
|
+
import { createTestContext } from "./test-helpers.ts";
|
|
21
|
+
|
|
22
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
23
|
+
|
|
24
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
function run(cmd: string, cwd: string): void {
|
|
27
|
+
execSync(cmd, { cwd, stdio: "ignore" });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function initRepo(dir: string): void {
|
|
31
|
+
run("git init", dir);
|
|
32
|
+
run("git config user.email test@test.com", dir);
|
|
33
|
+
run("git config user.name Test", dir);
|
|
34
|
+
run("git commit --allow-empty -m init", dir);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ─── Tests ────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
describe("worktree-bugfix", () => {
|
|
40
|
+
const dirs: string[] = [];
|
|
41
|
+
after(() => {
|
|
42
|
+
for (const d of dirs) rmSync(d, { recursive: true, force: true });
|
|
43
|
+
report();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("resolveGitDir returns .git directory in normal repo", () => {
|
|
47
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-wt-fix-"));
|
|
48
|
+
dirs.push(repo);
|
|
49
|
+
initRepo(repo);
|
|
50
|
+
const gitDir = resolveGitDir(repo);
|
|
51
|
+
assertTrue(gitDir.endsWith(".git"), "ends with .git");
|
|
52
|
+
assertTrue(existsSync(gitDir), ".git dir exists");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("resolveGitDir follows gitdir: pointer in worktree", () => {
|
|
56
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-wt-fix-"));
|
|
57
|
+
dirs.push(repo);
|
|
58
|
+
initRepo(repo);
|
|
59
|
+
|
|
60
|
+
// Simulate a worktree .git file (git worktree add creates these)
|
|
61
|
+
const wtDir = mkdtempSync(join(tmpdir(), "gsd-wt-fix-wt-"));
|
|
62
|
+
dirs.push(wtDir);
|
|
63
|
+
const realGitDir = join(repo, ".git", "worktrees", "test-wt");
|
|
64
|
+
mkdirSync(realGitDir, { recursive: true });
|
|
65
|
+
writeFileSync(join(wtDir, ".git"), `gitdir: ${realGitDir}\n`);
|
|
66
|
+
|
|
67
|
+
const resolved = resolveGitDir(wtDir);
|
|
68
|
+
assertEq(resolved, realGitDir, "resolves to real git dir");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("resolveGitDir returns default when .git doesn't exist", () => {
|
|
72
|
+
const noGit = mkdtempSync(join(tmpdir(), "gsd-wt-fix-"));
|
|
73
|
+
dirs.push(noGit);
|
|
74
|
+
const gitDir = resolveGitDir(noGit);
|
|
75
|
+
assertTrue(gitDir.endsWith(".git"), "returns default .git path");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("detectWorktreeName returns name for worktree path", () => {
|
|
79
|
+
assertEq(
|
|
80
|
+
detectWorktreeName("/project/.gsd/worktrees/M005"),
|
|
81
|
+
"M005",
|
|
82
|
+
"detects worktree name",
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("detectWorktreeName returns null for normal repo", () => {
|
|
87
|
+
assertEq(
|
|
88
|
+
detectWorktreeName("/project"),
|
|
89
|
+
null,
|
|
90
|
+
"null for non-worktree path",
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("captureIntegrationBranch is a no-op when in a worktree", () => {
|
|
95
|
+
const repo = mkdtempSync(join(tmpdir(), "gsd-wt-fix-"));
|
|
96
|
+
dirs.push(repo);
|
|
97
|
+
initRepo(repo);
|
|
98
|
+
|
|
99
|
+
// Create a fake worktree path structure
|
|
100
|
+
const wtPath = join(repo, ".gsd", "worktrees", "M005");
|
|
101
|
+
mkdirSync(wtPath, { recursive: true });
|
|
102
|
+
mkdirSync(join(wtPath, ".gsd", "milestones", "M005"), { recursive: true });
|
|
103
|
+
// Initialize git in the worktree so getService doesn't fail
|
|
104
|
+
initRepo(wtPath);
|
|
105
|
+
|
|
106
|
+
// captureIntegrationBranch should be a no-op — no META.json written
|
|
107
|
+
const metaPath = join(wtPath, ".gsd", "milestones", "M005", "M005-META.json");
|
|
108
|
+
captureIntegrationBranch(wtPath, "M005");
|
|
109
|
+
assertTrue(!existsSync(metaPath), "no META.json written in worktree");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("detectWorktreeName prevents pull in worktree context", () => {
|
|
113
|
+
// Verifies the guard pattern: if detectWorktreeName returns non-null,
|
|
114
|
+
// the caller should skip pull/fetch operations
|
|
115
|
+
const inWorktree = detectWorktreeName("/project/.gsd/worktrees/M006");
|
|
116
|
+
const inNormal = detectWorktreeName("/project");
|
|
117
|
+
assertTrue(inWorktree !== null, "worktree detected → skip pull");
|
|
118
|
+
assertTrue(inNormal === null, "normal repo → allow pull");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
interface TokenEncoder {
|
|
2
|
+
encode(text: string): Uint32Array | number[];
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
let encoder: TokenEncoder | null = null;
|
|
6
|
+
let encoderFailed = false;
|
|
7
|
+
|
|
8
|
+
async function getEncoder(): Promise<TokenEncoder | null> {
|
|
9
|
+
if (encoder) return encoder;
|
|
10
|
+
if (encoderFailed) return null;
|
|
11
|
+
try {
|
|
12
|
+
// @ts-ignore — tiktoken may not have type declarations in extensions tsconfig
|
|
13
|
+
const tiktoken = await import("tiktoken");
|
|
14
|
+
encoder = tiktoken.encoding_for_model("gpt-4o") as TokenEncoder;
|
|
15
|
+
return encoder;
|
|
16
|
+
} catch {
|
|
17
|
+
encoderFailed = true;
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function countTokens(text: string): Promise<number> {
|
|
23
|
+
const enc = await getEncoder();
|
|
24
|
+
if (enc) {
|
|
25
|
+
const tokens = enc.encode(text);
|
|
26
|
+
return tokens.length;
|
|
27
|
+
}
|
|
28
|
+
return Math.ceil(text.length / 4);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function countTokensSync(text: string): number {
|
|
32
|
+
if (encoder) {
|
|
33
|
+
return encoder.encode(text).length;
|
|
34
|
+
}
|
|
35
|
+
return Math.ceil(text.length / 4);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function initTokenCounter(): Promise<boolean> {
|
|
39
|
+
const enc = await getEncoder();
|
|
40
|
+
return enc !== null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function isAccurateCountingAvailable(): boolean {
|
|
44
|
+
return encoder !== null;
|
|
45
|
+
}
|
|
@@ -379,7 +379,7 @@ async function loadChangelog(basePath: string, milestones: VisualizerMilestone[]
|
|
|
379
379
|
path: f.path,
|
|
380
380
|
description: f.description,
|
|
381
381
|
})),
|
|
382
|
-
completedAt: summary.frontmatter.completed_at ?? '',
|
|
382
|
+
completedAt: String(summary.frontmatter.completed_at ?? ''),
|
|
383
383
|
};
|
|
384
384
|
|
|
385
385
|
changelogCache.set(cacheKey, { mtime, entry });
|
|
@@ -388,7 +388,7 @@ async function loadChangelog(basePath: string, milestones: VisualizerMilestone[]
|
|
|
388
388
|
}
|
|
389
389
|
|
|
390
390
|
// Sort by completedAt descending
|
|
391
|
-
entries.sort((a, b) => (b.completedAt || '').localeCompare(a.completedAt || ''));
|
|
391
|
+
entries.sort((a, b) => String(b.completedAt || '').localeCompare(String(a.completedAt || '')));
|
|
392
392
|
|
|
393
393
|
return { entries };
|
|
394
394
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* 4. remove() — git worktree remove + branch cleanup
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { existsSync, mkdirSync, realpathSync } from "node:fs";
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync } from "node:fs";
|
|
19
19
|
import { join, resolve, sep } from "node:path";
|
|
20
20
|
import {
|
|
21
21
|
nativeBranchDelete,
|
|
@@ -74,6 +74,34 @@ export function getMainBranch(basePath: string): string {
|
|
|
74
74
|
return nativeDetectMainBranch(basePath);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// ─── resolveGitDir ─────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the actual git directory for a given repository path.
|
|
81
|
+
*
|
|
82
|
+
* In a normal repo, .git is a directory → returns `<basePath>/.git`.
|
|
83
|
+
* In a worktree, .git is a file containing `gitdir: <path>` → resolves
|
|
84
|
+
* and returns that path.
|
|
85
|
+
*
|
|
86
|
+
* This is critical for operations that reference git metadata files like
|
|
87
|
+
* MERGE_HEAD, SQUASH_MSG, etc. — these live in the git directory, not
|
|
88
|
+
* in the working tree root. Without this, worktree merges fail because
|
|
89
|
+
* they look for MERGE_HEAD in the wrong location.
|
|
90
|
+
*/
|
|
91
|
+
export function resolveGitDir(basePath: string): string {
|
|
92
|
+
const gitPath = join(basePath, ".git");
|
|
93
|
+
if (!existsSync(gitPath)) return join(basePath, ".git");
|
|
94
|
+
try {
|
|
95
|
+
const content = readFileSync(gitPath, "utf-8").trim();
|
|
96
|
+
if (content.startsWith("gitdir: ")) {
|
|
97
|
+
return resolve(basePath, content.slice(8));
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
// Not a file or unreadable — fall through to default
|
|
101
|
+
}
|
|
102
|
+
return join(basePath, ".git");
|
|
103
|
+
}
|
|
104
|
+
|
|
77
105
|
export function worktreesDir(basePath: string): string {
|
|
78
106
|
return join(basePath, ".gsd", "worktrees");
|
|
79
107
|
}
|
|
@@ -55,6 +55,9 @@ export function setActiveMilestoneId(basePath: string, milestoneId: string | nul
|
|
|
55
55
|
* if on a GSD slice branch.
|
|
56
56
|
*/
|
|
57
57
|
export function captureIntegrationBranch(basePath: string, milestoneId: string, options?: { commitDocs?: boolean }): void {
|
|
58
|
+
// In a worktree, the base branch is implicit (worktree/<name>).
|
|
59
|
+
// Writing it to META.json would leave stale metadata after merge back to main.
|
|
60
|
+
if (detectWorktreeName(basePath)) return;
|
|
58
61
|
const svc = getService(basePath);
|
|
59
62
|
const current = svc.getCurrentBranch();
|
|
60
63
|
writeIntegrationBranch(basePath, milestoneId, current, options);
|
|
@@ -26,6 +26,8 @@ import { Text } from "@gsd/pi-tui";
|
|
|
26
26
|
import { Type } from "@sinclair/typebox";
|
|
27
27
|
import { execFile, exec } from "node:child_process";
|
|
28
28
|
import { promisify } from "node:util";
|
|
29
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
30
|
+
import { join } from "node:path";
|
|
29
31
|
|
|
30
32
|
const execFileAsync = promisify(execFile);
|
|
31
33
|
const execAsync = promisify(exec);
|
|
@@ -99,6 +101,37 @@ async function runMcporter(
|
|
|
99
101
|
return stdout;
|
|
100
102
|
}
|
|
101
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Read .mcp.json from the project root (cwd) and return servers not already
|
|
106
|
+
* discovered by mcporter. This bridges the gap where mcporter doesn't scan
|
|
107
|
+
* the standard .mcp.json config used by Claude Code, Cursor, etc.
|
|
108
|
+
*/
|
|
109
|
+
function readProjectMcpJson(knownNames: Set<string>): McpServer[] {
|
|
110
|
+
const servers: McpServer[] = [];
|
|
111
|
+
try {
|
|
112
|
+
const mcpJsonPath = join(process.cwd(), ".mcp.json");
|
|
113
|
+
if (!existsSync(mcpJsonPath)) return servers;
|
|
114
|
+
const raw = readFileSync(mcpJsonPath, "utf-8");
|
|
115
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
116
|
+
const mcpServers = (data.mcpServers ?? data.servers) as Record<string, Record<string, unknown>> | undefined;
|
|
117
|
+
if (!mcpServers || typeof mcpServers !== "object") return servers;
|
|
118
|
+
|
|
119
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
120
|
+
if (knownNames.has(name)) continue; // Already discovered by mcporter
|
|
121
|
+
const transport = (config.type as string) ?? (config.command ? "stdio" : config.url ? "http" : "unknown");
|
|
122
|
+
servers.push({
|
|
123
|
+
name,
|
|
124
|
+
status: "ok",
|
|
125
|
+
transport,
|
|
126
|
+
tools: [], // Tools unknown until mcp_discover is called
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
// Non-fatal — .mcp.json may not exist or be malformed
|
|
131
|
+
}
|
|
132
|
+
return servers;
|
|
133
|
+
}
|
|
134
|
+
|
|
102
135
|
async function getServerList(signal?: AbortSignal): Promise<McpServer[]> {
|
|
103
136
|
if (serverListCache) return serverListCache;
|
|
104
137
|
|
|
@@ -112,6 +145,14 @@ async function getServerList(signal?: AbortSignal): Promise<McpServer[]> {
|
|
|
112
145
|
if (!Array.isArray(data.servers)) {
|
|
113
146
|
throw new Error(`Unexpected mcporter response shape: ${JSON.stringify(Object.keys(data))}`);
|
|
114
147
|
}
|
|
148
|
+
|
|
149
|
+
// Merge servers from project-root .mcp.json that mcporter didn't discover
|
|
150
|
+
const knownNames = new Set(data.servers.map((s) => s.name));
|
|
151
|
+
const projectServers = readProjectMcpJson(knownNames);
|
|
152
|
+
if (projectServers.length > 0) {
|
|
153
|
+
data.servers.push(...projectServers);
|
|
154
|
+
}
|
|
155
|
+
|
|
115
156
|
serverListCache = data.servers;
|
|
116
157
|
return serverListCache;
|
|
117
158
|
}
|
|
@@ -122,12 +163,39 @@ async function getServerDetail(
|
|
|
122
163
|
): Promise<McpServerDetail> {
|
|
123
164
|
if (serverDetailCache.has(serverName)) return serverDetailCache.get(serverName)!;
|
|
124
165
|
|
|
125
|
-
|
|
166
|
+
// Check if this server came from .mcp.json (not known to mcporter natively)
|
|
167
|
+
const mcpJsonUrl = getMcpJsonServerUrl(serverName);
|
|
168
|
+
const args = mcpJsonUrl
|
|
169
|
+
? ["list", mcpJsonUrl, "--schema", "--json"]
|
|
170
|
+
: ["list", serverName, "--schema", "--json"];
|
|
171
|
+
|
|
172
|
+
const raw = await runMcporter(args, signal);
|
|
126
173
|
const data = JSON.parse(raw) as McpServerDetail;
|
|
174
|
+
// Preserve the user-facing name from .mcp.json
|
|
175
|
+
if (mcpJsonUrl) data.name = serverName;
|
|
127
176
|
serverDetailCache.set(serverName, data);
|
|
128
177
|
return data;
|
|
129
178
|
}
|
|
130
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Look up a server's URL from .mcp.json if it's an HTTP server not known to mcporter.
|
|
182
|
+
*/
|
|
183
|
+
function getMcpJsonServerUrl(serverName: string): string | null {
|
|
184
|
+
try {
|
|
185
|
+
const mcpJsonPath = join(process.cwd(), ".mcp.json");
|
|
186
|
+
if (!existsSync(mcpJsonPath)) return null;
|
|
187
|
+
const raw = readFileSync(mcpJsonPath, "utf-8");
|
|
188
|
+
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
189
|
+
const mcpServers = (data.mcpServers ?? data.servers) as Record<string, Record<string, unknown>> | undefined;
|
|
190
|
+
if (!mcpServers?.[serverName]) return null;
|
|
191
|
+
const config = mcpServers[serverName];
|
|
192
|
+
if (config.type === "http" && typeof config.url === "string") return config.url;
|
|
193
|
+
return null;
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
131
199
|
function formatServerList(servers: McpServer[]): string {
|
|
132
200
|
if (servers.length === 0) return "No MCP servers found.";
|
|
133
201
|
|
|
@@ -328,7 +396,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
328
396
|
|
|
329
397
|
async execute(_id, params, signal) {
|
|
330
398
|
// Build mcporter call command: mcporter call server.tool key:value ...
|
|
331
|
-
|
|
399
|
+
// For HTTP servers from .mcp.json, use the URL directly as the server identifier
|
|
400
|
+
const mcpJsonUrl = getMcpJsonServerUrl(params.server);
|
|
401
|
+
const serverRef = mcpJsonUrl ?? params.server;
|
|
402
|
+
const callTarget = `${serverRef}.${params.tool}`;
|
|
332
403
|
const cliArgs = ["call", callTarget, "--output", "raw"];
|
|
333
404
|
|
|
334
405
|
if (params.args && Object.keys(params.args).length > 0) {
|
|
@@ -418,12 +489,24 @@ export default function (pi: ExtensionAPI) {
|
|
|
418
489
|
pi.on("session_start", async (_event, ctx) => {
|
|
419
490
|
try {
|
|
420
491
|
const ver = (await runMcporter(["--version"], undefined, 5000)).trim();
|
|
421
|
-
|
|
492
|
+
ctx.ui.notify(`MCPorter ${ver} ready`, "info");
|
|
422
493
|
} catch {
|
|
423
|
-
ctx.ui.notify(
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
494
|
+
ctx.ui.notify("MCPorter not found — attempting auto-install…", "warning");
|
|
495
|
+
try {
|
|
496
|
+
await new Promise<void>((resolve, reject) => {
|
|
497
|
+
exec("npm install -g mcporter", { timeout: 60000 }, (err) => {
|
|
498
|
+
if (err) reject(err);
|
|
499
|
+
else resolve();
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
const ver = (await runMcporter(["--version"], undefined, 5000)).trim();
|
|
503
|
+
ctx.ui.notify(`MCPorter ${ver} auto-installed ✓`, "info");
|
|
504
|
+
} catch {
|
|
505
|
+
ctx.ui.notify(
|
|
506
|
+
"MCPorter auto-install failed. Install manually: npm i -g mcporter",
|
|
507
|
+
"error",
|
|
508
|
+
);
|
|
509
|
+
}
|
|
427
510
|
}
|
|
428
511
|
});
|
|
429
512
|
}
|
|
@@ -157,11 +157,13 @@ export function registerNativeSearchHooks(pi: NativeSearchPI): { getIsAnthropic:
|
|
|
157
157
|
const hasBrave = !!process.env.BRAVE_API_KEY;
|
|
158
158
|
const hasJina = !!process.env.JINA_API_KEY;
|
|
159
159
|
const hasAnswers = !!process.env.BRAVE_ANSWERS_KEY;
|
|
160
|
+
const hasTavily = !!process.env.TAVILY_API_KEY;
|
|
160
161
|
|
|
161
162
|
const parts: string[] = ["Web search v4 loaded"];
|
|
162
163
|
if (hasBrave) parts.push("Brave ✓");
|
|
163
164
|
if (hasAnswers) parts.push("Answers ✓");
|
|
164
165
|
if (hasJina) parts.push("Jina ✓");
|
|
166
|
+
if (hasTavily) parts.push("Tavily ✓");
|
|
165
167
|
|
|
166
168
|
ctx.ui.notify(parts.join(" · "), "info");
|
|
167
169
|
});
|
|
@@ -15,7 +15,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
15
15
|
|
|
16
16
|
import { LRUTTLCache } from "./cache.js";
|
|
17
17
|
import { fetchSimple, HttpError } from "./http.js";
|
|
18
|
-
import { extractDomain } from "./url-utils.js";
|
|
18
|
+
import { extractDomain, isBlockedUrl } from "./url-utils.js";
|
|
19
19
|
import { formatPageContent, type FormatPageOptions } from "./format.js";
|
|
20
20
|
import { getOllamaApiKey } from "./provider.js";
|
|
21
21
|
|
|
@@ -416,6 +416,14 @@ export function registerFetchPageTool(pi: ExtensionAPI) {
|
|
|
416
416
|
};
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
+
if (isBlockedUrl(url)) {
|
|
420
|
+
return {
|
|
421
|
+
content: [{ type: "text", text: `Blocked URL: requests to private/internal addresses are not allowed.` }],
|
|
422
|
+
isError: true,
|
|
423
|
+
details: { error: "SSRF blocked", url } satisfies Partial<FetchPageDetails>,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
419
427
|
// ------------------------------------------------------------------
|
|
420
428
|
// Cache lookup (full content cached, offset/truncation applied after)
|
|
421
429
|
// ------------------------------------------------------------------
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* URL normalization and
|
|
2
|
+
* URL normalization, query utilities, and SSRF protection.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
const BLOCKED_HOSTNAMES = new Set([
|
|
6
|
+
"localhost",
|
|
7
|
+
"metadata.google.internal",
|
|
8
|
+
"instance-data",
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const PRIVATE_IP_PATTERNS = [
|
|
12
|
+
/^127\./,
|
|
13
|
+
/^10\./,
|
|
14
|
+
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
15
|
+
/^192\.168\./,
|
|
16
|
+
/^169\.254\./,
|
|
17
|
+
/^0\./,
|
|
18
|
+
/^::1$/,
|
|
19
|
+
/^fc00:/i,
|
|
20
|
+
/^fd/i,
|
|
21
|
+
/^fe80:/i,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export function isBlockedUrl(url: string): boolean {
|
|
25
|
+
try {
|
|
26
|
+
const parsed = new URL(url);
|
|
27
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return true;
|
|
28
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
29
|
+
if (BLOCKED_HOSTNAMES.has(hostname)) return true;
|
|
30
|
+
for (const pattern of PRIVATE_IP_PATTERNS) {
|
|
31
|
+
if (pattern.test(hostname)) return true;
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
} catch {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
5
39
|
/** Normalize a search query into a stable cache key. */
|
|
6
40
|
export function normalizeQuery(query: string): string {
|
|
7
41
|
return query.trim().toLowerCase().replace(/\s+/g, " ").normalize("NFC");
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Terminals that lack this support silently swallow the key combos.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
const UNSUPPORTED_TERMS = ["apple_terminal"];
|
|
8
|
+
const UNSUPPORTED_TERMS = ["apple_terminal", "warpterm"];
|
|
9
9
|
|
|
10
10
|
export function supportsCtrlAltShortcuts(): boolean {
|
|
11
11
|
const term = (process.env.TERM_PROGRAM || "").toLowerCase();
|
|
@@ -58,6 +58,8 @@ export async function discoverAllConfigs(
|
|
|
58
58
|
const rules = allItems.filter((i) => i.type === "rule").length;
|
|
59
59
|
const contextFiles = allItems.filter((i) => i.type === "context-file").length;
|
|
60
60
|
const settings = allItems.filter((i) => i.type === "settings").length;
|
|
61
|
+
const claudeSkills = allItems.filter((i) => i.type === "claude-skill").length;
|
|
62
|
+
const claudePlugins = allItems.filter((i) => i.type === "claude-plugin").length;
|
|
61
63
|
const toolsWithConfig = toolResults.filter((r) => r.items.length > 0).length;
|
|
62
64
|
|
|
63
65
|
return {
|
|
@@ -68,6 +70,8 @@ export async function discoverAllConfigs(
|
|
|
68
70
|
rules,
|
|
69
71
|
contextFiles,
|
|
70
72
|
settings,
|
|
73
|
+
claudeSkills,
|
|
74
|
+
claudePlugins,
|
|
71
75
|
totalItems: allItems.length,
|
|
72
76
|
toolsScanned: TOOLS.length,
|
|
73
77
|
toolsWithConfig,
|
|
@@ -23,7 +23,7 @@ export function formatDiscoveryForTool(result: DiscoveryResult): string {
|
|
|
23
23
|
return lines.join("\n");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
lines.push(`Found: ${summary.mcpServers} MCP server(s), ${summary.rules} rule(s), ${summary.contextFiles} context file(s), ${summary.settings} settings file(s)`);
|
|
26
|
+
lines.push(`Found: ${summary.mcpServers} MCP server(s), ${summary.rules} rule(s), ${summary.contextFiles} context file(s), ${summary.settings} settings file(s), ${summary.claudeSkills} Claude skill(s), ${summary.claudePlugins} Claude plugin(s)`);
|
|
27
27
|
lines.push("");
|
|
28
28
|
|
|
29
29
|
for (const toolResult of result.tools) {
|
|
@@ -70,9 +70,24 @@ export function formatDiscoveryForTool(result: DiscoveryResult): string {
|
|
|
70
70
|
lines.push(` Settings (${byType.settings.length}):`);
|
|
71
71
|
for (const item of byType.settings) {
|
|
72
72
|
if (item.type !== "settings") continue;
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
lines.push(` - ${item.source.path} (${item.source.level})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (byType["claude-skill"]?.length) {
|
|
78
|
+
lines.push(` Claude Skills (${byType["claude-skill"].length}):`);
|
|
79
|
+
for (const item of byType["claude-skill"]) {
|
|
80
|
+
if (item.type !== "claude-skill") continue;
|
|
81
|
+
lines.push(` - ${item.name} (${item.source.level}) ${item.path}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (byType["claude-plugin"]?.length) {
|
|
86
|
+
lines.push(` Claude Plugins (${byType["claude-plugin"].length}):`);
|
|
87
|
+
for (const item of byType["claude-plugin"]) {
|
|
88
|
+
if (item.type !== "claude-plugin") continue;
|
|
89
|
+
const label = item.packageName ? `${item.name} [${item.packageName}]` : item.name;
|
|
90
|
+
lines.push(` - ${label} (${item.source.level}) ${item.path}`);
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
|
|
@@ -111,6 +126,8 @@ export function formatDiscoveryForCommand(result: DiscoveryResult): string[] {
|
|
|
111
126
|
lines.push(` Rules: ${summary.rules}`);
|
|
112
127
|
lines.push(` Context: ${summary.contextFiles}`);
|
|
113
128
|
lines.push(` Settings: ${summary.settings}`);
|
|
129
|
+
lines.push(` Claude skills: ${summary.claudeSkills}`);
|
|
130
|
+
lines.push(` Claude plugins: ${summary.claudePlugins}`);
|
|
114
131
|
lines.push("");
|
|
115
132
|
|
|
116
133
|
for (const toolResult of result.tools) {
|
|
@@ -122,6 +139,8 @@ export function formatDiscoveryForCommand(result: DiscoveryResult): string[] {
|
|
|
122
139
|
if (counts.rule) parts.push(`${counts.rule} rules`);
|
|
123
140
|
if (counts["context-file"]) parts.push(`${counts["context-file"]} context`);
|
|
124
141
|
if (counts.settings) parts.push(`${counts.settings} settings`);
|
|
142
|
+
if (counts["claude-skill"]) parts.push(`${counts["claude-skill"]} Claude skills`);
|
|
143
|
+
if (counts["claude-plugin"]) parts.push(`${counts["claude-plugin"]} Claude plugins`);
|
|
125
144
|
|
|
126
145
|
lines.push(` ${toolResult.tool.name}: ${parts.join(", ")}`);
|
|
127
146
|
|
|
@@ -131,6 +150,18 @@ export function formatDiscoveryForCommand(result: DiscoveryResult): string[] {
|
|
|
131
150
|
if (server.type !== "mcp-server") continue;
|
|
132
151
|
lines.push(` MCP: ${server.name} (${server.source.level})`);
|
|
133
152
|
}
|
|
153
|
+
|
|
154
|
+
const claudeSkills = toolResult.items.filter((i) => i.type === "claude-skill");
|
|
155
|
+
for (const skill of claudeSkills) {
|
|
156
|
+
if (skill.type !== "claude-skill") continue;
|
|
157
|
+
lines.push(` Skill: ${skill.name} (${skill.source.level})`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const claudePlugins = toolResult.items.filter((i) => i.type === "claude-plugin");
|
|
161
|
+
for (const plugin of claudePlugins) {
|
|
162
|
+
if (plugin.type !== "claude-plugin") continue;
|
|
163
|
+
lines.push(` Plugin: ${plugin.name} (${plugin.source.level})`);
|
|
164
|
+
}
|
|
134
165
|
}
|
|
135
166
|
|
|
136
167
|
if (result.warnings.length > 0) {
|