gsd-pi 2.10.12 → 2.12.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/dist/bundled-extension-paths.d.ts +2 -0
- package/dist/bundled-extension-paths.js +10 -0
- package/dist/loader.js +45 -28
- package/dist/onboarding.js +100 -0
- package/dist/resource-loader.js +12 -3
- package/dist/resources/GSD-WORKFLOW.md +661 -0
- package/dist/resources/agents/researcher.md +29 -0
- package/dist/resources/agents/scout.md +56 -0
- package/dist/resources/agents/worker.md +31 -0
- package/dist/resources/extensions/ask-user-questions.ts +289 -0
- package/dist/resources/extensions/async-jobs/async-bash-tool.ts +211 -0
- package/dist/resources/extensions/async-jobs/await-tool.ts +101 -0
- package/dist/resources/extensions/async-jobs/cancel-job-tool.ts +34 -0
- package/dist/resources/extensions/async-jobs/index.ts +133 -0
- package/dist/resources/extensions/async-jobs/job-manager.ts +211 -0
- package/dist/resources/extensions/bg-shell/index.ts +3179 -0
- package/dist/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
- package/dist/resources/extensions/browser-tools/capture.ts +165 -0
- package/dist/resources/extensions/browser-tools/core.d.ts +205 -0
- package/dist/resources/extensions/browser-tools/core.js +1057 -0
- package/dist/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
- package/dist/resources/extensions/browser-tools/index.ts +51 -0
- package/dist/resources/extensions/browser-tools/lifecycle.ts +270 -0
- package/dist/resources/extensions/browser-tools/package.json +24 -0
- package/dist/resources/extensions/browser-tools/refs.ts +264 -0
- package/dist/resources/extensions/browser-tools/settle.ts +197 -0
- package/dist/resources/extensions/browser-tools/state.ts +408 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
- package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
- package/dist/resources/extensions/browser-tools/tools/assertions.ts +342 -0
- package/dist/resources/extensions/browser-tools/tools/forms.ts +801 -0
- package/dist/resources/extensions/browser-tools/tools/inspection.ts +492 -0
- package/dist/resources/extensions/browser-tools/tools/intent.ts +614 -0
- package/dist/resources/extensions/browser-tools/tools/interaction.ts +865 -0
- package/dist/resources/extensions/browser-tools/tools/navigation.ts +232 -0
- package/dist/resources/extensions/browser-tools/tools/pages.ts +303 -0
- package/dist/resources/extensions/browser-tools/tools/refs.ts +541 -0
- package/dist/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
- package/dist/resources/extensions/browser-tools/tools/session.ts +400 -0
- package/dist/resources/extensions/browser-tools/tools/wait.ts +247 -0
- package/dist/resources/extensions/browser-tools/utils.ts +660 -0
- package/dist/resources/extensions/context7/index.ts +428 -0
- package/dist/resources/extensions/context7/package.json +11 -0
- package/dist/resources/extensions/get-secrets-from-user.ts +625 -0
- package/dist/resources/extensions/google-search/index.ts +336 -0
- package/dist/resources/extensions/google-search/package.json +9 -0
- package/dist/resources/extensions/gsd/activity-log.ts +116 -0
- package/dist/resources/extensions/gsd/auto.ts +3732 -0
- package/dist/resources/extensions/gsd/commands.ts +544 -0
- package/dist/resources/extensions/gsd/crash-recovery.ts +105 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +564 -0
- package/dist/resources/extensions/gsd/dispatch-guard.ts +78 -0
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +278 -0
- package/dist/resources/extensions/gsd/doctor.ts +766 -0
- package/dist/resources/extensions/gsd/exit-command.ts +18 -0
- package/dist/resources/extensions/gsd/files.ts +857 -0
- package/dist/resources/extensions/gsd/git-service.ts +904 -0
- package/dist/resources/extensions/gsd/gitignore.ts +200 -0
- package/dist/resources/extensions/gsd/guided-flow.ts +1054 -0
- package/dist/resources/extensions/gsd/index.ts +735 -0
- package/dist/resources/extensions/gsd/metrics.ts +374 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +218 -0
- package/dist/resources/extensions/gsd/migrate/index.ts +42 -0
- package/dist/resources/extensions/gsd/migrate/parser.ts +323 -0
- package/dist/resources/extensions/gsd/migrate/parsers.ts +624 -0
- package/dist/resources/extensions/gsd/migrate/preview.ts +48 -0
- package/dist/resources/extensions/gsd/migrate/transformer.ts +346 -0
- package/dist/resources/extensions/gsd/migrate/types.ts +370 -0
- package/dist/resources/extensions/gsd/migrate/validator.ts +55 -0
- package/dist/resources/extensions/gsd/migrate/writer.ts +539 -0
- package/dist/resources/extensions/gsd/native-git-bridge.ts +181 -0
- package/dist/resources/extensions/gsd/native-parser-bridge.ts +135 -0
- package/dist/resources/extensions/gsd/observability-validator.ts +408 -0
- package/dist/resources/extensions/gsd/package.json +11 -0
- package/dist/resources/extensions/gsd/paths.ts +339 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +449 -0
- package/dist/resources/extensions/gsd/preferences.ts +988 -0
- package/dist/resources/extensions/gsd/prompt-loader.ts +92 -0
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +27 -0
- package/dist/resources/extensions/gsd/prompts/discuss.md +237 -0
- package/dist/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +63 -0
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -0
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +61 -0
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +3 -0
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +29 -0
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -0
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +13 -0
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +78 -0
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +55 -0
- package/dist/resources/extensions/gsd/prompts/queue.md +100 -0
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +39 -0
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +37 -0
- package/dist/resources/extensions/gsd/prompts/research-slice.md +28 -0
- package/dist/resources/extensions/gsd/prompts/review-migration.md +66 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +109 -0
- package/dist/resources/extensions/gsd/prompts/system.md +190 -0
- package/dist/resources/extensions/gsd/prompts/worktree-merge.md +123 -0
- package/dist/resources/extensions/gsd/roadmap-slices.ts +50 -0
- package/dist/resources/extensions/gsd/session-forensics.ts +500 -0
- package/dist/resources/extensions/gsd/skill-discovery.ts +137 -0
- package/dist/resources/extensions/gsd/state.ts +569 -0
- package/dist/resources/extensions/gsd/templates/context.md +76 -0
- package/dist/resources/extensions/gsd/templates/decisions.md +8 -0
- package/dist/resources/extensions/gsd/templates/milestone-summary.md +73 -0
- package/dist/resources/extensions/gsd/templates/plan.md +131 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +23 -0
- package/dist/resources/extensions/gsd/templates/project.md +31 -0
- package/dist/resources/extensions/gsd/templates/reassessment.md +28 -0
- package/dist/resources/extensions/gsd/templates/requirements.md +81 -0
- package/dist/resources/extensions/gsd/templates/research.md +46 -0
- package/dist/resources/extensions/gsd/templates/roadmap.md +118 -0
- package/dist/resources/extensions/gsd/templates/secrets-manifest.md +22 -0
- package/dist/resources/extensions/gsd/templates/slice-context.md +58 -0
- package/dist/resources/extensions/gsd/templates/slice-summary.md +99 -0
- package/dist/resources/extensions/gsd/templates/state.md +19 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +52 -0
- package/dist/resources/extensions/gsd/templates/task-summary.md +57 -0
- package/dist/resources/extensions/gsd/templates/uat.md +54 -0
- package/dist/resources/extensions/gsd/tests/activity-log-prune.test.ts +297 -0
- package/dist/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
- package/dist/resources/extensions/gsd/tests/auto-draft-pause.test.ts +109 -0
- package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +45 -0
- package/dist/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +196 -0
- package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
- package/dist/resources/extensions/gsd/tests/collect-from-manifest.test.ts +469 -0
- package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +200 -0
- package/dist/resources/extensions/gsd/tests/cost-projection.test.ts +134 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +312 -0
- package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +299 -0
- package/dist/resources/extensions/gsd/tests/derive-state.test.ts +660 -0
- package/dist/resources/extensions/gsd/tests/discuss-prompt.test.ts +27 -0
- package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +91 -0
- package/dist/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +170 -0
- package/dist/resources/extensions/gsd/tests/doctor.test.ts +594 -0
- package/dist/resources/extensions/gsd/tests/draft-promotion.test.ts +169 -0
- package/dist/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +1788 -0
- package/dist/resources/extensions/gsd/tests/idle-recovery.test.ts +577 -0
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +580 -0
- package/dist/resources/extensions/gsd/tests/manifest-status.test.ts +283 -0
- package/dist/resources/extensions/gsd/tests/metrics-io.test.ts +176 -0
- package/dist/resources/extensions/gsd/tests/metrics.test.ts +188 -0
- package/dist/resources/extensions/gsd/tests/migrate-command.test.ts +369 -0
- package/dist/resources/extensions/gsd/tests/migrate-parser.test.ts +757 -0
- package/dist/resources/extensions/gsd/tests/migrate-transformer.test.ts +635 -0
- package/dist/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +414 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +297 -0
- package/dist/resources/extensions/gsd/tests/migrate-writer.test.ts +398 -0
- package/dist/resources/extensions/gsd/tests/must-have-parser.test.ts +291 -0
- package/dist/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
- package/dist/resources/extensions/gsd/tests/next-milestone-id.test.ts +66 -0
- package/dist/resources/extensions/gsd/tests/orphaned-branch.test.ts +353 -0
- package/dist/resources/extensions/gsd/tests/parsers.test.ts +1664 -0
- package/dist/resources/extensions/gsd/tests/plan-milestone.test.ts +133 -0
- package/dist/resources/extensions/gsd/tests/plan-quality-validator.test.ts +363 -0
- package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
- package/dist/resources/extensions/gsd/tests/queue-draft-detection.test.ts +126 -0
- package/dist/resources/extensions/gsd/tests/reassess-prompt.test.ts +142 -0
- package/dist/resources/extensions/gsd/tests/regex-hardening.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +155 -0
- package/dist/resources/extensions/gsd/tests/remote-status.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/replan-slice.test.ts +493 -0
- package/dist/resources/extensions/gsd/tests/requirements.test.ts +106 -0
- package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +35 -0
- package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +11 -0
- package/dist/resources/extensions/gsd/tests/roadmap-slices.test.ts +41 -0
- package/dist/resources/extensions/gsd/tests/run-uat.test.ts +316 -0
- package/dist/resources/extensions/gsd/tests/secure-env-collect.test.ts +185 -0
- package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +123 -0
- package/dist/resources/extensions/gsd/tests/test-helpers.ts +61 -0
- package/dist/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +219 -0
- package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +234 -0
- package/dist/resources/extensions/gsd/tests/workspace-index.test.ts +75 -0
- package/dist/resources/extensions/gsd/tests/worktree-integration.test.ts +234 -0
- package/dist/resources/extensions/gsd/tests/worktree-manager.test.ts +141 -0
- package/dist/resources/extensions/gsd/tests/worktree.test.ts +597 -0
- package/dist/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
- package/dist/resources/extensions/gsd/types.ts +296 -0
- package/dist/resources/extensions/gsd/unit-runtime.ts +184 -0
- package/dist/resources/extensions/gsd/workspace-index.ts +189 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +845 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +459 -0
- package/dist/resources/extensions/gsd/{worktree.js → worktree.ts} +92 -61
- package/dist/resources/extensions/mac-tools/index.ts +852 -0
- package/dist/resources/extensions/mac-tools/swift-cli/Package.swift +22 -0
- package/dist/resources/extensions/mac-tools/swift-cli/Sources/main.swift +1318 -0
- package/dist/resources/extensions/mcporter/index.ts +429 -0
- package/dist/resources/extensions/remote-questions/config.ts +81 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +128 -0
- package/dist/resources/extensions/remote-questions/format.ts +163 -0
- package/dist/resources/extensions/remote-questions/manager.ts +192 -0
- package/dist/resources/extensions/remote-questions/remote-command.ts +361 -0
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +92 -0
- package/dist/resources/extensions/remote-questions/status.ts +31 -0
- package/dist/resources/extensions/remote-questions/store.ts +77 -0
- package/dist/resources/extensions/remote-questions/types.ts +75 -0
- package/dist/resources/extensions/search-the-web/cache.ts +78 -0
- package/dist/resources/extensions/search-the-web/command-search-provider.ts +99 -0
- package/dist/resources/extensions/search-the-web/format.ts +258 -0
- package/dist/resources/extensions/search-the-web/http.ts +238 -0
- package/dist/resources/extensions/search-the-web/index.ts +65 -0
- package/dist/resources/extensions/search-the-web/native-search.ts +165 -0
- package/dist/resources/extensions/search-the-web/provider.ts +136 -0
- package/dist/resources/extensions/search-the-web/tavily.ts +116 -0
- package/dist/resources/extensions/search-the-web/tool-fetch-page.ts +581 -0
- package/dist/resources/extensions/search-the-web/tool-llm-context.ts +620 -0
- package/dist/resources/extensions/search-the-web/tool-search.ts +635 -0
- package/dist/resources/extensions/search-the-web/url-utils.ts +91 -0
- package/dist/resources/extensions/shared/bundled-extension-paths.ts +11 -0
- package/dist/resources/extensions/shared/confirm-ui.ts +126 -0
- package/dist/resources/extensions/shared/interview-ui.ts +613 -0
- package/dist/resources/extensions/shared/next-action-ui.ts +197 -0
- package/dist/resources/extensions/shared/progress-widget.ts +282 -0
- package/dist/resources/extensions/shared/terminal.ts +23 -0
- package/dist/resources/extensions/shared/thinking-widget.ts +107 -0
- package/dist/resources/extensions/shared/ui.ts +400 -0
- package/dist/resources/extensions/shared/wizard-ui.ts +551 -0
- package/dist/resources/extensions/slash-commands/audit.ts +88 -0
- package/dist/resources/extensions/slash-commands/clear.ts +10 -0
- package/dist/resources/extensions/slash-commands/create-extension.ts +297 -0
- package/dist/resources/extensions/slash-commands/create-slash-command.ts +234 -0
- package/dist/resources/extensions/slash-commands/index.ts +12 -0
- package/dist/resources/extensions/subagent/agents.ts +126 -0
- package/dist/resources/extensions/subagent/index.ts +1072 -0
- package/dist/resources/extensions/subagent/isolation.ts +498 -0
- package/dist/resources/extensions/ttsr/index.ts +163 -0
- package/dist/resources/extensions/ttsr/rule-loader.ts +121 -0
- package/dist/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
- package/dist/resources/extensions/ttsr/ttsr-manager.ts +430 -0
- package/dist/resources/extensions/universal-config/discovery.ts +78 -0
- package/dist/resources/extensions/universal-config/format.ts +160 -0
- package/dist/resources/extensions/universal-config/index.ts +118 -0
- package/dist/resources/extensions/universal-config/package.json +11 -0
- package/dist/resources/extensions/universal-config/scanners.ts +579 -0
- package/dist/resources/extensions/universal-config/tests/discovery.test.ts +111 -0
- package/dist/resources/extensions/universal-config/tests/format.test.ts +111 -0
- package/dist/resources/extensions/universal-config/tests/scanners.test.ts +438 -0
- package/dist/resources/extensions/universal-config/tools.ts +60 -0
- package/dist/resources/extensions/universal-config/types.ts +116 -0
- package/dist/resources/extensions/voice/index.ts +271 -0
- package/dist/resources/extensions/voice/speech-recognizer.py +504 -0
- package/dist/resources/extensions/voice/speech-recognizer.swift +154 -0
- package/dist/resources/skills/debug-like-expert/SKILL.md +231 -0
- package/dist/resources/skills/debug-like-expert/references/debugging-mindset.md +253 -0
- package/dist/resources/skills/debug-like-expert/references/hypothesis-testing.md +373 -0
- package/dist/resources/skills/debug-like-expert/references/investigation-techniques.md +337 -0
- package/dist/resources/skills/debug-like-expert/references/verification-patterns.md +425 -0
- package/dist/resources/skills/debug-like-expert/references/when-to-research.md +361 -0
- package/dist/resources/skills/frontend-design/SKILL.md +45 -0
- package/dist/resources/skills/github-workflows/SKILL.md +92 -0
- package/dist/resources/skills/github-workflows/references/gh/SKILL.md +255 -0
- package/dist/resources/skills/github-workflows/references/gh/references/issue-stories.md +204 -0
- package/dist/resources/skills/github-workflows/references/gh/references/labels.md +170 -0
- package/dist/resources/skills/github-workflows/references/gh/references/milestones.md +158 -0
- package/dist/resources/skills/github-workflows/references/gh/references/projects-v2.md +177 -0
- package/dist/resources/skills/github-workflows/references/gh/scripts/experiment_cleanup.py +191 -0
- package/dist/resources/skills/github-workflows/references/gh/scripts/github_project_setup.py +799 -0
- package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
- package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +608 -0
- package/dist/resources/skills/swiftui/SKILL.md +208 -0
- package/dist/resources/skills/swiftui/references/animations.md +921 -0
- package/dist/resources/skills/swiftui/references/architecture.md +1561 -0
- package/dist/resources/skills/swiftui/references/layout-system.md +1186 -0
- package/dist/resources/skills/swiftui/references/navigation.md +1492 -0
- package/dist/resources/skills/swiftui/references/networking-async.md +214 -0
- package/dist/resources/skills/swiftui/references/performance.md +1706 -0
- package/dist/resources/skills/swiftui/references/platform-integration.md +204 -0
- package/dist/resources/skills/swiftui/references/state-management.md +1443 -0
- package/dist/resources/skills/swiftui/references/swiftdata.md +297 -0
- package/dist/resources/skills/swiftui/references/testing-debugging.md +247 -0
- package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +218 -0
- package/dist/resources/skills/swiftui/workflows/add-feature.md +191 -0
- package/dist/resources/skills/swiftui/workflows/build-new-app.md +311 -0
- package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +192 -0
- package/dist/resources/skills/swiftui/workflows/optimize-performance.md +197 -0
- package/dist/resources/skills/swiftui/workflows/ship-app.md +203 -0
- package/dist/resources/skills/swiftui/workflows/write-tests.md +235 -0
- package/dist/wizard.js +1 -0
- package/package.json +8 -6
- package/packages/native/dist/fd/index.d.ts +1 -0
- package/packages/native/dist/fd/index.js +1 -0
- package/packages/native/dist/grep/index.d.ts +2 -1
- package/packages/native/dist/grep/index.js +1 -0
- package/packages/native/src/__tests__/fd.test.mjs +35 -0
- package/packages/native/src/__tests__/grep.test.mjs +26 -9
- package/packages/native/src/fd/index.ts +1 -0
- package/packages/native/src/grep/index.ts +3 -2
- package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent-loop.js +169 -55
- package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts +13 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +16 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +91 -1
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent-loop.ts +273 -63
- package/packages/pi-agent-core/src/agent.ts +24 -0
- package/packages/pi-agent-core/src/types.ts +98 -0
- package/packages/pi-ai/dist/env-api-keys.js +2 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +314 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +236 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +6 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/openai-completions.js +2 -0
- package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +1 -1
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +1 -1
- package/packages/pi-ai/src/env-api-keys.ts +2 -0
- package/packages/pi-ai/src/models.generated.ts +236 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +8 -0
- package/packages/pi-ai/src/providers/openai-completions.ts +3 -0
- package/packages/pi-ai/src/types.ts +2 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +1 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +137 -22
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +43 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts +51 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js +119 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js +183 -0
- package/packages/pi-coding-agent/dist/core/fallback-resolver.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/index.js +1 -0
- package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +12 -1
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +37 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/session-manager.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.test.js +61 -0
- package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +47 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/find.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/find.js +1 -0
- package/packages/pi-coding-agent/dist/core/tools/find.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +6 -15
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.d.ts +18 -1
- 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 +46 -4
- package/packages/pi-coding-agent/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +25 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +1 -0
- package/packages/pi-coding-agent/src/core/agent-session.ts +167 -22
- package/packages/pi-coding-agent/src/core/auth-storage.ts +45 -0
- package/packages/pi-coding-agent/src/core/fallback-resolver.test.ts +229 -0
- package/packages/pi-coding-agent/src/core/fallback-resolver.ts +165 -0
- package/packages/pi-coding-agent/src/core/index.ts +1 -0
- package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
- package/packages/pi-coding-agent/src/core/session-manager.test.ts +65 -0
- package/packages/pi-coding-agent/src/core/session-manager.ts +51 -1
- package/packages/pi-coding-agent/src/core/settings-manager.ts +66 -0
- package/packages/pi-coding-agent/src/core/tools/find.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +6 -16
- package/packages/pi-coding-agent/src/modes/interactive/components/login-dialog.ts +49 -4
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +28 -2
- package/packages/pi-tui/dist/autocomplete.d.ts.map +1 -1
- package/packages/pi-tui/dist/autocomplete.js +3 -4
- package/packages/pi-tui/dist/autocomplete.js.map +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts +7 -0
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +57 -50
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/autocomplete.ts +3 -5
- package/packages/pi-tui/src/components/editor.ts +70 -49
- package/pkg/dist/core/export-html/ansi-to-html.d.ts +22 -0
- package/pkg/dist/core/export-html/ansi-to-html.d.ts.map +1 -0
- package/pkg/dist/core/export-html/ansi-to-html.js +249 -0
- package/pkg/dist/core/export-html/ansi-to-html.js.map +1 -0
- package/pkg/dist/core/export-html/index.d.ts +37 -0
- package/pkg/dist/core/export-html/index.d.ts.map +1 -0
- package/pkg/dist/core/export-html/index.js +223 -0
- package/pkg/dist/core/export-html/index.js.map +1 -0
- package/pkg/dist/core/export-html/template.css +971 -0
- package/pkg/dist/core/export-html/template.html +54 -0
- package/pkg/dist/core/export-html/template.js +1583 -0
- package/pkg/dist/core/export-html/tool-renderer.d.ts +38 -0
- package/pkg/dist/core/export-html/tool-renderer.d.ts.map +1 -0
- package/pkg/dist/core/export-html/tool-renderer.js +70 -0
- package/pkg/dist/core/export-html/tool-renderer.js.map +1 -0
- package/pkg/dist/core/export-html/vendor/highlight.min.js +1213 -0
- package/pkg/dist/core/export-html/vendor/marked.min.js +6 -0
- package/scripts/postinstall.js +30 -17
- package/src/resources/extensions/bg-shell/index.ts +51 -7
- package/src/resources/extensions/gsd/activity-log.ts +61 -14
- package/src/resources/extensions/gsd/auto.ts +288 -19
- package/src/resources/extensions/gsd/commands.ts +9 -3
- package/src/resources/extensions/gsd/dashboard-overlay.ts +55 -12
- package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
- package/src/resources/extensions/gsd/doctor.ts +60 -3
- package/src/resources/extensions/gsd/files.ts +45 -1
- package/src/resources/extensions/gsd/git-service.ts +46 -41
- package/src/resources/extensions/gsd/guided-flow.ts +81 -9
- package/src/resources/extensions/gsd/index.ts +62 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +181 -0
- package/src/resources/extensions/gsd/paths.ts +35 -4
- package/src/resources/extensions/gsd/post-unit-hooks.ts +449 -0
- package/src/resources/extensions/gsd/preferences.ts +232 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -3
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +10 -8
- package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +4 -2
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +3 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +9 -12
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -3
- package/src/resources/extensions/gsd/prompts/queue.md +3 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/state.ts +130 -18
- package/src/resources/extensions/gsd/templates/context.md +1 -1
- package/src/resources/extensions/gsd/templates/state.md +3 -3
- package/src/resources/extensions/gsd/tests/activity-log-save.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/doctor.test.ts +115 -1
- package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +6 -2
- package/src/resources/extensions/gsd/tests/git-service.test.ts +35 -35
- package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +8 -8
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +4 -1
- package/src/resources/extensions/gsd/tests/network-error-fallback.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/orphaned-branch.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +297 -0
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +226 -0
- package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +12 -0
- package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/worktree.test.ts +19 -19
- package/src/resources/extensions/gsd/types.ts +109 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +72 -13
- package/src/resources/extensions/gsd/worktree.ts +5 -4
- package/src/resources/extensions/search-the-web/command-search-provider.ts +8 -4
- package/src/resources/extensions/search-the-web/native-search.ts +32 -23
- package/src/resources/extensions/search-the-web/provider.ts +19 -2
- package/src/resources/extensions/search-the-web/tool-fetch-page.ts +62 -0
- package/src/resources/extensions/search-the-web/tool-llm-context.ts +62 -3
- package/src/resources/extensions/search-the-web/tool-search.ts +62 -3
- package/src/resources/extensions/shared/bundled-extension-paths.ts +11 -0
- package/src/resources/extensions/subagent/index.ts +2 -1
- package/src/resources/skills/github-workflows/SKILL.md +5 -0
- package/dist/resources/extensions/gsd/git-service.d.ts +0 -225
- package/dist/resources/extensions/gsd/git-service.js +0 -756
- package/dist/resources/extensions/gsd/preferences.d.ts +0 -122
- package/dist/resources/extensions/gsd/preferences.js +0 -616
- package/dist/resources/extensions/gsd/worktree.d.ts +0 -106
- package/dist/resources/extensions/remote-questions/config.d.ts +0 -14
- package/dist/resources/extensions/remote-questions/config.js +0 -68
- package/dist/resources/extensions/remote-questions/discord-adapter.d.ts +0 -17
- package/dist/resources/extensions/remote-questions/discord-adapter.js +0 -118
- package/dist/resources/extensions/remote-questions/format.d.ts +0 -38
- package/dist/resources/extensions/remote-questions/format.js +0 -119
- package/dist/resources/extensions/remote-questions/manager.d.ts +0 -23
- package/dist/resources/extensions/remote-questions/manager.js +0 -161
- package/dist/resources/extensions/remote-questions/remote-command.d.ts +0 -6
- package/dist/resources/extensions/remote-questions/remote-command.js +0 -358
- package/dist/resources/extensions/remote-questions/slack-adapter.d.ts +0 -15
- package/dist/resources/extensions/remote-questions/slack-adapter.js +0 -82
- package/dist/resources/extensions/remote-questions/status.d.ts +0 -9
- package/dist/resources/extensions/remote-questions/status.js +0 -25
- package/dist/resources/extensions/remote-questions/store.d.ts +0 -11
- package/dist/resources/extensions/remote-questions/store.js +0 -66
- package/dist/resources/extensions/remote-questions/types.d.ts +0 -68
- package/dist/resources/extensions/remote-questions/types.js +0 -4
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join, dirname } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { createTestContext } from './test-helpers.ts';
|
|
5
|
+
|
|
6
|
+
// loadPrompt reads from ~/.gsd/agent/extensions/gsd/prompts/ (main checkout).
|
|
7
|
+
// In a worktree the file may not exist there yet, so we resolve prompts
|
|
8
|
+
// relative to this test file's location (the worktree copy).
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const worktreePromptsDir = join(__dirname, "..", "prompts");
|
|
11
|
+
|
|
12
|
+
const { assertTrue, report } = createTestContext();
|
|
13
|
+
/**
|
|
14
|
+
* Load a prompt template from the worktree prompts directory
|
|
15
|
+
* and apply variable substitution (mirrors loadPrompt logic).
|
|
16
|
+
*/
|
|
17
|
+
function loadPromptFromWorktree(name: string, vars: Record<string, string> = {}): string {
|
|
18
|
+
const path = join(worktreePromptsDir, `${name}.md`);
|
|
19
|
+
let content = readFileSync(path, "utf-8");
|
|
20
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
21
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
22
|
+
}
|
|
23
|
+
return content.trim();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
|
+
// Tests
|
|
28
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
29
|
+
|
|
30
|
+
async function main(): Promise<void> {
|
|
31
|
+
|
|
32
|
+
// ─── reassess-roadmap prompt loads and substitutes ─────────────────────
|
|
33
|
+
console.log("\n=== reassess-roadmap prompt loads and substitutes ===");
|
|
34
|
+
{
|
|
35
|
+
const testVars = {
|
|
36
|
+
milestoneId: "M099",
|
|
37
|
+
completedSliceId: "S03",
|
|
38
|
+
assessmentPath: ".gsd/milestones/M099/slices/S03/S03-ASSESSMENT.md",
|
|
39
|
+
roadmapPath: ".gsd/milestones/M099/M099-ROADMAP.md",
|
|
40
|
+
inlinedContext: "--- test inlined context block ---",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
let result: string;
|
|
44
|
+
let threw = false;
|
|
45
|
+
try {
|
|
46
|
+
result = loadPromptFromWorktree("reassess-roadmap", testVars);
|
|
47
|
+
} catch (err) {
|
|
48
|
+
threw = true;
|
|
49
|
+
result = "";
|
|
50
|
+
console.error(` ERROR: loadPrompt threw: ${err}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
assertTrue(!threw, "loadPrompt does not throw for reassess-roadmap");
|
|
54
|
+
assertTrue(typeof result === "string" && result.length > 0, "loadPrompt returns a non-empty string");
|
|
55
|
+
|
|
56
|
+
// Verify all test variables were substituted into the output
|
|
57
|
+
assertTrue(result.includes("M099"), "prompt contains milestoneId 'M099'");
|
|
58
|
+
assertTrue(result.includes("S03"), "prompt contains completedSliceId 'S03'");
|
|
59
|
+
assertTrue(result.includes(".gsd/milestones/M099/slices/S03/S03-ASSESSMENT.md"), "prompt contains assessmentPath");
|
|
60
|
+
assertTrue(result.includes(".gsd/milestones/M099/M099-ROADMAP.md"), "prompt contains roadmapPath");
|
|
61
|
+
assertTrue(result.includes("--- test inlined context block ---"), "prompt contains inlinedContext");
|
|
62
|
+
|
|
63
|
+
// Verify no un-substituted variables remain
|
|
64
|
+
assertTrue(!result.includes("{{milestoneId}}"), "no un-substituted {{milestoneId}}");
|
|
65
|
+
assertTrue(!result.includes("{{completedSliceId}}"), "no un-substituted {{completedSliceId}}");
|
|
66
|
+
assertTrue(!result.includes("{{assessmentPath}}"), "no un-substituted {{assessmentPath}}");
|
|
67
|
+
assertTrue(!result.includes("{{roadmapPath}}"), "no un-substituted {{roadmapPath}}");
|
|
68
|
+
assertTrue(!result.includes("{{inlinedContext}}"), "no un-substituted {{inlinedContext}}");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── reassess-roadmap contains coverage-check instruction ─────────────
|
|
72
|
+
console.log("\n=== reassess-roadmap contains coverage-check instruction ===");
|
|
73
|
+
{
|
|
74
|
+
const prompt = loadPromptFromWorktree("reassess-roadmap", {
|
|
75
|
+
milestoneId: "M001",
|
|
76
|
+
completedSliceId: "S01",
|
|
77
|
+
assessmentPath: ".gsd/milestones/M001/slices/S01/S01-ASSESSMENT.md",
|
|
78
|
+
roadmapPath: ".gsd/milestones/M001/M001-ROADMAP.md",
|
|
79
|
+
inlinedContext: "context",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Normalize to lowercase for case-insensitive matching
|
|
83
|
+
const lower = prompt.toLowerCase();
|
|
84
|
+
|
|
85
|
+
// The prompt must mention "each success criterion" or "every success criterion"
|
|
86
|
+
assertTrue(
|
|
87
|
+
lower.includes("each success criterion") || lower.includes("every success criterion"),
|
|
88
|
+
"prompt contains 'each success criterion' or 'every success criterion'"
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// The prompt must mention "owning slice" or "remaining slice"
|
|
92
|
+
assertTrue(
|
|
93
|
+
lower.includes("owning slice") || lower.includes("remaining slice"),
|
|
94
|
+
"prompt contains 'owning slice' or 'remaining slice'"
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// The prompt must mention "no remaining owner" or "no owner" or "no slice"
|
|
98
|
+
assertTrue(
|
|
99
|
+
lower.includes("no remaining owner") || lower.includes("no owner") || lower.includes("no slice"),
|
|
100
|
+
"prompt contains 'no remaining owner', 'no owner', or 'no slice'"
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// The prompt must mention "blocking issue" or "blocking"
|
|
104
|
+
assertTrue(
|
|
105
|
+
lower.includes("blocking issue") || lower.includes("blocking"),
|
|
106
|
+
"prompt contains 'blocking issue' or 'blocking'"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ─── coverage-check requires at-least-one semantics ───────────────────
|
|
111
|
+
console.log("\n=== coverage-check requires at-least-one semantics ===");
|
|
112
|
+
{
|
|
113
|
+
const prompt = loadPromptFromWorktree("reassess-roadmap", {
|
|
114
|
+
milestoneId: "M001",
|
|
115
|
+
completedSliceId: "S01",
|
|
116
|
+
assessmentPath: ".gsd/milestones/M001/slices/S01/S01-ASSESSMENT.md",
|
|
117
|
+
roadmapPath: ".gsd/milestones/M001/M001-ROADMAP.md",
|
|
118
|
+
inlinedContext: "context",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const lower = prompt.toLowerCase();
|
|
122
|
+
|
|
123
|
+
// The instruction must use "at least one" or equivalent inclusive language
|
|
124
|
+
assertTrue(
|
|
125
|
+
lower.includes("at least one") || lower.includes("at-least-one") || lower.includes("one or more"),
|
|
126
|
+
"prompt uses 'at least one' or equivalent inclusive language for slice ownership"
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// The instruction must NOT require "exactly one" — that would be too rigid
|
|
130
|
+
assertTrue(
|
|
131
|
+
!lower.includes("exactly one owner") && !lower.includes("exactly one slice"),
|
|
132
|
+
"prompt does NOT use 'exactly one' for slice ownership (would be too rigid)"
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
report();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
main().catch((error) => {
|
|
140
|
+
console.error(error);
|
|
141
|
+
process.exit(1);
|
|
142
|
+
});
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
// Regex-hardening tests for S02/T02 — proves all 12 regex/parser sites
|
|
2
|
+
// accept both M001 (classic) and M001-abc123 (unique) milestone ID formats.
|
|
3
|
+
//
|
|
4
|
+
// Sections:
|
|
5
|
+
// (a) Directory scanning regex — findMilestoneIds pattern
|
|
6
|
+
// (b) Title-strip regex — milestone title cleanup
|
|
7
|
+
// (c) SLICE_BRANCH_RE — branch name parsing (with/without worktree prefix)
|
|
8
|
+
// (d) Milestone detection regex — hasExistingMilestones pattern
|
|
9
|
+
// (e) MILESTONE_CONTEXT_RE — context write-gate filename match
|
|
10
|
+
// (f) Prompt dispatch regexes — executeMatch and resumeMatch capture
|
|
11
|
+
// (g) milestoneIdSort — mixed-format ordering
|
|
12
|
+
// (h) extractMilestoneSeq — numeric extraction from both formats
|
|
13
|
+
|
|
14
|
+
import { test } from 'node:test';
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
MILESTONE_ID_RE,
|
|
18
|
+
extractMilestoneSeq,
|
|
19
|
+
milestoneIdSort,
|
|
20
|
+
} from '../guided-flow.ts';
|
|
21
|
+
|
|
22
|
+
import { SLICE_BRANCH_RE } from '../worktree.ts';
|
|
23
|
+
import { createTestContext } from './test-helpers.ts';
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
const { assertEq, assertTrue, report } = createTestContext();
|
|
27
|
+
// ─── Tests ─────────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
async function main(): Promise<void> {
|
|
30
|
+
console.log('regex-hardening tests');
|
|
31
|
+
|
|
32
|
+
// (a) Directory scanning regex — used in state.ts, workspace-index.ts, files.ts
|
|
33
|
+
// Pattern: /^(M\d+(?:-[a-z0-9]{6})?)/
|
|
34
|
+
{
|
|
35
|
+
console.log(' (a) Directory scanning regex');
|
|
36
|
+
const DIR_SCAN_RE = /^(M\d+(?:-[a-z0-9]{6})?)/;
|
|
37
|
+
|
|
38
|
+
// Classic format matches
|
|
39
|
+
assertTrue(DIR_SCAN_RE.test('M001'), 'dir scan matches M001');
|
|
40
|
+
assertTrue(DIR_SCAN_RE.test('M042'), 'dir scan matches M042');
|
|
41
|
+
assertTrue(DIR_SCAN_RE.test('M999'), 'dir scan matches M999');
|
|
42
|
+
assertEq(('M001' as string).match(DIR_SCAN_RE)?.[1], 'M001', 'captures M001');
|
|
43
|
+
|
|
44
|
+
// Unique format matches
|
|
45
|
+
assertTrue(DIR_SCAN_RE.test('M001-abc123'), 'dir scan matches M001-abc123');
|
|
46
|
+
assertTrue(DIR_SCAN_RE.test('M042-z9a8b7'), 'dir scan matches M042-z9a8b7');
|
|
47
|
+
assertEq(('M001-abc123' as string).match(DIR_SCAN_RE)?.[1], 'M001-abc123', 'captures M001-abc123 from dir name');
|
|
48
|
+
|
|
49
|
+
// Rejects
|
|
50
|
+
assertTrue(!DIR_SCAN_RE.test('S01'), 'dir scan rejects S01');
|
|
51
|
+
assertTrue(!DIR_SCAN_RE.test('X001'), 'dir scan rejects X001');
|
|
52
|
+
assertTrue(!DIR_SCAN_RE.test('.DS_Store'), 'dir scan rejects .DS_Store');
|
|
53
|
+
assertTrue(!DIR_SCAN_RE.test('notes'), 'dir scan rejects notes');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// (b) Title-strip regex — used in state.ts, workspace-index.ts
|
|
57
|
+
// Pattern: /^M\d+(?:-[a-z0-9]{6})?[^:]*:\s*/
|
|
58
|
+
{
|
|
59
|
+
console.log(' (b) Title-strip regex');
|
|
60
|
+
const TITLE_STRIP_RE = /^M\d+(?:-[a-z0-9]{6})?[^:]*:\s*/;
|
|
61
|
+
|
|
62
|
+
// Classic format strip
|
|
63
|
+
assertEq('M001: Title'.replace(TITLE_STRIP_RE, ''), 'Title', 'strips M001: Title → Title');
|
|
64
|
+
assertEq('M042: Payment Integration'.replace(TITLE_STRIP_RE, ''), 'Payment Integration', 'strips M042: Payment Integration');
|
|
65
|
+
|
|
66
|
+
// Unique format strip
|
|
67
|
+
assertEq('M001-abc123: Title'.replace(TITLE_STRIP_RE, ''), 'Title', 'strips M001-abc123: Title → Title');
|
|
68
|
+
assertEq('M042-z9a8b7: Dashboard'.replace(TITLE_STRIP_RE, ''), 'Dashboard', 'strips M042-z9a8b7: Dashboard');
|
|
69
|
+
|
|
70
|
+
// Em dash in title — current format (M001: Title) correctly preserves em dash in title body
|
|
71
|
+
assertEq(
|
|
72
|
+
'M001: Foundation — Build Core'.replace(TITLE_STRIP_RE, ''),
|
|
73
|
+
'Foundation — Build Core',
|
|
74
|
+
'strips M001: prefix and preserves em dash in title body',
|
|
75
|
+
);
|
|
76
|
+
assertEq(
|
|
77
|
+
'M001-abc123: Foundation — Build Core'.replace(TITLE_STRIP_RE, ''),
|
|
78
|
+
'Foundation — Build Core',
|
|
79
|
+
'strips M001-abc123: prefix and preserves em dash in title body (unique format)',
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Edge case: dash-style separator (M001 — Title: Subtitle preserves colon in body)
|
|
83
|
+
assertEq(
|
|
84
|
+
'M001 — Unique Milestone IDs: Foo'.replace(TITLE_STRIP_RE, ''),
|
|
85
|
+
'Foo',
|
|
86
|
+
'strips M001 — Unique Milestone IDs: Foo → Foo (first colon consumed)',
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Edge case: colon inside title body preserved
|
|
90
|
+
assertEq(
|
|
91
|
+
'M001: Note: important'.replace(TITLE_STRIP_RE, ''),
|
|
92
|
+
'Note: important',
|
|
93
|
+
'preserves colons in title body',
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// No match — leaves non-milestone strings alone
|
|
97
|
+
assertEq('S01: Slice Title'.replace(TITLE_STRIP_RE, ''), 'S01: Slice Title', 'does not strip S01 prefix');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// (c) SLICE_BRANCH_RE — from worktree.ts
|
|
101
|
+
// Pattern: /^gsd\/(?:([a-zA-Z0-9_-]+)\/)?(M\d+(?:-[a-z0-9]{6})?)\/(S\d+)$/
|
|
102
|
+
{
|
|
103
|
+
console.log(' (c) SLICE_BRANCH_RE');
|
|
104
|
+
|
|
105
|
+
// Classic format — no worktree prefix
|
|
106
|
+
{
|
|
107
|
+
const m = 'gsd/M001/S01'.match(SLICE_BRANCH_RE);
|
|
108
|
+
assertTrue(m !== null, 'matches gsd/M001/S01');
|
|
109
|
+
assertEq(m?.[1], undefined, 'no worktree prefix for gsd/M001/S01');
|
|
110
|
+
assertEq(m?.[2], 'M001', 'captures M001');
|
|
111
|
+
assertEq(m?.[3], 'S01', 'captures S01');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Unique format — no worktree prefix
|
|
115
|
+
{
|
|
116
|
+
const m = 'gsd/M001-abc123/S01'.match(SLICE_BRANCH_RE);
|
|
117
|
+
assertTrue(m !== null, 'matches gsd/M001-abc123/S01');
|
|
118
|
+
assertEq(m?.[1], undefined, 'no worktree prefix for unique format');
|
|
119
|
+
assertEq(m?.[2], 'M001-abc123', 'captures M001-abc123');
|
|
120
|
+
assertEq(m?.[3], 'S01', 'captures S01');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Classic format — with worktree prefix
|
|
124
|
+
{
|
|
125
|
+
const m = 'gsd/worktree/M001/S01'.match(SLICE_BRANCH_RE);
|
|
126
|
+
assertTrue(m !== null, 'matches gsd/worktree/M001/S01');
|
|
127
|
+
assertEq(m?.[1], 'worktree', 'captures worktree prefix');
|
|
128
|
+
assertEq(m?.[2], 'M001', 'captures M001 with worktree');
|
|
129
|
+
assertEq(m?.[3], 'S01', 'captures S01 with worktree');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Unique format — with worktree prefix
|
|
133
|
+
{
|
|
134
|
+
const m = 'gsd/worktree/M001-abc123/S01'.match(SLICE_BRANCH_RE);
|
|
135
|
+
assertTrue(m !== null, 'matches gsd/worktree/M001-abc123/S01');
|
|
136
|
+
assertEq(m?.[1], 'worktree', 'captures worktree prefix with unique format');
|
|
137
|
+
assertEq(m?.[2], 'M001-abc123', 'captures M001-abc123 with worktree');
|
|
138
|
+
assertEq(m?.[3], 'S01', 'captures S01 with worktree and unique format');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Rejects
|
|
142
|
+
assertTrue(!SLICE_BRANCH_RE.test('gsd/S01'), 'rejects gsd/S01 (no milestone)');
|
|
143
|
+
assertTrue(!SLICE_BRANCH_RE.test('main'), 'rejects main');
|
|
144
|
+
assertTrue(!SLICE_BRANCH_RE.test('gsd/M001'), 'rejects gsd/M001 (no slice)');
|
|
145
|
+
assertTrue(!SLICE_BRANCH_RE.test('feature/M001/S01'), 'rejects feature/ prefix');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// (d) Milestone detection regex — used in worktree-command.ts (hasExistingMilestones)
|
|
149
|
+
// Pattern: /^M\d+(?:-[a-z0-9]{6})?/
|
|
150
|
+
{
|
|
151
|
+
console.log(' (d) Milestone detection regex');
|
|
152
|
+
const MILESTONE_DETECT_RE = /^M\d+(?:-[a-z0-9]{6})?/;
|
|
153
|
+
|
|
154
|
+
// Classic format matches
|
|
155
|
+
assertTrue(MILESTONE_DETECT_RE.test('M001'), 'detect matches M001');
|
|
156
|
+
assertTrue(MILESTONE_DETECT_RE.test('M042'), 'detect matches M042');
|
|
157
|
+
|
|
158
|
+
// Unique format matches
|
|
159
|
+
assertTrue(MILESTONE_DETECT_RE.test('M001-abc123'), 'detect matches M001-abc123');
|
|
160
|
+
assertTrue(MILESTONE_DETECT_RE.test('M042-z9a8b7'), 'detect matches M042-z9a8b7');
|
|
161
|
+
|
|
162
|
+
// Rejects
|
|
163
|
+
assertTrue(!MILESTONE_DETECT_RE.test('S01'), 'detect rejects S01');
|
|
164
|
+
assertTrue(!MILESTONE_DETECT_RE.test('notes'), 'detect rejects notes');
|
|
165
|
+
assertTrue(!MILESTONE_DETECT_RE.test('.DS_Store'), 'detect rejects .DS_Store');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// (e) MILESTONE_CONTEXT_RE — used in index.ts (write-gate)
|
|
169
|
+
// Pattern: /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/
|
|
170
|
+
{
|
|
171
|
+
console.log(' (e) MILESTONE_CONTEXT_RE');
|
|
172
|
+
const CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
|
|
173
|
+
|
|
174
|
+
// Classic format matches
|
|
175
|
+
assertTrue(CONTEXT_RE.test('M001-CONTEXT.md'), 'context matches M001-CONTEXT.md');
|
|
176
|
+
assertTrue(CONTEXT_RE.test('.gsd/milestones/M001/M001-CONTEXT.md'), 'context matches full path classic format');
|
|
177
|
+
|
|
178
|
+
// Unique format matches
|
|
179
|
+
assertTrue(CONTEXT_RE.test('M001-abc123-CONTEXT.md'), 'context matches M001-abc123-CONTEXT.md');
|
|
180
|
+
assertTrue(CONTEXT_RE.test('.gsd/milestones/M001-abc123/M001-abc123-CONTEXT.md'), 'context matches full path unique format');
|
|
181
|
+
|
|
182
|
+
// Rejects
|
|
183
|
+
assertTrue(!CONTEXT_RE.test('M001-ROADMAP.md'), 'context rejects M001-ROADMAP.md');
|
|
184
|
+
assertTrue(!CONTEXT_RE.test('M001-SUMMARY.md'), 'context rejects M001-SUMMARY.md');
|
|
185
|
+
assertTrue(!CONTEXT_RE.test('CONTEXT.md'), 'context rejects bare CONTEXT.md');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// (f) Prompt dispatch regexes — used in index.ts (executeMatch, resumeMatch)
|
|
189
|
+
{
|
|
190
|
+
console.log(' (f) Prompt dispatch regexes');
|
|
191
|
+
const EXECUTE_RE = /Execute the next task:\s+(T\d+)\s+\("([^"]+)"\)\s+in slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i;
|
|
192
|
+
const RESUME_RE = /Resume interrupted work\.[\s\S]*?slice\s+(S\d+)\s+of milestone\s+(M\d+(?:-[a-z0-9]{6})?)/i;
|
|
193
|
+
|
|
194
|
+
// Execute — classic format
|
|
195
|
+
{
|
|
196
|
+
const prompt = 'Execute the next task: T01 ("Write tests") in slice S01 of milestone M001';
|
|
197
|
+
const m = prompt.match(EXECUTE_RE);
|
|
198
|
+
assertTrue(m !== null, 'execute matches classic format');
|
|
199
|
+
assertEq(m?.[1], 'T01', 'execute captures T01');
|
|
200
|
+
assertEq(m?.[3], 'S01', 'execute captures S01');
|
|
201
|
+
assertEq(m?.[4], 'M001', 'execute captures M001');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Execute — unique format
|
|
205
|
+
{
|
|
206
|
+
const prompt = 'Execute the next task: T02 ("Build feature") in slice S03 of milestone M001-abc123';
|
|
207
|
+
const m = prompt.match(EXECUTE_RE);
|
|
208
|
+
assertTrue(m !== null, 'execute matches unique format');
|
|
209
|
+
assertEq(m?.[1], 'T02', 'execute captures T02 (unique format)');
|
|
210
|
+
assertEq(m?.[3], 'S03', 'execute captures S03 (unique format)');
|
|
211
|
+
assertEq(m?.[4], 'M001-abc123', 'execute captures M001-abc123');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Resume — classic format
|
|
215
|
+
{
|
|
216
|
+
const prompt = 'Resume interrupted work.\nContinuing slice S02 of milestone M001';
|
|
217
|
+
const m = prompt.match(RESUME_RE);
|
|
218
|
+
assertTrue(m !== null, 'resume matches classic format');
|
|
219
|
+
assertEq(m?.[1], 'S02', 'resume captures S02');
|
|
220
|
+
assertEq(m?.[2], 'M001', 'resume captures M001');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Resume — unique format
|
|
224
|
+
{
|
|
225
|
+
const prompt = 'Resume interrupted work.\nContinuing slice S01 of milestone M042-z9a8b7';
|
|
226
|
+
const m = prompt.match(RESUME_RE);
|
|
227
|
+
assertTrue(m !== null, 'resume matches unique format');
|
|
228
|
+
assertEq(m?.[1], 'S01', 'resume captures S01 (unique format)');
|
|
229
|
+
assertEq(m?.[2], 'M042-z9a8b7', 'resume captures M042-z9a8b7');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// (g) milestoneIdSort — mixed-format ordering
|
|
234
|
+
{
|
|
235
|
+
console.log(' (g) milestoneIdSort');
|
|
236
|
+
const mixed = ['M002-abc123', 'M001', 'M001-xyz789'];
|
|
237
|
+
const sorted = [...mixed].sort(milestoneIdSort);
|
|
238
|
+
assertEq(sorted, ['M001', 'M001-xyz789', 'M002-abc123'], 'sorts mixed IDs by sequence number');
|
|
239
|
+
|
|
240
|
+
// Stable within same seq — preserves insertion order
|
|
241
|
+
const sameSorted = ['M001-abc123', 'M001'].sort(milestoneIdSort);
|
|
242
|
+
assertEq(sameSorted[0], 'M001-abc123', 'same seq preserves order (first)');
|
|
243
|
+
assertEq(sameSorted[1], 'M001', 'same seq preserves order (second)');
|
|
244
|
+
|
|
245
|
+
// Classic format only
|
|
246
|
+
const oldOnly = ['M003', 'M001', 'M002'];
|
|
247
|
+
assertEq([...oldOnly].sort(milestoneIdSort), ['M001', 'M002', 'M003'], 'sorts classic-format IDs');
|
|
248
|
+
|
|
249
|
+
// Unique format only
|
|
250
|
+
const newOnly = ['M003-abc123', 'M001-def456', 'M002-ghi789'];
|
|
251
|
+
assertEq([...newOnly].sort(milestoneIdSort), ['M001-def456', 'M002-ghi789', 'M003-abc123'], 'sorts unique-format IDs');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// (h) extractMilestoneSeq — numeric extraction from both formats
|
|
255
|
+
{
|
|
256
|
+
console.log(' (h) extractMilestoneSeq');
|
|
257
|
+
|
|
258
|
+
// Classic format
|
|
259
|
+
assertEq(extractMilestoneSeq('M001'), 1, 'M001 → 1');
|
|
260
|
+
assertEq(extractMilestoneSeq('M042'), 42, 'M042 → 42');
|
|
261
|
+
assertEq(extractMilestoneSeq('M999'), 999, 'M999 → 999');
|
|
262
|
+
|
|
263
|
+
// Unique format — confirms dispatch-guard refactor correctness
|
|
264
|
+
assertEq(extractMilestoneSeq('M001-abc123'), 1, 'M001-abc123 → 1');
|
|
265
|
+
assertEq(extractMilestoneSeq('M042-z9a8b7'), 42, 'M042-z9a8b7 → 42');
|
|
266
|
+
assertEq(extractMilestoneSeq('M100-xyz789'), 100, 'M100-xyz789 → 100');
|
|
267
|
+
|
|
268
|
+
// Invalid → 0 (not NaN — the old parseInt(slice(1)) bug)
|
|
269
|
+
assertEq(extractMilestoneSeq(''), 0, 'empty → 0');
|
|
270
|
+
assertEq(extractMilestoneSeq('notes'), 0, 'notes → 0');
|
|
271
|
+
assertEq(extractMilestoneSeq('S01'), 0, 'S01 → 0');
|
|
272
|
+
assertTrue(!Number.isNaN(extractMilestoneSeq('M001-abc123')), 'unique format does not return NaN');
|
|
273
|
+
assertTrue(!Number.isNaN(extractMilestoneSeq('M001-ABCDEF')), 'invalid format does not return NaN');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
report();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
test('regex-hardening: all 12 sites accept both formats', async () => {
|
|
280
|
+
await main();
|
|
281
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { parseSlackReply, parseDiscordResponse } from "../../remote-questions/format.ts";
|
|
4
|
+
import { resolveRemoteConfig, isValidChannelId } from "../../remote-questions/config.ts";
|
|
5
|
+
import { sanitizeError } from "../../remote-questions/manager.ts";
|
|
6
|
+
|
|
7
|
+
test("parseSlackReply handles single-number single-question answers", () => {
|
|
8
|
+
const result = parseSlackReply("2", [{
|
|
9
|
+
id: "choice",
|
|
10
|
+
header: "Choice",
|
|
11
|
+
question: "Pick one",
|
|
12
|
+
allowMultiple: false,
|
|
13
|
+
options: [
|
|
14
|
+
{ label: "Alpha", description: "A" },
|
|
15
|
+
{ label: "Beta", description: "B" },
|
|
16
|
+
],
|
|
17
|
+
}]);
|
|
18
|
+
|
|
19
|
+
assert.deepEqual(result, { answers: { choice: { answers: ["Beta"] } } });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("parseSlackReply handles multiline multi-question answers", () => {
|
|
23
|
+
const result = parseSlackReply("1\ncustom note", [
|
|
24
|
+
{
|
|
25
|
+
id: "first",
|
|
26
|
+
header: "First",
|
|
27
|
+
question: "Pick one",
|
|
28
|
+
allowMultiple: false,
|
|
29
|
+
options: [
|
|
30
|
+
{ label: "Alpha", description: "A" },
|
|
31
|
+
{ label: "Beta", description: "B" },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "second",
|
|
36
|
+
header: "Second",
|
|
37
|
+
question: "Explain",
|
|
38
|
+
allowMultiple: false,
|
|
39
|
+
options: [
|
|
40
|
+
{ label: "Gamma", description: "G" },
|
|
41
|
+
{ label: "Delta", description: "D" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
assert.deepEqual(result, {
|
|
47
|
+
answers: {
|
|
48
|
+
first: { answers: ["Alpha"] },
|
|
49
|
+
second: { answers: [], user_note: "custom note" },
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("parseDiscordResponse handles single-question reactions", () => {
|
|
55
|
+
const result = parseDiscordResponse([{ emoji: "2️⃣", count: 1 }], null, [{
|
|
56
|
+
id: "choice",
|
|
57
|
+
header: "Choice",
|
|
58
|
+
question: "Pick one",
|
|
59
|
+
allowMultiple: false,
|
|
60
|
+
options: [
|
|
61
|
+
{ label: "Alpha", description: "A" },
|
|
62
|
+
{ label: "Beta", description: "B" },
|
|
63
|
+
],
|
|
64
|
+
}]);
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(result, { answers: { choice: { answers: ["Beta"] } } });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("parseDiscordResponse rejects multi-question reaction parsing", () => {
|
|
70
|
+
const result = parseDiscordResponse([{ emoji: "1️⃣", count: 1 }], null, [
|
|
71
|
+
{
|
|
72
|
+
id: "first",
|
|
73
|
+
header: "First",
|
|
74
|
+
question: "Pick one",
|
|
75
|
+
allowMultiple: false,
|
|
76
|
+
options: [{ label: "Alpha", description: "A" }],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: "second",
|
|
80
|
+
header: "Second",
|
|
81
|
+
question: "Pick one",
|
|
82
|
+
allowMultiple: false,
|
|
83
|
+
options: [{ label: "Beta", description: "B" }],
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
assert.match(String(result.answers.first.user_note), /single-question prompts/i);
|
|
88
|
+
assert.match(String(result.answers.second.user_note), /single-question prompts/i);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("parseSlackReply truncates user_note longer than 500 chars", () => {
|
|
92
|
+
const longText = "x".repeat(600);
|
|
93
|
+
const result = parseSlackReply(longText, [{
|
|
94
|
+
id: "q1",
|
|
95
|
+
header: "Q1",
|
|
96
|
+
question: "Pick",
|
|
97
|
+
allowMultiple: false,
|
|
98
|
+
options: [{ label: "A", description: "a" }],
|
|
99
|
+
}]);
|
|
100
|
+
|
|
101
|
+
const note = result.answers.q1.user_note!;
|
|
102
|
+
assert.ok(note.length <= 502, `note should be truncated, got ${note.length} chars`);
|
|
103
|
+
assert.ok(note.endsWith("…"), "truncated note should end with ellipsis");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("isValidChannelId rejects invalid Slack channel IDs", () => {
|
|
107
|
+
// Too short
|
|
108
|
+
assert.equal(isValidChannelId("slack", "C123"), false);
|
|
109
|
+
// Contains invalid chars (URL injection)
|
|
110
|
+
assert.equal(isValidChannelId("slack", "https://evil.com"), false);
|
|
111
|
+
// Lowercase
|
|
112
|
+
assert.equal(isValidChannelId("slack", "c12345678"), false);
|
|
113
|
+
// Too long
|
|
114
|
+
assert.equal(isValidChannelId("slack", "C1234567890AB"), false);
|
|
115
|
+
// Valid: 9-12 uppercase alphanumeric
|
|
116
|
+
assert.equal(isValidChannelId("slack", "C12345678"), true);
|
|
117
|
+
assert.equal(isValidChannelId("slack", "C12345678AB"), true);
|
|
118
|
+
assert.equal(isValidChannelId("slack", "C1234567890A"), true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("isValidChannelId rejects invalid Discord channel IDs", () => {
|
|
122
|
+
// Too short
|
|
123
|
+
assert.equal(isValidChannelId("discord", "12345"), false);
|
|
124
|
+
// Contains letters (not a snowflake)
|
|
125
|
+
assert.equal(isValidChannelId("discord", "abc12345678901234"), false);
|
|
126
|
+
// URL injection
|
|
127
|
+
assert.equal(isValidChannelId("discord", "https://evil.com"), false);
|
|
128
|
+
// Too long (21 digits)
|
|
129
|
+
assert.equal(isValidChannelId("discord", "123456789012345678901"), false);
|
|
130
|
+
// Valid: 17-20 digit snowflake
|
|
131
|
+
assert.equal(isValidChannelId("discord", "12345678901234567"), true);
|
|
132
|
+
assert.equal(isValidChannelId("discord", "11234567890123456789"), true);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("sanitizeError strips Slack token patterns from error messages", () => {
|
|
136
|
+
assert.equal(
|
|
137
|
+
sanitizeError("Auth failed: xoxb-1234-5678-abcdef"),
|
|
138
|
+
"Auth failed: [REDACTED]",
|
|
139
|
+
);
|
|
140
|
+
assert.equal(
|
|
141
|
+
sanitizeError("Bad token xoxp-abc-def-ghi in request"),
|
|
142
|
+
"Bad token [REDACTED] in request",
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("sanitizeError strips long opaque secrets", () => {
|
|
147
|
+
const fakeDiscordToken = "MTIzNDU2Nzg5MDEyMzQ1Njc4OQ.G1x2y3.abcdefghijklmnop";
|
|
148
|
+
assert.ok(!sanitizeError(`Token: ${fakeDiscordToken}`).includes(fakeDiscordToken));
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("sanitizeError preserves short safe messages", () => {
|
|
152
|
+
assert.equal(sanitizeError("HTTP 401: Unauthorized"), "HTTP 401: Unauthorized");
|
|
153
|
+
assert.equal(sanitizeError("Connection refused"), "Connection refused");
|
|
154
|
+
});
|
|
155
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { mkdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { createPromptRecord, writePromptRecord } from "../../remote-questions/store.ts";
|
|
7
|
+
import { getLatestPromptSummary } from "../../remote-questions/status.ts";
|
|
8
|
+
|
|
9
|
+
function withTempHome(fn: (tempHome: string) => void | Promise<void>) {
|
|
10
|
+
return async () => {
|
|
11
|
+
const savedHome = process.env.HOME;
|
|
12
|
+
const savedUserProfile = process.env.USERPROFILE;
|
|
13
|
+
const tempHome = join(tmpdir(), `gsd-remote-status-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
14
|
+
mkdirSync(join(tempHome, ".gsd", "runtime", "remote-questions"), { recursive: true });
|
|
15
|
+
process.env.HOME = tempHome;
|
|
16
|
+
process.env.USERPROFILE = tempHome;
|
|
17
|
+
try {
|
|
18
|
+
await fn(tempHome);
|
|
19
|
+
} finally {
|
|
20
|
+
process.env.HOME = savedHome;
|
|
21
|
+
process.env.USERPROFILE = savedUserProfile;
|
|
22
|
+
rmSync(tempHome, { recursive: true, force: true });
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test("getLatestPromptSummary returns latest stored prompt", withTempHome(() => {
|
|
28
|
+
const recordA = createPromptRecord({
|
|
29
|
+
id: "a-prompt",
|
|
30
|
+
channel: "slack",
|
|
31
|
+
createdAt: 1,
|
|
32
|
+
timeoutAt: 10,
|
|
33
|
+
pollIntervalMs: 5000,
|
|
34
|
+
questions: [],
|
|
35
|
+
});
|
|
36
|
+
recordA.updatedAt = 1;
|
|
37
|
+
writePromptRecord(recordA);
|
|
38
|
+
|
|
39
|
+
const recordB = createPromptRecord({
|
|
40
|
+
id: "z-prompt",
|
|
41
|
+
channel: "discord",
|
|
42
|
+
createdAt: 2,
|
|
43
|
+
timeoutAt: 10,
|
|
44
|
+
pollIntervalMs: 5000,
|
|
45
|
+
questions: [],
|
|
46
|
+
});
|
|
47
|
+
recordB.updatedAt = 2;
|
|
48
|
+
recordB.status = "answered";
|
|
49
|
+
writePromptRecord(recordB);
|
|
50
|
+
|
|
51
|
+
const latest = getLatestPromptSummary();
|
|
52
|
+
assert.equal(latest?.id, "z-prompt");
|
|
53
|
+
assert.equal(latest?.status, "answered");
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
test("getLatestPromptSummary sorts by updatedAt, not filename", withTempHome(() => {
|
|
57
|
+
// Record with alphabetically-LAST id but OLDEST timestamp
|
|
58
|
+
const old = createPromptRecord({
|
|
59
|
+
id: "zzz-oldest",
|
|
60
|
+
channel: "slack",
|
|
61
|
+
createdAt: 1000,
|
|
62
|
+
timeoutAt: 9999,
|
|
63
|
+
pollIntervalMs: 5000,
|
|
64
|
+
questions: [],
|
|
65
|
+
});
|
|
66
|
+
old.updatedAt = 1000;
|
|
67
|
+
writePromptRecord(old);
|
|
68
|
+
|
|
69
|
+
// Record with alphabetically-FIRST id but NEWEST timestamp
|
|
70
|
+
const newest = createPromptRecord({
|
|
71
|
+
id: "aaa-newest",
|
|
72
|
+
channel: "discord",
|
|
73
|
+
createdAt: 3000,
|
|
74
|
+
timeoutAt: 9999,
|
|
75
|
+
pollIntervalMs: 5000,
|
|
76
|
+
questions: [],
|
|
77
|
+
});
|
|
78
|
+
newest.updatedAt = 3000;
|
|
79
|
+
newest.status = "answered";
|
|
80
|
+
writePromptRecord(newest);
|
|
81
|
+
|
|
82
|
+
// Record in between
|
|
83
|
+
const middle = createPromptRecord({
|
|
84
|
+
id: "mmm-middle",
|
|
85
|
+
channel: "slack",
|
|
86
|
+
createdAt: 2000,
|
|
87
|
+
timeoutAt: 9999,
|
|
88
|
+
pollIntervalMs: 5000,
|
|
89
|
+
questions: [],
|
|
90
|
+
});
|
|
91
|
+
middle.updatedAt = 2000;
|
|
92
|
+
writePromptRecord(middle);
|
|
93
|
+
|
|
94
|
+
const latest = getLatestPromptSummary();
|
|
95
|
+
// Should return "aaa-newest" (updatedAt=3000), NOT "zzz-oldest" (alphabetically last)
|
|
96
|
+
assert.equal(latest?.id, "aaa-newest", "should pick the most recently updated prompt, not the alphabetically last filename");
|
|
97
|
+
assert.equal(latest?.status, "answered");
|
|
98
|
+
assert.equal(latest?.updatedAt, 3000);
|
|
99
|
+
}));
|