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
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collision Diagnostics Module
|
|
3
|
+
*
|
|
4
|
+
* Bridges NamespacedRegistry collision data and NamespacedResolver ambiguous
|
|
5
|
+
* resolution into a classified diagnostic taxonomy. Provides two functions:
|
|
6
|
+
* - analyzeCollisions: Scans registry and resolver state to produce classified diagnostics
|
|
7
|
+
* - doctorReport: Formats diagnostics into human-readable output with severity and remediation
|
|
8
|
+
*
|
|
9
|
+
* This module implements R010 (collision reporting) and R011 (doctor advice) for the
|
|
10
|
+
* namespaced component system.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { NamespacedRegistry, RegistryDiagnostic } from './namespaced-registry.js';
|
|
14
|
+
import type { NamespacedResolver, ResolutionResult } from './namespaced-resolver.js';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Type Definitions
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Classification of collision type.
|
|
22
|
+
* - canonical-conflict: Two plugins registered the same canonical name (hard error)
|
|
23
|
+
* - shorthand-overlap: Same bare name exists in multiple namespaces (ambiguity)
|
|
24
|
+
* - alias-conflict: Alias shadows a canonical name or bare component name
|
|
25
|
+
*/
|
|
26
|
+
export type CollisionClass = 'canonical-conflict' | 'shorthand-overlap' | 'alias-conflict';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Severity level for diagnostics.
|
|
30
|
+
* - error: Hard collision that prevents correct resolution
|
|
31
|
+
* - warning: Ambiguity that may cause surprising behavior
|
|
32
|
+
*/
|
|
33
|
+
export type DiagnosticSeverity = 'error' | 'warning';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A classified diagnostic with full context for remediation.
|
|
37
|
+
*/
|
|
38
|
+
export interface ClassifiedDiagnostic {
|
|
39
|
+
/** The collision classification */
|
|
40
|
+
class: CollisionClass;
|
|
41
|
+
|
|
42
|
+
/** Severity level */
|
|
43
|
+
severity: DiagnosticSeverity;
|
|
44
|
+
|
|
45
|
+
/** All canonical names involved in the collision */
|
|
46
|
+
involvedCanonicalNames: string[];
|
|
47
|
+
|
|
48
|
+
/** File paths to the conflicting components */
|
|
49
|
+
filePaths: string[];
|
|
50
|
+
|
|
51
|
+
/** Human-readable remediation advice */
|
|
52
|
+
remediation: string;
|
|
53
|
+
|
|
54
|
+
/** Optional: the bare name causing ambiguity (shorthand-overlap only) */
|
|
55
|
+
ambiguousBareName?: string;
|
|
56
|
+
|
|
57
|
+
/** Optional: the alias string (alias-conflict only) */
|
|
58
|
+
alias?: string;
|
|
59
|
+
|
|
60
|
+
/** Optional: the canonical name the alias points to (alias-conflict only) */
|
|
61
|
+
aliasTarget?: string;
|
|
62
|
+
|
|
63
|
+
/** Optional: type of alias conflict */
|
|
64
|
+
aliasConflictType?: 'shadows-canonical' | 'shadows-bare-name';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Doctor report with summary statistics and formatted entries.
|
|
69
|
+
*/
|
|
70
|
+
export interface DoctorReport {
|
|
71
|
+
/** Summary counts by class */
|
|
72
|
+
summary: {
|
|
73
|
+
/** Total diagnostics */
|
|
74
|
+
total: number;
|
|
75
|
+
/** Canonical conflicts (errors) */
|
|
76
|
+
canonicalConflicts: number;
|
|
77
|
+
/** Shorthand overlaps (warnings) */
|
|
78
|
+
shorthandOverlaps: number;
|
|
79
|
+
/** Alias conflicts (warnings) */
|
|
80
|
+
aliasConflicts: number;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Formatted report entries */
|
|
84
|
+
entries: string[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ============================================================================
|
|
88
|
+
// Implementation
|
|
89
|
+
// ============================================================================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Analyze a registry and resolver to produce classified diagnostics.
|
|
93
|
+
*
|
|
94
|
+
* This function:
|
|
95
|
+
* 1. Reads registry.getDiagnostics() for canonical conflicts (→ error severity)
|
|
96
|
+
* 2. Groups registry.getAll() by bare component.name
|
|
97
|
+
* 3. For groups with 2+ entries, calls resolver.resolve(bareName) to confirm ambiguity
|
|
98
|
+
* 4. Produces warning diagnostics for ambiguous shorthand resolution
|
|
99
|
+
*
|
|
100
|
+
* @param registry - The namespaced registry to analyze
|
|
101
|
+
* @param resolver - The resolver to test ambiguity
|
|
102
|
+
* @returns Array of classified diagnostics
|
|
103
|
+
*/
|
|
104
|
+
export function analyzeCollisions(
|
|
105
|
+
registry: NamespacedRegistry,
|
|
106
|
+
resolver: NamespacedResolver
|
|
107
|
+
): ClassifiedDiagnostic[] {
|
|
108
|
+
const diagnostics: ClassifiedDiagnostic[] = [];
|
|
109
|
+
|
|
110
|
+
// Step 1: Process canonical conflicts from registry diagnostics
|
|
111
|
+
const registryDiagnostics = registry.getDiagnostics();
|
|
112
|
+
for (const diag of registryDiagnostics) {
|
|
113
|
+
if (diag.type === 'collision') {
|
|
114
|
+
diagnostics.push({
|
|
115
|
+
class: 'canonical-conflict',
|
|
116
|
+
severity: 'error',
|
|
117
|
+
involvedCanonicalNames: [diag.collision.canonicalName],
|
|
118
|
+
filePaths: [diag.collision.winnerPath, diag.collision.loserPath],
|
|
119
|
+
remediation: `Canonical name "${diag.collision.canonicalName}" registered multiple times. ` +
|
|
120
|
+
`The first registration (${diag.collision.winnerSource ?? 'unknown source'}) ` +
|
|
121
|
+
`took precedence over subsequent registration (${diag.collision.loserSource ?? 'unknown source'}). ` +
|
|
122
|
+
`Rename one of the conflicting components to resolve.`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Step 2: Find shorthand overlaps by grouping components by bare name
|
|
128
|
+
const components = registry.getAll();
|
|
129
|
+
const byBareName = new Map<string, typeof components>();
|
|
130
|
+
|
|
131
|
+
for (const component of components) {
|
|
132
|
+
const bareName = component.name;
|
|
133
|
+
if (!byBareName.has(bareName)) {
|
|
134
|
+
byBareName.set(bareName, []);
|
|
135
|
+
}
|
|
136
|
+
byBareName.get(bareName)!.push(component);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Step 3: For groups with 2+ entries, check if resolver confirms ambiguity
|
|
140
|
+
for (const [bareName, candidates] of byBareName) {
|
|
141
|
+
if (candidates.length >= 2) {
|
|
142
|
+
// Use resolver to confirm ambiguity
|
|
143
|
+
const result = resolver.resolve(bareName);
|
|
144
|
+
|
|
145
|
+
if (result.resolution === 'ambiguous') {
|
|
146
|
+
// This is a shorthand overlap
|
|
147
|
+
const canonicalNames = candidates.map(c => c.canonicalName);
|
|
148
|
+
const filePaths = candidates.map(c => c.filePath);
|
|
149
|
+
|
|
150
|
+
diagnostics.push({
|
|
151
|
+
class: 'shorthand-overlap',
|
|
152
|
+
severity: 'warning',
|
|
153
|
+
involvedCanonicalNames: canonicalNames,
|
|
154
|
+
filePaths,
|
|
155
|
+
remediation: formatShorthandRemediation(bareName, canonicalNames),
|
|
156
|
+
ambiguousBareName: bareName,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
// If resolution is 'shorthand' or 'local-first', the overlap is resolved
|
|
160
|
+
// unambiguously by the resolver, so we don't warn
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Step 4: Check for alias conflicts
|
|
165
|
+
const aliases = registry.getAliases();
|
|
166
|
+
const canonicalNamesSet = new Set(components.map(c => c.canonicalName));
|
|
167
|
+
|
|
168
|
+
for (const [alias, targetCanonical] of aliases) {
|
|
169
|
+
// Check if alias shadows a canonical name
|
|
170
|
+
// (This can happen if a component was registered AFTER the alias was created)
|
|
171
|
+
if (canonicalNamesSet.has(alias)) {
|
|
172
|
+
const shadowedComponent = components.find(c => c.canonicalName === alias);
|
|
173
|
+
const aliasedComponent = components.find(c => c.canonicalName === targetCanonical);
|
|
174
|
+
|
|
175
|
+
diagnostics.push({
|
|
176
|
+
class: 'alias-conflict',
|
|
177
|
+
severity: 'warning',
|
|
178
|
+
involvedCanonicalNames: [alias, targetCanonical],
|
|
179
|
+
filePaths: [
|
|
180
|
+
shadowedComponent?.filePath ?? '<unknown>',
|
|
181
|
+
aliasedComponent?.filePath ?? '<unknown>',
|
|
182
|
+
],
|
|
183
|
+
remediation: formatAliasShadowsCanonicalRemediation(alias, targetCanonical),
|
|
184
|
+
alias,
|
|
185
|
+
aliasTarget: targetCanonical,
|
|
186
|
+
aliasConflictType: 'shadows-canonical',
|
|
187
|
+
});
|
|
188
|
+
continue; // Skip further checks for this alias
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if alias shadows a bare name (matches component.name in any namespace)
|
|
192
|
+
const matchingBareNames = components.filter(c => c.name === alias);
|
|
193
|
+
if (matchingBareNames.length > 0) {
|
|
194
|
+
const filePaths = matchingBareNames.map(c => c.filePath);
|
|
195
|
+
const aliasedComponent = components.find(c => c.canonicalName === targetCanonical);
|
|
196
|
+
if (aliasedComponent) filePaths.push(aliasedComponent.filePath);
|
|
197
|
+
|
|
198
|
+
diagnostics.push({
|
|
199
|
+
class: 'alias-conflict',
|
|
200
|
+
severity: 'warning',
|
|
201
|
+
involvedCanonicalNames: [targetCanonical, ...matchingBareNames.map(c => c.canonicalName)],
|
|
202
|
+
filePaths,
|
|
203
|
+
remediation: formatAliasShadowsBareNameRemediation(alias, targetCanonical, matchingBareNames.map(c => c.canonicalName)),
|
|
204
|
+
alias,
|
|
205
|
+
aliasTarget: targetCanonical,
|
|
206
|
+
aliasConflictType: 'shadows-bare-name',
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return diagnostics;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Format remediation advice for shorthand overlap.
|
|
216
|
+
*
|
|
217
|
+
* @param bareName - The ambiguous bare name
|
|
218
|
+
* @param canonicalNames - All canonical names that match
|
|
219
|
+
* @returns Human-readable remediation message
|
|
220
|
+
*/
|
|
221
|
+
function formatShorthandRemediation(bareName: string, canonicalNames: string[]): string {
|
|
222
|
+
const suggestions = canonicalNames
|
|
223
|
+
.map(cn => `\`${cn}\``)
|
|
224
|
+
.join(', ');
|
|
225
|
+
|
|
226
|
+
return `Bare name "${bareName}" is ambiguous across ${canonicalNames.length} namespaces. ` +
|
|
227
|
+
`Use a canonical name (${suggestions}) to avoid ambiguity.`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Format remediation advice for alias shadowing a canonical name.
|
|
232
|
+
*
|
|
233
|
+
* @param alias - The alias that shadows a canonical name
|
|
234
|
+
* @param targetCanonical - The canonical name the alias points to
|
|
235
|
+
* @returns Human-readable remediation message
|
|
236
|
+
*/
|
|
237
|
+
function formatAliasShadowsCanonicalRemediation(alias: string, targetCanonical: string): string {
|
|
238
|
+
return `Alias "${alias}" shadows an existing canonical name. ` +
|
|
239
|
+
`The alias points to "${targetCanonical}", but resolving "${alias}" will now match the component, not the alias. ` +
|
|
240
|
+
`Consider rename or remove the alias to avoid confusion.`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Format remediation advice for alias shadowing a bare name.
|
|
245
|
+
*
|
|
246
|
+
* @param alias - The alias that shadows bare names
|
|
247
|
+
* @param targetCanonical - The canonical name the alias points to
|
|
248
|
+
* @param shadowedCanonicals - The canonical names whose bare names are shadowed
|
|
249
|
+
* @returns Human-readable remediation message
|
|
250
|
+
*/
|
|
251
|
+
function formatAliasShadowsBareNameRemediation(
|
|
252
|
+
alias: string,
|
|
253
|
+
targetCanonical: string,
|
|
254
|
+
shadowedCanonicals: string[]
|
|
255
|
+
): string {
|
|
256
|
+
const shadowed = shadowedCanonicals.map(cn => `\`${cn}\``).join(', ');
|
|
257
|
+
return `Alias "${alias}" shadows ${shadowedCanonicals.length} component(s) with the same bare name (${shadowed}). ` +
|
|
258
|
+
`Resolving "${alias}" will use the alias (pointing to "${targetCanonical}"), not shorthand resolution. ` +
|
|
259
|
+
`Use canonical names to be explicit, or rename the alias if this is unintended.`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Format diagnostics into a human-readable doctor report.
|
|
264
|
+
*
|
|
265
|
+
* Each diagnostic is formatted with:
|
|
266
|
+
* - Severity icon (❌ error / ⚠️ warning)
|
|
267
|
+
* - Description of the issue
|
|
268
|
+
* - Involved file paths
|
|
269
|
+
* - Remediation advice
|
|
270
|
+
*
|
|
271
|
+
* @param diagnostics - Array of classified diagnostics
|
|
272
|
+
* @returns Doctor report with summary and formatted entries
|
|
273
|
+
*/
|
|
274
|
+
export function doctorReport(diagnostics: ClassifiedDiagnostic[]): DoctorReport {
|
|
275
|
+
const summary = {
|
|
276
|
+
total: diagnostics.length,
|
|
277
|
+
canonicalConflicts: diagnostics.filter(d => d.class === 'canonical-conflict').length,
|
|
278
|
+
shorthandOverlaps: diagnostics.filter(d => d.class === 'shorthand-overlap').length,
|
|
279
|
+
aliasConflicts: diagnostics.filter(d => d.class === 'alias-conflict').length,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const entries = diagnostics.map(diagnostic => formatDiagnosticEntry(diagnostic));
|
|
283
|
+
|
|
284
|
+
return { summary, entries };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Format a single diagnostic entry for display.
|
|
289
|
+
*
|
|
290
|
+
* @param diagnostic - The diagnostic to format
|
|
291
|
+
* @returns Formatted string entry
|
|
292
|
+
*/
|
|
293
|
+
function formatDiagnosticEntry(diagnostic: ClassifiedDiagnostic): string {
|
|
294
|
+
const icon = diagnostic.severity === 'error' ? '❌' : '⚠️';
|
|
295
|
+
const lines: string[] = [];
|
|
296
|
+
|
|
297
|
+
// Header with severity and class
|
|
298
|
+
lines.push(`${icon} ${diagnostic.class.toUpperCase()}`);
|
|
299
|
+
|
|
300
|
+
// Description
|
|
301
|
+
if (diagnostic.class === 'canonical-conflict') {
|
|
302
|
+
lines.push(` Canonical name conflict: ${diagnostic.involvedCanonicalNames[0]}`);
|
|
303
|
+
} else if (diagnostic.class === 'alias-conflict') {
|
|
304
|
+
if (diagnostic.aliasConflictType === 'shadows-canonical') {
|
|
305
|
+
lines.push(` Alias "${diagnostic.alias}" shadows canonical name (points to ${diagnostic.aliasTarget})`);
|
|
306
|
+
} else {
|
|
307
|
+
lines.push(` Alias "${diagnostic.alias}" shadows bare name (points to ${diagnostic.aliasTarget})`);
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
lines.push(` Shorthand overlap: "${diagnostic.ambiguousBareName}" matches ${diagnostic.involvedCanonicalNames.length} components`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// File paths
|
|
314
|
+
lines.push(' Files:');
|
|
315
|
+
for (const path of diagnostic.filePaths) {
|
|
316
|
+
lines.push(` - ${path}`);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Remediation
|
|
320
|
+
lines.push(` Remediation: ${diagnostic.remediation}`);
|
|
321
|
+
|
|
322
|
+
return lines.join('\n');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// Exports
|
|
327
|
+
// ============================================================================
|
|
328
|
+
|
|
329
|
+
export default {
|
|
330
|
+
analyzeCollisions,
|
|
331
|
+
doctorReport,
|
|
332
|
+
};
|
|
@@ -26,7 +26,8 @@ import {
|
|
|
26
26
|
loadEffectiveGSDPreferences,
|
|
27
27
|
resolveAllSkillReferences,
|
|
28
28
|
} from "./preferences.js";
|
|
29
|
-
import { loadFile, saveFile, appendOverride, appendKnowledge } from "./files.js";
|
|
29
|
+
import { loadFile, saveFile, appendOverride, appendKnowledge, splitFrontmatter, parseFrontmatterMap } from "./files.js";
|
|
30
|
+
import { runClaudeImportFlow } from "./claude-import.js";
|
|
30
31
|
import {
|
|
31
32
|
formatDoctorIssuesForPrompt,
|
|
32
33
|
formatDoctorReport,
|
|
@@ -68,13 +69,13 @@ function projectRoot(): string {
|
|
|
68
69
|
|
|
69
70
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
70
71
|
pi.registerCommand("gsd", {
|
|
71
|
-
description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|history|undo|skip|export|cleanup|mode|prefs|config|hooks|run-hook|skill-health|doctor|migrate|remote|steer|knowledge",
|
|
72
|
+
description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|history|undo|skip|export|cleanup|mode|prefs|config|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge",
|
|
72
73
|
getArgumentCompletions: (prefix: string) => {
|
|
73
74
|
const subcommands = [
|
|
74
75
|
"help", "next", "auto", "stop", "pause", "status", "visualize", "queue", "quick", "discuss",
|
|
75
76
|
"capture", "triage",
|
|
76
77
|
"history", "undo", "skip", "export", "cleanup", "mode", "prefs",
|
|
77
|
-
"config", "hooks", "run-hook", "skill-health", "doctor", "migrate", "remote", "steer", "inspect", "knowledge",
|
|
78
|
+
"config", "hooks", "run-hook", "skill-health", "doctor", "forensics", "migrate", "remote", "steer", "inspect", "knowledge",
|
|
78
79
|
];
|
|
79
80
|
const parts = prefix.trim().split(/\s+/);
|
|
80
81
|
|
|
@@ -100,7 +101,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
100
101
|
|
|
101
102
|
if (parts[0] === "prefs" && parts.length <= 2) {
|
|
102
103
|
const subPrefix = parts[1] ?? "";
|
|
103
|
-
return ["global", "project", "status", "wizard", "setup"]
|
|
104
|
+
return ["global", "project", "status", "wizard", "setup", "import-claude"]
|
|
104
105
|
.filter((cmd) => cmd.startsWith(subPrefix))
|
|
105
106
|
.map((cmd) => ({ value: `prefs ${cmd}`, label: cmd }));
|
|
106
107
|
}
|
|
@@ -204,6 +205,12 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
204
205
|
return;
|
|
205
206
|
}
|
|
206
207
|
|
|
208
|
+
if (trimmed === "forensics" || trimmed.startsWith("forensics ")) {
|
|
209
|
+
const { handleForensics } = await import("./forensics.js");
|
|
210
|
+
await handleForensics(trimmed.replace(/^forensics\s*/, "").trim(), ctx, pi);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
207
214
|
if (trimmed === "next" || trimmed.startsWith("next ")) {
|
|
208
215
|
if (trimmed.includes("--dry-run")) {
|
|
209
216
|
await handleDryRun(ctx, projectRoot());
|
|
@@ -274,6 +281,12 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
274
281
|
return;
|
|
275
282
|
}
|
|
276
283
|
|
|
284
|
+
if (trimmed === "cleanup") {
|
|
285
|
+
await handleCleanupBranches(ctx, projectRoot());
|
|
286
|
+
await handleCleanupSnapshots(ctx, projectRoot());
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
277
290
|
if (trimmed === "cleanup branches") {
|
|
278
291
|
await handleCleanupBranches(ctx, projectRoot());
|
|
279
292
|
return;
|
|
@@ -507,6 +520,15 @@ async function handlePrefs(args: string, ctx: ExtensionCommandContext): Promise<
|
|
|
507
520
|
return;
|
|
508
521
|
}
|
|
509
522
|
|
|
523
|
+
if (trimmed === "import-claude" || trimmed === "import-claude global") {
|
|
524
|
+
await handleImportClaude(ctx, "global");
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (trimmed === "import-claude project") {
|
|
529
|
+
await handleImportClaude(ctx, "project");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
510
532
|
if (trimmed === "status") {
|
|
511
533
|
const globalPrefs = loadGlobalGSDPreferences();
|
|
512
534
|
const projectPrefs = loadProjectGSDPreferences();
|
|
@@ -537,7 +559,38 @@ async function handlePrefs(args: string, ctx: ExtensionCommandContext): Promise<
|
|
|
537
559
|
return;
|
|
538
560
|
}
|
|
539
561
|
|
|
540
|
-
ctx.ui.notify("Usage: /gsd prefs [global|project|status|wizard|setup]", "info");
|
|
562
|
+
ctx.ui.notify("Usage: /gsd prefs [global|project|status|wizard|setup|import-claude [global|project]]", "info");
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function handleImportClaude(ctx: ExtensionCommandContext, scope: "global" | "project"): Promise<void> {
|
|
566
|
+
const path = scope === "project" ? getProjectGSDPreferencesPath() : getGlobalGSDPreferencesPath();
|
|
567
|
+
if (!existsSync(path)) {
|
|
568
|
+
await ensurePreferencesFile(path, ctx, scope);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const readPrefs = (): Record<string, unknown> => {
|
|
572
|
+
if (!existsSync(path)) return { version: 1 };
|
|
573
|
+
const content = readFileSync(path, "utf-8");
|
|
574
|
+
const [frontmatterLines] = splitFrontmatter(content);
|
|
575
|
+
return frontmatterLines ? parseFrontmatterMap(frontmatterLines) : { version: 1 };
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const writePrefs = async (prefs: Record<string, unknown>): Promise<void> => {
|
|
579
|
+
prefs.version = prefs.version || 1;
|
|
580
|
+
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
581
|
+
let body = "\n# GSD Skill Preferences\n\nSee `~/.gsd/agent/extensions/gsd/docs/preferences-reference.md` for full field documentation and examples.\n";
|
|
582
|
+
if (existsSync(path)) {
|
|
583
|
+
const existingContent = readFileSync(path, "utf-8");
|
|
584
|
+
const closingIdx = existingContent.indexOf("\n---", existingContent.indexOf("---"));
|
|
585
|
+
if (closingIdx !== -1) {
|
|
586
|
+
const afterFrontmatter = existingContent.slice(closingIdx + 4);
|
|
587
|
+
if (afterFrontmatter.trim()) body = afterFrontmatter;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
await saveFile(path, `---\n${frontmatter}---${body}`);
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
await runClaudeImportFlow(ctx, scope, readPrefs, writePrefs);
|
|
541
594
|
}
|
|
542
595
|
|
|
543
596
|
async function handlePrefsMode(ctx: ExtensionCommandContext, scope: "global" | "project"): Promise<void> {
|
|
@@ -991,7 +1044,7 @@ async function configureGit(ctx: ExtensionCommandContext, prefs: Record<string,
|
|
|
991
1044
|
const currentIsolation = git.isolation ? String(git.isolation) : "";
|
|
992
1045
|
const isolationChoice = await ctx.ui.select(
|
|
993
1046
|
`Git isolation strategy${currentIsolation ? ` (current: ${currentIsolation})` : " (default: worktree)"}:`,
|
|
994
|
-
["worktree", "branch", "(keep current)"],
|
|
1047
|
+
["worktree", "branch", "none", "(keep current)"],
|
|
995
1048
|
);
|
|
996
1049
|
if (isolationChoice && isolationChoice !== "(keep current)") {
|
|
997
1050
|
git.isolation = isolationChoice;
|
|
@@ -1095,7 +1148,7 @@ async function configureNotifications(ctx: ExtensionCommandContext, prefs: Recor
|
|
|
1095
1148
|
|
|
1096
1149
|
for (const field of notifFields) {
|
|
1097
1150
|
const current = notif[field.key];
|
|
1098
|
-
const currentStr = current !== undefined ? String(current) : "";
|
|
1151
|
+
const currentStr = current !== undefined && typeof current === "boolean" ? String(current) : "";
|
|
1099
1152
|
const choice = await ctx.ui.select(
|
|
1100
1153
|
`${field.label}${currentStr ? ` (current: ${currentStr})` : ` (default: ${field.defaultVal})`}:`,
|
|
1101
1154
|
["true", "false", "(keep current)"],
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff-aware context module — prioritizes recently-changed files when building
|
|
3
|
+
* context for the AI agent. Uses git diff/status to discover changes, then
|
|
4
|
+
* provides ranking utilities for context-window budget allocation.
|
|
5
|
+
*
|
|
6
|
+
* Standalone module: only imports node:child_process and node:path.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
|
|
12
|
+
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export interface ChangedFileInfo {
|
|
15
|
+
path: string;
|
|
16
|
+
changeType: "modified" | "added" | "deleted" | "staged";
|
|
17
|
+
linesChanged?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RecentFilesOptions {
|
|
21
|
+
/** Maximum number of files to return (default 20) */
|
|
22
|
+
maxFiles?: number;
|
|
23
|
+
/** Only consider commits within this many days (default 7) */
|
|
24
|
+
sinceDays?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const EXEC_OPTS = {
|
|
30
|
+
encoding: "utf-8" as const,
|
|
31
|
+
timeout: 5000,
|
|
32
|
+
stdio: ["pipe", "pipe", "pipe"] as ["pipe", "pipe", "pipe"],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function git(args: string[], cwd: string): string {
|
|
36
|
+
return execFileSync("git", args, { ...EXEC_OPTS, cwd }).trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function splitLines(output: string): string[] {
|
|
40
|
+
return output
|
|
41
|
+
.split("\n")
|
|
42
|
+
.map((l) => l.trim())
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ─── Public API ─────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns recently-changed file paths, deduplicated and sorted by recency
|
|
50
|
+
* (most recent first). Combines committed diffs, staged changes, and
|
|
51
|
+
* unstaged/untracked files from `git status`.
|
|
52
|
+
*/
|
|
53
|
+
export async function getRecentlyChangedFiles(
|
|
54
|
+
cwd: string,
|
|
55
|
+
options?: RecentFilesOptions,
|
|
56
|
+
): Promise<string[]> {
|
|
57
|
+
const maxFiles = options?.maxFiles ?? 20;
|
|
58
|
+
const sinceDays = options?.sinceDays ?? 7;
|
|
59
|
+
const dir = resolve(cwd);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// 1. Committed changes in the last N commits (or since sinceDays)
|
|
63
|
+
let committedFiles: string[] = [];
|
|
64
|
+
try {
|
|
65
|
+
const days = Math.max(1, Math.floor(Number(sinceDays)));
|
|
66
|
+
if (!Number.isFinite(days)) throw new Error("invalid sinceDays");
|
|
67
|
+
const raw = git(["log", "--diff-filter=ACMR", "--name-only", "--pretty=format:", `--since=${days} days ago`], dir);
|
|
68
|
+
committedFiles = splitLines(raw);
|
|
69
|
+
} catch {
|
|
70
|
+
// Fallback: use HEAD~10
|
|
71
|
+
try {
|
|
72
|
+
const raw = git(["diff", "--name-only", "HEAD~10"], dir);
|
|
73
|
+
committedFiles = splitLines(raw);
|
|
74
|
+
} catch {
|
|
75
|
+
// Shallow clone or <10 commits — ignore
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 2. Staged changes
|
|
80
|
+
let stagedFiles: string[] = [];
|
|
81
|
+
try {
|
|
82
|
+
const raw = git(["diff", "--cached", "--name-only"], dir);
|
|
83
|
+
stagedFiles = splitLines(raw);
|
|
84
|
+
} catch {
|
|
85
|
+
// ignore
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 3. Unstaged / untracked via porcelain status
|
|
89
|
+
let statusFiles: string[] = [];
|
|
90
|
+
try {
|
|
91
|
+
const raw = git(["status", "--porcelain"], dir);
|
|
92
|
+
statusFiles = splitLines(raw).map((line) => line.slice(3)); // strip XY + space
|
|
93
|
+
} catch {
|
|
94
|
+
// ignore
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Deduplicate, preserving insertion order (most-recent-first: status → staged → committed)
|
|
98
|
+
const seen = new Set<string>();
|
|
99
|
+
const result: string[] = [];
|
|
100
|
+
for (const file of [...statusFiles, ...stagedFiles, ...committedFiles]) {
|
|
101
|
+
if (!seen.has(file)) {
|
|
102
|
+
seen.add(file);
|
|
103
|
+
result.push(file);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return result.slice(0, maxFiles);
|
|
108
|
+
} catch {
|
|
109
|
+
// Non-git directory or git unavailable — graceful fallback
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Returns richer change metadata: change type and approximate line counts.
|
|
116
|
+
*/
|
|
117
|
+
export async function getChangedFilesWithContext(
|
|
118
|
+
cwd: string,
|
|
119
|
+
): Promise<ChangedFileInfo[]> {
|
|
120
|
+
const dir = resolve(cwd);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const result: ChangedFileInfo[] = [];
|
|
124
|
+
const seen = new Set<string>();
|
|
125
|
+
|
|
126
|
+
const add = (info: ChangedFileInfo) => {
|
|
127
|
+
if (!seen.has(info.path)) {
|
|
128
|
+
seen.add(info.path);
|
|
129
|
+
result.push(info);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// 1. Staged files with numstat
|
|
134
|
+
try {
|
|
135
|
+
const numstat = git(["diff", "--cached", "--numstat"], dir);
|
|
136
|
+
for (const line of splitLines(numstat)) {
|
|
137
|
+
const [added, deleted, filePath] = line.split("\t");
|
|
138
|
+
if (!filePath) continue;
|
|
139
|
+
const lines =
|
|
140
|
+
added === "-" || deleted === "-"
|
|
141
|
+
? undefined
|
|
142
|
+
: Number(added) + Number(deleted);
|
|
143
|
+
add({ path: filePath, changeType: "staged", linesChanged: lines });
|
|
144
|
+
}
|
|
145
|
+
} catch {
|
|
146
|
+
// ignore
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 2. Unstaged modifications with numstat
|
|
150
|
+
try {
|
|
151
|
+
const numstat = git(["diff", "--numstat"], dir);
|
|
152
|
+
for (const line of splitLines(numstat)) {
|
|
153
|
+
const [added, deleted, filePath] = line.split("\t");
|
|
154
|
+
if (!filePath) continue;
|
|
155
|
+
const lines =
|
|
156
|
+
added === "-" || deleted === "-"
|
|
157
|
+
? undefined
|
|
158
|
+
: Number(added) + Number(deleted);
|
|
159
|
+
add({ path: filePath, changeType: "modified", linesChanged: lines });
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
// ignore
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 3. Untracked / deleted from porcelain status
|
|
166
|
+
try {
|
|
167
|
+
const raw = git(["status", "--porcelain"], dir);
|
|
168
|
+
for (const line of splitLines(raw)) {
|
|
169
|
+
const code = line.slice(0, 2);
|
|
170
|
+
const filePath = line.slice(3);
|
|
171
|
+
if (seen.has(filePath)) continue;
|
|
172
|
+
|
|
173
|
+
if (code.includes("?")) {
|
|
174
|
+
add({ path: filePath, changeType: "added" });
|
|
175
|
+
} else if (code.includes("D")) {
|
|
176
|
+
add({ path: filePath, changeType: "deleted" });
|
|
177
|
+
} else if (code.includes("A")) {
|
|
178
|
+
add({ path: filePath, changeType: "added" });
|
|
179
|
+
} else {
|
|
180
|
+
add({ path: filePath, changeType: "modified" });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch {
|
|
184
|
+
// ignore
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
} catch {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Ranks a file list so that recently-changed files appear first.
|
|
195
|
+
* Files present in `changedFiles` are placed at the front (in their
|
|
196
|
+
* original changedFiles order), followed by unchanged files in their
|
|
197
|
+
* original order.
|
|
198
|
+
*/
|
|
199
|
+
export function rankFilesByRelevance(
|
|
200
|
+
files: string[],
|
|
201
|
+
changedFiles: string[],
|
|
202
|
+
): string[] {
|
|
203
|
+
const changedSet = new Set(changedFiles);
|
|
204
|
+
const changed: string[] = [];
|
|
205
|
+
const rest: string[] = [];
|
|
206
|
+
|
|
207
|
+
for (const f of files) {
|
|
208
|
+
if (changedSet.has(f)) {
|
|
209
|
+
changed.push(f);
|
|
210
|
+
} else {
|
|
211
|
+
rest.push(f);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Maintain changedFiles priority order within the changed group
|
|
216
|
+
const changedOrder = new Map(changedFiles.map((f, i) => [f, i]));
|
|
217
|
+
changed.sort((a, b) => (changedOrder.get(a) ?? 0) - (changedOrder.get(b) ?? 0));
|
|
218
|
+
|
|
219
|
+
return [...changed, ...rest];
|
|
220
|
+
}
|